From 730064277dc32d42e927a4262a1cc9f6503204a7 Mon Sep 17 00:00:00 2001 From: Paul Davis Date: Thu, 20 Oct 2022 22:07:52 -0600 Subject: [PATCH] a start at custom MIDI learn for trigger slots --- libs/ardour/ardour/triggerbox.h | 28 ++++++- libs/ardour/midi_port.cc | 15 ++-- libs/ardour/triggerbox.cc | 138 +++++++++++++++++++++++++++++++- 3 files changed, 170 insertions(+), 11 deletions(-) diff --git a/libs/ardour/ardour/triggerbox.h b/libs/ardour/ardour/triggerbox.h index 841d2946fd..dbe066ea28 100644 --- a/libs/ardour/ardour/triggerbox.h +++ b/libs/ardour/ardour/triggerbox.h @@ -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 > 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 learning_for; + static MIDI::Parser* learning_parser; + static PBD::Signal0 TriggerMIDILearned; + static void init_pool(); static std::atomic active_trigger_boxes; diff --git a/libs/ardour/midi_port.cc b/libs/ardour/midi_port.cc index f5ff712a3a..76f3e15f7b 100644 --- a/libs/ardour/midi_port.cc +++ b/libs/ardour/midi_port.cc @@ -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) { diff --git a/libs/ardour/triggerbox.cc b/libs/ardour/triggerbox.cc index 4c8dd523d9..ca8a561cc8 100644 --- a/libs/ardour/triggerbox.cc +++ b/libs/ardour/triggerbox.cc @@ -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 TriggerBox::active_trigger_boxes (0); TriggerBoxThread* TriggerBox::worker = 0; @@ -3041,6 +3041,12 @@ CueRecords TriggerBox::cue_records (256); std::atomic TriggerBox::_cue_recording (false); PBD::Signal0 TriggerBox::CueRecordingChanged; bool TriggerBox::roll_requested = false; +bool TriggerBox::_learning = false; +TriggerBox::CustomMidiMap TriggerBox::_custom_midi_map; +std::pair TriggerBox::learning_for; +PBD::ScopedConnectionList TriggerBox::midi_learn_connections; +MIDI::Parser* TriggerBox::learning_parser = 0; +PBD::Signal0 TriggerBox::TriggerMIDILearned; typedef std::map , boost::shared_ptr> 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 mp = boost::dynamic_pointer_cast (_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 mp = boost::dynamic_pointer_cast (_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 (); } }