diff --git a/libs/ardour/ardour/triggerbox.h b/libs/ardour/ardour/triggerbox.h index e2d4c3d42c..9aa168223d 100644 --- a/libs/ardour/ardour/triggerbox.h +++ b/libs/ardour/ardour/triggerbox.h @@ -333,6 +333,8 @@ class LIBARDOUR_API Trigger : public PBD::Stateful { TriggerBox& box() const { return _box; } double estimated_tempo() const { return _estimated_tempo; } + virtual double segment_tempo() const = 0; + virtual void set_segment_tempo (double t) = 0; Temporal::Meter meter() const { return _meter; } @@ -349,6 +351,11 @@ class LIBARDOUR_API Trigger : public PBD::Stateful { static void request_trigger_delete (Trigger* t); + /* these operations are provided to get/set all the "user visible" trigger properties at once */ + /* examples: drag+dropping from slot to slot, or "Range->Bounce to Slot", where a single operation sets many */ + void get_ui_state (UIState &state) const; + void set_ui_state (UIState &state); + protected: struct UIRequests { std::atomic stop; @@ -371,7 +378,6 @@ class LIBARDOUR_API Trigger : public PBD::Stateful { gain_t _velocity_gain; bool _cue_launched; - void copy_ui_state (UIState&); void copy_to_ui_state (); @@ -539,6 +545,11 @@ class LIBARDOUR_API MIDITrigger : public Trigger { void unset_all_patch_changes (); bool patch_change_set (uint8_t channel) const; + /* 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 API */ + virtual double segment_tempo() const {return 120.0;} + virtual void set_segment_tempo (double t) {} + void set_channel_map (int channel, int target); void unset_channel_map (int channel); int channel_map (int channel); @@ -710,7 +721,7 @@ class LIBARDOUR_API TriggerBox : public Processor void non_realtime_locate (samplepos_t now); void realtime_handle_transport_stopped (); - void enqueue_trigger_source (PBD::ID queued); + void enqueue_trigger_state_for_region (boost::shared_ptr, boost::shared_ptr); /* valid only within the ::run() call tree */ int32_t active_scene() const { return _active_scene; } @@ -832,8 +843,6 @@ class LIBARDOUR_API TriggerBox : public Processor static std::atomic active_trigger_boxes; static std::atomic _cue_recording; - - static std::string _enqueued_drop_source; }; class TriggerReference diff --git a/libs/ardour/triggerbox.cc b/libs/ardour/triggerbox.cc index 87d7755aed..8feef4c3c1 100644 --- a/libs/ardour/triggerbox.cc +++ b/libs/ardour/triggerbox.cc @@ -213,6 +213,49 @@ Trigger::request_trigger_delete (Trigger* t) TriggerBox::worker->request_delete_trigger (t); } +void +Trigger::get_ui_state (Trigger::UIState &state) const +{ + /* this is used for operations like d&d when we want to query the current state */ + /* you can't return ui_state here because that struct is used to queue properties that are being input *to* the trigger */ + /* TODO: rename our member variable ui_state to _queued_ui_state or similar @paul ? */ + state.launch_style = _launch_style; + state.follow_action0 = _follow_action0; + state.follow_action1 = _follow_action1; + state.follow_action_probability = _follow_action_probability; + state.follow_count = _follow_count; + state.quantization = _quantization; + state.follow_length = _follow_length; + state.use_follow_length = _use_follow_length; + state.legato = _legato; + state.gain = _gain; + state.velocity_effect = _velocity_effect; + state.stretchable = _stretchable; + state.cue_isolated = _cue_isolated; + state.stretch_mode = _stretch_mode; + + state.name = _name; + state.color = _color; + + /* tempo is currently not a property */ + state.tempo = segment_tempo(); +} + +void +Trigger::set_ui_state (Trigger::UIState &state) +{ + ui_state = state; + + /* increment ui_state generation so vals will get loaded when the trigger stops */ + unsigned int g = ui_state.generation.load(); + while (!ui_state.generation.compare_exchange_strong (g, g+1)); + + /* tempo is currently outside the scope of ui_state */ + if (state.tempo > 0) { + set_segment_tempo(state.tempo); + } +} + void Trigger::update_properties () { @@ -238,6 +281,15 @@ Trigger::update_properties () _stretchable = ui_state.stretchable; _cue_isolated = ui_state.cue_isolated; _stretch_mode = ui_state.stretch_mode; + _color = ui_state.color; + + /* during construction of a new trigger, the ui_state.name is initialized and queued + * ...but in the interim, we have likely been assigned a name from a region in a separate thread + * ...so don't overwrite our name if ui_state.name is empty + */ + if (ui_state.name != "" ) { + _name = ui_state.name; + } last_property_generation = g; } @@ -267,6 +319,8 @@ Trigger::copy_to_ui_state () ui_state.stretchable = _stretchable; ui_state.cue_isolated = _cue_isolated; ui_state.stretch_mode = _stretch_mode; + ui_state.name = _name; + ui_state.color = _color; } void @@ -2555,7 +2609,9 @@ TriggerBoxThread* TriggerBox::worker = 0; CueRecords TriggerBox::cue_records (256); std::atomic TriggerBox::_cue_recording (false); PBD::Signal0 TriggerBox::CueRecordingChanged; -std::string TriggerBox::_enqueued_drop_source("0"); + +typedef std::map , boost::shared_ptr> RegionStateMap; +RegionStateMap enqueued_state_map; void TriggerBox::init () @@ -2781,16 +2837,15 @@ TriggerBox::set_region (uint32_t slot, boost::shared_ptr region) return; } - /* set_region_in_worker_thread makes some guesses about whether a clip is a one-shot or looping*/ + /* set_region_in_worker_thread estimates a tempo, and makes some guesses about whether a clip is a one-shot or looping*/ t->set_region_in_worker_thread (region); - /* if we are the target of a drag&drop from another Trigger Slot, we probably want the name, color and other properties to carry over */ - boost::shared_ptr source = session().trigger_by_id (PBD::ID(_enqueued_drop_source)); - if (source) { - t->set_name(source->name()); - t->set_color(source->color()); - t->set_gain(source->gain()); - _enqueued_drop_source = "0"; + /* if we are the target of a drag&drop from another Trigger Slot, we need the name, color and other properties to carry over with the region */ + RegionStateMap::iterator rs; + if ((rs = enqueued_state_map.find (region)) != enqueued_state_map.end()) { + Trigger::UIState copy; copy = *(rs->second); + t->set_ui_state(*(rs->second)); + enqueued_state_map.erase(rs); } //* always preserve the launch-style and cue_isolate status. It's likely to be right, but if it's wrong the user can "see" it's wrong anyway */ @@ -2908,9 +2963,9 @@ TriggerBox::trigger_by_id (PBD::ID check) } void -TriggerBox::enqueue_trigger_source (PBD::ID queued) +TriggerBox::enqueue_trigger_state_for_region (boost::shared_ptr region, boost::shared_ptr state) { - _enqueued_drop_source = queued.to_s (); + enqueued_state_map.insert (std::make_pair(region, state)); } void