A few fixes to interpolation of MIDI controller data. Don't interpolate
authorCarl Hetherington <carl@carlh.net>
Wed, 14 Jul 2010 00:58:15 +0000 (00:58 +0000)
committerCarl Hetherington <carl@carlh.net>
Wed, 14 Jul 2010 00:58:15 +0000 (00:58 +0000)
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 <Source> 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

22 files changed:
gtk2_ardour/automation_line.cc
gtk2_ardour/automation_line.h
gtk2_ardour/automation_region_view.cc
gtk2_ardour/automation_streamview.cc
gtk2_ardour/automation_streamview.h
gtk2_ardour/automation_time_axis.cc
gtk2_ardour/automation_time_axis.h
libs/ardour/ardour/midi_model.h
libs/ardour/ardour/midi_source.h
libs/ardour/ardour/smf_source.h
libs/ardour/automatable.cc
libs/ardour/midi_model.cc
libs/ardour/midi_source.cc
libs/ardour/midi_stretch.cc
libs/ardour/route.cc
libs/ardour/smf_source.cc
libs/evoral/evoral/ControlList.hpp
libs/evoral/evoral/ControlSet.hpp
libs/evoral/evoral/Sequence.hpp
libs/evoral/src/ControlList.cpp
libs/evoral/src/ControlSet.cpp
libs/evoral/src/Sequence.cpp

index 82372f39e93394ec9ed92f518c469119bd006688..ed94b03355042069818923d47b327bc07e83a604 100644 (file)
@@ -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<ARDOUR::AutomationList> list)
+AutomationLine::set_list (boost::shared_ptr<ARDOUR::AutomationList> 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()
+               );
+}
index 3e0f0f4bb3c364b0a163f6d7b9f6a2180b943ed1..161d33a80bccd3ff5c93e121c06001b5cbc0d076 100644 (file)
@@ -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<double> _always_in_view;
 
        const Evoral::TimeConverter<double, ARDOUR::sframes_t>& _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;
 };
 
index 45bd5d770bd89e4c4999f61f792d0fa58ce9cfac..9f3d169ddefcc2df26fc9612685a9d22311e4561 100644 (file)
@@ -75,7 +75,6 @@ AutomationRegionView::create_line (boost::shared_ptr<ARDOUR::AutomationList> 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();
index 006834c59d87eee918120e95474e6c18066f8945..146cdc7b8918077f672e772b115533b00ff84fdb 100644 (file)
@@ -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<RegionView*>::iterator i = region_views.begin(); i != region_views.end(); ++i) {
+       for (list<RegionView*>::const_iterator i = region_views.begin(); i != region_views.end(); ++i) {
                AutomationRegionView* arv = dynamic_cast<AutomationRegionView*> (*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<AutomationRegionView*> (region_views.front());
+       assert (v);
+
+       return v->line()->the_list()->interpolation ();
 }
index 01d34357149d01718b37311bb2b1065732d93904..335d63ca7abf1c2a27b1408be50fe9ab3705f6a1 100644 (file)
@@ -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 ();
index 841ec65fb93597806ce5a66756cfc8b4df626016..a7114f8ff0ece3bea569cd8a5d4df8832b5cd600 100644 (file)
@@ -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<CheckMenuItem*>(&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<CheckMenuItem*>(&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<AutomationLine> 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);
index 9d4802f6fca3133f982aab443b75937822007e54..4b21ec85331ae0ffcf2a2a2264438c3cf058cac8 100644 (file)
@@ -116,7 +116,8 @@ class AutomationTimeAxisView : public TimeAxisView {
 
        ArdourCanvas::SimpleRect* _base_rect;
        boost::shared_ptr<AutomationLine> _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);
 
index 4e348af287d09acc21bda2a9b634fbddff7f39a5..a8303539b53c37e691e31865faf0a975b071c272 100644 (file)
@@ -148,6 +148,8 @@ public:
         InsertMergePolicy insert_merge_policy () const;
         void set_insert_merge_policy (InsertMergePolicy);
 
+       boost::shared_ptr<Evoral::Control> 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;
index 0d0b744a95b10dd1c540577cdea5fc74391f36ba..8d20f9c7b6542181124dbeaff1ee095f3363756c 100644 (file)
@@ -117,8 +117,15 @@ class MidiSource : virtual public Source
        void set_model (boost::shared_ptr<MidiModel>);
        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<MidiSource>);
+       void copy_interpolation_from (MidiSource *);
+
        /** Emitted when a different MidiModel is set */
        PBD::Signal0<void> ModelChanged;
+       /** Emitted when a parameter's interpolation style is changed */
+       PBD::Signal2<void, Evoral::Parameter, Evoral::ControlList::InterpolationStyle> 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<Evoral::Parameter, Evoral::ControlList::InterpolationStyle> InterpolationStyleMap;
+       InterpolationStyleMap _interpolation_style;
 };
 
 }
