From e932afaf267b186c213d888be4db7785e20be394 Mon Sep 17 00:00:00 2001 From: Ben Loftis Date: Thu, 24 Feb 2022 14:38:58 -0600 Subject: [PATCH] triggerbox: maintain UsedChannels and Patch Change data --- libs/ardour/ardour/triggerbox.h | 34 ++++++++++++++- libs/ardour/triggerbox.cc | 74 +++++++++++++++++++++++++++++---- 2 files changed, 99 insertions(+), 9 deletions(-) diff --git a/libs/ardour/ardour/triggerbox.h b/libs/ardour/ardour/triggerbox.h index d460346897..3e44d3e3d2 100644 --- a/libs/ardour/ardour/triggerbox.h +++ b/libs/ardour/ardour/triggerbox.h @@ -41,6 +41,7 @@ #include "temporal/tempo.h" #include "evoral/PatchChange.h" +#include "evoral/SMF.h" #include "ardour/midi_model.h" #include "ardour/midi_state_tracker.h" @@ -178,6 +179,9 @@ class LIBARDOUR_API Trigger : public PBD::Stateful { bool cue_isolated = false; StretchMode stretch_mode = Trigger::Crisp; + Evoral::SMF::UsedChannels used_channels = Evoral::SMF::UsedChannels(); + Evoral::PatchChange patch_change[16]; + std::string name = ""; color_t color = 0xBEBEBEFF; double tempo = 0; //unset @@ -204,6 +208,13 @@ class LIBARDOUR_API Trigger : public PBD::Stateful { stretchable = other.stretchable; cue_isolated = other.cue_isolated; stretch_mode = other.stretch_mode; + used_channels = other.used_channels; + + for (int i = 0; i<16; i++) { + if (other.patch_change[i].is_set()) { + patch_change[i] = other.patch_change[i]; + } + } name = other.name; color = other.color; @@ -345,9 +356,23 @@ class LIBARDOUR_API Trigger : public PBD::Stateful { TriggerBox& box() const { return _box; } double estimated_tempo() const { return _estimated_tempo; } + + /* the following functions deal with audio- or midi-specific SegmentDescriptor properties, provided as virtuals so we don't have to do lots of dynamic_casting */ + /* segment_tempo is currently a no-op for MIDI, but may be implemented later */ virtual double segment_tempo() const = 0; virtual void set_segment_tempo (double t) = 0; + /* used_channels is a no-op for audio */ + virtual Evoral::SMF::UsedChannels used_channels() const { return Evoral::SMF::UsedChannels(); } + virtual void set_used_channels (Evoral::SMF::UsedChannels) {} + + /* patch changes are a no-op for audio */ + virtual void set_patch_change (Evoral::PatchChange const &) {} + virtual Evoral::PatchChange const patch_change (uint8_t) const { return Evoral::PatchChange(); } + virtual void unset_patch_change (uint8_t channel) {} + virtual void unset_all_patch_changes () {} + virtual bool patch_change_set (uint8_t channel) const { return false; } + virtual void setup_stretcher () = 0; Temporal::Meter meter() const { return _meter; } @@ -552,11 +577,15 @@ class LIBARDOUR_API MIDITrigger : public Trigger { void start_and_roll_to (samplepos_t start, samplepos_t position); void set_patch_change (Evoral::PatchChange const &); - Evoral::PatchChange const & patch_change (uint8_t) const; + Evoral::PatchChange const patch_change (uint8_t) const; void unset_patch_change (uint8_t channel); void unset_all_patch_changes (); bool patch_change_set (uint8_t channel) const; + /* It's possible that a portion of a midi file would use a subset of the total channels used, so store that info in the segment descriptor */ + Evoral::SMF::UsedChannels used_channels() const { return _used_channels; } + void set_used_channels (Evoral::SMF::UsedChannels); + /* theoretically, MIDI files can have a dedicated tempo outside the session tempo map (*un-stretched*) but this is currently unimplemented */ /* boilerplate tempo functions are provided here so we don't have to do constant dynamic_cast checks to use the tempo+stretch APIs */ virtual double segment_tempo() const {return 120.0;} @@ -589,6 +618,8 @@ class LIBARDOUR_API MIDITrigger : public Trigger { Evoral::PatchChange _patch_change[16]; std::vector _channel_map; + Evoral::SMF::UsedChannels _used_channels; + int load_data (boost::shared_ptr); void compute_and_set_length (); void _startup (BufferSet&, pframes_t dest_offset, Temporal::BBT_Offset const &); @@ -892,6 +923,7 @@ namespace Properties { LIBARDOUR_API extern PBD::PropertyDescriptor cue_isolated; LIBARDOUR_API extern PBD::PropertyDescriptor patch_change; /* type not important */ LIBARDOUR_API extern PBD::PropertyDescriptor channel_map; /* type not important */ + LIBARDOUR_API extern PBD::PropertyDescriptor used_channels; /* type not important */ LIBARDOUR_API extern PBD::PropertyDescriptor tempo_meter; /* only used to transmit changes, not storage */ } diff --git a/libs/ardour/triggerbox.cc b/libs/ardour/triggerbox.cc index 9ccd68183f..9541a1b40f 100644 --- a/libs/ardour/triggerbox.cc +++ b/libs/ardour/triggerbox.cc @@ -19,6 +19,7 @@ #include "temporal/tempo.h" +#include "ardour/auditioner.h" #include "ardour/audioengine.h" #include "ardour/audioregion.h" #include "ardour/audio_buffer.h" @@ -68,6 +69,7 @@ namespace ARDOUR { PBD::PropertyDescriptor tempo_meter; /* only to transmit updates, not storage */ PBD::PropertyDescriptor patch_change; /* only to transmit updates, not storage */ PBD::PropertyDescriptor channel_map; /* only to transmit updates, not storage */ + PBD::PropertyDescriptor used_channels; /* only to transmit updates, not storage */ } } @@ -240,6 +242,11 @@ Trigger::get_ui_state (Trigger::UIState &state) const state.name = _name; state.color = _color; + state.used_channels = used_channels(); + for (int i = 0; i<16; i++) { + state.patch_change[i] = patch_change(i); + } + /* tempo is currently not a property */ state.tempo = segment_tempo(); } @@ -257,6 +264,13 @@ Trigger::set_ui_state (Trigger::UIState &state) if (state.tempo > 0) { set_segment_tempo(state.tempo); } + + set_used_channels(state.used_channels); + for (int chan = 0; chan<16; chan++) { + if (state.patch_change[chan].is_set()) { + set_patch_change(state.patch_change[chan]); + } + } } void @@ -303,6 +317,14 @@ Trigger::update_properties () _name = ui_state.name; } + set_used_channels(ui_state.used_channels); + + for (int chan = 0; chan<16; chan++) { + if (ui_state.patch_change[chan].is_set()) { + set_patch_change(ui_state.patch_change[chan]); + } + } + last_property_generation = g; } @@ -333,6 +355,11 @@ Trigger::copy_to_ui_state () ui_state.stretch_mode = _stretch_mode; ui_state.name = _name; ui_state.color = _color; + + ui_state.used_channels = used_channels(); + for (int i = 0; i<16; i++) { + ui_state.patch_change[i] = patch_change(i); //TODO: maybe these should be initialized here instead of later + } } void @@ -1999,12 +2026,8 @@ MIDITrigger::MIDITrigger (uint32_t n, TriggerBox& b) , last_event_beats (Temporal::Beats()) , _start_offset (0, 0, 0) , _legato_offset (0, 0, 0) + , _used_channels (Evoral::SMF::UsedChannels()) { -#if 0 /* for prototype + testing only */ - Evoral::PatchChange pc (0, 0, 12, 0); - set_patch_change (pc); -#endif - _channel_map.assign (16, -1); } @@ -2012,6 +2035,18 @@ MIDITrigger::~MIDITrigger () { } +void +MIDITrigger::set_used_channels (Evoral::SMF::UsedChannels used) +{ + if (_used_channels != used) { + + _used_channels = used; + + send_property_change (ARDOUR::Properties::used_channels); + _box.session().set_dirty(); + } +} + void MIDITrigger::set_channel_map (int channel, int target) { @@ -2091,11 +2126,15 @@ MIDITrigger::patch_change_set (uint8_t channel) const return _patch_change[channel].is_set(); } -Evoral::PatchChange const & +Evoral::PatchChange const MIDITrigger::patch_change (uint8_t channel) const { + Evoral::PatchChange ret; + assert (channel < 16); - return _patch_change[channel]; + ret = _patch_change[channel]; + + return ret; } @@ -2174,7 +2213,7 @@ MIDITrigger::_startup (BufferSet& bufs, pframes_t dest_offset, Temporal::BBT_Off /* Possibly inject patch changes, if set */ for (int chn = 0; chn < 16; ++chn) { - if (_patch_change[chn].is_set()) { + if (_used_channels.test(chn) && _patch_change[chn].is_set()) { _patch_change[chn].set_time (dest_offset); cerr << index() << " Injecting patch change " << _patch_change[chn].program() << " @ " << dest_offset << endl; for (int msg = 0; msg < _patch_change[chn].messages(); ++msg) { @@ -2227,6 +2266,9 @@ MIDITrigger::get_state (void) node.set_property (X_("start"), start_offset()); + std::string uchan = string_compose ("%1", _used_channels.to_ulong()); + node.set_property (X_("used-channels"), uchan); + XMLNode* patches_node = 0; for (int chn = 0; chn < 16; ++chn) { @@ -2274,6 +2316,18 @@ MIDITrigger::set_state (const XMLNode& node, int version) return -1; } + std::string uchan; + if (node.get_property (X_("used-channels"), uchan)) { + } else { + unsigned long ul; + std::stringstream ss (uchan); + ss >> ul; + if (!ss) { + return -1; + } + set_used_channels( Evoral::SMF::UsedChannels(ul) ); + } + node.get_property (X_("start"), t); Temporal::Beats b (t.beats()); /* XXX need to deal with bar offsets */ @@ -2312,6 +2366,9 @@ MIDITrigger::set_state (const XMLNode& node, int version) } } + /* we've changed our internal values; we need to update our queued UIState or they will be lost when UIState is applied */ + copy_to_ui_state (); + return 0; } @@ -2371,6 +2428,7 @@ MIDITrigger::natural_length() const return timepos_t (Temporal::BeatTime); } + int MIDITrigger::set_region_in_worker_thread (boost::shared_ptr r) {