triggerbox: maintain UsedChannels and Patch Change data

preserve this data across drag&drop, session save, etc
This commit is contained in:
Ben Loftis 2022-02-24 14:38:58 -06:00
parent dce3f2eb65
commit 0cee4f45ce
2 changed files with 106 additions and 8 deletions

View File

@ -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<MidiBuffer::TimeType> 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<MidiBuffer::TimeType> const &) {}
virtual Evoral::PatchChange<MidiBuffer::TimeType> const patch_change (uint8_t) const { return Evoral::PatchChange<MidiBuffer::TimeType>(); }
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<MidiBuffer::TimeType> const &);
Evoral::PatchChange<MidiBuffer::TimeType> const & patch_change (uint8_t) const;
Evoral::PatchChange<MidiBuffer::TimeType> 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<MidiBuffer::TimeType> _patch_change[16];
std::vector<int> _channel_map;
Evoral::SMF::UsedChannels _segment_used_channels;
int load_data (boost::shared_ptr<MidiRegion>);
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<bool> cue_isolated;
LIBARDOUR_API extern PBD::PropertyDescriptor<bool> patch_change; /* type not important */
LIBARDOUR_API extern PBD::PropertyDescriptor<bool> channel_map; /* type not important */
LIBARDOUR_API extern PBD::PropertyDescriptor<bool> used_channels; /* type not important */
LIBARDOUR_API extern PBD::PropertyDescriptor<bool> tempo_meter; /* only used to transmit changes, not storage */
}

View File

@ -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<bool> tempo_meter; /* only to transmit updates, not storage */
PBD::PropertyDescriptor<bool> patch_change; /* only to transmit updates, not storage */
PBD::PropertyDescriptor<bool> channel_map; /* only to transmit updates, not storage */
PBD::PropertyDescriptor<bool> 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<MidiBuffer::TimeType> 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<MidiBuffer::TimeType> const &
Evoral::PatchChange<MidiBuffer::TimeType> const
MIDITrigger::patch_change (uint8_t channel) const
{
Evoral::PatchChange<MidiBuffer::TimeType> 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;
}