switch to 5 new fade curves, taken from mixbus2 branch. make xfade context menus...
authorPaul Davis <paul@linuxaudiosystems.com>
Fri, 11 May 2012 21:30:36 +0000 (21:30 +0000)
committerPaul Davis <paul@linuxaudiosystems.com>
Fri, 11 May 2012 21:30:36 +0000 (21:30 +0000)
git-svn-id: svn://localhost/ardour2/branches/3.0@12253 d708f5d6-7413-0410-9779-e7cbd77b26cf

gtk2_ardour/audio_region_view.cc
gtk2_ardour/editor.cc
gtk2_ardour/editor.h
gtk2_ardour/editor_mouse.cc
libs/ardour/ardour/audioregion.h
libs/ardour/ardour/types.h
libs/ardour/audio_playlist.cc
libs/ardour/audioregion.cc
libs/ardour/enums.cc
libs/evoral/evoral/ControlList.hpp
libs/evoral/src/ControlList.cpp

index 165af740377705c20a28b5d36c28d73c21e26368..d9ad504aeed6f8eceb28015e652062df7b8d5cb9 100644 (file)
@@ -560,6 +560,7 @@ AudioRegionView::reset_fade_in_shape_width (framecnt_t width)
        }
 
        if (audio_region()->fade_in_is_xfade()) {
+               cerr << "Fade in changed, reset xfade\n";
                if (fade_in_handle) {
                        fade_in_handle->hide ();
                        fade_in_shape->hide ();
@@ -668,6 +669,7 @@ AudioRegionView::reset_fade_out_shape_width (framecnt_t width)
        }
 
        if (audio_region()->fade_out_is_xfade()) {
+               cerr << "Fade out changed, reset xfade\n";
                if (fade_out_handle) {
                        fade_out_handle->hide ();
                        fade_out_shape->hide ();
index 6dfe47230fbd0b3a705e43d5582ffd9542e9e6be..5e3810977cfceec6a173476e5e8a9ac01ba2f75d 100644 (file)
@@ -1341,64 +1341,98 @@ Editor::action_pre_activated (Glib::RefPtr<Action> const & a)
        }
 }
 
-/** Pop up a context menu for when the user clicks on a crossfade */
 void
-Editor::popup_xfade_context_menu (int button, int32_t time, ArdourCanvas::Item* item, ItemType item_type)
+Editor::fill_xfade_menu (Menu_Helpers::MenuList& items, bool start)
 {
        using namespace Menu_Helpers;
 
-       MenuList& items (xfade_context_menu.items());
+       void (Editor::*emf)(FadeShape);
+       std::map<ARDOUR::FadeShape,Gtk::Image*>* images;
+
+       if (start) {
+               images = &_xfade_in_images;
+               emf = &Editor::set_fade_in_shape;
+       } else {
+               images = &_xfade_out_images;
+               emf = &Editor::set_fade_out_shape;
+       }
+
+       items.push_back (
+               ImageMenuElem (
+                       _("Linear (for highly correlated material)"),
+                       *(*images)[FadeLinear],
+                       sigc::bind (sigc::mem_fun (*this, emf), FadeLinear)
+                       )
+               );
+       
+       dynamic_cast<ImageMenuItem*>(&items.back())->set_always_show_image ();
+       
+       items.push_back (
+               ImageMenuElem (
+                       _("ConstantPower"),
+                       *(*images)[FadeConstantPower],
+                       sigc::bind (sigc::mem_fun (*this, emf), FadeConstantPower)
+                       ));
+       
+       dynamic_cast<ImageMenuItem*>(&items.back())->set_always_show_image ();
        
+       items.push_back (
+               ImageMenuElem (
+                       _("Symmetric"),
+                       *(*images)[FadeSymmetric],
+                       sigc::bind (sigc::mem_fun (*this, emf), FadeSymmetric)
+                       )
+               );
+       
+       dynamic_cast<ImageMenuItem*>(&items.back())->set_always_show_image ();
+       
+       items.push_back (
+               ImageMenuElem (
+                       _("Slow"),
+                       *(*images)[FadeSlow],
+                       sigc::bind (sigc::mem_fun (*this, emf), FadeSlow)
+                       ));
+       
+       dynamic_cast<ImageMenuItem*>(&items.back())->set_always_show_image ();
+       
+       items.push_back (
+               ImageMenuElem (
+                       _("Fast"),
+                       *(*images)[FadeFast],
+                       sigc::bind (sigc::mem_fun (*this, emf), FadeFast)
+                       ));
+       
+       dynamic_cast<ImageMenuItem*>(&items.back())->set_always_show_image ();
+}
+
+/** Pop up a context menu for when the user clicks on a start crossfade */
+void
+Editor::popup_xfade_in_context_menu (int button, int32_t time, ArdourCanvas::Item* item, ItemType item_type)
+{
+       using namespace Menu_Helpers;
+
+       MenuList& items (xfade_in_context_menu.items());
+
        if (items.empty()) {
-               items.push_back (
-                       ImageMenuElem (
-                               _("Linear (for highly correlated material)"),
-                               *_xfade_images[FadeLinear],
-                               sigc::bind (sigc::mem_fun (*this, &Editor::set_fade_in_shape), FadeLinear)
-                               )
-                       );
-               
-               dynamic_cast<ImageMenuItem*>(&items.back())->set_always_show_image ();
-               
-               items.push_back (
-                       ImageMenuElem (
-                               _("ConstantPower (-6dB)"),
-                               *_xfade_images[FadeFast],
-                               sigc::bind (sigc::mem_fun (*this, &Editor::set_fade_in_shape), FadeFast)
-                               ));
-               
-               dynamic_cast<ImageMenuItem*>(&items.back())->set_always_show_image ();
-               
-               items.push_back (
-                       ImageMenuElem (
-                               _("Linear-dB"),
-                               *_xfade_images[FadeSlow],
-                               sigc::bind (sigc::mem_fun (*this, &Editor::set_fade_in_shape), FadeSlow)
-                               )
-                       );
-               
-               dynamic_cast<ImageMenuItem*>(&items.back())->set_always_show_image ();
-
-               items.push_back (
-                       ImageMenuElem (
-                               _("Smooth"),
-                               *_xfade_images[FadeLogB],
-                               sigc::bind (sigc::mem_fun (*this, &Editor::set_fade_in_shape), FadeLogB)
-                               ));
-               
-               dynamic_cast<ImageMenuItem*>(&items.back())->set_always_show_image ();
-               
-               items.push_back (
-                       ImageMenuElem (
-                               _("Fast"),
-                               *_xfade_images[FadeLogA],
-                               sigc::bind (sigc::mem_fun (*this, &Editor::set_fade_in_shape), FadeLogA)
-                               ));
-               
-               dynamic_cast<ImageMenuItem*>(&items.back())->set_always_show_image ();
+               fill_xfade_menu (items, true);
+       }
+
+       xfade_in_context_menu.popup (button, time);
+}
+
+/** Pop up a context menu for when the user clicks on an end crossfade */
+void
+Editor::popup_xfade_out_context_menu (int button, int32_t time, ArdourCanvas::Item* item, ItemType item_type)
+{
+       using namespace Menu_Helpers;
+
+       MenuList& items (xfade_out_context_menu.items());
+
+       if (items.empty()) {
+               fill_xfade_menu (items, false);
        }
 
-       xfade_context_menu.popup (button, time);
+       xfade_out_context_menu.popup (button, time);
 }
 
 
@@ -1448,36 +1482,34 @@ Editor::popup_fade_context_menu (int button, int32_t time, ArdourCanvas::Item* i
 
                        items.push_back (
                                ImageMenuElem (
-                                       _("Slowest"),
-                                       *_fade_in_images[FadeFast],
-                                       sigc::bind (sigc::mem_fun (*this, &Editor::set_fade_in_shape), FadeFast)
+                                       _("Slow"),
+                                       *_fade_in_images[FadeSlow],
+                                       sigc::bind (sigc::mem_fun (*this, &Editor::set_fade_in_shape), FadeSlow)
                                        ));
 
                        dynamic_cast<ImageMenuItem*>(&items.back())->set_always_show_image ();
 
                        items.push_back (
                                ImageMenuElem (
-                                       _("Slow"),
-                                       *_fade_in_images[FadeLogB],
-                                       sigc::bind (sigc::mem_fun (*this, &Editor::set_fade_in_shape), FadeLogB)
+                                       _("Fast"),
+                                       *_fade_in_images[FadeFast],
+                                       sigc::bind (sigc::mem_fun (*this, &Editor::set_fade_in_shape), FadeFast)
                                        ));
 
                        dynamic_cast<ImageMenuItem*>(&items.back())->set_always_show_image ();
 
                        items.push_back (
                                ImageMenuElem (
-                                       _("Fast"),
-                                       *_fade_in_images[FadeLogA],
-                                       sigc::bind (sigc::mem_fun (*this, &Editor::set_fade_in_shape), FadeLogA)
+                                       _("Symmetric"),
+                                       *_fade_in_images[FadeSlow],
+                                       sigc::bind (sigc::mem_fun (*this, &Editor::set_fade_in_shape), FadeSymmetric)
                                        ));
 
-                       dynamic_cast<ImageMenuItem*>(&items.back())->set_always_show_image ();
-
                        items.push_back (
                                ImageMenuElem (
-                                       _("Fastest"),
-                                       *_fade_in_images[FadeSlow],
-                                       sigc::bind (sigc::mem_fun (*this, &Editor::set_fade_in_shape), FadeSlow)
+                                       _("Constant Power"),
+                                       *_fade_in_images[FadeConstantPower],
+                                       sigc::bind (sigc::mem_fun (*this, &Editor::set_fade_in_shape), FadeConstantPower)
                                        ));
 
                        dynamic_cast<ImageMenuItem*>(&items.back())->set_always_show_image ();
@@ -1512,8 +1544,8 @@ Editor::popup_fade_context_menu (int button, int32_t time, ArdourCanvas::Item* i
 
                        items.push_back (
                                ImageMenuElem (
-                                       _("Slowest"),
-                                       *_fade_out_images[FadeFast],
+                                       _("Slow"),
+                                       *_fade_out_images[FadeSlow],
                                        sigc::bind (sigc::mem_fun (*this, &Editor::set_fade_out_shape), FadeSlow)
                                        ));
 
@@ -1521,27 +1553,25 @@ Editor::popup_fade_context_menu (int button, int32_t time, ArdourCanvas::Item* i
 
                        items.push_back (
                                ImageMenuElem (
-                                       _("Slow"),
-                                       *_fade_out_images[FadeLogB],
-                                       sigc::bind (sigc::mem_fun (*this, &Editor::set_fade_out_shape), FadeLogA)
+                                       _("Fast"),
+                                       *_fade_out_images[FadeFast],
+                                       sigc::bind (sigc::mem_fun (*this, &Editor::set_fade_out_shape), FadeFast)
                                        ));
 
                        dynamic_cast<ImageMenuItem*>(&items.back())->set_always_show_image ();
 
                        items.push_back (
                                ImageMenuElem (
-                                       _("Fast"),
-                                       *_fade_out_images[FadeLogA],
-                                       sigc::bind (sigc::mem_fun (*this, &Editor::set_fade_out_shape), FadeLogB)
+                                       _("Symmetric"),
+                                       *_fade_out_images[FadeSlow],
+                                       sigc::bind (sigc::mem_fun (*this, &Editor::set_fade_out_shape), FadeSymmetric)
                                        ));
 
-                       dynamic_cast<ImageMenuItem*>(&items.back())->set_always_show_image ();
-
                        items.push_back (
                                ImageMenuElem (
-                                       _("Fastest"),
-                                       *_fade_out_images[FadeSlow],
-                                       sigc::bind (sigc::mem_fun (*this, &Editor::set_fade_out_shape), FadeFast)
+                                       _("Constant Power"),
+                                       *_fade_out_images[FadeConstantPower],
+                                       sigc::bind (sigc::mem_fun (*this, &Editor::set_fade_out_shape), FadeConstantPower)
                                        ));
 
                        dynamic_cast<ImageMenuItem*>(&items.back())->set_always_show_image ();
@@ -5345,22 +5375,28 @@ void
 Editor::setup_fade_images ()
 {
        _fade_in_images[FadeLinear] = new Gtk::Image (get_icon_path (X_("fadein-linear")));
-       _fade_in_images[FadeFast] = new Gtk::Image (get_icon_path (X_("fadein-short-cut")));
-       _fade_in_images[FadeLogB] = new Gtk::Image (get_icon_path (X_("fadein-slow-cut")));
-       _fade_in_images[FadeLogA] = new Gtk::Image (get_icon_path (X_("fadein-fast-cut")));
-       _fade_in_images[FadeSlow] = new Gtk::Image (get_icon_path (X_("fadein-long-cut")));
+       _fade_in_images[FadeSymmetric] = new Gtk::Image (get_icon_path (X_("fadein-short-cut")));
+       _fade_in_images[FadeFast] = new Gtk::Image (get_icon_path (X_("fadein-slow-cut")));
+       _fade_in_images[FadeSlow] = new Gtk::Image (get_icon_path (X_("fadein-fast-cut")));
+       _fade_in_images[FadeConstantPower] = new Gtk::Image (get_icon_path (X_("fadein-long-cut")));
 
        _fade_out_images[FadeLinear] = new Gtk::Image (get_icon_path (X_("fadeout-linear")));
-       _fade_out_images[FadeFast] = new Gtk::Image (get_icon_path (X_("fadeout-short-cut")));
-       _fade_out_images[FadeLogB] = new Gtk::Image (get_icon_path (X_("fadeout-slow-cut")));
-       _fade_out_images[FadeLogA] = new Gtk::Image (get_icon_path (X_("fadeout-fast-cut")));
-       _fade_out_images[FadeSlow] = new Gtk::Image (get_icon_path (X_("fadeout-long-cut")));
-
-       _xfade_images[FadeLinear] = new Gtk::Image (get_icon_path (X_("fadeout-linear")));
-       _xfade_images[FadeFast] = new Gtk::Image (get_icon_path (X_("fadeout-short-cut")));
-       _xfade_images[FadeLogB] = new Gtk::Image (get_icon_path (X_("fadeout-slow-cut")));
-       _xfade_images[FadeLogA] = new Gtk::Image (get_icon_path (X_("fadeout-fast-cut")));
-       _xfade_images[FadeSlow] = new Gtk::Image (get_icon_path (X_("fadeout-long-cut")));
+       _fade_out_images[FadeSymmetric] = new Gtk::Image (get_icon_path (X_("fadeout-short-cut")));
+       _fade_out_images[FadeFast] = new Gtk::Image (get_icon_path (X_("fadeout-slow-cut")));
+       _fade_out_images[FadeSlow] = new Gtk::Image (get_icon_path (X_("fadeout-fast-cut")));
+       _fade_out_images[FadeConstantPower] = new Gtk::Image (get_icon_path (X_("fadeout-long-cut")));
+       
+       _xfade_in_images[FadeLinear] = new Gtk::Image (get_icon_path (X_("fadeout-linear")));
+       _xfade_in_images[FadeSymmetric] = new Gtk::Image (get_icon_path (X_("fadeout-short-cut")));
+       _xfade_in_images[FadeFast] = new Gtk::Image (get_icon_path (X_("fadeout-slow-cut")));
+       _xfade_in_images[FadeSlow] = new Gtk::Image (get_icon_path (X_("fadeout-fast-cut")));
+       _xfade_in_images[FadeConstantPower] = new Gtk::Image (get_icon_path (X_("fadeout-long-cut")));
+
+       _xfade_out_images[FadeLinear] = new Gtk::Image (get_icon_path (X_("fadeout-linear")));
+       _xfade_out_images[FadeSymmetric] = new Gtk::Image (get_icon_path (X_("fadeout-short-cut")));
+       _xfade_out_images[FadeFast] = new Gtk::Image (get_icon_path (X_("fadeout-slow-cut")));
+       _xfade_out_images[FadeSlow] = new Gtk::Image (get_icon_path (X_("fadeout-fast-cut")));
+       _xfade_out_images[FadeConstantPower] = new Gtk::Image (get_icon_path (X_("fadeout-long-cut")));
 
 }
 
index 3c1ebdbd4924ff2143f9d7c2601598e0aef82b97..2e203e9c75120493126d1f660da190731f953005 100644 (file)
@@ -1337,8 +1337,11 @@ class Editor : public PublicEditor, public PBD::ScopedConnectionList, public ARD
        Gtk::Menu fade_context_menu;
        void popup_fade_context_menu (int, int, ArdourCanvas::Item*, ItemType);
 
-       Gtk::Menu xfade_context_menu;
-       void popup_xfade_context_menu (int, int, ArdourCanvas::Item*, ItemType);
+       Gtk::Menu xfade_in_context_menu;
+       Gtk::Menu xfade_out_context_menu;
+       void popup_xfade_in_context_menu (int, int, ArdourCanvas::Item*, ItemType);
+       void popup_xfade_out_context_menu (int, int, ArdourCanvas::Item*, ItemType);
+       void fill_xfade_menu (Gtk::Menu_Helpers::MenuList& items, bool start);
 
        void set_fade_in_shape (ARDOUR::FadeShape);
        void set_fade_out_shape (ARDOUR::FadeShape);
@@ -2062,7 +2065,8 @@ class Editor : public PublicEditor, public PBD::ScopedConnectionList, public ARD
        void setup_fade_images ();
        std::map<ARDOUR::FadeShape, Gtk::Image*> _fade_in_images;
        std::map<ARDOUR::FadeShape, Gtk::Image*> _fade_out_images;
-       std::map<ARDOUR::FadeShape, Gtk::Image*> _xfade_images;
+       std::map<ARDOUR::FadeShape, Gtk::Image*> _xfade_in_images;
+       std::map<ARDOUR::FadeShape, Gtk::Image*> _xfade_out_images;
 
        Gtk::MenuItem& action_menu_item (std::string const &);
        void action_pre_activated (Glib::RefPtr<Gtk::Action> const &);
index 1724666976ab6205884371a0c7d32cbc98cc4077..d7d1c342806d02168fc29b142b4737048052392d 100644 (file)
@@ -640,15 +640,12 @@ Editor::button_selection (ArdourCanvas::Item* /*item*/, GdkEvent* event, ItemTyp
                }
                break;
 
-
-       case StartCrossFadeItem:
-       case EndCrossFadeItem:
-               break;
-
        case FadeInHandleItem:
        case FadeInItem:
        case FadeOutHandleItem:
        case FadeOutItem:
+       case StartCrossFadeItem:
+       case EndCrossFadeItem:
                if (doing_object_stuff() || (mouse_mode != MouseRange && mouse_mode != MouseObject)) {
                        set_selected_regionview_from_click (press, op);
                } else if (event->type == GDK_BUTTON_PRESS) {
@@ -1484,11 +1481,11 @@ Editor::button_release_handler (ArdourCanvas::Item* item, GdkEvent* event, ItemT
                                break;
 
                        case StartCrossFadeItem:
-                               popup_xfade_context_menu (1, event->button.time, item, item_type);
+                               popup_xfade_in_context_menu (1, event->button.time, item, item_type);
                                break;
 
                        case EndCrossFadeItem:
-                               popup_xfade_context_menu (1, event->button.time, item, item_type);
+                               popup_xfade_out_context_menu (1, event->button.time, item, item_type);
                                break;
 
                        case StreamItem:
index 013629637d4d9d07ebc3ad20c6491f203d2f895a..c03f32cb636d6ad72dbc9cdc076fd327de81a744 100644 (file)
@@ -180,7 +180,6 @@ class AudioRegion : public Region
 
   private:
        friend class RegionFactory;
-       friend class Crossfade;
 
        AudioRegion (boost::shared_ptr<AudioSource>);
        AudioRegion (const SourceList &);
index 127c840840ef20feafd6b5bd2d8ca15ae24ca6fb..6ac9ebfe70d681e80adf94cc7d932186b28738d2 100644 (file)
@@ -557,10 +557,8 @@ namespace ARDOUR {
                FadeLinear,
                FadeFast,
                FadeSlow,
-               FadeLogA,
-               FadeLogB,
-               FadeConstantPowerMinus3dB,
-               FadeConstantPowerMinus6dB,
+               FadeConstantPower,
+               FadeSymmetric,
        };
 
 } // namespace ARDOUR
index 867f6f0ce422bd67886d82060d6d90dbedb11e21..9b433d160bcfdb787faedc3ca4895e8f5d47d9d8 100644 (file)
@@ -328,12 +328,16 @@ AudioPlaylist::check_crossfades (Evoral::Range<framepos_t> range)
                                        top->set_fade_in_active (true);
                                        top->set_fade_in_is_xfade (true);
 
+                                       /* XXX may 2012: -3dB and -6dB curves
+                                        * are the same right now 
+                                        */
+
                                        switch (_session.config.get_xfade_choice ()) {
                                        case ConstantPowerMinus3dB:
-                                               top->set_fade_in (FadeConstantPowerMinus3dB, len);
+                                               top->set_fade_in (FadeConstantPower, len);
                                                break;
                                        case ConstantPowerMinus6dB:
-                                               top->set_fade_in (FadeConstantPowerMinus6dB, len);
+                                               top->set_fade_in (FadeConstantPower, len);
                                                break;
                                        case RegionFades:
                                                top->set_fade_in_length (len);
@@ -369,10 +373,10 @@ AudioPlaylist::check_crossfades (Evoral::Range<framepos_t> range)
 
                                        switch (_session.config.get_xfade_choice ()) {
                                        case ConstantPowerMinus3dB:
-                                               top->set_fade_out (FadeConstantPowerMinus3dB, len);
+                                               top->set_fade_out (FadeConstantPower, len);
                                                break;
                                        case ConstantPowerMinus6dB:
-                                               top->set_fade_out (FadeConstantPowerMinus6dB, len);
+                                               top->set_fade_out (FadeConstantPower, len);
                                                break;
                                        case RegionFades:
                                                top->set_fade_out_length (len);
index 066cf368f51ed16c2edbdbd161493f509bcaf922..1e85b61ce5d41e0f8d98a4d0fe72197093636105 100644 (file)
@@ -65,6 +65,90 @@ namespace ARDOUR {
        }
 }
 
+static const double VERY_SMALL_SIGNAL = 0.0000001;  //-140dB
+
+/* Curve manipulations */
+
+static void
+reverse_curve (boost::shared_ptr<Evoral::ControlList> dst, boost::shared_ptr<const Evoral::ControlList> src)
+{
+       size_t len = src->back()->when;
+       
+       for (Evoral::ControlList::const_iterator it = src->begin(); it!=src->end(); it++) {
+               dst->add ( len - (*it)->when, (*it)->value );
+       }
+}
+
+static void
+generate_inverse_power_curve (boost::shared_ptr<Evoral::ControlList> dst, boost::shared_ptr<const Evoral::ControlList> src)
+{
+       //calc inverse curve using sum of squares
+       for (Evoral::ControlList::const_iterator it = src->begin(); it!=src->end(); ++it ) {
+               float value = (*it)->value;
+               value = 1 - powf(value,2);
+               value = sqrtf(value);
+               dst->fast_simple_add ( (*it)->when, value );
+       }
+}
+
+/*
+static void
+generate_inverse_coefficient_curve (boost::shared_ptr<Evoral::ControlList> dst, boost::shared_ptr<const Evoral::ControlList> src)
+{
+       //calc inverse gain coefficient curve
+       for (Evoral::ControlList::const_iterator it = src->begin(); it!=src->end(); ++it ) {
+               float value = 1.0 - (*it)->value;
+               dst->fast_simple_add ( (*it)->when, value );
+       }
+}
+*/
+
+static void
+generate_db_fade (boost::shared_ptr<Evoral::ControlList> dst, double len, int num_steps, float dB_drop)
+{
+       dst->fast_simple_add (0, 1);
+
+       //generate a fade-out curve by successively applying a gain drop
+       float fade_speed = dB_to_coefficient(dB_drop / (float) num_steps);
+       for (int i = 1; i < (num_steps-1); i++) {
+               float coeff = 1.0;
+               for (int j = 0; j < i; j++) {
+                       coeff *= fade_speed;
+               }
+               dst->fast_simple_add (len*(double)i/(double)num_steps, coeff);
+       }
+
+       dst->fast_simple_add (len, VERY_SMALL_SIGNAL);
+}
+
+static void
+merge_curves (boost::shared_ptr<Evoral::ControlList> dst, 
+             boost::shared_ptr<const Evoral::ControlList> curve1, 
+             boost::shared_ptr<const Evoral::ControlList> curve2)
+{
+       Evoral::ControlList::EventList::size_type size = curve1->size();
+
+       //curve lengths must match for now
+       if (size != curve2->size()) {
+               return;
+       }
+       
+       Evoral::ControlList::const_iterator c1 = curve1->begin();
+       int count = 0;
+       for (Evoral::ControlList::const_iterator c2 = curve2->begin(); c2!=curve2->end(); c2++ ) {
+               float v1 = accurate_coefficient_to_dB((*c1)->value);
+               float v2 = accurate_coefficient_to_dB((*c2)->value);
+               
+               double interp = v1 * ( 1.0-( (double)count / (double)size) );
+               interp += v2 * ( (double)count / (double)size );
+
+               interp = dB_to_coefficient(interp);
+               dst->add ( (*c1)->when, interp );
+               c1++;
+               count++;
+       }
+}
+
 void
 AudioRegion::make_property_quarks ()
 {
@@ -133,7 +217,9 @@ AudioRegion::AudioRegion (Session& s, framepos_t start, framecnt_t len, std::str
        , AUDIOREGION_STATE_DEFAULT
        , _automatable (s)
        , _fade_in (new AutomationList(Evoral::Parameter(FadeInAutomation)))
+       , _inverse_fade_in (new AutomationList(Evoral::Parameter(FadeInAutomation)))
        , _fade_out (new AutomationList(Evoral::Parameter(FadeOutAutomation)))
+       , _inverse_fade_out (new AutomationList(Evoral::Parameter(FadeOutAutomation)))
        , _envelope (new AutomationList(Evoral::Parameter(EnvelopeAutomation)))
        , _fade_in_suspended (0)
        , _fade_out_suspended (0)
@@ -150,7 +236,9 @@ AudioRegion::AudioRegion (const SourceList& srcs)
        , AUDIOREGION_STATE_DEFAULT
        , _automatable(srcs[0]->session())
        , _fade_in (new AutomationList(Evoral::Parameter(FadeInAutomation)))
+       , _inverse_fade_in (new AutomationList(Evoral::Parameter(FadeInAutomation)))
        , _fade_out (new AutomationList(Evoral::Parameter(FadeOutAutomation)))
+       , _inverse_fade_out (new AutomationList(Evoral::Parameter(FadeOutAutomation)))
        , _envelope (new AutomationList(Evoral::Parameter(EnvelopeAutomation)))
        , _fade_in_suspended (0)
        , _fade_out_suspended (0)
@@ -166,7 +254,9 @@ AudioRegion::AudioRegion (boost::shared_ptr<const AudioRegion> other)
        , AUDIOREGION_COPY_STATE (other)
        , _automatable (other->session())
        , _fade_in (new AutomationList (*other->_fade_in))
+       , _inverse_fade_in (new AutomationList(*other->_inverse_fade_in))
        , _fade_out (new AutomationList (*other->_fade_out))
+       , _inverse_fade_out (new AutomationList (*other->_inverse_fade_out))
          /* As far as I can see, the _envelope's times are relative to region position, and have nothing
             to do with sources (and hence _start).  So when we copy the envelope, we just use the supplied offset.
          */
@@ -192,7 +282,9 @@ AudioRegion::AudioRegion (boost::shared_ptr<const AudioRegion> other, framecnt_t
        , AUDIOREGION_COPY_STATE (other)
        , _automatable (other->session())
        , _fade_in (new AutomationList (*other->_fade_in))
+       , _inverse_fade_in (new AutomationList(*other->_inverse_fade_in))
        , _fade_out (new AutomationList (*other->_fade_out))
+       , _inverse_fade_out (new AutomationList (*other->_inverse_fade_out))
          /* As far as I can see, the _envelope's times are relative to region position, and have nothing
             to do with sources (and hence _start).  So when we copy the envelope, we just use the supplied offset.
          */
@@ -218,7 +310,9 @@ AudioRegion::AudioRegion (boost::shared_ptr<const AudioRegion> other, const Sour
        , AUDIOREGION_COPY_STATE (other)
        , _automatable (other->session())
        , _fade_in (new AutomationList (*other->_fade_in))
+       , _inverse_fade_in (new AutomationList(*other->_inverse_fade_in))
        , _fade_out (new AutomationList (*other->_fade_out))
+       , _inverse_fade_out (new AutomationList (*other->_inverse_fade_out))
        , _envelope (new AutomationList (*other->_envelope))
        , _fade_in_suspended (0)
        , _fade_out_suspended (0)
@@ -241,7 +335,9 @@ AudioRegion::AudioRegion (SourceList& srcs)
        , AUDIOREGION_STATE_DEFAULT
        , _automatable(srcs[0]->session())
        , _fade_in (new AutomationList(Evoral::Parameter(FadeInAutomation)))
+       , _inverse_fade_in (new AutomationList(Evoral::Parameter(FadeInAutomation)))
        , _fade_out (new AutomationList(Evoral::Parameter(FadeOutAutomation)))
+       , _inverse_fade_out (new AutomationList(Evoral::Parameter(FadeOutAutomation)))
        , _envelope (new AutomationList(Evoral::Parameter(EnvelopeAutomation)))
        , _fade_in_suspended (0)
        , _fade_out_suspended (0)
@@ -830,17 +926,11 @@ AudioRegion::_set_state (const XMLNode& node, int version, PropertyChange& what_
                } else if (child->name() == "InvFadeIn") {
                        XMLNode* grandchild = child->child ("AutomationList");
                        if (grandchild) {
-                               if (!_inverse_fade_in) {
-                                       _inverse_fade_in.reset (new AutomationList (Evoral::Parameter (FadeInAutomation)));
-                               }
                                _inverse_fade_in->set_state (*grandchild, version);
                        }
                } else if (child->name() == "InvFadeOut") {
                        XMLNode* grandchild = child->child ("AutomationList");
                        if (grandchild) {
-                               if (!_inverse_fade_out) {
-                                       _inverse_fade_out.reset (new AutomationList (Evoral::Parameter (FadeOutAutomation)));
-                               }
                                _inverse_fade_out->set_state (*grandchild, version);
                        }
                }
@@ -893,117 +983,70 @@ AudioRegion::set_fade_in (boost::shared_ptr<AutomationList> f)
 void
 AudioRegion::set_fade_in (FadeShape shape, framecnt_t len)
 {
+       boost::shared_ptr<Evoral::ControlList> c1 (new Evoral::ControlList (FadeInAutomation));
+       boost::shared_ptr<Evoral::ControlList> c2 (new Evoral::ControlList (FadeInAutomation));
+       boost::shared_ptr<Evoral::ControlList> c3 (new Evoral::ControlList (FadeInAutomation));
+
+       cerr << "Resetting fade in to " << shape << " len = " << len << endl;
+
        _fade_in->freeze ();
        _fade_in->clear ();
+       _inverse_fade_in->clear ();
 
        switch (shape) {
        case FadeLinear:
                _fade_in->fast_simple_add (0.0, 0.0);
                _fade_in->fast_simple_add (len, 1.0);
-               _inverse_fade_in.reset ();
+               reverse_curve (_inverse_fade_in, _fade_in);
                break;
 
        case FadeFast:
-               _fade_in->fast_simple_add (0, 0);
-               _fade_in->fast_simple_add (len * 0.389401, 0.0333333);
-               _fade_in->fast_simple_add (len * 0.629032, 0.0861111);
-               _fade_in->fast_simple_add (len * 0.829493, 0.233333);
-               _fade_in->fast_simple_add (len * 0.9447, 0.483333);
-               _fade_in->fast_simple_add (len * 0.976959, 0.697222);
-               _fade_in->fast_simple_add (len, 1);
-               _inverse_fade_in.reset ();
+               generate_db_fade (_fade_in, len, 10, -60);
+               reverse_curve (c1, _fade_in);
+               _fade_in->copy_events (*c1);
+               generate_inverse_power_curve (_inverse_fade_in, _fade_in);
                break;
 
        case FadeSlow:
-               _fade_in->fast_simple_add (0, 0);
-               _fade_in->fast_simple_add (len * 0.0207373, 0.197222);
-               _fade_in->fast_simple_add (len * 0.0645161, 0.525);
-               _fade_in->fast_simple_add (len * 0.152074, 0.802778);
-               _fade_in->fast_simple_add (len * 0.276498, 0.919444);
-               _fade_in->fast_simple_add (len * 0.481567, 0.980556);
-               _fade_in->fast_simple_add (len * 0.767281, 1);
-               _fade_in->fast_simple_add (len, 1);
-               _inverse_fade_in.reset ();
-               break;
-
-       case FadeLogA:
-               _fade_in->fast_simple_add (0, 0);
-               _fade_in->fast_simple_add (len * 0.0737327, 0.308333);
-               _fade_in->fast_simple_add (len * 0.246544, 0.658333);
-               _fade_in->fast_simple_add (len * 0.470046, 0.886111);
-               _fade_in->fast_simple_add (len * 0.652074, 0.972222);
-               _fade_in->fast_simple_add (len * 0.771889, 0.988889);
-               _fade_in->fast_simple_add (len, 1);
-               _inverse_fade_in.reset ();
-               break;
-
-       case FadeLogB:
-               _fade_in->fast_simple_add (0, 0);
-               _fade_in->fast_simple_add (len * 0.304147, 0.0694444);
-               _fade_in->fast_simple_add (len * 0.529954, 0.152778);
-               _fade_in->fast_simple_add (len * 0.725806, 0.333333);
-               _fade_in->fast_simple_add (len * 0.847926, 0.558333);
-               _fade_in->fast_simple_add (len * 0.919355, 0.730556);
-               _fade_in->fast_simple_add (len, 1);
-               _inverse_fade_in.reset ();
+               generate_db_fade (c1, len, 10, -1);  // start off with a slow fade
+               generate_db_fade (c2, len, 10, -80); // end with a fast fade
+               merge_curves (_fade_in, c1, c2);
+               generate_inverse_power_curve (_inverse_fade_in, _fade_in);
                break;
 
-       case FadeConstantPowerMinus3dB:
-               _fade_in->fast_simple_add (0.0, 0.0);
-               _fade_in->fast_simple_add ((len * 0.166667), 0.282192);
-               _fade_in->fast_simple_add ((len * 0.333333), 0.518174);
-               _fade_in->fast_simple_add ((len * 0.500000), 0.707946);
-               _fade_in->fast_simple_add ((len * 0.666667), 0.851507);
-               _fade_in->fast_simple_add ((len * 0.833333), 0.948859);
-               _fade_in->fast_simple_add (len, 1.0);
-
-               /* setup complementary fade out for lower layers */
-
-               if (!_inverse_fade_in) {
-                       _inverse_fade_in.reset (new AutomationList (Evoral::Parameter (FadeInAutomation)));
+       case FadeConstantPower:
+               for (int i = 0; i < 9; ++i) {
+                       float dist = (float) i / 10.0f;
+                       _fade_in->fast_simple_add (len*dist, sin (dist*M_PI/2));
                }
-
-               _inverse_fade_in->clear ();
-               _inverse_fade_in->fast_simple_add (0.0, 1.0);
-               _inverse_fade_in->fast_simple_add ((len * 0.166667), 0.948859);
-               _inverse_fade_in->fast_simple_add ((len * 0.333333), 0.851507);
-               _inverse_fade_in->fast_simple_add ((len * 0.500000), 0.707946);
-               _inverse_fade_in->fast_simple_add ((len * 0.666667), 0.518174);
-               _inverse_fade_in->fast_simple_add ((len * 0.833333), 0.282192);
-               _inverse_fade_in->fast_simple_add (len, 0.0);
-
+               _fade_in->fast_simple_add (len, 1.0);
+               generate_inverse_power_curve (_inverse_fade_in, _fade_in);
                break;
                
-       case FadeConstantPowerMinus6dB:
-               _fade_in->fast_simple_add (0.0, 0.0);
-               _fade_in->fast_simple_add ((len * 0.166667), 0.166366);
-               _fade_in->fast_simple_add ((len * 0.333333), 0.332853);
-               _fade_in->fast_simple_add ((len * 0.500000), 0.499459);
-               _fade_in->fast_simple_add ((len * 0.666667), 0.666186);
-               _fade_in->fast_simple_add ((len * 0.833333), 0.833033);
-               _fade_in->fast_simple_add (len, 1.0);
-
-               /* setup complementary fade out for lower layers */
-
-               if (!_inverse_fade_in) {
-                       _inverse_fade_in.reset (new AutomationList (Evoral::Parameter (FadeInAutomation)));
+       case FadeSymmetric:
+               // starts kind of like a constant power but has a slower fadeout
+               // however it is NOT constant power and there will be a level drop in the middle of the crossfade
+               c1->fast_simple_add (0.0, 1.0);
+               for ( int i = 1; i < 9; i++ ) {
+                       float dist = (float)i/10.0;
+                       c1->fast_simple_add ((len * dist), cos(dist*M_PI/10.0));
                }
+               c1->fast_simple_add (len, VERY_SMALL_SIGNAL);
 
-               _inverse_fade_in->clear ();
-               _inverse_fade_in->fast_simple_add (0.0, 1.0);
-               _inverse_fade_in->fast_simple_add ((len * 0.166667), 0.833033);
-               _inverse_fade_in->fast_simple_add ((len * 0.333333), 0.666186);
-               _inverse_fade_in->fast_simple_add ((len * 0.500000), 0.499459);
-               _inverse_fade_in->fast_simple_add ((len * 0.666667), 0.332853);
-               _inverse_fade_in->fast_simple_add ((len * 0.833333), 0.166366);
-               _inverse_fade_in->fast_simple_add (len, 0.0);
+               //curve 2 is a slow fade at end
+               generate_db_fade (c2, len, 10, -30 );
 
+               merge_curves (c3, c1, c2);
+               reverse_curve (_fade_in, c3);
+               reverse_curve (_inverse_fade_in, _fade_in );
                break;
        }
 
        _default_fade_in = false;
        _fade_in->thaw ();
+       cerr << "SEND CHANGE SIGNAL\n";
        send_change (PropertyChange (Properties::fade_in));
+       cerr << "DONE CHANGE SIGNAL\n";
 }
 
 void
@@ -1020,115 +1063,65 @@ AudioRegion::set_fade_out (boost::shared_ptr<AutomationList> f)
 void
 AudioRegion::set_fade_out (FadeShape shape, framecnt_t len)
 {
+       boost::shared_ptr<Evoral::ControlList> c1 (new Evoral::ControlList (FadeOutAutomation));
+       boost::shared_ptr<Evoral::ControlList> c2 (new Evoral::ControlList (FadeOutAutomation));
+
        _fade_out->freeze ();
        _fade_out->clear ();
+       _inverse_fade_out->clear ();
 
        switch (shape) {
-       case FadeFast:
-               _fade_out->fast_simple_add (0.0, 1.0);
-               _fade_out->fast_simple_add (len * 0.023041, 0.697222);
-               _fade_out->fast_simple_add (len * 0.0553,   0.483333);
-               _fade_out->fast_simple_add (len * 0.170507, 0.233333);
-               _fade_out->fast_simple_add (len * 0.370968, 0.0861111);
-               _fade_out->fast_simple_add (len * 0.610599, 0.0333333);
-               _fade_out->fast_simple_add (1.0, 0.0);
-               _inverse_fade_out.reset ();
-               break;
-
-       case FadeLogA:
-               _fade_out->fast_simple_add (0, 1.0);
-               _fade_out->fast_simple_add (len * 0.228111, 0.988889);
-               _fade_out->fast_simple_add (len * 0.347926, 0.972222);
-               _fade_out->fast_simple_add (len * 0.529954, 0.886111);
-               _fade_out->fast_simple_add (len * 0.753456, 0.658333);
-               _fade_out->fast_simple_add (len * 0.9262673, 0.308333);
-               _fade_out->fast_simple_add (len, 0.0);
-               _inverse_fade_out.reset ();
-               break;
-
-       case FadeSlow:
+       case FadeLinear:
                _fade_out->fast_simple_add (0.0, 1.0);
-               _fade_out->fast_simple_add (len * 0.305556, 1);
-               _fade_out->fast_simple_add (len * 0.548611, 0.991736);
-               _fade_out->fast_simple_add (len * 0.759259, 0.931129);
-               _fade_out->fast_simple_add (len * 0.918981, 0.68595);
-               _fade_out->fast_simple_add (len * 0.976852, 0.22865);
-               _fade_out->fast_simple_add (len, 0.0);
-               _inverse_fade_out.reset ();
+               _fade_out->fast_simple_add (len, VERY_SMALL_SIGNAL);
+               reverse_curve (_inverse_fade_out, _fade_out);
                break;
-
-       case FadeLogB:
-               _fade_out->fast_simple_add (0.0, 1.0);
-               _fade_out->fast_simple_add (len * 0.080645, 0.730556);
-               _fade_out->fast_simple_add (len * 0.277778, 0.289256);
-               _fade_out->fast_simple_add (len * 0.470046, 0.152778);
-               _fade_out->fast_simple_add (len * 0.695853, 0.0694444);
-               _fade_out->fast_simple_add (len, 0.0);
-               _inverse_fade_out.reset ();     
+               
+       case FadeFast: 
+               generate_db_fade (_fade_out, len, 10, -60 );
+               generate_inverse_power_curve (_inverse_fade_out, _fade_out);
                break;
-
-       case FadeLinear:
-               _fade_out->fast_simple_add (0.0, 1.0);
-               _fade_out->fast_simple_add (len, 0.0);
-               _inverse_fade_out.reset ();
+               
+       case FadeSlow: 
+               generate_db_fade (c1, len, 10, -1 );  //start off with a slow fade
+               generate_db_fade (c2, len, 10, -80 );  //end with a fast fade
+               merge_curves (_fade_out, c1, c2);
+               generate_inverse_power_curve (_inverse_fade_out, _fade_out);
                break;
 
-       case FadeConstantPowerMinus3dB:
+       case FadeConstantPower:
+               //constant-power fades use a sin/cos relationship
+               //the cutoff is abrupt but it has the benefit of being symmetrical
                _fade_out->fast_simple_add (0.0, 1.0);
-               _fade_out->fast_simple_add ((len * 0.166667), 0.948859);
-               _fade_out->fast_simple_add ((len * 0.333333), 0.851507);
-               _fade_out->fast_simple_add ((len * 0.500000), 0.707946);
-               _fade_out->fast_simple_add ((len * 0.666667), 0.518174);
-               _fade_out->fast_simple_add ((len * 0.833333), 0.282192);
-               _fade_out->fast_simple_add (len, 0.0);
-
-               /* setup complementary fade in for lower layers */
-
-               if (!_inverse_fade_out) {
-                       _inverse_fade_out.reset (new AutomationList (Evoral::Parameter (FadeOutAutomation)));
+               for (int i = 1; i < 9; i++ ) {
+                       float dist = (float)i/10.0;
+                       _fade_out->fast_simple_add ((len * dist), cos(dist*M_PI/2));
                }
-
-               _inverse_fade_out->clear ();
-               _inverse_fade_out->fast_simple_add (0.0, 0.0);
-               _inverse_fade_out->fast_simple_add ((len * 0.166667), 0.282192);
-               _inverse_fade_out->fast_simple_add ((len * 0.333333), 0.518174);
-               _inverse_fade_out->fast_simple_add ((len * 0.500000), 0.707946);
-               _inverse_fade_out->fast_simple_add ((len * 0.666667), 0.851507);
-               _inverse_fade_out->fast_simple_add ((len * 0.833333), 0.948859);
-               _inverse_fade_out->fast_simple_add (len, 1.0);
-
+               _fade_out->fast_simple_add (len, VERY_SMALL_SIGNAL);
+               generate_inverse_power_curve (_inverse_fade_out, _fade_out);
                break;
-
-       case FadeConstantPowerMinus6dB:
-               _fade_out->fast_simple_add (0.0, 1.0);
-               _fade_out->fast_simple_add ((len * 0.166667), 0.833033);
-               _fade_out->fast_simple_add ((len * 0.333333), 0.666186);
-               _fade_out->fast_simple_add ((len * 0.500000), 0.499459);
-               _fade_out->fast_simple_add ((len * 0.666667), 0.332853);
-               _fade_out->fast_simple_add ((len * 0.833333), 0.166366);
-               _fade_out->fast_simple_add (len, 0.0);
-
-               /* setup complementary fade in for lower layers */
-
-               if (!_inverse_fade_out) {
-                       _inverse_fade_out.reset (new AutomationList (Evoral::Parameter (FadeOutAutomation)));
+               
+       case FadeSymmetric:
+               //starts kind of like a constant power but has a slower fadeout
+               //however it is NOT constant power and there will be a level drop in the middle of the crossfade
+               c1->fast_simple_add (0.0, 1.0);
+               for ( int i = 1; i < 9; i++ ) {
+                       float dist = (float)i/10.0;
+                       c1->fast_simple_add ((len * dist), cos(dist*M_PI/10.0));  //cheesy way of making a flat line
                }
+               c1->fast_simple_add (len, VERY_SMALL_SIGNAL);
 
-               _inverse_fade_out->clear ();
-               _inverse_fade_out->fast_simple_add (0.0, 0.0);
-               _inverse_fade_out->fast_simple_add ((len * 0.166667), 0.166366);
-               _inverse_fade_out->fast_simple_add ((len * 0.333333), 0.332853);
-               _inverse_fade_out->fast_simple_add ((len * 0.500000), 0.499459);
-               _inverse_fade_out->fast_simple_add ((len * 0.666667), 0.666186);
-               _inverse_fade_out->fast_simple_add ((len * 0.833333), 0.833033);
-               _inverse_fade_out->fast_simple_add (len, 1.0);
+               //curve 2 is a slow fade at end
+               generate_db_fade (c2, len, 10, -30);
 
+               merge_curves (_fade_out,  c1, c2);
+               reverse_curve (_inverse_fade_out, _fade_out);
                break;
        }
 
        _default_fade_out = false;
        _fade_out->thaw ();
-       send_change (PropertyChange (Properties::fade_in));
+       send_change (PropertyChange (Properties::fade_out));
 }
 
 void
@@ -1879,6 +1872,11 @@ AudioRegion::verify_xfade_bounds (framecnt_t len, bool start)
        boost::shared_ptr<Region> other = get_single_other_xfade_region (start);
        framecnt_t maxlen;
 
+       if (!other) {
+               /* zero or > 2 regions here, don't care about len */
+               return len;
+       }
+
        /* we overlap a single region. clamp the length of an xfade to
           the maximum possible duration of the overlap (if the other
           region were trimmed appropriately).
index 54c6214ee5bfa414b56f0d391decc5cb679eb15d..c0d6107639eb0218974889572e92fbfa1c46a9e3 100644 (file)
@@ -414,10 +414,8 @@ setup_enum_writer ()
        REGISTER_ENUM (FadeLinear);
        REGISTER_ENUM (FadeFast);
        REGISTER_ENUM (FadeSlow);
-       REGISTER_ENUM (FadeLogA);
-       REGISTER_ENUM (FadeLogB);
-       REGISTER_ENUM (FadeConstantPowerMinus3dB);
-       REGISTER_ENUM (FadeConstantPowerMinus6dB);
+       REGISTER_ENUM (FadeConstantPower);
+       REGISTER_ENUM (FadeSymmetric);
        REGISTER (_FadeShape);
 
        REGISTER_CLASS_ENUM (Diskstream, Recordable);
index 30b9fca4307d594035f3034644b64f9fd2da3178..c54c231ca70716ffdb910b4448541f4b39e3aaf4 100644 (file)
@@ -95,6 +95,7 @@ public:
 
        ControlList& operator= (const ControlList&);
        bool operator== (const ControlList&);
+        void copy_events (const ControlList&);
 
        virtual void freeze();
        virtual void thaw ();
index 9c38f67b299315e66e713f8ed583fdf7ff73022a..9a820294efb7da7144f9f775abe63b2cce49d9f5 100644 (file)
@@ -80,9 +80,7 @@ ControlList::ControlList (const ControlList& other)
        _search_cache.first = _events.end();
        _sort_pending = false;
 
-       for (const_iterator i = other._events.begin(); i != other._events.end(); ++i) {
-               _events.push_back (new ControlEvent (**i));
-       }
+       copy_events (other);
 
        mark_dirty ();
 }
@@ -106,9 +104,7 @@ ControlList::ControlList (const ControlList& other, double start, double end)
        boost::shared_ptr<ControlList> section = const_cast<ControlList*>(&other)->copy (start, end);
 
        if (!section->empty()) {
-               for (iterator i = section->begin(); i != section->end(); ++i) {
-                       _events.push_back (new ControlEvent ((*i)->when, (*i)->value));
-               }
+               copy_events (*(section.get()));
        }
 
        mark_dirty ();
@@ -147,23 +143,30 @@ ControlList::operator= (const ControlList& other)
 {
        if (this != &other) {
 
-               _events.clear ();
-
-               for (const_iterator i = other._events.begin(); i != other._events.end(); ++i) {
-                       _events.push_back (new ControlEvent (**i));
-               }
-
                _min_yval = other._min_yval;
                _max_yval = other._max_yval;
                _default_value = other._default_value;
-
-               mark_dirty ();
-               maybe_signal_changed ();
+               
+               copy_events (other);
        }
 
        return *this;
 }
 
+void
+ControlList::copy_events (const ControlList& other)
+{
+       {
+               Glib::Mutex::Lock lm (_lock);
+               _events.clear ();
+               for (const_iterator i = other.begin(); i != other.end(); ++i) {
+                       _events.push_back (new ControlEvent ((*i)->when, (*i)->value));
+               }
+               mark_dirty ();
+       }
+       maybe_signal_changed ();
+}
+
 void
 ControlList::create_curve()
 {