From 0cee4f45cea2016ba28d125cbd256d2c1cec4afb 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 preserve this data across drag&drop, session save, etc --- libs/ardour/ardour/triggerbox.h | 33 +++++++++++++- libs/ardour/triggerbox.cc | 81 ++++++++++++++++++++++++++++++--- 2 files changed, 106 insertions(+), 8 deletions(-) diff --git a/libs/ardour/ardour/triggerbox.h b/libs/ardour/ardour/triggerbox.h index 078b4c1c7c..bf834205b1 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,11 @@ 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++) { + patch_change[i] = other.patch_change[i]; + } name = other.name; color = other.color; @@ -229,6 +238,7 @@ class LIBARDOUR_API Trigger : public PBD::Stateful { TRIGGERBOX_PROPERTY_DECL (stretchable, bool); TRIGGERBOX_PROPERTY_DECL (cue_isolated, bool); TRIGGERBOX_PROPERTY_DECL (stretch_mode, StretchMode); + TRIGGERBOX_PROPERTY_DECL (used_channels, Evoral::SMF::UsedChannels); TRIGGERBOX_PROPERTY_DECL (color, color_t); TRIGGERBOX_PROPERTY_DECL_CONST_REF (name, std::string); @@ -345,9 +355,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_segment_channels is a no-op for audio */ + virtual Evoral::SMF::UsedChannels segment_used_channels() const { return Evoral::SMF::UsedChannels(); } + virtual void set_segment_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; } @@ -554,11 +578,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 segment_used_channels() const { return _segment_used_channels; } + void set_segment_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;} @@ -591,6 +619,8 @@ class LIBARDOUR_API MIDITrigger : public Trigger { Evoral::PatchChange _patch_change[16]; std::vector _channel_map; + Evoral::SMF::UsedChannels _segment_used_channels; + int load_data (boost::shared_ptr); void compute_and_set_length (); void _startup (BufferSet&, pframes_t dest_offset, Temporal::BBT_Offset const &); @@ -894,6 +924,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 19aa1f77f2..7cc30b035b 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 = segment_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_segment_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_segment_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 = segment_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) + , _segment_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,28 @@ MIDITrigger::~MIDITrigger () { } +void +MIDITrigger::set_segment_used_channels (Evoral::SMF::UsedChannels used) +{ + if (_segment_used_channels != used) { + + _segment_used_channels = used; + + send_property_change (ARDOUR::Properties::used_channels); + _box.session().set_dirty(); + } + + /* TODO: once we have a Region Trimmer, this could get more complicated: + * this segment might overlap another SD (Coverage==Internal|Start|End) + * in which case we might be setting both SDs, or not. TBD*/ + if (_region) { + SegmentDescriptor segment = get_segment_descriptor(); + for (auto & src : _region->sources()) { + src->set_segment_descriptor (segment); + } + } +} + void MIDITrigger::set_channel_map (int channel, int target) { @@ -2091,11 +2136,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; } @@ -2227,6 +2276,9 @@ MIDITrigger::get_state (void) node.set_property (X_("start"), start_offset()); + std::string uchan = string_compose ("%1", _segment_used_channels.to_ulong()); + node.set_property (X_("used-channels"), uchan); + XMLNode* patches_node = 0; for (int chn = 0; chn < 16; ++chn) { @@ -2274,6 +2326,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_segment_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 +2376,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; }