index d271cb0dbad5bb7144197109a1a048e270a63d28..6dcea9dd6096a1a9d549e3e01f262654683bcbd8 100644 (file)
@@ -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 */
index bed99d56603abc5b31b5ae3d4f106c853d89532a..40b6eb2a07a04715c489f3bfe83383887d74db37 100644 (file)
@@ -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<Evoral::Control> ac (control_factory (i->first));
-                _controls[ac->parameter()] = ac;
+               add_control (ac);
         }
 }
 int
index 6e2c477a99b9abf458a2dcd45ae1e0a6fe8b61e0..b5d5d24713cad60c7b9be262a13083471b265cf3 100644 (file)
@@ -41,8 +41,9 @@ using namespace PBD;
 
 MidiModel::MidiModel(MidiSource* s)
        : AutomatableSequence<TimeType>(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<MidiSource> source)
@@ -773,7 +777,7 @@ MidiModel::write_to (boost::shared_ptr<MidiSource> source)
         source->drop_model();
         source->mark_streaming_midi_write_started(note_mode(), _midi_source->timeline_position());
 
-        for (Evoral::Sequence<TimeType>::const_iterator i = begin(); i != end(); ++i) {
+        for (Evoral::Sequence<TimeType>::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<TimeType>::const_iterator i = begin(); i != end(); ++i) {
+        for (Evoral::Sequence<TimeType>::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<MidiSource> source, Evoral::Music
         source->drop_model();
         source->mark_streaming_midi_write_started(note_mode(), _midi_source->timeline_position());
 
-        for (Evoral::Sequence<TimeType>::const_iterator i = begin(); i != end(); ++i) {
+        for (Evoral::Sequence<TimeType>::const_iterator i = begin(0, true); i != end(); ++i) {
                 const Evoral::Event<Evoral::MusicalTime>& 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<Evoral::Control>
+MidiModel::control_factory (Evoral::Parameter const & p)
+{
+       boost::shared_ptr<Evoral::Control> 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;
 }
index 3f831b348dd6deff61255d5d76ce594a8a9c42f6..dbc41c8ab63a6331bc8fb6980e3e0cc0595960d6 100644 (file)
@@ -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<Evoral::ControlList::InterpolationStyle> (string_2_enum (prop->value(), s));
+                       set_interpolation_of (p, s);
+               }
+       }
+
        return 0;
 }
 
@@ -160,7 +188,7 @@ MidiSource::midi_read (Evoral::EventSink<nframes_t>& 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<MidiModel> 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<MidiSource> s)
+{
+       copy_interpolation_from (s.get ());
+}
+
+void
+MidiSource::copy_interpolation_from (MidiSource* s)
+{
+       _interpolation_style = s->_interpolation_style;
+
+       /* XXX: should probably emit signals here */
+}
index 585e0a07b27ad68b025db2d9c70668beab56533b..21b5453da47c89e79cd3fd2cb0ed81287a8f5962 100644 (file)
@@ -90,7 +90,10 @@ MidiStretch::run (boost::shared_ptr<Region> r)
        boost::shared_ptr<MidiModel> new_model = new_src->model();
        new_model->start_write();
 
-       for (Evoral::Sequence<MidiModel::TimeType>::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<MidiModel::TimeType>::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<Region> 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);
index e7f5e28b94a6d05ef5b0b35f854bf7d92a9e19b6..e8ddf678c9c17392a036ffa765c53ba131131b20 100644 (file)
@@ -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);
 
index fe5a0f7c8f8e91950d91bb50e86631b938578226..bbdf958815e65e12e837eb90ceb9df9140413e16 100644 (file)
@@ -257,10 +257,6 @@ SMFSource::write_unlocked (MidiRingBuffer<nframes_t>& 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 ()
 {
index 5f842775eea4a318a266864e03b3286954034b77..d207c769259e19e4bf5a50fac1bd78b5853df48a 100644 (file)
@@ -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<void> Dirty;
+       /** Emitted when our interpolation style changes */
+       PBD::Signal1<void, InterpolationStyle> 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<ControlList> cut_copy_clear (double, double, int op);
index b775bb3b4bbf52e08a2d9d4dec9badb7a053409f..95de58dead03f14a231001fa9af3052a4b85cef1 100644 (file)
 #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;
 };
 
 
index 3cddeb38cac2d92d582d9cb1f1d3e8227b1a0392..24a3c44625f6fb2d22bd3ef20f044ac20e46a2c4 100644 (file)
@@ -185,7 +185,7 @@ public:
        class const_iterator {
        public:
                const_iterator();
-               const_iterator(const Sequence<Time>& seq, Time t, std::set<Evoral::Parameter> const &);
+               const_iterator(const Sequence<Time>& seq, Time t, bool, std::set<Evoral::Parameter> const &);
                ~const_iterator();
 
                inline bool valid() const { return !_is_end && _event; }
@@ -220,10 +220,11 @@ public:
                typename SysExes::const_iterator _sysex_iter;
                ControlIterators                 _control_iters;
                ControlIterators::iterator       _control_iter;
+               bool                             _force_discrete;
        };
 
-       const_iterator begin (Time t=0, std::set<Evoral::Parameter> const & f = std::set<Evoral::Parameter> ()) const {
-               return const_iterator (*this, t, f);
+       const_iterator begin (Time t = 0, bool force_discrete = false, std::set<Evoral::Parameter> const & f = std::set<Evoral::Parameter> ()) const {
+               return const_iterator (*this, t, force_discrete, f);
        }
        const const_iterator& end()           const { return _end_iter; }
 
index 3eb6c7d4ce969db3520a1b148509ab15584e0e31..bfe4c953659fd3fa0adbb28d6859fae82ef51078 100644 (file)
@@ -954,10 +954,11 @@ ControlList::rt_safe_earliest_event(double start, double end, double& x, double&
 bool
 ControlList::rt_safe_earliest_event_unlocked(double start, double end, double& x, double& y, bool inclusive) const
 {
-       if (_interpolation == Discrete)
+       if (_interpolation == Discrete) {
                return rt_safe_earliest_event_discrete_unlocked(start, end, x, y, inclusive);
-       else
+       } else {
                return rt_safe_earliest_event_linear_unlocked(start, end, x, y, inclusive);
+       }
 }
 
 
@@ -1356,5 +1357,16 @@ ControlList::move_ranges (const list< RangeMove<double> >& movements)
        maybe_signal_changed ();
 }
 
+void
+ControlList::set_interpolation (InterpolationStyle s)
+{
+       if (_interpolation == s) {
+               return;
+       }
+
+       _interpolation = s;
+       InterpolationChanged (s); /* EMIT SIGNAL */
+}
+
 } // namespace Evoral
 
index d985e347d74c86873fc6bfc27e037ae13367b24d..6142f08870f2ff3152b2d0aa2cb2583334fd9746 100644 (file)
@@ -16,6 +16,7 @@
  * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
  */
 
+#include <iostream>
 #include <limits>
 #include "evoral/ControlSet.hpp"
 #include "evoral/ControlList.hpp"
@@ -43,6 +44,10 @@ ControlSet::add_control(boost::shared_ptr<Control> ac)
        _controls[ac->parameter()] = ac;
 
        ac->ListMarkedDirty.connect_same_thread (_control_connections, boost::bind (&ControlSet::control_list_marked_dirty, this));
+
+       ac->list()->InterpolationChanged.connect_same_thread (
+               _list_connections, boost::bind (&ControlSet::control_list_interpolation_changed, this, ac->parameter(), _1)
+               );
 }
 
 void
@@ -111,10 +116,10 @@ ControlSet::clear_controls ()
        Glib::Mutex::Lock lm (_control_lock);
 
        _control_connections.drop_connections ();
+       _list_connections.drop_connections ();
 
        for (Controls::iterator li = _controls.begin(); li != _controls.end(); ++li)
                li->second->list()->clear();
 }
 
