From 593b421180290f46f39efcb21ed8192b624bbc73 Mon Sep 17 00:00:00 2001 From: Carl Hetherington Date: Wed, 14 Jul 2010 00:58:15 +0000 Subject: [PATCH] A few fixes to interpolation of MIDI controller data. Don't interpolate when writing these data back to a source, otherwise surprising new interpolated points appear in MIDI automation. Similarly don't interpolate when reading the model during MIDI stretch. Fix handling of interpolation state; controllers that have been set by the user to use a different interpolation style are noted in the tag of the session file and this state is sprayed around to MidiModel and the GUI as necessary. git-svn-id: svn://localhost/ardour2/branches/3.0@7409 d708f5d6-7413-0410-9779-e7cbd77b26cf --- gtk2_ardour/automation_line.cc | 36 ++++++++----- gtk2_ardour/automation_line.h | 10 ++-- gtk2_ardour/automation_region_view.cc | 1 - gtk2_ardour/automation_streamview.cc | 22 ++++++-- gtk2_ardour/automation_streamview.h | 1 + gtk2_ardour/automation_time_axis.cc | 50 +++++++++-------- gtk2_ardour/automation_time_axis.h | 7 +-- libs/ardour/ardour/midi_model.h | 7 +++ libs/ardour/ardour/midi_source.h | 13 +++++ libs/ardour/ardour/smf_source.h | 2 - libs/ardour/automatable.cc | 2 +- libs/ardour/midi_model.cc | 62 +++++++++++++++++++-- libs/ardour/midi_source.cc | 77 ++++++++++++++++++++++++++- libs/ardour/midi_stretch.cc | 7 ++- libs/ardour/route.cc | 2 +- libs/ardour/smf_source.cc | 18 ------- libs/evoral/evoral/ControlList.hpp | 6 ++- libs/evoral/evoral/ControlSet.hpp | 5 +- libs/evoral/evoral/Sequence.hpp | 7 +-- libs/evoral/src/ControlList.cpp | 16 +++++- libs/evoral/src/ControlSet.cpp | 7 ++- libs/evoral/src/Sequence.cpp | 23 +++++--- 22 files changed, 285 insertions(+), 96 deletions(-) diff --git a/gtk2_ardour/automation_line.cc b/gtk2_ardour/automation_line.cc index 82372f39e9..ed94b03355 100644 --- a/gtk2_ardour/automation_line.cc +++ b/gtk2_ardour/automation_line.cc @@ -67,7 +67,6 @@ AutomationLine::AutomationLine (const string& name, TimeAxisView& tv, ArdourCanv , _parent_group (parent) , _time_converter (converter ? (*converter) : default_converter) { - _interpolation = al->interpolation(); points_visible = false; update_pending = false; _uses_gain_mapping = false; @@ -86,7 +85,7 @@ AutomationLine::AutomationLine (const string& name, TimeAxisView& tv, ArdourCanv line->signal_event().connect (sigc::mem_fun (*this, &AutomationLine::event_handler)); - alist->StateChanged.connect (_state_connection, invalidator (*this), boost::bind (&AutomationLine::list_changed, this), gui_context()); + connect_to_list (); trackview.session()->register_with_memento_command_factory(alist->id(), this); @@ -95,7 +94,9 @@ AutomationLine::AutomationLine (const string& name, TimeAxisView& tv, ArdourCanv set_uses_gain_mapping (true); } - set_interpolation(alist->interpolation()); + interpolation_changed (alist->interpolation ()); + + connect_to_list (); } AutomationLine::~AutomationLine () @@ -122,7 +123,7 @@ AutomationLine::queue_reset () void AutomationLine::show () { - if (_interpolation != AutomationList::Discrete) { + if (alist->interpolation() != AutomationList::Discrete) { line->show(); } @@ -148,7 +149,7 @@ AutomationLine::hide () double AutomationLine::control_point_box_size () { - if (_interpolation == AutomationList::Discrete) { + if (alist->interpolation() == AutomationList::Discrete) { return max((_height*4.0) / (double)(alist->parameter().max() - alist->parameter().min()), 4.0); } @@ -470,7 +471,7 @@ AutomationLine::determine_visible_control_points (ALPoints& points) line->property_points() = line_points; - if (_visible && _interpolation != AutomationList::Discrete) { + if (_visible && alist->interpolation() != AutomationList::Discrete) { line->show(); } @@ -1117,10 +1118,11 @@ AutomationLine::change_model (AutomationList::iterator /*i*/, double /*x*/, doub } void -AutomationLine::set_list(boost::shared_ptr list) +AutomationLine::set_list (boost::shared_ptr list) { alist = list; - queue_reset(); + queue_reset (); + connect_to_list (); } void @@ -1222,13 +1224,10 @@ AutomationLine::model_to_view_coord (double& x, double& y) const x = _time_converter.to(x); } - +/** Called when our list has announced that its interpolation style has changed */ void -AutomationLine::set_interpolation(AutomationList::InterpolationStyle style) +AutomationLine::interpolation_changed (AutomationList::InterpolationStyle style) { - _interpolation = style; - alist->set_interpolation (_interpolation); - if (style == AutomationList::Discrete) { show_all_control_points(); line->hide(); @@ -1301,3 +1300,14 @@ AutomationLine::clear_always_in_view () alist->apply_to_points (*this, &AutomationLine::reset_callback); } +void +AutomationLine::connect_to_list () +{ + _list_connections.drop_connections (); + + alist->StateChanged.connect (_list_connections, invalidator (*this), boost::bind (&AutomationLine::list_changed, this), gui_context()); + + alist->InterpolationChanged.connect ( + _list_connections, invalidator (*this), boost::bind (&AutomationLine::interpolation_changed, this, _1), gui_context() + ); +} diff --git a/gtk2_ardour/automation_line.h b/gtk2_ardour/automation_line.h index 3e0f0f4bb3..161d33a80b 100644 --- a/gtk2_ardour/automation_line.h +++ b/gtk2_ardour/automation_line.h @@ -52,6 +52,7 @@ namespace Gnome { } } +/** A GUI representation of an ARDOUR::AutomationList */ class AutomationLine : public sigc::trackable, public PBD::StatefulDestructible { public: @@ -91,8 +92,6 @@ class AutomationLine : public sigc::trackable, public PBD::StatefulDestructible void set_line_color (uint32_t); uint32_t get_line_color() const { return _line_color; } - void set_interpolation(ARDOUR::AutomationList::InterpolationStyle style); - void show (); void hide (); void set_height (guint32); @@ -174,7 +173,6 @@ class AutomationLine : public sigc::trackable, public PBD::StatefulDestructible void reset_callback (const Evoral::ControlList&); void list_changed (); - PBD::ScopedConnection _state_connection; virtual bool event_handler (GdkEvent*); virtual void add_model_point (ALPoints& tmp_points, double frame, double yfract); @@ -189,12 +187,12 @@ class AutomationLine : public sigc::trackable, public PBD::StatefulDestructible std::list _always_in_view; const Evoral::TimeConverter& _time_converter; - ARDOUR::AutomationList::InterpolationStyle _interpolation; void reset_line_coords (ControlPoint&); void add_visible_control_point (uint32_t, uint32_t, double, double, ARDOUR::AutomationList::iterator, uint32_t); - double control_point_box_size (); + void connect_to_list (); + void interpolation_changed (ARDOUR::AutomationList::InterpolationStyle); struct ModelRepresentation { ARDOUR::AutomationList::iterator start; @@ -211,6 +209,8 @@ class AutomationLine : public sigc::trackable, public PBD::StatefulDestructible void model_representation (ControlPoint&, ModelRepresentation&); + PBD::ScopedConnectionList _list_connections; + friend class AudioRegionGainLine; }; diff --git a/gtk2_ardour/automation_region_view.cc b/gtk2_ardour/automation_region_view.cc index 45bd5d770b..9f3d169dde 100644 --- a/gtk2_ardour/automation_region_view.cc +++ b/gtk2_ardour/automation_region_view.cc @@ -75,7 +75,6 @@ AutomationRegionView::create_line (boost::shared_ptr lis ARDOUR::EventTypeMap::instance().to_symbol(list->parameter()), trackview, *get_canvas_group(), list, &_time_converter)); _line->set_colors(); - _line->set_interpolation(list->interpolation()); _line->set_height ((uint32_t)rint(trackview.current_height() - NAME_HIGHLIGHT_SIZE)); _line->show(); _line->show_all_control_points(); diff --git a/gtk2_ardour/automation_streamview.cc b/gtk2_ardour/automation_streamview.cc index 006834c59d..146cdc7b89 100644 --- a/gtk2_ardour/automation_streamview.cc +++ b/gtk2_ardour/automation_streamview.cc @@ -237,14 +237,28 @@ AutomationStreamView::has_automation () const return false; } +/** Our parent AutomationTimeAxisView calls this when the user requests a particular + * InterpolationStyle; tell the AutomationLists in our regions. + */ void AutomationStreamView::set_interpolation (AutomationList::InterpolationStyle s) { - for (list::iterator i = region_views.begin(); i != region_views.end(); ++i) { + for (list::const_iterator i = region_views.begin(); i != region_views.end(); ++i) { AutomationRegionView* arv = dynamic_cast (*i); assert (arv); - if (arv->line()) { - arv->line()->set_interpolation (s); - } + arv->line()->the_list()->set_interpolation (s); } } + +AutomationList::InterpolationStyle +AutomationStreamView::interpolation () const +{ + if (region_views.empty()) { + return AutomationList::Linear; + } + + AutomationRegionView* v = dynamic_cast (region_views.front()); + assert (v); + + return v->line()->the_list()->interpolation (); +} diff --git a/gtk2_ardour/automation_streamview.h b/gtk2_ardour/automation_streamview.h index 01d3435714..335d63ca7a 100644 --- a/gtk2_ardour/automation_streamview.h +++ b/gtk2_ardour/automation_streamview.h @@ -57,6 +57,7 @@ class AutomationStreamView : public StreamView bool has_automation () const; void set_interpolation (ARDOUR::AutomationList::InterpolationStyle); + ARDOUR::AutomationList::InterpolationStyle interpolation () const; private: void setup_rec_box (); diff --git a/gtk2_ardour/automation_time_axis.cc b/gtk2_ardour/automation_time_axis.cc index 841ec65fb9..a7114f8ff0 100644 --- a/gtk2_ardour/automation_time_axis.cc +++ b/gtk2_ardour/automation_time_axis.cc @@ -270,7 +270,6 @@ AutomationTimeAxisView::set_automation_state (AutoState state) #endif } - cout << "_view = " << _view << "\n"; if (_view) { _view->set_automation_state (state); @@ -347,13 +346,12 @@ AutomationTimeAxisView::automation_state_changed () } } +/** The interpolation style of our AutomationList has changed, so update */ void -AutomationTimeAxisView::interpolation_changed () +AutomationTimeAxisView::interpolation_changed (AutomationList::InterpolationStyle s) { - AutomationList::InterpolationStyle style = _control->list()->interpolation(); - if (mode_line_item && mode_discrete_item) { - if (style == AutomationList::Discrete) { + if (s == AutomationList::Discrete) { mode_discrete_item->set_active(true); mode_line_item->set_active(false); } else { @@ -361,25 +359,20 @@ AutomationTimeAxisView::interpolation_changed () mode_discrete_item->set_active(false); } } - - if (_line) { - _line->set_interpolation(style); - } - - if (_view) { - _view->set_interpolation (style); - } } +/** A menu item has been selected to change our interpolation mode */ void AutomationTimeAxisView::set_interpolation (AutomationList::InterpolationStyle style) { - _control->list()->set_interpolation(style); - if (_line) { - _line->set_interpolation(style); - } + /* Tell our view's list, if we have one, otherwise tell our own. + * Everything else will be signalled back from that. + */ + if (_view) { _view->set_interpolation (style); + } else { + _control->list()->set_interpolation (style); } } @@ -546,6 +539,9 @@ AutomationTimeAxisView::build_display_menu () /* mode menu */ + /* current interpolation state */ + AutomationList::InterpolationStyle const s = _view ? _view->interpolation() : _control->list()->interpolation (); + if (EventTypeMap::instance().is_midi_parameter(_control->parameter())) { Menu* auto_mode_menu = manage (new Menu); @@ -558,17 +554,13 @@ AutomationTimeAxisView::build_display_menu () sigc::mem_fun(*this, &AutomationTimeAxisView::set_interpolation), AutomationList::Discrete))); mode_discrete_item = dynamic_cast(&am_items.back()); - mode_discrete_item->set_active(_control->list()->interpolation() == AutomationList::Discrete); + mode_discrete_item->set_active (s == AutomationList::Discrete); am_items.push_back (RadioMenuElem (group, _("Linear"), sigc::bind ( sigc::mem_fun(*this, &AutomationTimeAxisView::set_interpolation), AutomationList::Linear))); mode_line_item = dynamic_cast(&am_items.back()); - - // Set default interpolation type to linear if this isn't a (usually) discrete controller - if (EventTypeMap::instance().interpolation_of(_control->parameter()) == Evoral::ControlList::Linear) { - mode_line_item->set_active(_control->list()->interpolation() == AutomationList::Linear); - } + mode_line_item->set_active (s == AutomationList::Linear); items.push_back (MenuElem (_("Mode"), *auto_mode_menu)); } @@ -576,7 +568,7 @@ AutomationTimeAxisView::build_display_menu () /* make sure the automation menu state is correct */ automation_state_changed (); - interpolation_changed (); + interpolation_changed (s); } void @@ -834,7 +826,7 @@ void AutomationTimeAxisView::clear_lines () { _line.reset(); - automation_connection.disconnect (); + _list_connections.drop_connections (); } void @@ -844,7 +836,13 @@ AutomationTimeAxisView::add_line (boost::shared_ptr line) assert(!_line); assert(line->the_list() == _control->list()); - _control->alist()->automation_state_changed.connect (automation_connection, invalidator (*this), boost::bind (&AutomationTimeAxisView::automation_state_changed, this), gui_context()); + _control->alist()->automation_state_changed.connect ( + _list_connections, invalidator (*this), boost::bind (&AutomationTimeAxisView::automation_state_changed, this), gui_context() + ); + + _control->alist()->InterpolationChanged.connect ( + _list_connections, invalidator (*this), boost::bind (&AutomationTimeAxisView::interpolation_changed, this, _1), gui_context() + ); _line = line; //_controller = AutomationController::create(_session, line->the_list(), _control); diff --git a/gtk2_ardour/automation_time_axis.h b/gtk2_ardour/automation_time_axis.h index 9d4802f6fc..4b21ec8533 100644 --- a/gtk2_ardour/automation_time_axis.h +++ b/gtk2_ardour/automation_time_axis.h @@ -116,7 +116,8 @@ class AutomationTimeAxisView : public TimeAxisView { ArdourCanvas::SimpleRect* _base_rect; boost::shared_ptr _line; - AutomationStreamView* _view; + /** AutomationStreamView if we are editing region-based automation (for MIDI), otherwise 0 */ + AutomationStreamView* _view; std::string _name; bool ignore_toggle; @@ -156,9 +157,9 @@ class AutomationTimeAxisView : public TimeAxisView { void automation_state_changed (); void set_interpolation (ARDOUR::AutomationList::InterpolationStyle); - void interpolation_changed (); + void interpolation_changed (ARDOUR::AutomationList::InterpolationStyle); - PBD::ScopedConnection automation_connection; + PBD::ScopedConnectionList _list_connections; void update_extra_xml_shown (bool editor_shown); diff --git a/libs/ardour/ardour/midi_model.h b/libs/ardour/ardour/midi_model.h index 4e348af287..a8303539b5 100644 --- a/libs/ardour/ardour/midi_model.h +++ b/libs/ardour/ardour/midi_model.h @@ -148,6 +148,8 @@ public: InsertMergePolicy insert_merge_policy () const; void set_insert_merge_policy (InsertMergePolicy); + boost::shared_ptr control_factory(const Evoral::Parameter& id); + protected: int resolve_overlaps_unlocked (const NotePtr, void* arg = 0); @@ -170,6 +172,11 @@ public: private: friend class DeltaCommand; + void source_interpolation_changed (Evoral::Parameter, Evoral::ControlList::InterpolationStyle); + void control_list_interpolation_changed (Evoral::Parameter, Evoral::ControlList::InterpolationStyle); + + PBD::ScopedConnectionList _midi_source_connections; + // We cannot use a boost::shared_ptr here to avoid a retain cycle MidiSource* _midi_source; InsertMergePolicy _insert_merge_policy; diff --git a/libs/ardour/ardour/midi_source.h b/libs/ardour/ardour/midi_source.h index 0d0b744a95..8d20f9c7b6 100644 --- a/libs/ardour/ardour/midi_source.h +++ b/libs/ardour/ardour/midi_source.h @@ -117,8 +117,15 @@ class MidiSource : virtual public Source void set_model (boost::shared_ptr); void drop_model(); + Evoral::ControlList::InterpolationStyle interpolation_of (Evoral::Parameter) const; + void set_interpolation_of (Evoral::Parameter, Evoral::ControlList::InterpolationStyle); + void copy_interpolation_from (boost::shared_ptr); + void copy_interpolation_from (MidiSource *); + /** Emitted when a different MidiModel is set */ PBD::Signal0 ModelChanged; + /** Emitted when a parameter's interpolation style is changed */ + PBD::Signal2 InterpolationChanged; protected: virtual void flush_midi() = 0; @@ -146,6 +153,12 @@ class MidiSource : virtual public Source mutable double _length_beats; mutable sframes_t _last_read_end; sframes_t _last_write_end; + + /** Map of interpolation styles to use for Parameters; if they are not in this map, + * the correct interpolation style can be obtained from EventTypeMap::interpolation_of () + */ + typedef std::map InterpolationStyleMap; + InterpolationStyleMap _interpolation_style; }; } diff --git a/libs/ardour/ardour/smf_source.h b/libs/ardour/ardour/smf_source.h index d271cb0dba..6dcea9dd60 100644 --- a/libs/ardour/ardour/smf_source.h +++ b/libs/ardour/ardour/smf_source.h @@ -82,8 +82,6 @@ private: sframes_t position, nframes_t cnt); - void set_default_controls_interpolation (); - double _last_ev_time_beats; sframes_t _last_ev_time_frames; /** end time (start + duration) of last call to read_unlocked */ diff --git a/libs/ardour/automatable.cc b/libs/ardour/automatable.cc index bed99d5660..40b6eb2a07 100644 --- a/libs/ardour/automatable.cc +++ b/libs/ardour/automatable.cc @@ -58,7 +58,7 @@ Automatable::Automatable (const Automatable& other) for (Controls::const_iterator i = other._controls.begin(); i != other._controls.end(); ++i) { boost::shared_ptr ac (control_factory (i->first)); - _controls[ac->parameter()] = ac; + add_control (ac); } } int diff --git a/libs/ardour/midi_model.cc b/libs/ardour/midi_model.cc index 6e2c477a99..b5d5d24713 100644 --- a/libs/ardour/midi_model.cc +++ b/libs/ardour/midi_model.cc @@ -41,8 +41,9 @@ using namespace PBD; MidiModel::MidiModel(MidiSource* s) : AutomatableSequence(s->session()) - , _midi_source(s) + , _midi_source (0) { + set_midi_source (s); } /** Start a new Diff command. @@ -761,6 +762,9 @@ MidiModel::DiffCommand::get_state () * user can switch a recorded track (with note durations from some instrument) * to percussive, save, reload, then switch it back to sustained without * destroying the original note durations. + * + * Similarly, control events are written without interpolation (as with the + * `Discrete' mode). */ bool MidiModel::write_to (boost::shared_ptr source) @@ -773,7 +777,7 @@ MidiModel::write_to (boost::shared_ptr source) source->drop_model(); source->mark_streaming_midi_write_started(note_mode(), _midi_source->timeline_position()); - for (Evoral::Sequence::const_iterator i = begin(); i != end(); ++i) { + for (Evoral::Sequence::const_iterator i = begin(0, true); i != end(); ++i) { source->append_event_unlocked_beats(*i); } @@ -800,7 +804,7 @@ MidiModel::sync_to_source () _midi_source->mark_streaming_midi_write_started(note_mode(), _midi_source->timeline_position()); - for (Evoral::Sequence::const_iterator i = begin(); i != end(); ++i) { + for (Evoral::Sequence::const_iterator i = begin(0, true); i != end(); ++i) { _midi_source->append_event_unlocked_beats(*i); } @@ -832,7 +836,7 @@ MidiModel::write_section_to (boost::shared_ptr source, Evoral::Music source->drop_model(); source->mark_streaming_midi_write_started(note_mode(), _midi_source->timeline_position()); - for (Evoral::Sequence::const_iterator i = begin(); i != end(); ++i) { + for (Evoral::Sequence::const_iterator i = begin(0, true); i != end(); ++i) { const Evoral::Event& ev (*i); if (ev.time() >= begin_time && ev.time() < end_time) { @@ -1138,6 +1142,54 @@ MidiModel::insert_merge_policy () const void MidiModel::set_midi_source (MidiSource* s) { - _midi_source->invalidate (); + if (_midi_source) { + _midi_source->invalidate (); + } + + _midi_source_connections.drop_connections (); + _midi_source = s; + + _midi_source->InterpolationChanged.connect_same_thread ( + _midi_source_connections, boost::bind (&MidiModel::source_interpolation_changed, this, _1, _2) + ); +} + +/** The source has signalled that the interpolation style for a parameter has changed. In order to + * keep MidiSource and ControlList interpolation state the same, we pass this change onto the + * appropriate ControlList. + * + * The idea is that MidiSource and the MidiModel's ControlList states are kept in sync, and the + * MidiSource's InterpolationChanged signal is listened to by the GUI. + */ +void +MidiModel::source_interpolation_changed (Evoral::Parameter p, Evoral::ControlList::InterpolationStyle s) +{ + Glib::Mutex::Lock lm (_control_lock); + control(p)->list()->set_interpolation (s); +} + +/** A ControlList has signalled that its interpolation style has changed. Again, in order to keep + * MidiSource and ControlList interpolation state in sync, we pass this change onto our MidiSource. + */ +void +MidiModel::control_list_interpolation_changed (Evoral::Parameter p, Evoral::ControlList::InterpolationStyle s) +{ + _midi_source->set_interpolation_of (p, s); +} + +boost::shared_ptr +MidiModel::control_factory (Evoral::Parameter const & p) +{ + boost::shared_ptr c = Automatable::control_factory (p); + + /* Set up newly created control's lists to the appropriate interpolation state + from our source. + */ + + assert (_midi_source); + + c->list()->set_interpolation (_midi_source->interpolation_of (p)); + + return c; } diff --git a/libs/ardour/midi_source.cc b/libs/ardour/midi_source.cc index 3f831b348d..dbc41c8ab6 100644 --- a/libs/ardour/midi_source.cc +++ b/libs/ardour/midi_source.cc @@ -97,6 +97,12 @@ MidiSource::get_state () node.add_property ("captured-for", _captured_for); } + for (InterpolationStyleMap::const_iterator i = _interpolation_style.begin(); i != _interpolation_style.end(); ++i) { + XMLNode* child = node.add_child (X_("InterpolationStyle")); + child->add_property (X_("parameter"), EventTypeMap::instance().to_symbol (i->first)); + child->add_property (X_("style"), enum_2_string (i->second)); + } + return node; } @@ -109,6 +115,28 @@ MidiSource::set_state (const XMLNode& node, int /*version*/) _captured_for = prop->value(); } + XMLNodeList children = node.children (); + for (XMLNodeConstIterator i = children.begin(); i != children.end(); ++i) { + if ((*i)->name() == X_("InterpolationStyle")) { + XMLProperty* prop; + + if ((prop = (*i)->property (X_("parameter"))) == 0) { + error << _("Missing parameter property on InterpolationStyle") << endmsg; + return -1; + } + + Evoral::Parameter p = EventTypeMap::instance().new_parameter (prop->value()); + + if ((prop = (*i)->property (X_("style"))) == 0) { + error << _("Missing style property on InterpolationStyle") << endmsg; + return -1; + } + + Evoral::ControlList::InterpolationStyle s = static_cast (string_2_enum (prop->value(), s)); + set_interpolation_of (p, s); + } + } + return 0; } @@ -160,7 +188,7 @@ MidiSource::midi_read (Evoral::EventSink& dst, sframes_t source_start // If the cached iterator is invalid, search for the first event past start if (_last_read_end == 0 || start != _last_read_end || !_model_iter_valid) { DEBUG_TRACE (DEBUG::MidiSourceIO, string_compose ("*** %1 search for relevant iterator for %1 / %2\n", _name, source_start, start)); - for (i = _model->begin(0, filtered); i != _model->end(); ++i) { + for (i = _model->begin(0, false, filtered); i != _model->end(); ++i) { if (converter.to(i->time()) >= start) { break; } @@ -260,6 +288,7 @@ MidiSource::clone (Evoral::MusicalTime begin, Evoral::MusicalTime end) newpath, false, _session.frame_rate())); newsrc->set_timeline_position(_timeline_position); + newsrc->copy_interpolation_from (this); if (_model) { if (begin == Evoral::MinMusicalTime && end == Evoral::MaxMusicalTime) { @@ -344,3 +373,49 @@ MidiSource::set_model (boost::shared_ptr m) _model = m; ModelChanged (); /* EMIT SIGNAL */ } + +/** @return Interpolation style that should be used for control parameter \a p */ +Evoral::ControlList::InterpolationStyle +MidiSource::interpolation_of (Evoral::Parameter p) const +{ + InterpolationStyleMap::const_iterator i = _interpolation_style.find (p); + if (i == _interpolation_style.end()) { + return EventTypeMap::instance().interpolation_of (p); + } + + return i->second; +} + +/** Set interpolation style to be used for a given parameter. This change will be + * propagated to anyone who needs to know. + */ +void +MidiSource::set_interpolation_of (Evoral::Parameter p, Evoral::ControlList::InterpolationStyle s) +{ + if (interpolation_of (p) == s) { + return; + } + + if (EventTypeMap::instance().interpolation_of (p) == s) { + /* interpolation type is being set to the default, so we don't need a note in our map */ + _interpolation_style.erase (p); + } else { + _interpolation_style[p] = s; + } + + InterpolationChanged (p, s); /* EMIT SIGNAL */ +} + +void +MidiSource::copy_interpolation_from (boost::shared_ptr s) +{ + copy_interpolation_from (s.get ()); +} + +void +MidiSource::copy_interpolation_from (MidiSource* s) +{ + _interpolation_style = s->_interpolation_style; + + /* XXX: should probably emit signals here */ +} diff --git a/libs/ardour/midi_stretch.cc b/libs/ardour/midi_stretch.cc index 585e0a07b2..21b5453da4 100644 --- a/libs/ardour/midi_stretch.cc +++ b/libs/ardour/midi_stretch.cc @@ -90,7 +90,10 @@ MidiStretch::run (boost::shared_ptr r) boost::shared_ptr new_model = new_src->model(); new_model->start_write(); - for (Evoral::Sequence::const_iterator i = old_model->begin(); + /* Note: pass true into force_discrete for the begin() iterator so that the model doesn't + * do interpolation of controller data when we stretch. + */ + for (Evoral::Sequence::const_iterator i = old_model->begin (0, true); i != old_model->end(); ++i) { const double new_time = i->time() * _request.time_fraction; @@ -103,6 +106,8 @@ MidiStretch::run (boost::shared_ptr r) new_model->end_write(); new_model->set_edited(true); + new_src->copy_interpolation_from (src); + const int ret = finish (region, nsrcs, new_name); results[0]->set_length((nframes_t) floor (r->length() * _request.time_fraction), NULL); diff --git a/libs/ardour/route.cc b/libs/ardour/route.cc index e7f5e28b94..e8ddf678c9 100644 --- a/libs/ardour/route.cc +++ b/libs/ardour/route.cc @@ -109,7 +109,7 @@ Route::init () _solo_control->set_flags (Controllable::Flag (_solo_control->flags() | Controllable::Toggle)); _mute_control->set_flags (Controllable::Flag (_solo_control->flags() | Controllable::Toggle)); - + add_control (_solo_control); add_control (_mute_control); diff --git a/libs/ardour/smf_source.cc b/libs/ardour/smf_source.cc index fe5a0f7c8f..bbdf958815 100644 --- a/libs/ardour/smf_source.cc +++ b/libs/ardour/smf_source.cc @@ -257,10 +257,6 @@ SMFSource::write_unlocked (MidiRingBuffer& source, sframes_t position append_event_unlocked_frames(ev, position); } - if (_model) { - set_default_controls_interpolation(); - } - Evoral::SMF::flush(); free(buf); @@ -471,8 +467,6 @@ SMFSource::load_model (bool lock, bool force_reload) _length_beats = max(_length_beats, ev.time()); } - set_default_controls_interpolation(); - _model->end_write(false); _model->set_edited(false); @@ -481,18 +475,6 @@ SMFSource::load_model (bool lock, bool force_reload) free(buf); } -void -SMFSource::set_default_controls_interpolation () -{ - // set interpolation style to defaults, can be changed by the GUI later - Evoral::ControlSet::Controls controls = _model->controls(); - for (Evoral::ControlSet::Controls::iterator c = controls.begin(); c != controls.end(); ++c) { - (*c).second->list()->set_interpolation( - EventTypeMap::instance().interpolation_of((*c).first)); - } -} - - void SMFSource::destroy_model () { diff --git a/libs/evoral/evoral/ControlList.hpp b/libs/evoral/evoral/ControlList.hpp index 5f842775ee..d207c76925 100644 --- a/libs/evoral/evoral/ControlList.hpp +++ b/libs/evoral/evoral/ControlList.hpp @@ -218,6 +218,7 @@ public: bool rt_safe_earliest_event (double start, double end, double& x, double& y, bool start_inclusive=false) const; bool rt_safe_earliest_event_unlocked (double start, double end, double& x, double& y, bool start_inclusive=false) const; + bool rt_safe_earliest_event_discrete_unlocked (double start, double end, double& x, double& y, bool inclusive) const; void create_curve(); void destroy_curve(); @@ -234,10 +235,12 @@ public: }; InterpolationStyle interpolation() const { return _interpolation; } - void set_interpolation(InterpolationStyle style) { _interpolation = style; } + void set_interpolation (InterpolationStyle); /** Emitted when mark_dirty() is called on this object */ mutable PBD::Signal0 Dirty; + /** Emitted when our interpolation style changes */ + PBD::Signal1 InterpolationChanged; protected: @@ -246,7 +249,6 @@ protected: void build_search_cache_if_necessary(double start, double end) const; - bool rt_safe_earliest_event_discrete_unlocked (double start, double end, double& x, double& y, bool inclusive) const; bool rt_safe_earliest_event_linear_unlocked (double start, double end, double& x, double& y, bool inclusive) const; boost::shared_ptr cut_copy_clear (double, double, int op); diff --git a/libs/evoral/evoral/ControlSet.hpp b/libs/evoral/evoral/ControlSet.hpp index b775bb3b4b..95de58dead 100644 --- a/libs/evoral/evoral/ControlSet.hpp +++ b/libs/evoral/evoral/ControlSet.hpp @@ -27,11 +27,11 @@ #include "pbd/signals.h" #include "evoral/types.hpp" #include "evoral/Parameter.hpp" +#include "evoral/ControlList.hpp" namespace Evoral { class Control; -class ControlList; class ControlEvent; class ControlSet : public boost::noncopyable { @@ -69,12 +69,15 @@ public: protected: virtual void control_list_marked_dirty () {} + virtual void control_list_interpolation_changed (Parameter, ControlList::InterpolationStyle) {} mutable Glib::Mutex _control_lock; Controls _controls; private: + PBD::ScopedConnectionList _control_connections; + PBD::ScopedConnectionList _list_connections; }; diff --git a/libs/evoral/evoral/Sequence.hpp b/libs/evoral/evoral/Sequence.hpp index 3cddeb38ca..24a3c44625 100644 --- a/libs/evoral/evoral/Sequence.hpp +++ b/libs/evoral/evoral/Sequence.hpp @@ -185,7 +185,7 @@ public: class const_iterator { public: const_iterator(); - const_iterator(const Sequence