a start at custom MIDI learn for trigger slots

This commit is contained in:
Paul Davis 2022-10-20 22:07:52 -06:00
parent a130041547
commit 730064277d
3 changed files with 170 additions and 11 deletions

View File

@ -36,6 +36,8 @@
#include "pbd/ringbuffer.h"
#include "pbd/stateful.h"
#include "midi++/types.h"
#include "temporal/beats.h"
#include "temporal/bbt_time.h"
#include "temporal/tempo.h"
@ -58,6 +60,10 @@ namespace RubberBand {
class RubberBandStretcher;
}
namespace MIDI {
class Parser;
}
namespace ARDOUR {
class Session;
@ -802,12 +808,22 @@ class LIBARDOUR_API TriggerBox : public Processor
enum TriggerMidiMapMode {
AbletonPush,
SequentialNote,
ByMidiChannel
ByMidiChannel,
Custom,
};
/* This is null for TriggerBoxen constructed with DataType::AUDIO */
MidiStateTracker* tracker;
static bool lookup_custom_midi_binding (int id, int& x, int& y);
static void add_custom_midi_binding (int id, int x, int y);
static void remove_custom_midi_binding (int x, int y);
static void clear_custom_midi_bindings ();
void begin_midi_learn (int index);
void midi_unlearn (int index);
void stop_midi_learn ();
static Temporal::BBT_Offset assumed_trigger_duration () { return _assumed_trigger_duration; }
static void set_assumed_trigger_duration (Temporal::BBT_Offset const &);
@ -919,6 +935,16 @@ class LIBARDOUR_API TriggerBox : public Processor
PBD::ScopedConnection stop_all_connection;
typedef std::map<int,std::pair<int,int> > CustomMidiMap;
static CustomMidiMap _custom_midi_map;
static void midi_learn_input_handler (MIDI::Parser&, MIDI::byte*, size_t, samplecnt_t);
static PBD::ScopedConnectionList midi_learn_connections;
static bool _learning;
static std::pair<int,int> learning_for;
static MIDI::Parser* learning_parser;
static PBD::Signal0<void> TriggerMIDILearned;
static void init_pool();
static std::atomic<int> active_trigger_boxes;

View File

@ -206,6 +206,8 @@ MidiPort::read_and_parse_entire_midi_buffer_with_no_speed_adjustment (pframes_t
*
* As of July 2018, this is only used by TransportMasters which
* read MIDI before the process() cycle really gets started.
*
* October 2022: Now also used by trigger custom midi learn.
*/
if ((buf[0] & 0xF0) == 0x90 && buf[2] == 0) {
@ -303,7 +305,6 @@ MidiPort::flush_buffers (pframes_t nframes)
}
}
// event times are in samples, relative to cycle start
#ifndef NDEBUG
@ -383,18 +384,18 @@ MidiPort::reset ()
_buffer = new MidiBuffer (AudioEngine::instance()->raw_buffer_size (DataType::MIDI));
}
void
MidiPort::set_input_active (bool yn)
{
_input_active = yn;
}
void
MidiPort::set_trace (MIDI::Parser * p)
{
_trace_parser = p;
}
void
MidiPort::set_input_active (bool yn)
{
_input_active = yn;
}
int
MidiPort::add_shadow_port (string const & name, MidiFilter mf)
{

View File

@ -37,6 +37,7 @@
#include "temporal/tempo.h"
#include "ardour/async_midi_port.h"
#include "ardour/auditioner.h"
#include "ardour/audioengine.h"
#include "ardour/audioregion.h"
@ -3032,8 +3033,7 @@ Trigger::make_property_quarks ()
}
Temporal::BBT_Offset TriggerBox::_assumed_trigger_duration (4, 0, 0);
//TriggerBox::TriggerMidiMapMode TriggerBox::_midi_map_mode (TriggerBox::AbletonPush);
TriggerBox::TriggerMidiMapMode TriggerBox::_midi_map_mode (TriggerBox::SequentialNote);
TriggerBox::TriggerMidiMapMode TriggerBox::_midi_map_mode (TriggerBox::Custom);
int TriggerBox::_first_midi_note = 60;
std::atomic<int> TriggerBox::active_trigger_boxes (0);
TriggerBoxThread* TriggerBox::worker = 0;
@ -3041,6 +3041,12 @@ CueRecords TriggerBox::cue_records (256);
std::atomic<bool> TriggerBox::_cue_recording (false);
PBD::Signal0<void> TriggerBox::CueRecordingChanged;
bool TriggerBox::roll_requested = false;
bool TriggerBox::_learning = false;
TriggerBox::CustomMidiMap TriggerBox::_custom_midi_map;
std::pair<int,int> TriggerBox::learning_for;
PBD::ScopedConnectionList TriggerBox::midi_learn_connections;
MIDI::Parser* TriggerBox::learning_parser = 0;
PBD::Signal0<void> TriggerBox::TriggerMIDILearned;
typedef std::map <boost::shared_ptr<Region>, boost::shared_ptr<Trigger::UIState>> RegionStateMap;
RegionStateMap enqueued_state_map;
@ -3105,7 +3111,13 @@ TriggerBox::parameter_changed (std::string const & param)
{
if (param == X_("default-trigger-input-port")) {
reconnect_to_default ();
if (!Config->get_default_trigger_input_port().empty()) {
if (!_sidechain) {
add_midi_sidechain ();
} else {
reconnect_to_default ();
}
}
} else if (param == "cue-behavior") {
const bool follow = (_session.config.get_cue_behavior() & FollowCues);
@ -3826,6 +3838,8 @@ TriggerBox::note_to_trigger (int midi_note, int channel)
const int column = _order;
int first_note;
int top;
int x;
int y;
switch (_midi_map_mode) {
@ -3850,6 +3864,17 @@ TriggerBox::note_to_trigger (int midi_note, int channel)
first_note = 3;
break;
case Custom:
if (!_learning) {
if (lookup_custom_midi_binding (channel * 16 + midi_note, x, y)) {
if (x == _order) {
std::cerr << "yes, slot " << y << std::endl;
return y;
}
}
}
return -1;
default:
break;
@ -3858,6 +3883,111 @@ TriggerBox::note_to_trigger (int midi_note, int channel)
return midi_note;
}
bool
TriggerBox::lookup_custom_midi_binding (int id, int& x, int& y)
{
CustomMidiMap::iterator i = _custom_midi_map.find (id);
if (i == _custom_midi_map.end()) {
return false;
}
x = i->second.first;
y = i->second.second;
return true;
}
void
TriggerBox::midi_learn_input_handler (MIDI::Parser&, MIDI::byte* ev, size_t sz, samplecnt_t)
{
if (!_learning) {
return;
}
if ((ev[0] & 0xf0) == MIDI::on) {
int channel = ev[0] & 0xf;
int note = ev[1] & 0x7f;
add_custom_midi_binding (channel * 16 + note, learning_for.first, learning_for.second);
TriggerMIDILearned (); /* emit signal */
return;
}
return;
}
void
TriggerBox::begin_midi_learn (int index)
{
if (!_sidechain) {
return;
}
learning_for.first = order(); /* x */
learning_for.second = index; /* y */
_learning = true;
boost::shared_ptr<MidiPort> mp = boost::dynamic_pointer_cast<MidiPort> (_sidechain->input()->nth(0));
if (!mp) {
return;
}
if (!learning_parser) {
learning_parser = new MIDI::Parser;
}
TriggerMIDILearned.connect_same_thread (midi_learn_connections, boost::bind (&TriggerBox::stop_midi_learn, this));
learning_parser->any.connect_same_thread (midi_learn_connections, boost::bind (&TriggerBox::midi_learn_input_handler, _1, _2, _3, _4));
mp->set_trace (learning_parser);
}
void
TriggerBox::stop_midi_learn ()
{
if (_learning) {
_learning = false;
midi_learn_connections.drop_connections ();
boost::shared_ptr<MidiPort> mp = boost::dynamic_pointer_cast<MidiPort> (_sidechain->input()->nth(0));
if (mp) {
mp->set_trace (0);
}
}
}
void
TriggerBox::midi_unlearn (int index)
{
remove_custom_midi_binding (order(), index);
}
void
TriggerBox::clear_custom_midi_bindings ()
{
_custom_midi_map.clear ();
}
void
TriggerBox::add_custom_midi_binding (int id, int x, int y)
{
_custom_midi_map.insert (std::make_pair (id, std::make_pair (x, y)));
}
void
TriggerBox::remove_custom_midi_binding (int x, int y)
{
/* this searches the whole map in case there are multiple entries
*(keyed by note/channel) for the same pad (x,y)
*/
for (CustomMidiMap::iterator i = _custom_midi_map.begin(); i != _custom_midi_map.end(); ++i) {
if (i->second.first == x && i->second.second == y) {
_custom_midi_map.erase (i);
}
}
}
void
TriggerBox::process_midi_trigger_requests (BufferSet& bufs)
{
@ -3903,10 +4033,12 @@ TriggerBox::process_midi_trigger_requests (BufferSet& bufs)
*/
t->set_velocity_gain (1.0 - (t->velocity_effect() * (*ev).velocity() / 127.f));
}
std:: cerr << "bang\n";
t->bang ();
} else if ((*ev).is_note_off()) {
std:: cerr << "unbang\n";
t->unbang ();
}
}