-
 } // namespace Evoral
index a67f32e993654e18c9e4112dd2d0d701b348757f..a9d55056bdf6d19a2214f9ad2cae091d6fd8807a 100644 (file)
@@ -52,14 +52,16 @@ Sequence<Time>::const_iterator::const_iterator()
        _event = boost::shared_ptr< Event<Time> >(new Event<Time>());
 }
 
+/** @param force_discrete true to force ControlLists to use discrete evaluation, otherwise false to get them to use their configured mode */
 template<typename Time>
-Sequence<Time>::const_iterator::const_iterator(const Sequence<Time>& seq, Time t, std::set<Evoral::Parameter> const & filtered)
+Sequence<Time>::const_iterator::const_iterator(const Sequence<Time>& seq, Time t, bool force_discrete, std::set<Evoral::Parameter> const & filtered)
        : _seq(&seq)
        , _type(NIL)
        , _is_end((t == DBL_MAX) || seq.empty())
        , _note_iter(seq.notes().end())
        , _sysex_iter(seq.sysexes().end())
        , _control_iter(_control_iters.end())
+       , _force_discrete (force_discrete)
 {
        DEBUG_TRACE (DEBUG::Sequence, string_compose ("Created Iterator @ %1 (is end: %2)\n)", t, _is_end));
 
@@ -98,7 +100,12 @@ Sequence<Time>::const_iterator::const_iterator(const Sequence<Time>& seq, Time t
                
                DEBUG_TRACE (DEBUG::Sequence, string_compose ("Iterator: control: %1\n", seq._type_map.to_symbol(i->first)));
                double x, y;
-               bool ret = i->second->list()->rt_safe_earliest_event_unlocked(t, DBL_MAX, x, y, true);
+               bool ret;
+               if (_force_discrete) {
+                       ret = i->second->list()->rt_safe_earliest_event_discrete_unlocked (t, DBL_MAX, x, y, true);
+               } else {
+                       ret = i->second->list()->rt_safe_earliest_event_unlocked(t, DBL_MAX, x, y, true);
+               }
                if (!ret) {
                        DEBUG_TRACE (DEBUG::Sequence, string_compose ("Iterator: CC %1 (size %2) has no events past %3\n",
                                                                       i->first.id(), i->second->list()->size(), t));
@@ -246,8 +253,11 @@ Sequence<Time>::const_iterator::operator++()
                break;
        case CONTROL:
                // Increment current controller iterator
-               ret = _control_iter->list->rt_safe_earliest_event_unlocked(
-                               _control_iter->x, DBL_MAX, x, y, false);
+               if (_force_discrete) {
+                       ret = _control_iter->list->rt_safe_earliest_event_discrete_unlocked (_control_iter->x, DBL_MAX, x, y, false);
+               } else {
+                       ret = _control_iter->list->rt_safe_earliest_event_unlocked (_control_iter->x, DBL_MAX, x, y, false);
+               }
                assert(!ret || x > _control_iter->x);
                if (ret) {
                        _control_iter->x = x;
@@ -366,6 +376,7 @@ Sequence<Time>::const_iterator::operator=(const const_iterator& other)
        _note_iter     = other._note_iter;
        _sysex_iter    = other._sysex_iter;
        _control_iters = other._control_iters;
+       _force_discrete = other._force_discrete;
 
        if (other._lock)
                _lock = _seq->read_lock();
@@ -391,7 +402,7 @@ Sequence<Time>::Sequence(const TypeMap& type_map)
         , _overlap_pitch_resolution (FirstOnFirstOff)
        , _writing(false)
        , _type_map(type_map)
-       , _end_iter(*this, DBL_MAX, std::set<Evoral::Parameter> ())
+       , _end_iter(*this, DBL_MAX, false, std::set<Evoral::Parameter> ())
        , _percussive(false)
        , _lowest_note(127)
        , _highest_note(0)
@@ -409,7 +420,7 @@ Sequence<Time>::Sequence(const Sequence<Time>& other)
         , _overlap_pitch_resolution (other._overlap_pitch_resolution)
        , _writing(false)
        , _type_map(other._type_map)
-       , _end_iter(*this, DBL_MAX, std::set<Evoral::Parameter> ())
+       , _end_iter(*this, DBL_MAX, false, std::set<Evoral::Parameter> ())
        , _percussive(other._percussive)
        , _lowest_note(other._lowest_note)
        , _highest_note(other._highest_note)