rename all Evoral source from .(hpp|cpp)$ to .(h|cc)
authorPaul Davis <paul@linuxaudiosystems.com>
Fri, 25 Oct 2019 19:13:51 +0000 (13:13 -0600)
committerPaul Davis <paul@linuxaudiosystems.com>
Sat, 2 Nov 2019 22:32:18 +0000 (16:32 -0600)
171 files changed:
gtk2_ardour/audio_region_view.cc
gtk2_ardour/automation_line.cc
gtk2_ardour/automation_line.h
gtk2_ardour/automation_selection.h
gtk2_ardour/crossfade_edit.h
gtk2_ardour/curvetest.cc
gtk2_ardour/generic_pluginui.cc
gtk2_ardour/ghostregion.cc
gtk2_ardour/hit.cc
gtk2_ardour/item_counts.h
gtk2_ardour/midi_list_editor.cc
gtk2_ardour/midi_region_view.cc
gtk2_ardour/note.cc
gtk2_ardour/note_base.cc
gtk2_ardour/note_player.h
gtk2_ardour/patch_change_dialog.h
gtk2_ardour/patch_change_widget.cc
gtk2_ardour/public_editor.h
gtk2_ardour/region_gain_line.cc
gtk2_ardour/route_time_axis.cc
gtk2_ardour/sfdb_ui.cc
gtk2_ardour/time_axis_view.h
libs/ardour/amp.cc
libs/ardour/ardour/async_midi_port.h
libs/ardour/ardour/automatable.h
libs/ardour/ardour/automatable_sequence.h
libs/ardour/ardour/automation_control.h
libs/ardour/ardour/automation_list.h
libs/ardour/ardour/beats_samples_converter.h
libs/ardour/ardour/buffer_set.h
libs/ardour/ardour/control_group.h
libs/ardour/ardour/event_ring_buffer.h
libs/ardour/ardour/event_type_map.h
libs/ardour/ardour/evoral_types_convert.h
libs/ardour/ardour/gain_control.h
libs/ardour/ardour/instrument_info.h
libs/ardour/ardour/lua_api.h
libs/ardour/ardour/midi_automation_list_binder.h
libs/ardour/ardour/midi_buffer.h
libs/ardour/ardour/midi_cursor.h
libs/ardour/ardour/midi_model.h
libs/ardour/ardour/midi_operator.h
libs/ardour/ardour/midi_playlist.h
libs/ardour/ardour/midi_region.h
libs/ardour/ardour/midi_scene_change.h
libs/ardour/ardour/midi_source.h
libs/ardour/ardour/mute_master.h
libs/ardour/ardour/pan_controllable.h
libs/ardour/ardour/pannable.h
libs/ardour/ardour/parameter_descriptor.h
libs/ardour/ardour/parameter_types.h
libs/ardour/ardour/playlist.h
libs/ardour/ardour/rt_midibuffer.h
libs/ardour/ardour/session.h
libs/ardour/ardour/slavable.h
libs/ardour/ardour/smf_source.h
libs/ardour/ardour/types.h
libs/ardour/audio_track.cc
libs/ardour/audioregion.cc
libs/ardour/automation_list.cc
libs/ardour/enums.cc
libs/ardour/event_type_map.cc
libs/ardour/gain_control.cc
libs/ardour/import.cc
libs/ardour/luabindings.cc
libs/ardour/midi_model.cc
libs/ardour/midi_playlist.cc
libs/ardour/midi_scene_changer.cc
libs/ardour/midi_source.cc
libs/ardour/midi_state_tracker.cc
libs/ardour/mute_control.cc
libs/ardour/panner_shell.cc
libs/ardour/session_command.cc
libs/ardour/session_state.cc
libs/ardour/slavable_automation_control.cc
libs/ardour/smf_source.cc
libs/ardour/test/combine_regions_test.cc
libs/evoral/evoral/Control.h [new file with mode: 0644]
libs/evoral/evoral/Control.hpp [deleted file]
libs/evoral/evoral/ControlList.h [new file with mode: 0644]
libs/evoral/evoral/ControlList.hpp [deleted file]
libs/evoral/evoral/ControlSet.h [new file with mode: 0644]
libs/evoral/evoral/ControlSet.hpp [deleted file]
libs/evoral/evoral/Curve.h [new file with mode: 0644]
libs/evoral/evoral/Curve.hpp [deleted file]
libs/evoral/evoral/Event.h [new file with mode: 0644]
libs/evoral/evoral/Event.hpp [deleted file]
libs/evoral/evoral/EventList.h [new file with mode: 0644]
libs/evoral/evoral/EventList.hpp [deleted file]
libs/evoral/evoral/EventSink.h [new file with mode: 0644]
libs/evoral/evoral/EventSink.hpp [deleted file]
libs/evoral/evoral/MIDIXML.h [new file with mode: 0644]
libs/evoral/evoral/MIDIXML.hpp [deleted file]
libs/evoral/evoral/Note.h [new file with mode: 0644]
libs/evoral/evoral/Note.hpp [deleted file]
libs/evoral/evoral/Parameter.h [new file with mode: 0644]
libs/evoral/evoral/Parameter.hpp [deleted file]
libs/evoral/evoral/ParameterDescriptor.h [new file with mode: 0644]
libs/evoral/evoral/ParameterDescriptor.hpp [deleted file]
libs/evoral/evoral/PatchChange.h [new file with mode: 0644]
libs/evoral/evoral/PatchChange.hpp [deleted file]
libs/evoral/evoral/Range.h [new file with mode: 0644]
libs/evoral/evoral/Range.hpp [deleted file]
libs/evoral/evoral/SMF.h [new file with mode: 0644]
libs/evoral/evoral/SMF.hpp [deleted file]
libs/evoral/evoral/SMFReader.h [new file with mode: 0644]
libs/evoral/evoral/SMFReader.hpp [deleted file]
libs/evoral/evoral/Sequence.h [new file with mode: 0644]
libs/evoral/evoral/Sequence.hpp [deleted file]
libs/evoral/evoral/TimeConverter.h [new file with mode: 0644]
libs/evoral/evoral/TimeConverter.hpp [deleted file]
libs/evoral/evoral/TypeMap.h [new file with mode: 0644]
libs/evoral/evoral/TypeMap.hpp [deleted file]
libs/evoral/evoral/types.h [new file with mode: 0644]
libs/evoral/evoral/types.hpp [deleted file]
libs/evoral/src/Control.cc [new file with mode: 0644]
libs/evoral/src/Control.cpp [deleted file]
libs/evoral/src/ControlList.cc [new file with mode: 0644]
libs/evoral/src/ControlList.cpp [deleted file]
libs/evoral/src/ControlSet.cc [new file with mode: 0644]
libs/evoral/src/ControlSet.cpp [deleted file]
libs/evoral/src/Curve.cc [new file with mode: 0644]
libs/evoral/src/Curve.cpp [deleted file]
libs/evoral/src/Event.cc [new file with mode: 0644]
libs/evoral/src/Event.cpp [deleted file]
libs/evoral/src/Note.cc [new file with mode: 0644]
libs/evoral/src/Note.cpp [deleted file]
libs/evoral/src/SMF.cc [new file with mode: 0644]
libs/evoral/src/SMF.cpp [deleted file]
libs/evoral/src/SMFReader.cc [new file with mode: 0644]
libs/evoral/src/SMFReader.cpp [deleted file]
libs/evoral/src/Sequence.cc [new file with mode: 0644]
libs/evoral/src/Sequence.cpp [deleted file]
libs/evoral/src/TimeConverter.cc [new file with mode: 0644]
libs/evoral/src/TimeConverter.cpp [deleted file]
libs/evoral/src/debug.cc [new file with mode: 0644]
libs/evoral/src/debug.cpp [deleted file]
libs/evoral/test/BeatsTest.cc [new file with mode: 0644]
libs/evoral/test/BeatsTest.cpp [deleted file]
libs/evoral/test/BeatsTest.h [new file with mode: 0644]
libs/evoral/test/BeatsTest.hpp [deleted file]
libs/evoral/test/CurveTest.cc [new file with mode: 0644]
libs/evoral/test/CurveTest.cpp [deleted file]
libs/evoral/test/CurveTest.h [new file with mode: 0644]
libs/evoral/test/CurveTest.hpp [deleted file]
libs/evoral/test/NoteTest.cc [new file with mode: 0644]
libs/evoral/test/NoteTest.cpp [deleted file]
libs/evoral/test/NoteTest.h [new file with mode: 0644]
libs/evoral/test/NoteTest.hpp [deleted file]
libs/evoral/test/RangeTest.cc [new file with mode: 0644]
libs/evoral/test/RangeTest.cpp [deleted file]
libs/evoral/test/RangeTest.h [new file with mode: 0644]
libs/evoral/test/RangeTest.hpp [deleted file]
libs/evoral/test/SMFTest.cc [new file with mode: 0644]
libs/evoral/test/SMFTest.cpp [deleted file]
libs/evoral/test/SMFTest.h [new file with mode: 0644]
libs/evoral/test/SMFTest.hpp [deleted file]
libs/evoral/test/SequenceTest.cc [new file with mode: 0644]
libs/evoral/test/SequenceTest.cpp [deleted file]
libs/evoral/test/SequenceTest.h [new file with mode: 0644]
libs/evoral/test/SequenceTest.hpp [deleted file]
libs/evoral/test/testrunner.cc [new file with mode: 0644]
libs/evoral/test/testrunner.cpp [deleted file]
libs/evoral/wscript
libs/midi++2/midi++/event.h
libs/panners/1in2out/panner_1in2out.cc
libs/panners/2in2out/panner_2in2out.cc
libs/panners/stereobalance/panner_balance.cc
libs/surfaces/mackie/strip.h
libs/surfaces/us2400/strip.h
session_utils/fix_bbtppq.cc

index e767a6b971938377ed1cea51f0fb39e1e4d0a83e..b736f294169734ac69da77623cd02971f40425c0 100644 (file)
@@ -41,7 +41,7 @@
 #include "pbd/memento_command.h"
 #include "pbd/stacktrace.h"
 
-#include "evoral/Curve.hpp"
+#include "evoral/Curve.h"
 
 #include "gtkmm2ext/gtk_ui.h"
 #include "gtkmm2ext/utils.h"
index 543620bd66842e53a65feba0ed25e9da1518c6c0..748828f35a50fd774acab34e83bd3f17cd541d53 100644 (file)
@@ -51,7 +51,7 @@
 #include "ardour/parameter_types.h"
 #include "ardour/tempo.h"
 
-#include "evoral/Curve.hpp"
+#include "evoral/Curve.h"
 
 #include "canvas/debug.h"
 
index 05ce52721b7c948015db1eb96effe4a367252aa1..0d89025964ba48dbd716faffa1f13afcf2046de9 100644 (file)
@@ -33,7 +33,7 @@
 
 #include <sigc++/signal.h>
 
-#include "evoral/TimeConverter.hpp"
+#include "evoral/TimeConverter.h"
 
 #include "pbd/undo.h"
 #include "pbd/statefuldestructible.h"
index 26dfeb8992c33d5c966bf1263a0d17a8f129fcd6..8460613e94cb8e5e87685f8936063c35deafc0fd 100644 (file)
@@ -22,7 +22,7 @@
 #include <list>
 
 #include "ardour/automation_list.h"
-#include "evoral/Parameter.hpp"
+#include "evoral/Parameter.h"
 
 class AutomationSelection : public std::list<boost::shared_ptr<ARDOUR::AutomationList> > {
 public:
index 4c4333c5d640b8a89d83ee0e30994224283bb949..0c496660959545a24cf59d47ac60bc52bf12dab1 100644 (file)
@@ -32,7 +32,7 @@
 
 #include "canvas/canvas.h"
 
-#include "evoral/Curve.hpp"
+#include "evoral/Curve.h"
 #include "ardour/session_handle.h"
 
 #include "ardour_dialog.h"
index 33ad5fb895fe09426efe390600a4692608250c99..365665e2032569bc505d4a820a5f647ba60fb600 100644 (file)
@@ -23,7 +23,7 @@
 #include <unistd.h>
 
 #include "ardour/automation_list.h"
-#include "evoral/Curve.hpp"
+#include "evoral/Curve.h"
 
 using namespace std;
 using namespace ARDOUR;
index bff75800e087f39550811fe7e5e62936e0394a59..a08fd45c43b00b96701e0ee424220b1b285ce1a8 100644 (file)
@@ -43,7 +43,7 @@
 #include "pbd/failed_constructor.h"
 
 #include "evoral/midi_events.h"
-#include "evoral/PatchChange.hpp"
+#include "evoral/PatchChange.h"
 
 #include "midi++/midnam_patch.h"
 
index 23578ee0972ed883d539dd60618da37d23a5866a..53c3f7eb733942def8083a6eb5066100c4d5381e 100644 (file)
@@ -24,7 +24,7 @@
  * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
  */
 
-#include "evoral/Note.hpp"
+#include "evoral/Note.h"
 
 #include "ardour/parameter_descriptor.h"
 
index d750ed3f9d75a63750f9ead980e80e41e03dde14..643770b44a373a6f06fa37b0babdd7d1e9648e75 100644 (file)
@@ -19,7 +19,7 @@
 
 #include "temporal/beats.h"
 
-#include "evoral/Note.hpp"
+#include "evoral/Note.h"
 
 #include "canvas/polygon.h"
 #include "canvas/debug.h"
index 4c982bb5047530e914c70b8c943dc194992d5cc3..94bedd04e7d2e65c5ddcc721c137474f1999f919 100644 (file)
@@ -24,7 +24,7 @@
 #include <utility>
 
 #include "ardour/data_type.h"
-#include "evoral/Parameter.hpp"
+#include "evoral/Parameter.h"
 
 /** A count of various GUI items.
  *
index 535a04a05f2a10d27a6302851bf4028599deaf11..bc17c7aee02bdd730f04cdbd7958a45d1bb7f963 100644 (file)
@@ -24,7 +24,7 @@
 #include <gtkmm/cellrenderercombo.h>
 
 #include "evoral/midi_util.h"
-#include "evoral/Note.hpp"
+#include "evoral/Note.h"
 
 #include "ardour/beats_samples_converter.h"
 #include "ardour/midi_model.h"
index 249c198bfdbb0890ad62f38b2616b5acaf0fe258..01fcfab51e8ad4f04354090b65f26a593ec2ddf5 100644 (file)
@@ -48,9 +48,9 @@
 #include "ardour/operations.h"
 #include "ardour/session.h"
 
-#include "evoral/Parameter.hpp"
-#include "evoral/Event.hpp"
-#include "evoral/Control.hpp"
+#include "evoral/Parameter.h"
+#include "evoral/Event.h"
+#include "evoral/Control.h"
 #include "evoral/midi_util.h"
 
 #include "canvas/debug.h"
index a23d0f103bebe8f7dcb3c1a55eb7872e9def2128..7cc1bd6a868bebcb2768af69dca32a3e09fc9f55 100644 (file)
@@ -17,7 +17,7 @@
  * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
  */
 
-#include "evoral/Note.hpp"
+#include "evoral/Note.h"
 
 #include "canvas/note.h"
 #include "canvas/debug.h"
index c1935b0ba3808e4a61d4a43aed7b1e5013628c8b..3e6bb402dab9a3ea5e423a8d6adb09b18124a1fb 100644 (file)
@@ -23,7 +23,7 @@
 
 #include "gtkmm2ext/keyboard.h"
 
-#include "evoral/Note.hpp"
+#include "evoral/Note.h"
 
 #include "canvas/text.h"
 
index 6c0cd5082e2f7f36e5613f65aa56d4f2a1b7cf1d..03970c4706136f76cfe49f782bad812e1bcd3f63 100644 (file)
@@ -24,7 +24,7 @@
 #include <boost/shared_ptr.hpp>
 #include <sigc++/trackable.h>
 
-#include "evoral/Note.hpp"
+#include "evoral/Note.h"
 
 namespace ARDOUR {
        class MidiTrack;
index 4059c5032c8af655d6737584f411b7a20e0808d3..7a7b69119e37309744b785a312d3def8ab94bcf5 100644 (file)
@@ -22,7 +22,7 @@
 #include <gtkmm/spinbutton.h>
 #include <gtkmm/comboboxtext.h>
 
-#include "evoral/PatchChange.hpp"
+#include "evoral/PatchChange.h"
 #include "ardour_dialog.h"
 #include "audio_clock.h"
 
index 51cae03f7e555ece88c45efb8cc4050f7bda6f64..fdffc8157457e76c3506b9f57579f6619df006d0 100644 (file)
@@ -22,7 +22,7 @@
 #include "pbd/unwind.h"
 
 #include "evoral/midi_events.h"
-#include "evoral/PatchChange.hpp"
+#include "evoral/PatchChange.h"
 
 #include "midi++/midnam_patch.h"
 
index bb667a82133a44a9a638a8cd8290c81cf338a11c..0093dc32162f382fa98c2f19a050d0a3b459fa96 100644 (file)
@@ -47,7 +47,7 @@
 
 #include "temporal/beats.h"
 
-#include "evoral/Note.hpp"
+#include "evoral/Note.h"
 
 #include "ardour/session_handle.h"
 
index 1a35dfbff1e6b48b7864db53ef8198179d21eccd..a31d31b76e057608ce48abeefdcb3efcbc39f136 100644 (file)
@@ -22,7 +22,7 @@
  * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
  */
 
-#include "evoral/Curve.hpp"
+#include "evoral/Curve.h"
 #include "pbd/memento_command.h"
 #include "pbd/stateful_diff_command.h"
 
index 6a6ca719f97b375926041b53360cea0a3eb3b32a..f40ee82c7237f204ef9a62e620359fb9ff14427f 100644 (file)
@@ -49,7 +49,7 @@
 #include "pbd/enumwriter.h"
 #include "pbd/stateful_diff_command.h"
 
-#include "evoral/Parameter.hpp"
+#include "evoral/Parameter.h"
 
 #include "ardour/amp.h"
 #include "ardour/meter.h"
index 850adb93a8d6cca49a7674fd83f422916d0fceae..4b8906397d009a4bd5f488b62477cf6031e5e45a 100644 (file)
@@ -57,7 +57,7 @@
 
 #include <gtkmm2ext/utils.h>
 
-#include "evoral/SMF.hpp"
+#include "evoral/SMF.h"
 
 #include "ardour/audio_library.h"
 #include "ardour/auditioner.h"
index fa95c592ae4328f9cd9b86d5ca60b4bdc59f35ad..f5a6dc2fdc6881259dbe9ca4274e7be2a8f23970 100644 (file)
@@ -43,7 +43,7 @@
 #include "pbd/stateful.h"
 #include "pbd/signals.h"
 
-#include "evoral/Parameter.hpp"
+#include "evoral/Parameter.h"
 
 #include "ardour/types.h"
 #include "ardour/presentation_info.h"
index 80cb5b6fcbbf193436074e3249c99c85cb7a5b96..4731ab376a0b85f9f67218a7027803302c200e87 100644 (file)
@@ -25,7 +25,7 @@
 #include <cmath>
 #include <algorithm>
 
-#include "evoral/Curve.hpp"
+#include "evoral/Curve.h"
 
 #include "ardour/amp.h"
 #include "ardour/audio_buffer.h"
index 23aa86345851cbd570ebcb57702816859049f41b..fdc7c1ffad887df6aa89ba3d339b7b549a1e3f85 100644 (file)
@@ -30,7 +30,7 @@
 #include "pbd/signals.h"
 #include "pbd/ringbuffer.h"
 
-#include "evoral/Event.hpp"
+#include "evoral/Event.h"
 
 #include "midi++/types.h"
 #include "midi++/parser.h"
index d384b6c8f56ecf516c7d6ee665c97b8d7f302cc8..46160c60b097153d31f209d41cb6b75a826ca8b2 100644 (file)
@@ -31,7 +31,7 @@
 #include "pbd/rcu.h"
 #include "pbd/signals.h"
 
-#include "evoral/ControlSet.hpp"
+#include "evoral/ControlSet.h"
 
 #include "ardour/libardour_visibility.h"
 #include "ardour/slavable.h"
index d18e56736384801c24b5403e977784488374ee57..f5da0889de8392b936531cf5f1d118a98e1d7057 100644 (file)
@@ -20,7 +20,7 @@
 #ifndef __ardour_automatable_sequence_h__
 #define __ardour_automatable_sequence_h__
 
-#include "evoral/Sequence.hpp"
+#include "evoral/Sequence.h"
 #include "ardour/automatable.h"
 #include "ardour/event_type_map.h"
 
index 99252f2552a8c9926eba4983b91a4ace367eb8ce..3a13d1ce16e9f95cdf4919152f1346e181863367 100644 (file)
@@ -31,8 +31,8 @@
 
 #include "pbd/controllable.h"
 
-#include "evoral/types.hpp"
-#include "evoral/Control.hpp"
+#include "evoral/types.h"
+#include "evoral/Control.h"
 
 #include "ardour/automation_list.h"
 #include "ardour/control_group_member.h"
index 1654d94de699ea38611bcb4959bf1faf1b4fae55..c18b71d9a304714a4e264a69bd13e97bc0efa11f 100644 (file)
@@ -30,8 +30,8 @@
 
 #include <glibmm/threads.h>
 
-#include "evoral/ControlList.hpp"
-#include "evoral/Parameter.hpp"
+#include "evoral/ControlList.h"
+#include "evoral/Parameter.h"
 
 #include "pbd/undo.h"
 #include "pbd/xml++.h"
index 33cd62fb50e4ed4d15512af98c817315932eddbf..754c467f910a54864a628013a8f4f5b4a4e00fe3 100644 (file)
@@ -17,7 +17,7 @@
  */
 
 #include "temporal/beats.h"
-#include "evoral/TimeConverter.hpp"
+#include "evoral/TimeConverter.h"
 
 #include "ardour/libardour_visibility.h"
 #include "ardour/types.h"
index 4c928498436b4b8e2130423fbac5cc542472c2c0..85041b6728349bc5c9d63dd9ac5017fb00c77d80 100644 (file)
@@ -34,7 +34,7 @@
 #include "ardour/types.h"
 
 #if defined WINDOWS_VST_SUPPORT || defined LXVST_SUPPORT || defined MACVST_SUPPORT
-#include "evoral/Event.hpp"
+#include "evoral/Event.h"
 struct _VstEvents;
 typedef struct _VstEvents VstEvents;
 struct _VstMidiEvent;
index 6974699ea30efc2e8bb11ac2c5654b2bd2932c14..e77e184bd6e1eccdb7fcb3e01feaf781b4e74750 100644 (file)
@@ -29,7 +29,7 @@
 
 #include "pbd/controllable.h"
 
-#include "evoral/Parameter.hpp"
+#include "evoral/Parameter.h"
 
 #include "ardour/automation_control.h"
 #include "ardour/types.h"
index f805b80421032af10538c49d82156b91da87e1e6..f8c8e005db4d1de4e4a548486bf997a33500b5c4 100644 (file)
@@ -24,8 +24,8 @@
 
 #include "pbd/ringbufferNPT.h"
 
-#include "evoral/EventSink.hpp"
-#include "evoral/types.hpp"
+#include "evoral/EventSink.h"
+#include "evoral/types.h"
 
 namespace ARDOUR {
 
index 316268bad14a13d105b807cc83450794bd91508f..8a902523c28437884430af7279ad595a9d5b74dd 100644 (file)
@@ -24,9 +24,9 @@
 #include <map>
 #include <string>
 
-#include "evoral/TypeMap.hpp"
-#include "evoral/ControlList.hpp"
-#include "evoral/ParameterDescriptor.hpp"
+#include "evoral/TypeMap.h"
+#include "evoral/ControlList.h"
+#include "evoral/ParameterDescriptor.h"
 
 #include "ardour/libardour_visibility.h"
 
index 6945ea4946384e533d4519fc3ba9a3ffc5ca8ac7..26315e58f03592fb6b5410b8cc8a5a909669c073 100644 (file)
@@ -23,7 +23,7 @@
 #include "pbd/enum_convert.h"
 
 #include "temporal/beats.h"
-#include "evoral/ControlList.hpp"
+#include "evoral/ControlList.h"
 
 namespace PBD {
 
index 9136096f0ec5d23809ad4c0ca4cc4b440a3fc8e9..0b3e2fa5b25b291fdb467975dee490a7f991cf60 100644 (file)
@@ -26,7 +26,7 @@
 
 #include "pbd/controllable.h"
 
-#include "evoral/Parameter.hpp"
+#include "evoral/Parameter.h"
 
 #include "ardour/slavable_automation_control.h"
 #include "ardour/libardour_visibility.h"
index 00af06f0b45d6caf74e3565d89b9a13422229c41..59ff34fc3a99fd3d01b043f2dc409cc29e220992 100644 (file)
@@ -26,7 +26,7 @@
 
 #include "pbd/signals.h"
 
-#include "evoral/Parameter.hpp"
+#include "evoral/Parameter.h"
 
 #include "midi++/libmidi_visibility.h"
 #include "ardour/libardour_visibility.h"
index 3ae76f8e5b65e459d20841f8095a94ddb79b306a..bed9db5aa2885cf850b1137259141c33b0be4677 100644 (file)
@@ -24,7 +24,7 @@
 #include <boost/shared_ptr.hpp>
 #include <vamp-hostsdk/Plugin.h>
 
-#include "evoral/Note.hpp"
+#include "evoral/Note.h"
 
 #include "ardour/libardour_visibility.h"
 
index ee78b56a6342d198697a59fb59ca831c684edb01..f07247e5b6689708f6913bc812e5d7947526f465 100644 (file)
@@ -20,7 +20,7 @@
 #define __ardour_midi_automation_list_binder_h__
 
 #include "pbd/memento_command.h"
-#include "evoral/Parameter.hpp"
+#include "evoral/Parameter.h"
 #include "ardour/session.h"
 
 namespace ARDOUR {
index 24a784d13dc602db16c656d19460944f516e3042..d713f8787f107b22b1fe52e5b9ad5b62bd3ac253 100644 (file)
@@ -23,9 +23,9 @@
 #ifndef __ardour_midi_buffer_h__
 #define __ardour_midi_buffer_h__
 
-#include "evoral/EventSink.hpp"
+#include "evoral/EventSink.h"
 #include "evoral/midi_util.h"
-#include "evoral/types.hpp"
+#include "evoral/types.h"
 
 #include "midi++/event.h"
 
index 94e40520973702479ecabd4fc1e755d6b08bedbc..d7d65e616023116d0ab4be7d7c9ecb549aebe4e9 100644 (file)
@@ -28,7 +28,7 @@
 #include "pbd/signals.h"
 
 #include "temporal/beats.h"
-#include "evoral/Sequence.hpp"
+#include "evoral/Sequence.h"
 
 #include "ardour/types.h"
 
index 90375754e986f8200cbfa4ff3fcbf520e6b06078..b55ac5886d36b2b902ac1d7966678f61eeb7ef92 100644 (file)
@@ -40,8 +40,8 @@
 #include "ardour/types.h"
 #include "ardour/variant.h"
 
-#include "evoral/Note.hpp"
-#include "evoral/Sequence.hpp"
+#include "evoral/Note.h"
+#include "evoral/Sequence.h"
 
 namespace ARDOUR {
 
index 163268bbad0444451280460a92f1b19a9d59450f..1cbfd8d3b9dc6e528b17d78d14fc507ee0ee80e2 100644 (file)
@@ -24,7 +24,7 @@
 #include <string>
 
 #include "temporal/beats.h"
-#include "evoral/Sequence.hpp"
+#include "evoral/Sequence.h"
 
 class Command;
 
index 83ba5ecb613a553dedf5e122d99cc55f9cae42e0..e41d1fbdfacdf8258cbd8751bbcd12afacafacee 100644 (file)
 
 #include <boost/utility.hpp>
 
-#include "evoral/Parameter.hpp"
+#include "evoral/Parameter.h"
 
 #include "ardour/ardour.h"
 #include "ardour/midi_cursor.h"
 #include "ardour/midi_model.h"
 #include "ardour/midi_state_tracker.h"
 #include "ardour/playlist.h"
-#include "evoral/Note.hpp"
-#include "evoral/Parameter.hpp"
+#include "evoral/Note.h"
+#include "evoral/Parameter.h"
 #include "ardour/rt_midibuffer.h"
 
 namespace Evoral {
index 6ae1375899960dcba91cd814300ea4f1a4d48f0d..7181ab8d1b7944f2129dcae6d95719cd56c051fc 100644 (file)
@@ -26,7 +26,7 @@
 #include <vector>
 
 #include "temporal/beats.h"
-#include "evoral/Range.hpp"
+#include "evoral/Range.h"
 
 #include "pbd/string_convert.h"
 
index 29113f131d87c3971b5f7d324ba18b3935703d97..b24abe1fa5d59243aac694852c99298dfccb5517 100644 (file)
@@ -19,7 +19,7 @@
 #ifndef __libardour_midi_scene_change_h__
 #define __libardour_midi_scene_change_h__
 
-#include "evoral/PatchChange.hpp"
+#include "evoral/PatchChange.h"
 
 #include "pbd/signals.h"
 
index 0334b95b06fb8a7200b4d915cd57eabe07129258..a3308250811a312e566537cb282e5890ade1d67a 100644 (file)
@@ -29,8 +29,8 @@
 #include <boost/enable_shared_from_this.hpp>
 #include "pbd/stateful.h"
 #include "pbd/xml++.h"
-#include "evoral/Sequence.hpp"
-#include "evoral/Range.hpp"
+#include "evoral/Sequence.h"
+#include "evoral/Range.h"
 #include "ardour/ardour.h"
 #include "ardour/buffer.h"
 #include "ardour/midi_cursor.h"
index 6010a5148e4217a3f477110b70363bf664606352..8410b2922ff1fafbd161c92e564847c394134b4d 100644 (file)
@@ -27,7 +27,7 @@
 #include "pbd/signals.h"
 #include "pbd/stateful.h"
 
-#include "evoral/Parameter.hpp"
+#include "evoral/Parameter.h"
 
 #include "ardour/session_handle.h"
 #include "ardour/types.h"
index e460f80c384e714555fa5002d0f82606d872347f..c72f0dbe45e577c1ca18befa506b6323fd8d0a3f 100644 (file)
@@ -24,7 +24,7 @@
 
 #include <boost/shared_ptr.hpp>
 
-#include "evoral/Parameter.hpp"
+#include "evoral/Parameter.h"
 
 #include "ardour/automation_control.h"
 #include "ardour/automation_list.h"
index d866548fdb9cb16e3fa02d244b41e7328bc7774d..d282ff7dded1c68af5e15f5f60bdec27643ba747 100644 (file)
@@ -25,7 +25,7 @@
 #include <boost/shared_ptr.hpp>
 
 #include "pbd/stateful.h"
-#include "evoral/Parameter.hpp"
+#include "evoral/Parameter.h"
 
 #include "ardour/automatable.h"
 #include "ardour/session_handle.h"
index e531133b15f4984731af6772911d65ee48c41840..b9c0417ce6d25d047e13c7364961135de57f82b6 100644 (file)
@@ -23,8 +23,8 @@
 #include "ardour/types.h"
 #include "ardour/variant.h"
 
-#include "evoral/Parameter.hpp"
-#include "evoral/ParameterDescriptor.hpp"
+#include "evoral/Parameter.h"
+#include "evoral/ParameterDescriptor.h"
 
 namespace ARDOUR {
 
index 4de360a99e004fa0994e95c4bae9165004f8fe11..ac82dca0adcfbb34059de6ca3f12cd590ca3f023 100644 (file)
@@ -22,7 +22,7 @@
 #include <stdint.h>
 
 #include "ardour/types.h"
-#include "evoral/Parameter.hpp"
+#include "evoral/Parameter.h"
 #include "evoral/midi_events.h"
 
 namespace ARDOUR {
index 032a4fb7fb53a908243791cd9e2d97e03a9e2a9c..75ab8b4f2f1b427c67acccd14e3e761bfe6c8152 100644 (file)
@@ -45,7 +45,7 @@
 #include "pbd/sequence_property.h"
 #include "pbd/stacktrace.h"
 
-#include "evoral/Range.hpp"
+#include "evoral/Range.h"
 
 #include "ardour/ardour.h"
 #include "ardour/region.h"
index 9bc63d458d6e47c09967ae23712dbd4b4e8b0881..cfe0302ef2407a7a654483620fd7b0fea86b717b 100644 (file)
@@ -27,8 +27,8 @@
 
 #include <glibmm/threads.h>
 
-#include "evoral/Event.hpp"
-#include "evoral/EventSink.hpp"
+#include "evoral/Event.h"
+#include "evoral/EventSink.h"
 #include "ardour/types.h"
 
 namespace ARDOUR {
index 2c760a6b1ebfc84e6b473a6d40f83fe0f22aec33..22224449826d7a9d92be8a46c1df683e571f96a4 100644 (file)
@@ -65,7 +65,7 @@
 
 #include "lua/luastate.h"
 
-#include "evoral/Range.hpp"
+#include "evoral/Range.h"
 
 #include "midi++/types.h"
 #include "midi++/mmc.h"
index 0dd7d84e697c08197e131a2fc5c2f4e344d18ddb..60cf770fd70fdbcac1b500477df0703009d31413 100644 (file)
@@ -28,7 +28,7 @@
 
 #include "pbd/signals.h"
 
-#include "evoral/Parameter.hpp"
+#include "evoral/Parameter.h"
 
 #include "ardour/types.h"
 #include "ardour/libardour_visibility.h"
index 2b1f7c4f59feace6ebd7269825a539e6dffab52f..12ec5271e2957e434e2d5d3a5d530e7d76071366 100644 (file)
@@ -25,7 +25,7 @@
 
 #include <cstdio>
 #include <time.h>
-#include "evoral/SMF.hpp"
+#include "evoral/SMF.h"
 #include "ardour/midi_source.h"
 #include "ardour/file_source.h"
 
index fcf776f60b72814afc501963f380acc04edfd016..ea96c7fd6dc9ff25711739f6db591d6dcd80dd74 100644 (file)
@@ -48,7 +48,7 @@
 
 #include "pbd/id.h"
 
-#include "evoral/Range.hpp"
+#include "evoral/Range.h"
 
 #include "ardour/chan_count.h"
 #include "ardour/plugin_types.h"
@@ -124,7 +124,7 @@ enum InsertMergePolicy {
        InsertMergeExtend   ///< extend new (or old) to the range of old+new
 };
 
-/** See evoral/Parameter.hpp
+/** See evoral/Parameter.h
  *
  * When you add things here, you REALLY SHOULD add a case clause to
  * the constructor of ParameterDescriptor, unless the Controllables
index 167f6de6f8563279d728cda870e4cf19472f19ab..0f4d7dbd1df76a7b20e0e9f19ed8c796cee04931 100644 (file)
@@ -28,7 +28,7 @@
 #include "pbd/enumwriter.h"
 #include "pbd/error.h"
 
-#include "evoral/Curve.hpp"
+#include "evoral/Curve.h"
 
 #include "ardour/amp.h"
 #include "ardour/audio_buffer.h"
index 6bd1e6e0bbb3c8690659a59016fbee825c9b4f9a..b6e6f5a5728c92d555f91b46474e96f7810268bc 100644 (file)
@@ -40,7 +40,7 @@
 #include "pbd/enumwriter.h"
 #include "pbd/convert.h"
 
-#include "evoral/Curve.hpp"
+#include "evoral/Curve.h"
 
 #include "ardour/audioregion.h"
 #include "ardour/session.h"
index e9691a46d45a825f10b7dea7090a8c2615e15907..fa7854873c0d67315836556a5e54cd6c38ab952d 100644 (file)
@@ -34,7 +34,7 @@
 #include "ardour/parameter_types.h"
 #include "ardour/evoral_types_convert.h"
 #include "ardour/types_convert.h"
-#include "evoral/Curve.hpp"
+#include "evoral/Curve.h"
 #include "pbd/memento_command.h"
 #include "pbd/stacktrace.h"
 #include "pbd/enumwriter.h"
index cdcdbd0c4f116b3772eb9ff0488c435e7b9b8ede..f374d70e759cba0b39bc2e3fbd2f097d16568742 100644 (file)
@@ -29,7 +29,7 @@
 #include "pbd/enumwriter.h"
 #include "midi++/types.h"
 
-#include "evoral/Range.hpp" // shouldn't Evoral have its own enum registration?
+#include "evoral/Range.h" // shouldn't Evoral have its own enum registration?
 
 #include "ardour/delivery.h"
 #include "ardour/disk_io.h"
index b07a82ff1eac26522625d1080af23dbe38b470f0..17a1daef07a62326e78d3985a30fc27eb92310c5 100644 (file)
@@ -29,8 +29,8 @@
 #ifdef LV2_SUPPORT
 #include "ardour/uri_map.h"
 #endif
-#include "evoral/Parameter.hpp"
-#include "evoral/ParameterDescriptor.hpp"
+#include "evoral/Parameter.h"
+#include "evoral/ParameterDescriptor.h"
 #include "evoral/midi_events.h"
 #include "pbd/error.h"
 #include "pbd/compose.h"
index e7ea8305e30250a840413a6d0ccafaec937b2de3..2e552813a9293b203830eff0b305dca8af47dc7f 100644 (file)
@@ -22,7 +22,7 @@
 #include "pbd/convert.h"
 #include "pbd/strsplit.h"
 
-#include "evoral/Curve.hpp"
+#include "evoral/Curve.h"
 
 #include "ardour/dB.h"
 #include "ardour/gain_control.h"
index cbc6ee31582882a84c732009e2eedfb549147c16..ee6bd6db897f879c5280938d830b7ec072764f99 100644 (file)
@@ -47,7 +47,7 @@
 #include "pbd/basename.h"
 #include "pbd/convert.h"
 
-#include "evoral/SMF.hpp"
+#include "evoral/SMF.h"
 
 #include "ardour/analyser.h"
 #include "ardour/ardour.h"
index 6e0a82b20ded074755145b1335514201bf2fdf42..b05a625396a2e1e64522391bd7c5e0fad091cacd 100644 (file)
@@ -27,9 +27,9 @@
 
 #include "temporal/bbt_time.h"
 
-#include "evoral/Control.hpp"
-#include "evoral/ControlList.hpp"
-#include "evoral/Range.hpp"
+#include "evoral/Control.h"
+#include "evoral/ControlList.h"
+#include "evoral/Range.h"
 
 #include "ardour/amp.h"
 #include "ardour/async_midi_port.h"
index d14eeb8c19e7ce1574e5b05e8a14acfe6b233631..b3a441db36a7dd0a8520d24a7c11391aba915725 100644 (file)
@@ -33,7 +33,7 @@
 #include "pbd/enumwriter.h"
 #include "pbd/error.h"
 
-#include "evoral/Control.hpp"
+#include "evoral/Control.h"
 
 #include "midi++/events.h"
 
index 00d1f5badda76636ffda0ae4721867ba1f0f5956..21dec88ad411c2aa905b93107b7f90762f87e69e 100644 (file)
@@ -25,8 +25,8 @@
 #include <iostream>
 #include <utility>
 
-#include "evoral/EventList.hpp"
-#include "evoral/Control.hpp"
+#include "evoral/EventList.h"
+#include "evoral/Control.h"
 
 #include "ardour/beats_samples_converter.h"
 #include "ardour/debug.h"
index 05bfdbf1e751b3cc1bc1f279faf5f5661692b2cc..83cccbeb1582a24c74843da31a7dda0e921be2be 100644 (file)
@@ -17,7 +17,7 @@
  * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
  */
 
-#include "evoral/Event.hpp"
+#include "evoral/Event.h"
 #include "midi++/channel.h"
 #include "midi++/parser.h"
 #include "midi++/port.h"
index 2f6c62f4a8d481da0077cdb19e3a2c3bfaef3983..3d188898f455bbf0d34f4b0ba6d5e1dbf4464f9e 100644 (file)
@@ -40,8 +40,8 @@
 #include "pbd/basename.h"
 #include "pbd/timing.h"
 
-#include "evoral/Control.hpp"
-#include "evoral/EventSink.hpp"
+#include "evoral/Control.h"
+#include "evoral/EventSink.h"
 
 #include "ardour/debug.h"
 #include "ardour/file_source.h"
index bdba604a4ab8a1dd8a6980df0f6a89154c6f0287..ecc860fa9272381a64df66c2fefbe89cc7cfe9bb 100644 (file)
@@ -23,7 +23,7 @@
 #include "pbd/compose.h"
 #include "pbd/stacktrace.h"
 
-#include "evoral/EventSink.hpp"
+#include "evoral/EventSink.h"
 
 #include "ardour/debug.h"
 #include "ardour/midi_source.h"
index 105cb3e7b2081c0a9cd7bd59d1b51c1f77036934..530ca7bac81205a5d7956acd29953d72e0e725bc 100644 (file)
@@ -17,7 +17,7 @@
  * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
  */
 
-#include "evoral/ControlList.hpp"
+#include "evoral/ControlList.h"
 
 #include "ardour/mute_master.h"
 #include "ardour/session.h"
index e135fcd163e831f62875f2b4f45fcaa464dd4e67..45b8d7e80bbd619f86472d9fa15d021d4f81ad41 100644 (file)
@@ -42,7 +42,7 @@
 #include "pbd/xml++.h"
 #include "pbd/enumwriter.h"
 
-#include "evoral/Curve.hpp"
+#include "evoral/Curve.h"
 
 #include "ardour/audio_buffer.h"
 #include "ardour/audioengine.h"
index ca0afeb6d101afaf4062804907354fce13fd4de4..44bacd24096975223d2c5df3a863c39ab400fb2a 100644 (file)
@@ -32,7 +32,7 @@
 #include "ardour/session_playlists.h"
 #include "ardour/source.h"
 #include "ardour/tempo.h"
-#include "evoral/Curve.hpp"
+#include "evoral/Curve.h"
 #include "pbd/error.h"
 #include "pbd/failed_constructor.h"
 #include "pbd/id.h"
index fe3368c50c5c761315603161c5f0e69071f10fa2..ea78133c86a3f33e0ef90374d941d9dd7d19f917 100644 (file)
@@ -71,7 +71,7 @@
 #include "midi++/mmc.h"
 #include "midi++/port.h"
 
-#include "evoral/SMF.hpp"
+#include "evoral/SMF.h"
 
 #include "pbd/basename.h"
 #include "pbd/debug.h"
index 50ebe6ed258189c979e03ff21a380ded2bf102fb..505c8ebfc2582e3e92b1962a4d638797b32a08f0 100644 (file)
@@ -26,7 +26,7 @@
 #include "pbd/memento_command.h"
 #include "pbd/types_convert.h"
 
-#include "evoral/Curve.hpp"
+#include "evoral/Curve.h"
 
 #include "ardour/audioengine.h"
 #include "ardour/runtime_functions.h"
index 9b7acb94027157081bd76bbf47923c31ebf784ea..e0d65176c39e5c3238242bfca729f98096f79e3d 100644 (file)
@@ -40,8 +40,8 @@
 #include <glibmm/miscutils.h>
 #include <glibmm/fileutils.h>
 
-#include "evoral/Control.hpp"
-#include "evoral/SMF.hpp"
+#include "evoral/Control.h"
+#include "evoral/SMF.h"
 
 #include "ardour/debug.h"
 #include "ardour/midi_channel_filter.h"
index 8c35e942fa59a50fbae19801632592473926b3c5..fe58ac21fc20f8fa6c91339b3a7a64fa441686f5 100644 (file)
@@ -21,7 +21,7 @@
 #include "ardour/audioplaylist.h"
 #include "ardour/region.h"
 #include "ardour/audioregion.h"
-#include "evoral/Curve.hpp"
+#include "evoral/Curve.h"
 
 CPPUNIT_TEST_SUITE_REGISTRATION (CombineRegionsTest);
 
diff --git a/libs/evoral/evoral/Control.h b/libs/evoral/evoral/Control.h
new file mode 100644 (file)
index 0000000..fd1f5ea
--- /dev/null
@@ -0,0 +1,88 @@
+/*
+ * Copyright (C) 2008-2014 David Robillard <d@drobilla.net>
+ * Copyright (C) 2010-2012 Carl Hetherington <carl@carlh.net>
+ * Copyright (C) 2010-2013 Paul Davis <paul@linuxaudiosystems.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef EVORAL_CONTROL_HPP
+#define EVORAL_CONTROL_HPP
+
+#include <set>
+#include <map>
+#include <boost/shared_ptr.hpp>
+#include "pbd/signals.h"
+
+#include "evoral/visibility.h"
+#include "evoral/Parameter.h"
+#include "evoral/ParameterDescriptor.h"
+
+namespace Evoral {
+
+class ControlList;
+class ParameterDescriptor;
+class Transport;
+class TypeMap;
+
+/** Base class representing some kind of (automatable) control; a fader's gain,
+ *  for example, or a compressor plugin's threshold.
+ *
+ *  The class knows the Evoral::Parameter that it is controlling, and has
+ *  a list of values for automation.
+ */
+class LIBEVORAL_API Control
+{
+public:
+       Control(const Parameter&               parameter,
+               const ParameterDescriptor&     desc,
+               boost::shared_ptr<ControlList> list);
+
+       virtual ~Control() {}
+
+       virtual void   set_double (double val, double frame=0, bool to_list=false);
+       virtual double get_double (bool from_list=false, double frame=0) const;
+
+       /** Get the latest user-set value
+        * (which may not equal get_value() when automation is playing back).
+        *
+        * Automation write/touch works by periodically sampling this value
+        * and adding it to the ControlList.
+        */
+       double user_double() const { return _user_value; }
+
+       void set_list(boost::shared_ptr<ControlList>);
+
+       boost::shared_ptr<ControlList>       list()       { return _list; }
+       boost::shared_ptr<const ControlList> list() const { return _list; }
+
+       inline const Parameter& parameter() const { return _parameter; }
+
+       /** Emitted when the our ControlList is marked dirty */
+       PBD::Signal0<void> ListMarkedDirty;
+
+protected:
+       Parameter                      _parameter;
+       boost::shared_ptr<ControlList> _list;
+       double                         _user_value;
+       PBD::ScopedConnection          _list_marked_dirty_connection;
+
+private:
+       void list_marked_dirty ();
+};
+
+} // namespace Evoral
+
+#endif // EVORAL_CONTROL_HPP
diff --git a/libs/evoral/evoral/Control.hpp b/libs/evoral/evoral/Control.hpp
deleted file mode 100644 (file)
index 25a313f..0000000
+++ /dev/null
@@ -1,88 +0,0 @@
-/*
- * Copyright (C) 2008-2014 David Robillard <d@drobilla.net>
- * Copyright (C) 2010-2012 Carl Hetherington <carl@carlh.net>
- * Copyright (C) 2010-2013 Paul Davis <paul@linuxaudiosystems.com>
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-
-#ifndef EVORAL_CONTROL_HPP
-#define EVORAL_CONTROL_HPP
-
-#include <set>
-#include <map>
-#include <boost/shared_ptr.hpp>
-#include "pbd/signals.h"
-
-#include "evoral/visibility.h"
-#include "evoral/Parameter.hpp"
-#include "evoral/ParameterDescriptor.hpp"
-
-namespace Evoral {
-
-class ControlList;
-class ParameterDescriptor;
-class Transport;
-class TypeMap;
-
-/** Base class representing some kind of (automatable) control; a fader's gain,
- *  for example, or a compressor plugin's threshold.
- *
- *  The class knows the Evoral::Parameter that it is controlling, and has
- *  a list of values for automation.
- */
-class LIBEVORAL_API Control
-{
-public:
-       Control(const Parameter&               parameter,
-               const ParameterDescriptor&     desc,
-               boost::shared_ptr<ControlList> list);
-
-       virtual ~Control() {}
-
-       virtual void   set_double (double val, double frame=0, bool to_list=false);
-       virtual double get_double (bool from_list=false, double frame=0) const;
-
-       /** Get the latest user-set value
-        * (which may not equal get_value() when automation is playing back).
-        *
-        * Automation write/touch works by periodically sampling this value
-        * and adding it to the ControlList.
-        */
-       double user_double() const { return _user_value; }
-
-       void set_list(boost::shared_ptr<ControlList>);
-
-       boost::shared_ptr<ControlList>       list()       { return _list; }
-       boost::shared_ptr<const ControlList> list() const { return _list; }
-
-       inline const Parameter& parameter() const { return _parameter; }
-
-       /** Emitted when the our ControlList is marked dirty */
-       PBD::Signal0<void> ListMarkedDirty;
-
-protected:
-       Parameter                      _parameter;
-       boost::shared_ptr<ControlList> _list;
-       double                         _user_value;
-       PBD::ScopedConnection          _list_marked_dirty_connection;
-
-private:
-       void list_marked_dirty ();
-};
-
-} // namespace Evoral
-
-#endif // EVORAL_CONTROL_HPP
diff --git a/libs/evoral/evoral/ControlList.h b/libs/evoral/evoral/ControlList.h
new file mode 100644 (file)
index 0000000..baa00dc
--- /dev/null
@@ -0,0 +1,384 @@
+/*
+ * Copyright (C) 2008-2012 Carl Hetherington <carl@carlh.net>
+ * Copyright (C) 2008-2014 Paul Davis <paul@linuxaudiosystems.com>
+ * Copyright (C) 2008-2015 David Robillard <d@drobilla.net>
+ * Copyright (C) 2012-2017 Robin Gareus <robin@gareus.org>
+ * Copyright (C) 2015 Nick Mainsbridge <mainsbridge@gmail.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef EVORAL_CONTROL_LIST_HPP
+#define EVORAL_CONTROL_LIST_HPP
+
+#include <cassert>
+#include <list>
+#include <stdint.h>
+
+#include <boost/pool/pool.hpp>
+#include <boost/pool/pool_alloc.hpp>
+
+#include <glibmm/threads.h>
+
+#include "pbd/signals.h"
+
+#include "evoral/visibility.h"
+#include "evoral/Range.h"
+#include "evoral/Parameter.h"
+#include "evoral/ParameterDescriptor.h"
+
+namespace Evoral {
+
+class Curve;
+class TypeMap;
+
+/** A single event (time-stamped value) for a control
+ */
+class LIBEVORAL_API ControlEvent {
+public:
+       ControlEvent (double w, double v)
+               : when (w), value (v), coeff (0)
+       {}
+
+       ControlEvent (const ControlEvent& other)
+               : when (other.when), value (other.value), coeff (0)
+       {
+               if (other.coeff) {
+                       create_coeffs();
+                       for (size_t i = 0; i < 4; ++i)
+                               coeff[i] = other.coeff[i];
+               }
+       }
+
+       ~ControlEvent() { if (coeff) delete[] coeff; }
+
+       void create_coeffs() {
+               if (!coeff)
+                       coeff = new double[4];
+
+               coeff[0] = coeff[1] = coeff[2] = coeff[3] = 0.0;
+       }
+
+       double  when;
+       double  value;
+       double* coeff; ///< double[4] allocated by Curve as needed
+};
+
+/** A list (sequence) of time-stamped values for a control
+ */
+class LIBEVORAL_API ControlList
+{
+public:
+       typedef std::list<ControlEvent*> EventList;
+       typedef EventList::iterator iterator;
+       typedef EventList::reverse_iterator reverse_iterator;
+       typedef EventList::const_iterator const_iterator;
+       typedef EventList::const_reverse_iterator const_reverse_iterator;
+
+       ControlList (const Parameter& id, const ParameterDescriptor& desc);
+       ControlList (const ControlList&);
+       ControlList (const ControlList&, double start, double end);
+       virtual ~ControlList();
+
+       virtual boost::shared_ptr<ControlList> create(const Parameter& id, const ParameterDescriptor& desc);
+
+       void dump (std::ostream&);
+
+       ControlList& operator= (const ControlList&);
+       bool operator== (const ControlList&);
+       void copy_events (const ControlList&);
+
+       virtual void freeze();
+       virtual void thaw ();
+       bool frozen() const { return _frozen; }
+
+       const Parameter& parameter() const                 { return _parameter; }
+       void             set_parameter(const Parameter& p) { _parameter = p; }
+
+       const ParameterDescriptor& descriptor() const                           { return _desc; }
+       void                       set_descriptor(const ParameterDescriptor& d) { _desc = d; }
+
+       EventList::size_type size() const { return _events.size(); }
+
+       /** @return time-stamp of first or last event in the list */
+       double when (bool at_start) const {
+               Glib::Threads::RWLock::ReaderLock lm (_lock);
+               if (_events.empty()) {
+                       return 0.0;
+               }
+               return at_start ? _events.front()->when : _events.back()->when;
+       }
+
+       double length() const {
+               Glib::Threads::RWLock::ReaderLock lm (_lock);
+               return _events.empty() ? 0.0 : _events.back()->when;
+       }
+       bool empty() const { return _events.empty(); }
+
+       void clear ();
+       void x_scale (double factor);
+       bool extend_to (double);
+       void slide (iterator before, double distance);
+       void shift (double before, double distance);
+
+       void y_transform (boost::function<double(double)> callback);
+       void list_merge (ControlList const& other, boost::function<double(double, double)> callback);
+
+       /** add automation events
+        * @param when absolute time in samples
+        * @param value parameter value
+        * @param with_guards if true, add guard-points
+        * @param with_initial if true, add an initial point if the list is empty
+        */
+       virtual void add (double when, double value, bool with_guards=true, bool with_initial=true);
+
+       virtual bool editor_add (double when, double value, bool with_guard);
+
+       /* to be used only for loading pre-sorted data from saved state */
+       void fast_simple_add (double when, double value);
+
+       void erase_range (double start, double end);
+       void erase (iterator);
+       void erase (iterator, iterator);
+       void erase (double, double);
+       bool move_ranges (std::list< RangeMove<double> > const &);
+       void modify (iterator, double, double);
+
+       /** Thin the number of events in this list.
+        *
+        * The thinning factor corresponds to the area of a triangle computed
+        * between three points in the list (time-difference * value-difference).
+        * If the area is large, it indicates significant non-linearity between
+        * the points.
+        *
+        * Time is measured in samples, value is usually normalized to 0..1.
+        *
+        * During automation recording we thin the recorded points using this
+        * value.  If a point is sufficiently co-linear with its neighbours (as
+        * defined by the area of the triangle formed by three of them), we will
+        * not include it in the list.  The larger the value, the more points are
+        * excluded, so this effectively measures the amount of thinning to be
+        * done.
+        *
+        * @param thinning_factor area-size (default: 20)
+        */
+       void thin (double thinning_factor);
+
+       boost::shared_ptr<ControlList> cut (double, double);
+       boost::shared_ptr<ControlList> copy (double, double);
+
+       /** remove all automation events between the given time range
+        * @param start start of range (inclusive) in audio samples
+        * @param end end of range (inclusive) in audio samples
+        */
+       void clear (double start, double end);
+
+       bool paste (const ControlList&, double position);
+
+       /** truncate the event list after the given time
+        * @param last_coordinate last event to include
+        */
+       void truncate_end (double last_coordinate);
+       /** truncate the event list to the given time
+        * @param overall_length overall length
+        */
+       void truncate_start (double overall_length);
+
+       iterator            begin()       { return _events.begin(); }
+       const_iterator      begin() const { return _events.begin(); }
+       iterator            end()         { return _events.end(); }
+       const_iterator      end()   const { return _events.end(); }
+       reverse_iterator            rbegin()       { return _events.rbegin(); }
+       const_reverse_iterator      rbegin() const { return _events.rbegin(); }
+       reverse_iterator            rend()         { return _events.rend(); }
+       const_reverse_iterator      rend()   const { return _events.rend(); }
+       ControlEvent*       back()        { return _events.back(); }
+       const ControlEvent* back()  const { return _events.back(); }
+       ControlEvent*       front()       { return _events.front(); }
+       const ControlEvent* front() const { return _events.front(); }
+
+       std::pair<ControlList::iterator,ControlList::iterator> control_points_adjacent (double when);
+
+       template<class T> void apply_to_points (T& obj, void (T::*method)(const ControlList&)) {
+               Glib::Threads::RWLock::WriterLock lm (_lock);
+               (obj.*method)(*this);
+       }
+
+       /** query value at given time (takes a read-lock, not safe while writing automation)
+        * @param where absolute time in samples
+        * @returns parameter value
+        */
+       double eval (double where) const {
+               Glib::Threads::RWLock::ReaderLock lm (_lock);
+               return unlocked_eval (where);
+       }
+
+       /** realtime safe version of eval, may fail if read-lock cannot be taken
+        * @param where absolute time in samples
+        * @param ok boolean reference if returned value is valid
+        * @returns parameter value
+        */
+       double rt_safe_eval (double where, bool& ok) const {
+
+               Glib::Threads::RWLock::ReaderLock lm (_lock, Glib::Threads::TRY_LOCK);
+
+               if ((ok = lm.locked())) {
+                       return unlocked_eval (where);
+               } else {
+                       return 0.0;
+               }
+       }
+
+       static inline bool time_comparator (const ControlEvent* a, const ControlEvent* b) {
+               return a->when < b->when;
+       }
+
+       /** Lookup cache for eval functions, range contains equivalent values */
+       struct LookupCache {
+               LookupCache() : left(-1) {}
+               double left;  /* leftmost x coordinate used when finding "range" */
+               std::pair<ControlList::const_iterator,ControlList::const_iterator> range;
+       };
+
+       /** Lookup cache for point finding, range contains points after left */
+       struct SearchCache {
+               SearchCache () : left(-1) {}
+               double left;  /* leftmost x coordinate used when finding "first" */
+               ControlList::const_iterator first;
+       };
+
+       const EventList& events() const { return _events; }
+
+       // FIXME: const violations for Curve
+       Glib::Threads::RWLock& lock()       const { return _lock; }
+       LookupCache& lookup_cache() const { return _lookup_cache; }
+       SearchCache& search_cache() const { return _search_cache; }
+
+       /** Called by locked entry point and various private
+        * locations where we already hold the lock.
+        *
+        * FIXME: Should this be private?  Curve needs it..
+        */
+       double unlocked_eval (double x) const;
+
+       bool rt_safe_earliest_event (double start, double& x, double& y, bool start_inclusive=false) const;
+       bool rt_safe_earliest_event_unlocked (double start, double& x, double& y, bool start_inclusive=false) const;
+       bool rt_safe_earliest_event_linear_unlocked (double start, double& x, double& y, bool inclusive) const;
+       bool rt_safe_earliest_event_discrete_unlocked (double start, double& x, double& y, bool inclusive) const;
+
+       void create_curve();
+       void destroy_curve();
+
+       Curve&       curve()       { assert(_curve); return *_curve; }
+       const Curve& curve() const { assert(_curve); return *_curve; }
+
+       void mark_dirty () const;
+
+       enum InterpolationStyle {
+               Discrete,
+               Linear,
+               Curved, // spline, used for x-fades
+               Logarithmic,
+               Exponential // fader, gain
+       };
+
+       /** query interpolation style of the automation data
+        * @returns Interpolation Style
+        */
+       InterpolationStyle interpolation() const { return _interpolation; }
+
+       /** query default interpolation for parameter-descriptor */
+       virtual InterpolationStyle default_interpolation() const;
+
+       /** set the interpolation style of the automation data.
+        *
+        * This will fail when asking for Logarithmic scale and min,max crosses 0
+        * or Exponential scale with min != 0.
+        *
+        * @param is interpolation style
+        * @returns true if style change was successful
+        */
+       bool set_interpolation (InterpolationStyle is);
+
+       virtual bool touching() const { return false; }
+       virtual bool writing() const { return false; }
+       virtual bool touch_enabled() const { return false; }
+       void start_write_pass (double when);
+       void write_pass_finished (double when, double thinning_factor=0.0);
+       void set_in_write_pass (bool, bool add_point = false, double when = 0.0);
+       bool in_write_pass () const;
+       bool in_new_write_pass () { return new_write_pass; }
+
+       PBD::Signal0<void> WritePassStarted;
+       /** 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;
+
+       bool operator!= (ControlList const &) const;
+
+       void invalidate_insert_iterator ();
+
+protected:
+
+       /** Called by unlocked_eval() to handle cases of 3 or more control points. */
+       double multipoint_eval (double x) const;
+
+       void build_search_cache_if_necessary (double start) const;
+
+       boost::shared_ptr<ControlList> cut_copy_clear (double, double, int op);
+       bool erase_range_internal (double start, double end, EventList &);
+
+       void     maybe_add_insert_guard (double when);
+       iterator erase_from_iterator_to (iterator iter, double when);
+       bool     maybe_insert_straight_line (double when, double value);
+
+       virtual void maybe_signal_changed ();
+
+       void _x_scale (double factor);
+
+       mutable LookupCache   _lookup_cache;
+       mutable SearchCache   _search_cache;
+
+       mutable Glib::Threads::RWLock _lock;
+
+       Parameter             _parameter;
+       ParameterDescriptor   _desc;
+       InterpolationStyle    _interpolation;
+       EventList             _events;
+       int8_t                _frozen;
+       bool                  _changed_when_thawed;
+       bool                  _sort_pending;
+
+       Curve* _curve;
+
+private:
+       iterator   most_recent_insert_iterator;
+       double     insert_position;
+       bool       new_write_pass;
+       bool       did_write_during_pass;
+       bool       _in_write_pass;
+
+       void unlocked_remove_duplicates ();
+       void unlocked_invalidate_insert_iterator ();
+       void add_guard_point (double when, double offset);
+
+       bool is_sorted () const;
+};
+
+} // namespace Evoral
+
+#endif // EVORAL_CONTROL_LIST_HPP
+
diff --git a/libs/evoral/evoral/ControlList.hpp b/libs/evoral/evoral/ControlList.hpp
deleted file mode 100644 (file)
index a2cc446..0000000
+++ /dev/null
@@ -1,384 +0,0 @@
-/*
- * Copyright (C) 2008-2012 Carl Hetherington <carl@carlh.net>
- * Copyright (C) 2008-2014 Paul Davis <paul@linuxaudiosystems.com>
- * Copyright (C) 2008-2015 David Robillard <d@drobilla.net>
- * Copyright (C) 2012-2017 Robin Gareus <robin@gareus.org>
- * Copyright (C) 2015 Nick Mainsbridge <mainsbridge@gmail.com>
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-
-#ifndef EVORAL_CONTROL_LIST_HPP
-#define EVORAL_CONTROL_LIST_HPP
-
-#include <cassert>
-#include <list>
-#include <stdint.h>
-
-#include <boost/pool/pool.hpp>
-#include <boost/pool/pool_alloc.hpp>
-
-#include <glibmm/threads.h>
-
-#include "pbd/signals.h"
-
-#include "evoral/visibility.h"
-#include "evoral/Range.hpp"
-#include "evoral/Parameter.hpp"
-#include "evoral/ParameterDescriptor.hpp"
-
-namespace Evoral {
-
-class Curve;
-class TypeMap;
-
-/** A single event (time-stamped value) for a control
- */
-class LIBEVORAL_API ControlEvent {
-public:
-       ControlEvent (double w, double v)
-               : when (w), value (v), coeff (0)
-       {}
-
-       ControlEvent (const ControlEvent& other)
-               : when (other.when), value (other.value), coeff (0)
-       {
-               if (other.coeff) {
-                       create_coeffs();
-                       for (size_t i = 0; i < 4; ++i)
-                               coeff[i] = other.coeff[i];
-               }
-       }
-
-       ~ControlEvent() { if (coeff) delete[] coeff; }
-
-       void create_coeffs() {
-               if (!coeff)
-                       coeff = new double[4];
-
-               coeff[0] = coeff[1] = coeff[2] = coeff[3] = 0.0;
-       }
-
-       double  when;
-       double  value;
-       double* coeff; ///< double[4] allocated by Curve as needed
-};
-
-/** A list (sequence) of time-stamped values for a control
- */
-class LIBEVORAL_API ControlList
-{
-public:
-       typedef std::list<ControlEvent*> EventList;
-       typedef EventList::iterator iterator;
-       typedef EventList::reverse_iterator reverse_iterator;
-       typedef EventList::const_iterator const_iterator;
-       typedef EventList::const_reverse_iterator const_reverse_iterator;
-
-       ControlList (const Parameter& id, const ParameterDescriptor& desc);
-       ControlList (const ControlList&);
-       ControlList (const ControlList&, double start, double end);
-       virtual ~ControlList();
-
-       virtual boost::shared_ptr<ControlList> create(const Parameter& id, const ParameterDescriptor& desc);
-
-       void dump (std::ostream&);
-
-       ControlList& operator= (const ControlList&);
-       bool operator== (const ControlList&);
-       void copy_events (const ControlList&);
-
-       virtual void freeze();
-       virtual void thaw ();
-       bool frozen() const { return _frozen; }
-
-       const Parameter& parameter() const                 { return _parameter; }
-       void             set_parameter(const Parameter& p) { _parameter = p; }
-
-       const ParameterDescriptor& descriptor() const                           { return _desc; }
-       void                       set_descriptor(const ParameterDescriptor& d) { _desc = d; }
-
-       EventList::size_type size() const { return _events.size(); }
-
-       /** @return time-stamp of first or last event in the list */
-       double when (bool at_start) const {
-               Glib::Threads::RWLock::ReaderLock lm (_lock);
-               if (_events.empty()) {
-                       return 0.0;
-               }
-               return at_start ? _events.front()->when : _events.back()->when;
-       }
-
-       double length() const {
-               Glib::Threads::RWLock::ReaderLock lm (_lock);
-               return _events.empty() ? 0.0 : _events.back()->when;
-       }
-       bool empty() const { return _events.empty(); }
-
-       void clear ();
-       void x_scale (double factor);
-       bool extend_to (double);
-       void slide (iterator before, double distance);
-       void shift (double before, double distance);
-
-       void y_transform (boost::function<double(double)> callback);
-       void list_merge (ControlList const& other, boost::function<double(double, double)> callback);
-
-       /** add automation events
-        * @param when absolute time in samples
-        * @param value parameter value
-        * @param with_guards if true, add guard-points
-        * @param with_initial if true, add an initial point if the list is empty
-        */
-       virtual void add (double when, double value, bool with_guards=true, bool with_initial=true);
-
-       virtual bool editor_add (double when, double value, bool with_guard);
-
-       /* to be used only for loading pre-sorted data from saved state */
-       void fast_simple_add (double when, double value);
-
-       void erase_range (double start, double end);
-       void erase (iterator);
-       void erase (iterator, iterator);
-       void erase (double, double);
-       bool move_ranges (std::list< RangeMove<double> > const &);
-       void modify (iterator, double, double);
-
-       /** Thin the number of events in this list.
-        *
-        * The thinning factor corresponds to the area of a triangle computed
-        * between three points in the list (time-difference * value-difference).
-        * If the area is large, it indicates significant non-linearity between
-        * the points.
-        *
-        * Time is measured in samples, value is usually normalized to 0..1.
-        *
-        * During automation recording we thin the recorded points using this
-        * value.  If a point is sufficiently co-linear with its neighbours (as
-        * defined by the area of the triangle formed by three of them), we will
-        * not include it in the list.  The larger the value, the more points are
-        * excluded, so this effectively measures the amount of thinning to be
-        * done.
-        *
-        * @param thinning_factor area-size (default: 20)
-        */
-       void thin (double thinning_factor);
-
-       boost::shared_ptr<ControlList> cut (double, double);
-       boost::shared_ptr<ControlList> copy (double, double);
-
-       /** remove all automation events between the given time range
-        * @param start start of range (inclusive) in audio samples
-        * @param end end of range (inclusive) in audio samples
-        */
-       void clear (double start, double end);
-
-       bool paste (const ControlList&, double position);
-
-       /** truncate the event list after the given time
-        * @param last_coordinate last event to include
-        */
-       void truncate_end (double last_coordinate);
-       /** truncate the event list to the given time
-        * @param overall_length overall length
-        */
-       void truncate_start (double overall_length);
-
-       iterator            begin()       { return _events.begin(); }
-       const_iterator      begin() const { return _events.begin(); }
-       iterator            end()         { return _events.end(); }
-       const_iterator      end()   const { return _events.end(); }
-       reverse_iterator            rbegin()       { return _events.rbegin(); }
-       const_reverse_iterator      rbegin() const { return _events.rbegin(); }
-       reverse_iterator            rend()         { return _events.rend(); }
-       const_reverse_iterator      rend()   const { return _events.rend(); }
-       ControlEvent*       back()        { return _events.back(); }
-       const ControlEvent* back()  const { return _events.back(); }
-       ControlEvent*       front()       { return _events.front(); }
-       const ControlEvent* front() const { return _events.front(); }
-
-       std::pair<ControlList::iterator,ControlList::iterator> control_points_adjacent (double when);
-
-       template<class T> void apply_to_points (T& obj, void (T::*method)(const ControlList&)) {
-               Glib::Threads::RWLock::WriterLock lm (_lock);
-               (obj.*method)(*this);
-       }
-
-       /** query value at given time (takes a read-lock, not safe while writing automation)
-        * @param where absolute time in samples
-        * @returns parameter value
-        */
-       double eval (double where) const {
-               Glib::Threads::RWLock::ReaderLock lm (_lock);
-               return unlocked_eval (where);
-       }
-
-       /** realtime safe version of eval, may fail if read-lock cannot be taken
-        * @param where absolute time in samples
-        * @param ok boolean reference if returned value is valid
-        * @returns parameter value
-        */
-       double rt_safe_eval (double where, bool& ok) const {
-
-               Glib::Threads::RWLock::ReaderLock lm (_lock, Glib::Threads::TRY_LOCK);
-
-               if ((ok = lm.locked())) {
-                       return unlocked_eval (where);
-               } else {
-                       return 0.0;
-               }
-       }
-
-       static inline bool time_comparator (const ControlEvent* a, const ControlEvent* b) {
-               return a->when < b->when;
-       }
-
-       /** Lookup cache for eval functions, range contains equivalent values */
-       struct LookupCache {
-               LookupCache() : left(-1) {}
-               double left;  /* leftmost x coordinate used when finding "range" */
-               std::pair<ControlList::const_iterator,ControlList::const_iterator> range;
-       };
-
-       /** Lookup cache for point finding, range contains points after left */
-       struct SearchCache {
-               SearchCache () : left(-1) {}
-               double left;  /* leftmost x coordinate used when finding "first" */
-               ControlList::const_iterator first;
-       };
-
-       const EventList& events() const { return _events; }
-
-       // FIXME: const violations for Curve
-       Glib::Threads::RWLock& lock()       const { return _lock; }
-       LookupCache& lookup_cache() const { return _lookup_cache; }
-       SearchCache& search_cache() const { return _search_cache; }
-
-       /** Called by locked entry point and various private
-        * locations where we already hold the lock.
-        *
-        * FIXME: Should this be private?  Curve needs it..
-        */
-       double unlocked_eval (double x) const;
-
-       bool rt_safe_earliest_event (double start, double& x, double& y, bool start_inclusive=false) const;
-       bool rt_safe_earliest_event_unlocked (double start, double& x, double& y, bool start_inclusive=false) const;
-       bool rt_safe_earliest_event_linear_unlocked (double start, double& x, double& y, bool inclusive) const;
-       bool rt_safe_earliest_event_discrete_unlocked (double start, double& x, double& y, bool inclusive) const;
-
-       void create_curve();
-       void destroy_curve();
-
-       Curve&       curve()       { assert(_curve); return *_curve; }
-       const Curve& curve() const { assert(_curve); return *_curve; }
-
-       void mark_dirty () const;
-
-       enum InterpolationStyle {
-               Discrete,
-               Linear,
-               Curved, // spline, used for x-fades
-               Logarithmic,
-               Exponential // fader, gain
-       };
-
-       /** query interpolation style of the automation data
-        * @returns Interpolation Style
-        */
-       InterpolationStyle interpolation() const { return _interpolation; }
-
-       /** query default interpolation for parameter-descriptor */
-       virtual InterpolationStyle default_interpolation() const;
-
-       /** set the interpolation style of the automation data.
-        *
-        * This will fail when asking for Logarithmic scale and min,max crosses 0
-        * or Exponential scale with min != 0.
-        *
-        * @param is interpolation style
-        * @returns true if style change was successful
-        */
-       bool set_interpolation (InterpolationStyle is);
-
-       virtual bool touching() const { return false; }
-       virtual bool writing() const { return false; }
-       virtual bool touch_enabled() const { return false; }
-       void start_write_pass (double when);
-       void write_pass_finished (double when, double thinning_factor=0.0);
-       void set_in_write_pass (bool, bool add_point = false, double when = 0.0);
-       bool in_write_pass () const;
-       bool in_new_write_pass () { return new_write_pass; }
-
-       PBD::Signal0<void> WritePassStarted;
-       /** 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;
-
-       bool operator!= (ControlList const &) const;
-
-       void invalidate_insert_iterator ();
-
-protected:
-
-       /** Called by unlocked_eval() to handle cases of 3 or more control points. */
-       double multipoint_eval (double x) const;
-
-       void build_search_cache_if_necessary (double start) const;
-
-       boost::shared_ptr<ControlList> cut_copy_clear (double, double, int op);
-       bool erase_range_internal (double start, double end, EventList &);
-
-       void     maybe_add_insert_guard (double when);
-       iterator erase_from_iterator_to (iterator iter, double when);
-       bool     maybe_insert_straight_line (double when, double value);
-
-       virtual void maybe_signal_changed ();
-
-       void _x_scale (double factor);
-
-       mutable LookupCache   _lookup_cache;
-       mutable SearchCache   _search_cache;
-
-       mutable Glib::Threads::RWLock _lock;
-
-       Parameter             _parameter;
-       ParameterDescriptor   _desc;
-       InterpolationStyle    _interpolation;
-       EventList             _events;
-       int8_t                _frozen;
-       bool                  _changed_when_thawed;
-       bool                  _sort_pending;
-
-       Curve* _curve;
-
-private:
-       iterator   most_recent_insert_iterator;
-       double     insert_position;
-       bool       new_write_pass;
-       bool       did_write_during_pass;
-       bool       _in_write_pass;
-
-       void unlocked_remove_duplicates ();
-       void unlocked_invalidate_insert_iterator ();
-       void add_guard_point (double when, double offset);
-
-       bool is_sorted () const;
-};
-
-} // namespace Evoral
-
-#endif // EVORAL_CONTROL_LIST_HPP
-
diff --git a/libs/evoral/evoral/ControlSet.h b/libs/evoral/evoral/ControlSet.h
new file mode 100644 (file)
index 0000000..81d2558
--- /dev/null
@@ -0,0 +1,88 @@
+/*
+ * Copyright (C) 2008-2015 David Robillard <d@drobilla.net>
+ * Copyright (C) 2010-2017 Paul Davis <paul@linuxaudiosystems.com>
+ * Copyright (C) 2010 Carl Hetherington <carl@carlh.net>
+ * Copyright (C) 2015 Robin Gareus <robin@gareus.org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef EVORAL_CONTROLLABLE_HPP
+#define EVORAL_CONTROLLABLE_HPP
+
+#include <set>
+#include <map>
+#include <boost/shared_ptr.hpp>
+#include <boost/utility.hpp>
+#include <glibmm/threads.h>
+#include "pbd/signals.h"
+
+#include "evoral/visibility.h"
+#include "evoral/Parameter.h"
+#include "evoral/ControlList.h"
+
+namespace Evoral {
+
+class Control;
+class ControlEvent;
+
+class LIBEVORAL_API ControlSet : public boost::noncopyable {
+public:
+       ControlSet();
+       ControlSet (const ControlSet&);
+        virtual ~ControlSet() {}
+
+       virtual boost::shared_ptr<Evoral::Control> control_factory(const Evoral::Parameter& id) = 0;
+
+       boost::shared_ptr<Control>
+       control (const Parameter& id, bool create_if_missing=false);
+
+       inline boost::shared_ptr<const Control>
+       control (const Parameter& id) const {
+               const Controls::const_iterator i = _controls.find(id);
+               return (i != _controls.end() ? i->second : boost::shared_ptr<Control>());
+       }
+
+       typedef std::map< Parameter, boost::shared_ptr<Control> > Controls;
+       inline Controls&       controls()       { return _controls; }
+       inline const Controls& controls() const { return _controls; }
+
+       virtual void add_control(boost::shared_ptr<Control>);
+
+       virtual bool controls_empty() const { return _controls.size() == 0; }
+       virtual void clear_controls();
+
+       void what_has_data(std::set<Parameter>&) const;
+
+       Glib::Threads::Mutex& control_lock() const { return _control_lock; }
+
+protected:
+       virtual void control_list_marked_dirty () {}
+       virtual void control_list_interpolation_changed (Parameter, ControlList::InterpolationStyle) {}
+
+       mutable Glib::Threads::Mutex _control_lock;
+       Controls            _controls;
+
+       PBD::ScopedConnectionList _list_connections;
+
+private:
+
+       PBD::ScopedConnectionList _control_connections;
+};
+
+
+} // namespace Evoral
+
+#endif // EVORAL_CONTROLLABLE_HPP
diff --git a/libs/evoral/evoral/ControlSet.hpp b/libs/evoral/evoral/ControlSet.hpp
deleted file mode 100644 (file)
index ef7e8f3..0000000
+++ /dev/null
@@ -1,88 +0,0 @@
-/*
- * Copyright (C) 2008-2015 David Robillard <d@drobilla.net>
- * Copyright (C) 2010-2017 Paul Davis <paul@linuxaudiosystems.com>
- * Copyright (C) 2010 Carl Hetherington <carl@carlh.net>
- * Copyright (C) 2015 Robin Gareus <robin@gareus.org>
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-
-#ifndef EVORAL_CONTROLLABLE_HPP
-#define EVORAL_CONTROLLABLE_HPP
-
-#include <set>
-#include <map>
-#include <boost/shared_ptr.hpp>
-#include <boost/utility.hpp>
-#include <glibmm/threads.h>
-#include "pbd/signals.h"
-
-#include "evoral/visibility.h"
-#include "evoral/Parameter.hpp"
-#include "evoral/ControlList.hpp"
-
-namespace Evoral {
-
-class Control;
-class ControlEvent;
-
-class LIBEVORAL_API ControlSet : public boost::noncopyable {
-public:
-       ControlSet();
-       ControlSet (const ControlSet&);
-        virtual ~ControlSet() {}
-
-       virtual boost::shared_ptr<Evoral::Control> control_factory(const Evoral::Parameter& id) = 0;
-
-       boost::shared_ptr<Control>
-       control (const Parameter& id, bool create_if_missing=false);
-
-       inline boost::shared_ptr<const Control>
-       control (const Parameter& id) const {
-               const Controls::const_iterator i = _controls.find(id);
-               return (i != _controls.end() ? i->second : boost::shared_ptr<Control>());
-       }
-
-       typedef std::map< Parameter, boost::shared_ptr<Control> > Controls;
-       inline Controls&       controls()       { return _controls; }
-       inline const Controls& controls() const { return _controls; }
-
-       virtual void add_control(boost::shared_ptr<Control>);
-
-       virtual bool controls_empty() const { return _controls.size() == 0; }
-       virtual void clear_controls();
-
-       void what_has_data(std::set<Parameter>&) const;
-
-       Glib::Threads::Mutex& control_lock() const { return _control_lock; }
-
-protected:
-       virtual void control_list_marked_dirty () {}
-       virtual void control_list_interpolation_changed (Parameter, ControlList::InterpolationStyle) {}
-
-       mutable Glib::Threads::Mutex _control_lock;
-       Controls            _controls;
-
-       PBD::ScopedConnectionList _list_connections;
-
-private:
-
-       PBD::ScopedConnectionList _control_connections;
-};
-
-
-} // namespace Evoral
-
-#endif // EVORAL_CONTROLLABLE_HPP
diff --git a/libs/evoral/evoral/Curve.h b/libs/evoral/evoral/Curve.h
new file mode 100644 (file)
index 0000000..33bcf1d
--- /dev/null
@@ -0,0 +1,55 @@
+/*
+ * Copyright (C) 2008-2016 David Robillard <d@drobilla.net>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef EVORAL_CURVE_HPP
+#define EVORAL_CURVE_HPP
+
+#include <inttypes.h>
+#include <boost/utility.hpp>
+
+#include "evoral/visibility.h"
+
+namespace Evoral {
+
+class ControlList;
+
+class LIBEVORAL_API Curve : public boost::noncopyable
+{
+public:
+       Curve (const ControlList& cl);
+
+       bool rt_safe_get_vector (double x0, double x1, float *arg, int32_t veclen) const;
+       void get_vector (double x0, double x1, float *arg, int32_t veclen) const;
+
+       void solve () const;
+
+       void mark_dirty() const { _dirty = true; }
+
+private:
+       double multipoint_eval (double x) const;
+
+       void _get_vector (double x0, double x1, float *arg, int32_t veclen) const;
+
+       mutable bool       _dirty;
+       const ControlList& _list;
+};
+
+} // namespace Evoral
+
+#endif // EVORAL_CURVE_HPP
+
diff --git a/libs/evoral/evoral/Curve.hpp b/libs/evoral/evoral/Curve.hpp
deleted file mode 100644 (file)
index 33bcf1d..0000000
+++ /dev/null
@@ -1,55 +0,0 @@
-/*
- * Copyright (C) 2008-2016 David Robillard <d@drobilla.net>
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-
-#ifndef EVORAL_CURVE_HPP
-#define EVORAL_CURVE_HPP
-
-#include <inttypes.h>
-#include <boost/utility.hpp>
-
-#include "evoral/visibility.h"
-
-namespace Evoral {
-
-class ControlList;
-
-class LIBEVORAL_API Curve : public boost::noncopyable
-{
-public:
-       Curve (const ControlList& cl);
-
-       bool rt_safe_get_vector (double x0, double x1, float *arg, int32_t veclen) const;
-       void get_vector (double x0, double x1, float *arg, int32_t veclen) const;
-
-       void solve () const;
-
-       void mark_dirty() const { _dirty = true; }
-
-private:
-       double multipoint_eval (double x) const;
-
-       void _get_vector (double x0, double x1, float *arg, int32_t veclen) const;
-
-       mutable bool       _dirty;
-       const ControlList& _list;
-};
-
-} // namespace Evoral
-
-#endif // EVORAL_CURVE_HPP
-
diff --git a/libs/evoral/evoral/Event.h b/libs/evoral/evoral/Event.h
new file mode 100644 (file)
index 0000000..6780b4e
--- /dev/null
@@ -0,0 +1,221 @@
+/*
+ * Copyright (C) 2008-2016 David Robillard <d@drobilla.net>
+ * Copyright (C) 2009-2013 Paul Davis <paul@linuxaudiosystems.com>
+ * Copyright (C) 2009 Hans Baier <hansfbaier@googlemail.com>
+ * Copyright (C) 2010 Carl Hetherington <carl@carlh.net>
+ * Copyright (C) 2015-2016 Robin Gareus <robin@gareus.org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef EVORAL_EVENT_HPP
+#define EVORAL_EVENT_HPP
+
+#include <cstdlib>
+#include <cstring>
+#include <sstream>
+#include <stdint.h>
+
+#include "evoral/midi_events.h"
+#include "evoral/types.h"
+#include "evoral/visibility.h"
+
+/** If this is not defined, all methods of MidiEvent are RT safe
+ * but MidiEvent will never deep copy and (depending on the scenario)
+ * may not be usable in STL containers, signals, etc.
+ */
+#define EVORAL_EVENT_ALLOC 1
+
+namespace Evoral {
+
+LIBEVORAL_API event_id_t event_id_counter();
+LIBEVORAL_API event_id_t next_event_id();
+LIBEVORAL_API void       init_event_id_counter(event_id_t n);
+
+/** An event (much like a type generic jack_midi_event_t)
+ *
+ * Template parameter Time is the type of the time stamp used for this event.
+ */
+template<typename Time>
+class LIBEVORAL_API Event {
+public:
+#ifdef EVORAL_EVENT_ALLOC
+       Event(EventType type=NO_EVENT, Time time=Time(), uint32_t size=0, uint8_t* buf=NULL, bool alloc=false);
+
+       Event(EventType type, Time time, uint32_t size, const uint8_t* buf);
+
+       /** Copy \a copy.
+        *
+        * If \a alloc is true, the buffer will be copied and this method
+        * is NOT REALTIME SAFE.  Otherwise both events share a buffer and
+        * memory management semantics are the caller's problem.
+        */
+       Event(const Event& copy, bool alloc);
+
+       ~Event();
+
+       void assign(const Event& other);
+
+       void set(const uint8_t* buf, uint32_t size, Time t);
+
+       inline bool operator==(const Event& other) const {
+               if (_type != other._type || _time != other._time || _size != other._size) {
+                       return false;
+               }
+               return !memcmp(_buf, other._buf, _size);
+       }
+
+       inline bool operator!=(const Event& other) const { return ! operator==(other); }
+
+       inline bool owns_buffer() const { return _owns_buf; }
+
+       /** set event data (e.g. midi data)
+        * @param size number of bytes
+        * @param buf raw 8bit data
+        * @param own set to true if the buffer owns the data (copy, allocate/free) or false to reference previously allocated data.
+        */
+       inline void set_buffer(uint32_t size, uint8_t* buf, bool own) {
+               if (_owns_buf) {
+                       free(_buf);
+                       _buf = NULL;
+               }
+               _size     = size;
+               _buf      = buf;
+               _owns_buf = own;
+       }
+
+       inline void realloc(uint32_t size) {
+               if (_owns_buf) {
+                       if (size > _size)
+                               _buf = (uint8_t*) ::realloc(_buf, size);
+               } else {
+                       _buf = (uint8_t*) ::malloc(size);
+                       _owns_buf = true;
+               }
+
+               _size = size;
+       }
+
+       inline void clear() {
+               _type = NO_EVENT;
+               _time = Time();
+               _size = 0;
+               _buf  = NULL;
+       }
+
+#endif // EVORAL_EVENT_ALLOC
+
+       inline EventType      event_type()    const { return _type; }
+       inline Time           time()          const { return _time; }
+       inline uint32_t       size()          const { return _size; }
+       inline const uint8_t* buffer()        const { return _buf; }
+       inline uint8_t*       buffer()              { return _buf; }
+
+       inline void set_event_type(EventType t) { _type = t; }
+
+       inline void set_time(Time t) { _time = t; }
+
+       inline event_id_t id() const           { return _id; }
+       inline void       set_id(event_id_t n) { _id = n; }
+
+       /* The following methods are type specific and only make sense for the
+          correct event type.  It is the caller's responsibility to only call
+          methods which make sense for the given event type.  Currently this means
+          they all only make sense for MIDI, but built-in support may be added for
+          other protocols in the future, or the internal representation may change
+          to be protocol agnostic. */
+
+       uint8_t  type()                const { return _buf[0] & 0xF0; }
+       uint8_t  channel()             const { return _buf[0] & 0x0F; }
+       bool     is_note_on()          const { return type() == MIDI_CMD_NOTE_ON; }
+       bool     is_note_off()         const { return type() == MIDI_CMD_NOTE_OFF; }
+       bool     is_note()             const { return is_note_on() || is_note_off(); }
+       bool     is_poly_pressure()    const { return type() == MIDI_CMD_NOTE_PRESSURE; }
+       bool     is_channel_pressure() const { return type() == MIDI_CMD_CHANNEL_PRESSURE; }
+       bool     is_cc()               const { return type() == MIDI_CMD_CONTROL; }
+       bool     is_pgm_change()       const { return type() == MIDI_CMD_PGM_CHANGE; }
+       bool     is_pitch_bender()     const { return type() == MIDI_CMD_BENDER; }
+       bool     is_channel_event()    const { return (0x80 <= type()) && (type() <= 0xE0); }
+       bool     is_smf_meta_event()   const { return _buf[0] == 0xFF; }
+       bool     is_sysex()            const { return _buf[0] == 0xF0 || _buf[0] == 0xF7; }
+       bool     is_spp()              const { return _buf[0] == 0xF2 && size() == 1; }
+       bool     is_mtc_quarter()      const { return _buf[0] == 0xF1 && size() == 1; }
+       bool     is_mtc_full()         const { return (size() == 10 &&
+                                                      _buf[0] == 0xF0 && _buf[1] == 0x7F &&
+                                                      _buf[3] == 0x01 && _buf[4] == 0x01); }
+
+       uint8_t  note()               const { return _buf[1]; }
+       uint8_t  velocity()           const { return _buf[2]; }
+       uint8_t  poly_note()          const { return _buf[1]; }
+       uint8_t  poly_pressure()      const { return _buf[2]; }
+       uint8_t  channel_pressure()   const { return _buf[1]; }
+       uint8_t  cc_number()          const { return _buf[1]; }
+       uint8_t  cc_value()           const { return _buf[2]; }
+       uint8_t  pgm_number()         const { return _buf[1]; }
+       uint8_t  pitch_bender_lsb()   const { return _buf[1]; }
+       uint8_t  pitch_bender_msb()   const { return _buf[2]; }
+       uint16_t pitch_bender_value() const { return ((0x7F & _buf[2]) << 7 | (0x7F & _buf[1])); }
+
+       void set_channel(uint8_t channel)  { _buf[0] = (0xF0 & _buf[0]) | (0x0F & channel); }
+       void set_type(uint8_t type)        { _buf[0] = (0x0F & _buf[0]) | (0xF0 & type); }
+       void set_note(uint8_t num)         { _buf[1] = num; }
+       void set_velocity(uint8_t val)     { _buf[2] = val; }
+       void set_cc_number(uint8_t num)    { _buf[1] = num; }
+       void set_cc_value(uint8_t val)     { _buf[2] = val; }
+       void set_pgm_number(uint8_t num)   { _buf[1] = num; }
+
+       uint16_t value() const {
+               switch (type()) {
+               case MIDI_CMD_CONTROL:
+                       return cc_value();
+               case MIDI_CMD_BENDER:
+                       return pitch_bender_value();
+               case MIDI_CMD_NOTE_PRESSURE:
+                       return poly_pressure();
+               case MIDI_CMD_CHANNEL_PRESSURE:
+                       return channel_pressure();
+               case MIDI_CMD_PGM_CHANGE:
+                       return pgm_number();
+               default:
+                       return 0;
+               }
+       }
+
+protected:
+       EventType  _type;      ///< Type of event (application relative, NOT MIDI 'type')
+       Time       _time;      ///< Time stamp of event
+       uint32_t   _size;      ///< Size of buffer in bytes
+       uint8_t*   _buf;       ///< Event contents (e.g. raw MIDI data)
+       event_id_t _id;        ///< Unique event ID
+#ifdef EVORAL_EVENT_ALLOC
+       bool       _owns_buf;  ///< Whether buffer is locally allocated
+#endif
+};
+
+template<typename Time>
+/*LIBEVORAL_API*/ std::ostream& operator<<(std::ostream& o, const Evoral::Event<Time>& ev) {
+       o << "Event #" << ev.id() << " type = " << ev.event_type() << " @ " << ev.time();
+       o << std::hex;
+       for (uint32_t n = 0; n < ev.size(); ++n) {
+               o << ' ' << (int) ev.buffer()[n];
+       }
+       o << std::dec;
+       return o;
+}
+
+} // namespace Evoral
+
+#endif // EVORAL_EVENT_HPP
+
diff --git a/libs/evoral/evoral/Event.hpp b/libs/evoral/evoral/Event.hpp
deleted file mode 100644 (file)
index 662d7db..0000000
+++ /dev/null
@@ -1,221 +0,0 @@
-/*
- * Copyright (C) 2008-2016 David Robillard <d@drobilla.net>
- * Copyright (C) 2009-2013 Paul Davis <paul@linuxaudiosystems.com>
- * Copyright (C) 2009 Hans Baier <hansfbaier@googlemail.com>
- * Copyright (C) 2010 Carl Hetherington <carl@carlh.net>
- * Copyright (C) 2015-2016 Robin Gareus <robin@gareus.org>
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-
-#ifndef EVORAL_EVENT_HPP
-#define EVORAL_EVENT_HPP
-
-#include <cstdlib>
-#include <cstring>
-#include <sstream>
-#include <stdint.h>
-
-#include "evoral/midi_events.h"
-#include "evoral/types.hpp"
-#include "evoral/visibility.h"
-
-/** If this is not defined, all methods of MidiEvent are RT safe
- * but MidiEvent will never deep copy and (depending on the scenario)
- * may not be usable in STL containers, signals, etc.
- */
-#define EVORAL_EVENT_ALLOC 1
-
-namespace Evoral {
-
-LIBEVORAL_API event_id_t event_id_counter();
-LIBEVORAL_API event_id_t next_event_id();
-LIBEVORAL_API void       init_event_id_counter(event_id_t n);
-
-/** An event (much like a type generic jack_midi_event_t)
- *
- * Template parameter Time is the type of the time stamp used for this event.
- */
-template<typename Time>
-class LIBEVORAL_API Event {
-public:
-#ifdef EVORAL_EVENT_ALLOC
-       Event(EventType type=NO_EVENT, Time time=Time(), uint32_t size=0, uint8_t* buf=NULL, bool alloc=false);
-
-       Event(EventType type, Time time, uint32_t size, const uint8_t* buf);
-
-       /** Copy \a copy.
-        *
-        * If \a alloc is true, the buffer will be copied and this method
-        * is NOT REALTIME SAFE.  Otherwise both events share a buffer and
-        * memory management semantics are the caller's problem.
-        */
-       Event(const Event& copy, bool alloc);
-
-       ~Event();
-
-       void assign(const Event& other);
-
-       void set(const uint8_t* buf, uint32_t size, Time t);
-
-       inline bool operator==(const Event& other) const {
-               if (_type != other._type || _time != other._time || _size != other._size) {
-                       return false;
-               }
-               return !memcmp(_buf, other._buf, _size);
-       }
-
-       inline bool operator!=(const Event& other) const { return ! operator==(other); }
-
-       inline bool owns_buffer() const { return _owns_buf; }
-
-       /** set event data (e.g. midi data)
-        * @param size number of bytes
-        * @param buf raw 8bit data
-        * @param own set to true if the buffer owns the data (copy, allocate/free) or false to reference previously allocated data.
-        */
-       inline void set_buffer(uint32_t size, uint8_t* buf, bool own) {
-               if (_owns_buf) {
-                       free(_buf);
-                       _buf = NULL;
-               }
-               _size     = size;
-               _buf      = buf;
-               _owns_buf = own;
-       }
-
-       inline void realloc(uint32_t size) {
-               if (_owns_buf) {
-                       if (size > _size)
-                               _buf = (uint8_t*) ::realloc(_buf, size);
-               } else {
-                       _buf = (uint8_t*) ::malloc(size);
-                       _owns_buf = true;
-               }
-
-               _size = size;
-       }
-
-       inline void clear() {
-               _type = NO_EVENT;
-               _time = Time();
-               _size = 0;
-               _buf  = NULL;
-       }
-
-#endif // EVORAL_EVENT_ALLOC
-
-       inline EventType      event_type()    const { return _type; }
-       inline Time           time()          const { return _time; }
-       inline uint32_t       size()          const { return _size; }
-       inline const uint8_t* buffer()        const { return _buf; }
-       inline uint8_t*       buffer()              { return _buf; }
-
-       inline void set_event_type(EventType t) { _type = t; }
-
-       inline void set_time(Time t) { _time = t; }
-
-       inline event_id_t id() const           { return _id; }
-       inline void       set_id(event_id_t n) { _id = n; }
-
-       /* The following methods are type specific and only make sense for the
-          correct event type.  It is the caller's responsibility to only call
-          methods which make sense for the given event type.  Currently this means
-          they all only make sense for MIDI, but built-in support may be added for
-          other protocols in the future, or the internal representation may change
-          to be protocol agnostic. */
-
-       uint8_t  type()                const { return _buf[0] & 0xF0; }
-       uint8_t  channel()             const { return _buf[0] & 0x0F; }
-       bool     is_note_on()          const { return type() == MIDI_CMD_NOTE_ON; }
-       bool     is_note_off()         const { return type() == MIDI_CMD_NOTE_OFF; }
-       bool     is_note()             const { return is_note_on() || is_note_off(); }
-       bool     is_poly_pressure()    const { return type() == MIDI_CMD_NOTE_PRESSURE; }
-       bool     is_channel_pressure() const { return type() == MIDI_CMD_CHANNEL_PRESSURE; }
-       bool     is_cc()               const { return type() == MIDI_CMD_CONTROL; }
-       bool     is_pgm_change()       const { return type() == MIDI_CMD_PGM_CHANGE; }
-       bool     is_pitch_bender()     const { return type() == MIDI_CMD_BENDER; }
-       bool     is_channel_event()    const { return (0x80 <= type()) && (type() <= 0xE0); }
-       bool     is_smf_meta_event()   const { return _buf[0] == 0xFF; }
-       bool     is_sysex()            const { return _buf[0] == 0xF0 || _buf[0] == 0xF7; }
-       bool     is_spp()              const { return _buf[0] == 0xF2 && size() == 1; }
-       bool     is_mtc_quarter()      const { return _buf[0] == 0xF1 && size() == 1; }
-       bool     is_mtc_full()         const { return (size() == 10 &&
-                                                      _buf[0] == 0xF0 && _buf[1] == 0x7F &&
-                                                      _buf[3] == 0x01 && _buf[4] == 0x01); }
-
-       uint8_t  note()               const { return _buf[1]; }
-       uint8_t  velocity()           const { return _buf[2]; }
-       uint8_t  poly_note()          const { return _buf[1]; }
-       uint8_t  poly_pressure()      const { return _buf[2]; }
-       uint8_t  channel_pressure()   const { return _buf[1]; }
-       uint8_t  cc_number()          const { return _buf[1]; }
-       uint8_t  cc_value()           const { return _buf[2]; }
-       uint8_t  pgm_number()         const { return _buf[1]; }
-       uint8_t  pitch_bender_lsb()   const { return _buf[1]; }
-       uint8_t  pitch_bender_msb()   const { return _buf[2]; }
-       uint16_t pitch_bender_value() const { return ((0x7F & _buf[2]) << 7 | (0x7F & _buf[1])); }
-
-       void set_channel(uint8_t channel)  { _buf[0] = (0xF0 & _buf[0]) | (0x0F & channel); }
-       void set_type(uint8_t type)        { _buf[0] = (0x0F & _buf[0]) | (0xF0 & type); }
-       void set_note(uint8_t num)         { _buf[1] = num; }
-       void set_velocity(uint8_t val)     { _buf[2] = val; }
-       void set_cc_number(uint8_t num)    { _buf[1] = num; }
-       void set_cc_value(uint8_t val)     { _buf[2] = val; }
-       void set_pgm_number(uint8_t num)   { _buf[1] = num; }
-
-       uint16_t value() const {
-               switch (type()) {
-               case MIDI_CMD_CONTROL:
-                       return cc_value();
-               case MIDI_CMD_BENDER:
-                       return pitch_bender_value();
-               case MIDI_CMD_NOTE_PRESSURE:
-                       return poly_pressure();
-               case MIDI_CMD_CHANNEL_PRESSURE:
-                       return channel_pressure();
-               case MIDI_CMD_PGM_CHANGE:
-                       return pgm_number();
-               default:
-                       return 0;
-               }
-       }
-
-protected:
-       EventType  _type;      ///< Type of event (application relative, NOT MIDI 'type')
-       Time       _time;      ///< Time stamp of event
-       uint32_t   _size;      ///< Size of buffer in bytes
-       uint8_t*   _buf;       ///< Event contents (e.g. raw MIDI data)
-       event_id_t _id;        ///< Unique event ID
-#ifdef EVORAL_EVENT_ALLOC
-       bool       _owns_buf;  ///< Whether buffer is locally allocated
-#endif
-};
-
-template<typename Time>
-/*LIBEVORAL_API*/ std::ostream& operator<<(std::ostream& o, const Evoral::Event<Time>& ev) {
-       o << "Event #" << ev.id() << " type = " << ev.event_type() << " @ " << ev.time();
-       o << std::hex;
-       for (uint32_t n = 0; n < ev.size(); ++n) {
-               o << ' ' << (int) ev.buffer()[n];
-       }
-       o << std::dec;
-       return o;
-}
-
-} // namespace Evoral
-
-#endif // EVORAL_EVENT_HPP
-
diff --git a/libs/evoral/evoral/EventList.h b/libs/evoral/evoral/EventList.h
new file mode 100644 (file)
index 0000000..afa251f
--- /dev/null
@@ -0,0 +1,51 @@
+/*
+ * Copyright (C) 2009-2013 Paul Davis <paul@linuxaudiosystems.com>
+ * Copyright (C) 2009-2015 David Robillard <d@drobilla.net>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef EVORAL_EVENT_LIST_HPP
+#define EVORAL_EVENT_LIST_HPP
+
+#include <list>
+
+#include "evoral/Event.h"
+#include "evoral/EventSink.h"
+#include "evoral/visibility.h"
+
+namespace Evoral {
+
+
+/** A list of events (generic time-stamped binary "blobs").
+ *
+ * Used when we need an unsorted list of Events that is also an EventSink. Absolutely nothing more.
+ */
+template<typename Time>
+class /*LIBEVORAL_API*/ EventList : public std::list<Evoral::Event<Time> *>, public Evoral::EventSink<Time> {
+public:
+       EventList() {}
+
+       uint32_t write(Time  time, EventType  type, uint32_t  size, const uint8_t* buf) {
+               this->push_back(new Evoral::Event<Time>(
+                                 type, time, size, const_cast<uint8_t*>(buf), true)); // Event copies buffer
+               return size;
+       }
+};
+
+
+} // namespace Evoral
+
+#endif // EVORAL_EVENT_LIST_HPP
diff --git a/libs/evoral/evoral/EventList.hpp b/libs/evoral/evoral/EventList.hpp
deleted file mode 100644 (file)
index a655380..0000000
+++ /dev/null
@@ -1,51 +0,0 @@
-/*
- * Copyright (C) 2009-2013 Paul Davis <paul@linuxaudiosystems.com>
- * Copyright (C) 2009-2015 David Robillard <d@drobilla.net>
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-
-#ifndef EVORAL_EVENT_LIST_HPP
-#define EVORAL_EVENT_LIST_HPP
-
-#include <list>
-
-#include "evoral/Event.hpp"
-#include "evoral/EventSink.hpp"
-#include "evoral/visibility.h"
-
-namespace Evoral {
-
-
-/** A list of events (generic time-stamped binary "blobs").
- *
- * Used when we need an unsorted list of Events that is also an EventSink. Absolutely nothing more.
- */
-template<typename Time>
-class /*LIBEVORAL_API*/ EventList : public std::list<Evoral::Event<Time> *>, public Evoral::EventSink<Time> {
-public:
-       EventList() {}
-
-       uint32_t write(Time  time, EventType  type, uint32_t  size, const uint8_t* buf) {
-               this->push_back(new Evoral::Event<Time>(
-                                 type, time, size, const_cast<uint8_t*>(buf), true)); // Event copies buffer
-               return size;
-       }
-};
-
-
-} // namespace Evoral
-
-#endif // EVORAL_EVENT_LIST_HPP
diff --git a/libs/evoral/evoral/EventSink.h b/libs/evoral/evoral/EventSink.h
new file mode 100644 (file)
index 0000000..d3f8c76
--- /dev/null
@@ -0,0 +1,41 @@
+/*
+ * Copyright (C) 2008-2011 David Robillard <d@drobilla.net>
+ * Copyright (C) 2008-2013 Paul Davis <paul@linuxaudiosystems.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef EVORAL_EVENT_SINK_HPP
+#define EVORAL_EVENT_SINK_HPP
+
+#include "evoral/visibility.h"
+#include "evoral/types.h"
+
+namespace Evoral {
+
+/** Pure virtual base for anything you can write events to.
+ */
+template<typename Time>
+class /*LIBEVORAL_API*/ EventSink {
+public:
+       virtual ~EventSink() {}
+       virtual uint32_t write(Time time, EventType type, uint32_t size, const uint8_t* buf) = 0;
+};
+
+
+} // namespace Evoral
+
+#endif // EVORAL_EVENT_SINK_HPP
+
diff --git a/libs/evoral/evoral/EventSink.hpp b/libs/evoral/evoral/EventSink.hpp
deleted file mode 100644 (file)
index 6e097ce..0000000
+++ /dev/null
@@ -1,41 +0,0 @@
-/*
- * Copyright (C) 2008-2011 David Robillard <d@drobilla.net>
- * Copyright (C) 2008-2013 Paul Davis <paul@linuxaudiosystems.com>
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-
-#ifndef EVORAL_EVENT_SINK_HPP
-#define EVORAL_EVENT_SINK_HPP
-
-#include "evoral/visibility.h"
-#include "evoral/types.hpp"
-
-namespace Evoral {
-
-/** Pure virtual base for anything you can write events to.
- */
-template<typename Time>
-class /*LIBEVORAL_API*/ EventSink {
-public:
-       virtual ~EventSink() {}
-       virtual uint32_t write(Time time, EventType type, uint32_t size, const uint8_t* buf) = 0;
-};
-
-
-} // namespace Evoral
-
-#endif // EVORAL_EVENT_SINK_HPP
-
diff --git a/libs/evoral/evoral/MIDIXML.h b/libs/evoral/evoral/MIDIXML.h
new file mode 100644 (file)
index 0000000..1cd6655
--- /dev/null
@@ -0,0 +1,96 @@
+/*
+ * Copyright (C) 2016 David Robillard <d@drobilla.net>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef EVORAL_MIDI_XML_HPP
+#define EVORAL_MIDI_XML_HPP
+
+#include "evoral/Event.h"
+#include "pbd/xml++.h"
+
+namespace Evoral {
+namespace MIDIXML {
+
+template<typename Time>
+bool
+xml_to_midi(const XMLNode& node, Evoral::Event<Time>& ev)
+{
+       if (node.name() == "ControlChange") {
+               ev.set_type(MIDI_CMD_CONTROL);
+               ev.set_cc_number(atoi(node.property("Control")->value().c_str()));
+               ev.set_cc_value(atoi(node.property("Value")->value().c_str()));
+               return true;
+       } else if (node.name() == "ProgramChange") {
+               ev.set_type(MIDI_CMD_PGM_CHANGE);
+               ev.set_pgm_number(atoi(node.property("Number")->value().c_str()));
+               return true;
+       }
+
+       return false;
+}
+
+template<typename Time>
+boost::shared_ptr<XMLNode>
+midi_to_xml(const Evoral::Event<Time>& ev)
+{
+       XMLNode* result = 0;
+
+       switch (ev.type()) {
+       case MIDI_CMD_CONTROL:
+               result = new XMLNode("ControlChange");
+               result->add_property("Channel", long(ev.channel()));
+               result->add_property("Control", long(ev.cc_number()));
+               result->add_property("Value",   long(ev.cc_value()));
+               break;
+
+       case MIDI_CMD_PGM_CHANGE:
+               result = new XMLNode("ProgramChange");
+               result->add_property("Channel", long(ev.channel()));
+               result->add_property("Number",  long(ev.pgm_number()));
+               break;
+
+       case MIDI_CMD_NOTE_ON:
+               result = new XMLNode("NoteOn");
+               result->add_property("Channel",  long(ev.channel()));
+               result->add_property("Note",     long(ev.note()));
+               result->add_property("Velocity", long(ev.velocity()));
+               break;
+
+       case MIDI_CMD_NOTE_OFF:
+               result = new XMLNode("NoteOff");
+               result->add_property("Channel",  long(ev.channel()));
+               result->add_property("Note",     long(ev.note()));
+               result->add_property("Velocity", long(ev.velocity()));
+               break;
+
+       case MIDI_CMD_BENDER:
+               result = new XMLNode("PitchBendChange");
+               result->add_property("Channel", long(ev.channel()));
+               result->add_property("Value",   long(ev.pitch_bender_value()));
+               break;
+
+       default:
+               return boost::shared_ptr<XMLNode>();
+       }
+
+       return boost::shared_ptr<XMLNode>(result);
+}
+
+} // namespace MIDIXML
+} // namespace Evoral
+
+#endif // EVORAL_MIDI_XML_HPP
diff --git a/libs/evoral/evoral/MIDIXML.hpp b/libs/evoral/evoral/MIDIXML.hpp
deleted file mode 100644 (file)
index 15483ae..0000000
+++ /dev/null
@@ -1,96 +0,0 @@
-/*
- * Copyright (C) 2016 David Robillard <d@drobilla.net>
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-
-#ifndef EVORAL_MIDI_XML_HPP
-#define EVORAL_MIDI_XML_HPP
-
-#include "evoral/Event.hpp"
-#include "pbd/xml++.h"
-
-namespace Evoral {
-namespace MIDIXML {
-
-template<typename Time>
-bool
-xml_to_midi(const XMLNode& node, Evoral::Event<Time>& ev)
-{
-       if (node.name() == "ControlChange") {
-               ev.set_type(MIDI_CMD_CONTROL);
-               ev.set_cc_number(atoi(node.property("Control")->value().c_str()));
-               ev.set_cc_value(atoi(node.property("Value")->value().c_str()));
-               return true;
-       } else if (node.name() == "ProgramChange") {
-               ev.set_type(MIDI_CMD_PGM_CHANGE);
-               ev.set_pgm_number(atoi(node.property("Number")->value().c_str()));
-               return true;
-       }
-
-       return false;
-}
-
-template<typename Time>
-boost::shared_ptr<XMLNode>
-midi_to_xml(const Evoral::Event<Time>& ev)
-{
-       XMLNode* result = 0;
-
-       switch (ev.type()) {
-       case MIDI_CMD_CONTROL:
-               result = new XMLNode("ControlChange");
-               result->add_property("Channel", long(ev.channel()));
-               result->add_property("Control", long(ev.cc_number()));
-               result->add_property("Value",   long(ev.cc_value()));
-               break;
-
-       case MIDI_CMD_PGM_CHANGE:
-               result = new XMLNode("ProgramChange");
-               result->add_property("Channel", long(ev.channel()));
-               result->add_property("Number",  long(ev.pgm_number()));
-               break;
-
-       case MIDI_CMD_NOTE_ON:
-               result = new XMLNode("NoteOn");
-               result->add_property("Channel",  long(ev.channel()));
-               result->add_property("Note",     long(ev.note()));
-               result->add_property("Velocity", long(ev.velocity()));
-               break;
-
-       case MIDI_CMD_NOTE_OFF:
-               result = new XMLNode("NoteOff");
-               result->add_property("Channel",  long(ev.channel()));
-               result->add_property("Note",     long(ev.note()));
-               result->add_property("Velocity", long(ev.velocity()));
-               break;
-
-       case MIDI_CMD_BENDER:
-               result = new XMLNode("PitchBendChange");
-               result->add_property("Channel", long(ev.channel()));
-               result->add_property("Value",   long(ev.pitch_bender_value()));
-               break;
-
-       default:
-               return boost::shared_ptr<XMLNode>();
-       }
-
-       return boost::shared_ptr<XMLNode>(result);
-}
-
-} // namespace MIDIXML
-} // namespace Evoral
-
-#endif // EVORAL_MIDI_XML_HPP
diff --git a/libs/evoral/evoral/Note.h b/libs/evoral/evoral/Note.h
new file mode 100644 (file)
index 0000000..5929c33
--- /dev/null
@@ -0,0 +1,131 @@
+/*
+ * Copyright (C) 2008-2016 David Robillard <d@drobilla.net>
+ * Copyright (C) 2009-2014 Paul Davis <paul@linuxaudiosystems.com>
+ * Copyright (C) 2014 John Emmas <john@creativepost.co.uk>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef EVORAL_NOTE_HPP
+#define EVORAL_NOTE_HPP
+
+#include <algorithm>
+#include <assert.h>
+#include <glib.h>
+#include <stdint.h>
+
+#include "evoral/visibility.h"
+#include "evoral/Event.h"
+
+namespace Evoral {
+
+/** An abstract (protocol agnostic) note.
+ *
+ * Currently a note is defined as (on event, length, off event).
+ */
+template<typename Time>
+#ifdef COMPILER_MSVC
+class LIBEVORAL_LOCAL Note {
+#else
+class LIBEVORAL_TEMPLATE_API Note {
+#endif
+public:
+       Note(uint8_t chan=0, Time time=Time(), Time len=Time(), uint8_t note=0, uint8_t vel=0x40);
+       Note(const Note<Time>& copy);
+       ~Note();
+
+       inline bool operator==(const Note<Time>& other) {
+               return time() == other.time() &&
+                       note() == other.note() &&
+                       length() == other.length() &&
+                       velocity() == other.velocity() &&
+                       off_velocity() == other.off_velocity() &&
+                       channel()  == other.channel();
+       }
+
+       inline event_id_t id() const { return _on_event.id(); }
+       void set_id (event_id_t);
+
+       inline Time    time()         const { return _on_event.time(); }
+       inline Time    end_time()     const { return _off_event.time(); }
+       inline uint8_t note()         const { return _on_event.note(); }
+       inline uint8_t velocity()     const { return _on_event.velocity(); }
+       inline uint8_t off_velocity() const { return _off_event.velocity(); }
+       inline Time    length()       const { return _off_event.time() - _on_event.time(); }
+       inline uint8_t channel()      const {
+               assert(_on_event.channel() == _off_event.channel());
+               return _on_event.channel();
+       }
+
+private:
+       const Note<Time>& operator=(const Note<Time>& copy);  // undefined (unsafe)
+
+       inline int clamp(int val, int low, int high) {
+               return std::min (std::max (val, low), high);
+       }
+
+public:
+       inline void set_time(Time t) {
+               _off_event.set_time(t + length());
+               _on_event.set_time(t);
+       }
+       inline void set_note(uint8_t n) {
+               const uint8_t nn = clamp(n, 0, 127);
+               _on_event.buffer()[1] = nn;
+               _off_event.buffer()[1] = nn;
+       }
+       inline void set_velocity(uint8_t n) {
+               _on_event.buffer()[2] = clamp(n, 0, 127);
+       }
+       inline void set_off_velocity(uint8_t n) {
+               _off_event.buffer()[2] = clamp(n, 0, 127);
+       }
+       inline void set_length(Time l) {
+               _off_event.set_time(_on_event.time() + l);
+       }
+       inline void set_channel(uint8_t c) {
+               const uint8_t cc = clamp(c, 0, 16);
+               _on_event.set_channel(cc);
+               _off_event.set_channel(cc);
+       }
+
+       inline       Event<Time>& on_event()        { return _on_event; }
+       inline const Event<Time>& on_event()  const { return _on_event; }
+       inline       Event<Time>& off_event()       { return _off_event; }
+       inline const Event<Time>& off_event() const { return _off_event; }
+
+private:
+       // Event buffers are self-contained
+       Event<Time> _on_event;
+       Event<Time> _off_event;
+};
+
+template<typename Time>
+/*LIBEVORAL_API*/ std::ostream& operator<<(std::ostream& o, const Evoral::Note<Time>& n) {
+       o << "Note #" << n.id() << ": pitch = " << (int) n.note()
+         << " @ " << n.time() << " .. " << n.end_time()
+         << " velocity " << (int) n.velocity()
+         << " chn " << (int) n.channel();
+       return o;
+}
+
+} // namespace Evoral
+
+#ifdef COMPILER_MSVC
+#include "../src/Note.impl"
+#endif
+
+#endif // EVORAL_NOTE_HPP
+
diff --git a/libs/evoral/evoral/Note.hpp b/libs/evoral/evoral/Note.hpp
deleted file mode 100644 (file)
index af88f29..0000000
+++ /dev/null
@@ -1,131 +0,0 @@
-/*
- * Copyright (C) 2008-2016 David Robillard <d@drobilla.net>
- * Copyright (C) 2009-2014 Paul Davis <paul@linuxaudiosystems.com>
- * Copyright (C) 2014 John Emmas <john@creativepost.co.uk>
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-
-#ifndef EVORAL_NOTE_HPP
-#define EVORAL_NOTE_HPP
-
-#include <algorithm>
-#include <assert.h>
-#include <glib.h>
-#include <stdint.h>
-
-#include "evoral/visibility.h"
-#include "evoral/Event.hpp"
-
-namespace Evoral {
-
-/** An abstract (protocol agnostic) note.
- *
- * Currently a note is defined as (on event, length, off event).
- */
-template<typename Time>
-#ifdef COMPILER_MSVC
-class LIBEVORAL_LOCAL Note {
-#else
-class LIBEVORAL_TEMPLATE_API Note {
-#endif
-public:
-       Note(uint8_t chan=0, Time time=Time(), Time len=Time(), uint8_t note=0, uint8_t vel=0x40);
-       Note(const Note<Time>& copy);
-       ~Note();
-
-       inline bool operator==(const Note<Time>& other) {
-               return time() == other.time() &&
-                       note() == other.note() &&
-                       length() == other.length() &&
-                       velocity() == other.velocity() &&
-                       off_velocity() == other.off_velocity() &&
-                       channel()  == other.channel();
-       }
-
-       inline event_id_t id() const { return _on_event.id(); }
-       void set_id (event_id_t);
-
-       inline Time    time()         const { return _on_event.time(); }
-       inline Time    end_time()     const { return _off_event.time(); }
-       inline uint8_t note()         const { return _on_event.note(); }
-       inline uint8_t velocity()     const { return _on_event.velocity(); }
-       inline uint8_t off_velocity() const { return _off_event.velocity(); }
-       inline Time    length()       const { return _off_event.time() - _on_event.time(); }
-       inline uint8_t channel()      const {
-               assert(_on_event.channel() == _off_event.channel());
-               return _on_event.channel();
-       }
-
-private:
-       const Note<Time>& operator=(const Note<Time>& copy);  // undefined (unsafe)
-
-       inline int clamp(int val, int low, int high) {
-               return std::min (std::max (val, low), high);
-       }
-
-public:
-       inline void set_time(Time t) {
-               _off_event.set_time(t + length());
-               _on_event.set_time(t);
-       }
-       inline void set_note(uint8_t n) {
-               const uint8_t nn = clamp(n, 0, 127);
-               _on_event.buffer()[1] = nn;
-               _off_event.buffer()[1] = nn;
-       }
-       inline void set_velocity(uint8_t n) {
-               _on_event.buffer()[2] = clamp(n, 0, 127);
-       }
-       inline void set_off_velocity(uint8_t n) {
-               _off_event.buffer()[2] = clamp(n, 0, 127);
-       }
-       inline void set_length(Time l) {
-               _off_event.set_time(_on_event.time() + l);
-       }
-       inline void set_channel(uint8_t c) {
-               const uint8_t cc = clamp(c, 0, 16);
-               _on_event.set_channel(cc);
-               _off_event.set_channel(cc);
-       }
-
-       inline       Event<Time>& on_event()        { return _on_event; }
-       inline const Event<Time>& on_event()  const { return _on_event; }
-       inline       Event<Time>& off_event()       { return _off_event; }
-       inline const Event<Time>& off_event() const { return _off_event; }
-
-private:
-       // Event buffers are self-contained
-       Event<Time> _on_event;
-       Event<Time> _off_event;
-};
-
-template<typename Time>
-/*LIBEVORAL_API*/ std::ostream& operator<<(std::ostream& o, const Evoral::Note<Time>& n) {
-       o << "Note #" << n.id() << ": pitch = " << (int) n.note()
-         << " @ " << n.time() << " .. " << n.end_time()
-         << " velocity " << (int) n.velocity()
-         << " chn " << (int) n.channel();
-       return o;
-}
-
-} // namespace Evoral
-
-#ifdef COMPILER_MSVC
-#include "../src/Note.impl"
-#endif
-
-#endif // EVORAL_NOTE_HPP
-
diff --git a/libs/evoral/evoral/Parameter.h b/libs/evoral/evoral/Parameter.h
new file mode 100644 (file)
index 0000000..672afec
--- /dev/null
@@ -0,0 +1,97 @@
+/*
+ * Copyright (C) 2008-2016 David Robillard <d@drobilla.net>
+ * Copyright (C) 2008-2018 Paul Davis <paul@linuxaudiosystems.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef EVORAL_PARAMETER_HPP
+#define EVORAL_PARAMETER_HPP
+
+#include <string>
+#include <map>
+#include <stdint.h>
+#include <boost/shared_ptr.hpp>
+
+#include "evoral/visibility.h"
+#include "evoral/types.h"
+
+namespace Evoral {
+
+/** ID of a [play|record|automate]able parameter.
+ *
+ * A parameter is defined by (type, id, channel).  Type is an integer which
+ * can be used in any way by the application (e.g. cast to a custom enum,
+ * map to/from a URI, etc).  ID is type specific (e.g. MIDI controller #).
+ *
+ * This class defines a < operator which is a strict weak ordering, so
+ * Parameter may be stored in a std::set, used as a std::map key, etc.
+ */
+class LIBEVORAL_API Parameter
+{
+public:
+       inline Parameter(ParameterType type, uint8_t channel=0, uint32_t id=0)
+               : _type(type), _id(id), _channel(channel)
+       {}
+
+       inline ParameterType type()    const { return _type; }
+       inline uint8_t       channel() const { return _channel; }
+       inline uint32_t      id()      const { return _id; }
+
+       /** Equivalence operator
+        * It is obvious from the definition that this operator
+        * is transitive, as required by stict weak ordering
+        * (see: http://www.sgi.com/tech/stl/StrictWeakOrdering.html)
+        */
+       inline bool operator==(const Parameter& id) const {
+               return (_type == id._type && _channel == id._channel && _id == id._id );
+       }
+
+       inline bool operator!=(const Parameter& id) const {
+               return !operator==(id);
+       }
+
+       /** Strict weak ordering
+        * See: http://www.sgi.com/tech/stl/StrictWeakOrdering.html
+        * Sort Parameters first according to type then to channel and lastly to ID.
+        */
+       inline bool operator<(const Parameter& other) const {
+               if (_type < other._type) {
+                       return true;
+               } else if (_type == other._type && _channel < other._channel) {
+                       return true;
+               } else if (_type == other._type && _channel == other._channel && _id < other._id ) {
+                       return true;
+               }
+
+               return false;
+       }
+
+       inline operator bool() const { return (_type != 0); }
+
+private:
+       ParameterType _type;
+       uint32_t      _id;
+       uint8_t       _channel;
+};
+
+} // namespace Evoral
+
+namespace std {
+std::ostream& operator<< (std::ostream &str, Evoral::Parameter const &);
+}
+
+#endif // EVORAL_PARAMETER_HPP
+
diff --git a/libs/evoral/evoral/Parameter.hpp b/libs/evoral/evoral/Parameter.hpp
deleted file mode 100644 (file)
index 0874605..0000000
+++ /dev/null
@@ -1,97 +0,0 @@
-/*
- * Copyright (C) 2008-2016 David Robillard <d@drobilla.net>
- * Copyright (C) 2008-2018 Paul Davis <paul@linuxaudiosystems.com>
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-
-#ifndef EVORAL_PARAMETER_HPP
-#define EVORAL_PARAMETER_HPP
-
-#include <string>
-#include <map>
-#include <stdint.h>
-#include <boost/shared_ptr.hpp>
-
-#include "evoral/visibility.h"
-#include "evoral/types.hpp"
-
-namespace Evoral {
-
-/** ID of a [play|record|automate]able parameter.
- *
- * A parameter is defined by (type, id, channel).  Type is an integer which
- * can be used in any way by the application (e.g. cast to a custom enum,
- * map to/from a URI, etc).  ID is type specific (e.g. MIDI controller #).
- *
- * This class defines a < operator which is a strict weak ordering, so
- * Parameter may be stored in a std::set, used as a std::map key, etc.
- */
-class LIBEVORAL_API Parameter
-{
-public:
-       inline Parameter(ParameterType type, uint8_t channel=0, uint32_t id=0)
-               : _type(type), _id(id), _channel(channel)
-       {}
-
-       inline ParameterType type()    const { return _type; }
-       inline uint8_t       channel() const { return _channel; }
-       inline uint32_t      id()      const { return _id; }
-
-       /** Equivalence operator
-        * It is obvious from the definition that this operator
-        * is transitive, as required by stict weak ordering
-        * (see: http://www.sgi.com/tech/stl/StrictWeakOrdering.html)
-        */
-       inline bool operator==(const Parameter& id) const {
-               return (_type == id._type && _channel == id._channel && _id == id._id );
-       }
-
-       inline bool operator!=(const Parameter& id) const {
-               return !operator==(id);
-       }
-
-       /** Strict weak ordering
-        * See: http://www.sgi.com/tech/stl/StrictWeakOrdering.html
-        * Sort Parameters first according to type then to channel and lastly to ID.
-        */
-       inline bool operator<(const Parameter& other) const {
-               if (_type < other._type) {
-                       return true;
-               } else if (_type == other._type && _channel < other._channel) {
-                       return true;
-               } else if (_type == other._type && _channel == other._channel && _id < other._id ) {
-                       return true;
-               }
-
-               return false;
-       }
-
-       inline operator bool() const { return (_type != 0); }
-
-private:
-       ParameterType _type;
-       uint32_t      _id;
-       uint8_t       _channel;
-};
-
-} // namespace Evoral
-
-namespace std {
-std::ostream& operator<< (std::ostream &str, Evoral::Parameter const &);
-}
-
-#endif // EVORAL_PARAMETER_HPP
-
diff --git a/libs/evoral/evoral/ParameterDescriptor.h b/libs/evoral/evoral/ParameterDescriptor.h
new file mode 100644 (file)
index 0000000..33fe4ce
--- /dev/null
@@ -0,0 +1,46 @@
+/*
+ * Copyright (C) 2014 David Robillard <d@drobilla.net>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef EVORAL_PARAMETER_DESCRIPTOR_HPP
+#define EVORAL_PARAMETER_DESCRIPTOR_HPP
+
+namespace Evoral {
+
+/** Description of the value range of a parameter or control. */
+struct ParameterDescriptor
+{
+       ParameterDescriptor()
+               : normal(0.0)
+               , lower(0.0)
+               , upper(1.0)
+               , toggled(false)
+               , logarithmic(false)
+               , rangesteps (0)
+       {}
+
+       float normal;      ///< Default value
+       float lower;       ///< Minimum value (in Hz, for frequencies)
+       float upper;       ///< Maximum value (in Hz, for frequencies)
+       bool  toggled;     ///< True iff parameter is boolean
+       bool  logarithmic; ///< True for log-scale parameters
+       unsigned int rangesteps; ///< number of steps, [min,max] (inclusive). <= 1 means continuous. == 2 only min, max. For integer controls this is usually (1 + max - min)
+};
+
+} // namespace Evoral
+
+#endif // EVORAL_PARAMETER_DESCRIPTOR_HPP
diff --git a/libs/evoral/evoral/ParameterDescriptor.hpp b/libs/evoral/evoral/ParameterDescriptor.hpp
deleted file mode 100644 (file)
index 33fe4ce..0000000
+++ /dev/null
@@ -1,46 +0,0 @@
-/*
- * Copyright (C) 2014 David Robillard <d@drobilla.net>
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-
-#ifndef EVORAL_PARAMETER_DESCRIPTOR_HPP
-#define EVORAL_PARAMETER_DESCRIPTOR_HPP
-
-namespace Evoral {
-
-/** Description of the value range of a parameter or control. */
-struct ParameterDescriptor
-{
-       ParameterDescriptor()
-               : normal(0.0)
-               , lower(0.0)
-               , upper(1.0)
-               , toggled(false)
-               , logarithmic(false)
-               , rangesteps (0)
-       {}
-
-       float normal;      ///< Default value
-       float lower;       ///< Minimum value (in Hz, for frequencies)
-       float upper;       ///< Maximum value (in Hz, for frequencies)
-       bool  toggled;     ///< True iff parameter is boolean
-       bool  logarithmic; ///< True for log-scale parameters
-       unsigned int rangesteps; ///< number of steps, [min,max] (inclusive). <= 1 means continuous. == 2 only min, max. For integer controls this is usually (1 + max - min)
-};
-
-} // namespace Evoral
-
-#endif // EVORAL_PARAMETER_DESCRIPTOR_HPP
diff --git a/libs/evoral/evoral/PatchChange.h b/libs/evoral/evoral/PatchChange.h
new file mode 100644 (file)
index 0000000..1d7d752
--- /dev/null
@@ -0,0 +1,175 @@
+/*
+ * Copyright (C) 2011 Carl Hetherington <carl@carlh.net>
+ * Copyright (C) 2011-2013 Paul Davis <paul@linuxaudiosystems.com>
+ * Copyright (C) 2011-2016 David Robillard <d@drobilla.net>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef EVORAL_PATCH_CHANGE_HPP
+#define EVORAL_PATCH_CHANGE_HPP
+
+#include "evoral/visibility.h"
+#include "evoral/Event.h"
+#include "evoral/Event.h"
+
+namespace Evoral {
+
+/** Event representing a `patch change', composed of a LSB and MSB
+ *  bank select and then a program change.
+ */
+template<typename Time>
+class /*LIBEVORAL_API*/ PatchChange
+{
+public:
+       /** @param t Time.
+        *  @param c Channel.
+        *  @param p Program change number (counted from 0).
+        *  @param b Bank number (counted from 0, 14-bit).
+        */
+       PatchChange (Time t, uint8_t c, uint8_t p, int b)
+               : _bank_change_msb (MIDI_EVENT, t, 3, 0, true)
+               , _bank_change_lsb (MIDI_EVENT, t, 3, 0, true)
+               , _program_change (MIDI_EVENT, t, 2, 0, true)
+       {
+               _bank_change_msb.buffer()[0] = MIDI_CMD_CONTROL | c;
+               _bank_change_msb.buffer()[1] = MIDI_CTL_MSB_BANK;
+               _bank_change_msb.buffer()[2] = (b >> 7) & 0x7f;
+
+               _bank_change_lsb.buffer()[0] = MIDI_CMD_CONTROL | c;
+               _bank_change_lsb.buffer()[1] = MIDI_CTL_LSB_BANK;
+               _bank_change_lsb.buffer()[2] = b & 0x7f;
+
+               _program_change.buffer()[0] = MIDI_CMD_PGM_CHANGE | c;
+               _program_change.buffer()[1] = p;
+       }
+
+       PatchChange (const PatchChange & other)
+               : _bank_change_msb (other._bank_change_msb, true)
+               , _bank_change_lsb (other._bank_change_lsb, true)
+               , _program_change (other._program_change, true)
+       {
+               set_id (other.id ());
+       }
+
+       event_id_t id () const {
+               return _program_change.id ();
+       }
+
+       void set_id (event_id_t id) {
+               _bank_change_msb.set_id (id);
+               _bank_change_lsb.set_id (id);
+               _program_change.set_id (id);
+       }
+
+       Time time () const {
+               return _program_change.time ();
+       }
+
+       void set_time (Time t) {
+               _bank_change_msb.set_time (t);
+               _bank_change_lsb.set_time (t);
+               _program_change.set_time (t);
+       }
+
+       void set_channel (uint8_t c) {
+               _bank_change_msb.buffer()[0] &= 0xf0;
+               _bank_change_msb.buffer()[0] |= c;
+               _bank_change_lsb.buffer()[0] &= 0xf0;
+               _bank_change_lsb.buffer()[0] |= c;
+               _program_change.buffer()[0] &= 0xf0;
+               _program_change.buffer()[0] |= c;
+       }
+
+       uint8_t program () const {
+               return _program_change.buffer()[1];
+       }
+
+       void set_program (uint8_t p) {
+               _program_change.buffer()[1] = p;
+       }
+
+       int bank () const {
+               return (bank_msb() << 7) | bank_lsb();
+       }
+
+       void set_bank (int b) {
+               _bank_change_msb.buffer()[2] = (b >> 7) & 0x7f;
+               _bank_change_lsb.buffer()[2] = b & 0x7f;
+       }
+
+       uint8_t bank_msb () const {
+               return _bank_change_msb.buffer()[2];
+       }
+
+       uint8_t bank_lsb () const {
+               return _bank_change_lsb.buffer()[2];
+       }
+
+       uint8_t channel () const { return _program_change.buffer()[0] & 0xf; }
+
+       inline bool operator< (const PatchChange<Time>& o) const {
+               if (time() != o.time()) {
+                       return time() < o.time();
+               }
+
+               if (bank() != o.bank()) {
+                       return bank() < o.bank();
+               }
+
+               return (program() < o.program());
+       }
+
+       inline bool operator== (const PatchChange<Time>& o) const {
+               return (time() == o.time() && program() == o.program() && bank() == o.bank());
+       }
+
+       /** The PatchChange is made up of messages() MIDI messages; this method returns them by index.
+        *  @param i index of message to return.
+        */
+       Event<Time> const & message (int i) const {
+               switch (i) {
+               case 0:
+                       return _bank_change_msb;
+               case 1:
+                       return _bank_change_lsb;
+               case 2:
+                       return _program_change;
+               default:
+                       abort(); /*NOTREACHED*/
+                       return _program_change;
+               }
+       }
+
+       /** @return Number of MIDI messages that make up this change */
+       int messages () const {
+               return 3;
+       }
+
+private:
+       Event<Time> _bank_change_msb;
+       Event<Time> _bank_change_lsb;
+       Event<Time> _program_change;
+};
+
+}
+
+template<typename Time>
+/*LIBEVORAL_API*/ std::ostream& operator<< (std::ostream& o, const Evoral::PatchChange<Time>& p) {
+       o << "Patch Change " << p.id() << " @ " << p.time() << " bank " << (int) p.bank() << " program " << (int) p.program();
+       return o;
+}
+
+#endif
diff --git a/libs/evoral/evoral/PatchChange.hpp b/libs/evoral/evoral/PatchChange.hpp
deleted file mode 100644 (file)
index 83f17a2..0000000
+++ /dev/null
@@ -1,175 +0,0 @@
-/*
- * Copyright (C) 2011 Carl Hetherington <carl@carlh.net>
- * Copyright (C) 2011-2013 Paul Davis <paul@linuxaudiosystems.com>
- * Copyright (C) 2011-2016 David Robillard <d@drobilla.net>
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-
-#ifndef EVORAL_PATCH_CHANGE_HPP
-#define EVORAL_PATCH_CHANGE_HPP
-
-#include "evoral/visibility.h"
-#include "evoral/Event.hpp"
-#include "evoral/Event.hpp"
-
-namespace Evoral {
-
-/** Event representing a `patch change', composed of a LSB and MSB
- *  bank select and then a program change.
- */
-template<typename Time>
-class /*LIBEVORAL_API*/ PatchChange
-{
-public:
-       /** @param t Time.
-        *  @param c Channel.
-        *  @param p Program change number (counted from 0).
-        *  @param b Bank number (counted from 0, 14-bit).
-        */
-       PatchChange (Time t, uint8_t c, uint8_t p, int b)
-               : _bank_change_msb (MIDI_EVENT, t, 3, 0, true)
-               , _bank_change_lsb (MIDI_EVENT, t, 3, 0, true)
-               , _program_change (MIDI_EVENT, t, 2, 0, true)
-       {
-               _bank_change_msb.buffer()[0] = MIDI_CMD_CONTROL | c;
-               _bank_change_msb.buffer()[1] = MIDI_CTL_MSB_BANK;
-               _bank_change_msb.buffer()[2] = (b >> 7) & 0x7f;
-
-               _bank_change_lsb.buffer()[0] = MIDI_CMD_CONTROL | c;
-               _bank_change_lsb.buffer()[1] = MIDI_CTL_LSB_BANK;
-               _bank_change_lsb.buffer()[2] = b & 0x7f;
-
-               _program_change.buffer()[0] = MIDI_CMD_PGM_CHANGE | c;
-               _program_change.buffer()[1] = p;
-       }
-
-       PatchChange (const PatchChange & other)
-               : _bank_change_msb (other._bank_change_msb, true)
-               , _bank_change_lsb (other._bank_change_lsb, true)
-               , _program_change (other._program_change, true)
-       {
-               set_id (other.id ());
-       }
-
-       event_id_t id () const {
-               return _program_change.id ();
-       }
-
-       void set_id (event_id_t id) {
-               _bank_change_msb.set_id (id);
-               _bank_change_lsb.set_id (id);
-               _program_change.set_id (id);
-       }
-
-       Time time () const {
-               return _program_change.time ();
-       }
-
-       void set_time (Time t) {
-               _bank_change_msb.set_time (t);
-               _bank_change_lsb.set_time (t);
-               _program_change.set_time (t);
-       }
-
-       void set_channel (uint8_t c) {
-               _bank_change_msb.buffer()[0] &= 0xf0;
-               _bank_change_msb.buffer()[0] |= c;
-               _bank_change_lsb.buffer()[0] &= 0xf0;
-               _bank_change_lsb.buffer()[0] |= c;
-               _program_change.buffer()[0] &= 0xf0;
-               _program_change.buffer()[0] |= c;
-       }
-
-       uint8_t program () const {
-               return _program_change.buffer()[1];
-       }
-
-       void set_program (uint8_t p) {
-               _program_change.buffer()[1] = p;
-       }
-
-       int bank () const {
-               return (bank_msb() << 7) | bank_lsb();
-       }
-
-       void set_bank (int b) {
-               _bank_change_msb.buffer()[2] = (b >> 7) & 0x7f;
-               _bank_change_lsb.buffer()[2] = b & 0x7f;
-       }
-
-       uint8_t bank_msb () const {
-               return _bank_change_msb.buffer()[2];
-       }
-
-       uint8_t bank_lsb () const {
-               return _bank_change_lsb.buffer()[2];
-       }
-
-       uint8_t channel () const { return _program_change.buffer()[0] & 0xf; }
-
-       inline bool operator< (const PatchChange<Time>& o) const {
-               if (time() != o.time()) {
-                       return time() < o.time();
-               }
-
-               if (bank() != o.bank()) {
-                       return bank() < o.bank();
-               }
-
-               return (program() < o.program());
-       }
-
-       inline bool operator== (const PatchChange<Time>& o) const {
-               return (time() == o.time() && program() == o.program() && bank() == o.bank());
-       }
-
-       /** The PatchChange is made up of messages() MIDI messages; this method returns them by index.
-        *  @param i index of message to return.
-        */
-       Event<Time> const & message (int i) const {
-               switch (i) {
-               case 0:
-                       return _bank_change_msb;
-               case 1:
-                       return _bank_change_lsb;
-               case 2:
-                       return _program_change;
-               default:
-                       abort(); /*NOTREACHED*/
-                       return _program_change;
-               }
-       }
-
-       /** @return Number of MIDI messages that make up this change */
-       int messages () const {
-               return 3;
-       }
-
-private:
-       Event<Time> _bank_change_msb;
-       Event<Time> _bank_change_lsb;
-       Event<Time> _program_change;
-};
-
-}
-
-template<typename Time>
-/*LIBEVORAL_API*/ std::ostream& operator<< (std::ostream& o, const Evoral::PatchChange<Time>& p) {
-       o << "Patch Change " << p.id() << " @ " << p.time() << " bank " << (int) p.bank() << " program " << (int) p.program();
-       return o;
-}
-
-#endif
diff --git a/libs/evoral/evoral/Range.h b/libs/evoral/evoral/Range.h
new file mode 100644 (file)
index 0000000..71e02be
--- /dev/null
@@ -0,0 +1,298 @@
+/*
+ * Copyright (C) 2012 Carl Hetherington <carl@carlh.net>
+ * Copyright (C) 2013-2016 Paul Davis <paul@linuxaudiosystems.com>
+ * Copyright (C) 2014 Colin Fletcher <colin.m.fletcher@googlemail.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef EVORAL_RANGE_HPP
+#define EVORAL_RANGE_HPP
+
+#include <list>
+#include <assert.h>
+#include <iostream>
+#include "evoral/visibility.h"
+
+
+
+namespace Evoral {
+
+enum /*LIBEVORAL_API*/ OverlapType {
+       OverlapNone,      // no overlap
+       OverlapInternal,  // the overlap is 100% within the object
+       OverlapStart,     // overlap covers start, but ends within
+       OverlapEnd,       // overlap begins within and covers end
+       OverlapExternal   // overlap extends to (at least) begin+end
+};
+
+template<typename T>
+/*LIBEVORAL_API*/ OverlapType coverage (T sa, T ea, T sb, T eb) {
+       /* OverlapType returned reflects how the second (B)
+        * range overlaps the first (A).
+        *
+        * The diagram shows the OverlapType of each possible relative
+        * placement of A and B.
+        *
+        * Notes:
+        *    Internal: the start and end points cannot coincide
+        *    External: the start and end points can coincide
+        *    Start: end points can coincide
+        *    End: start points can coincide
+        *
+        * Internal disallows start and end point equality, and thus implies
+        * that there are two disjoint portions of A which do not overlap B.
+        *
+        * A:    |---|
+        * B starts before A
+        * B: |-|          None
+        * B: |--|         Start
+        * B: |----|       Start
+        * B: |------|     External
+        * B: |--------|   External
+        * B starts equal to A
+        * B:    |-|       Start
+        * B:    |---|     External
+        * B:    |----|    External
+        * B starts inside A
+        * B:     |-|      Internal
+        * B:     |--|     End
+        * B:     |---|    End
+        * B starts at end of A
+        * B:        |--|  End
+        * B starts after A
+        * B:         |-|  None
+        * A:    |---|
+        */
+
+
+       if (sa > ea) {
+               // seems we are sometimes called with negative length ranges
+               return OverlapNone;
+       }
+
+       if (sb > eb) {
+               // seems we are sometimes called with negative length ranges
+               return OverlapNone;
+       }
+
+       if (sb < sa) {  // B starts before A
+               if (eb < sa) {
+                       return OverlapNone;
+               } else if (eb == sa) {
+                       return OverlapStart;
+               } else { // eb > sa
+                       if (eb < ea) {
+                               return OverlapStart;
+                       } else if (eb == ea) {
+                               return OverlapExternal;
+                       } else {
+                               return OverlapExternal;
+                       }
+               }
+       } else if (sb == sa) { // B starts equal to A
+               if (eb < ea) {
+                       return OverlapStart;
+               } else if (eb == ea) {
+                       return OverlapExternal;
+               } else { // eb > ea
+                       return OverlapExternal;
+               }
+       } else { // sb > sa
+               if (eb < ea) {
+                       return OverlapInternal;
+               } else if (eb == ea) {
+                       return OverlapEnd;
+               } else { // eb > ea
+                       if (sb < ea) { // B starts inside A
+                               return OverlapEnd;
+                       } else if (sb == ea) { // B starts at end of A
+                               return OverlapEnd;
+                       } else { // sb > ea, B starts after A
+                               return OverlapNone;
+                       }
+               }
+       }
+
+       std::cerr << "unknown overlap type!" << sa << ", " << ea << "; " << sb << ", " << eb << std::endl;
+       assert(!"unknown overlap type!");
+       return OverlapNone;
+}
+
+/** Type to describe a time range */
+template<typename T>
+struct /*LIBEVORAL_API*/ Range {
+       Range (T f, T t) : from (f), to (t) {}
+       T from; ///< start of the range
+       T to;   ///< end of the range (inclusive: to lies inside the range)
+       bool empty() const { return from == to; }
+       T length() const { return to - from + 1; }
+
+       /** for a T, return a mapping of it into the range (used for
+        * looping). If the argument is earlier than or equal to the end of
+        * this range, do nothing.
+        */
+       T squish (T t) const {
+               if (t > to) {
+                       t = (from + ((t - from) % length()));
+               }
+               return t;
+       }
+};
+
+template<typename T>
+bool operator== (Range<T> a, Range<T> b) {
+       return a.from == b.from && a.to == b.to;
+}
+
+template<typename T>
+class /*LIBEVORAL_API*/ RangeList {
+public:
+       RangeList () : _dirty (false) {}
+
+       typedef std::list<Range<T> > List;
+
+       List const & get () {
+               coalesce ();
+               return _list;
+       }
+
+       void add (Range<T> const & range) {
+               _dirty = true;
+               _list.push_back (range);
+       }
+
+       bool empty () const {
+               return _list.empty ();
+       }
+
+       void coalesce () {
+               if (!_dirty) {
+                       return;
+               }
+
+       restart:
+               for (typename List::iterator i = _list.begin(); i != _list.end(); ++i) {
+                       for (typename List::iterator j = _list.begin(); j != _list.end(); ++j) {
+
+                               if (i == j) {
+                                       continue;
+                               }
+
+                               if (coverage (i->from, i->to, j->from, j->to) != OverlapNone) {
+                                       i->from = std::min (i->from, j->from);
+                                       i->to = std::max (i->to, j->to);
+                                       _list.erase (j);
+                                       goto restart;
+                               }
+                       }
+               }
+
+               _dirty = false;
+       }
+
+private:
+
+       List _list;
+       bool _dirty;
+};
+
+/** Type to describe the movement of a time range */
+template<typename T>
+struct /*LIBEVORAL_API*/ RangeMove {
+       RangeMove (T f, double l, T t) : from (f), length (l), to (t) {}
+       T         from;   ///< start of the range
+       double    length; ///< length of the range
+       T         to;     ///< new start of the range
+};
+
+/** Subtract the ranges in `sub' from that in `range',
+ *  returning the result.
+ */
+template<typename T>
+RangeList<T> subtract (Range<T> range, RangeList<T> sub)
+{
+       /* Start with the input range */
+       RangeList<T> result;
+       result.add (range);
+
+       if (sub.empty () || range.empty()) {
+               return result;
+       }
+
+       typename RangeList<T>::List s = sub.get ();
+
+       /* The basic idea here is to keep a list of the result ranges, and subtract
+          the bits of `sub' from them one by one.
+       */
+
+       for (typename RangeList<T>::List::const_iterator i = s.begin(); i != s.end(); ++i) {
+
+               /* Here's where we'll put the new current result after subtracting *i from it */
+               RangeList<T> new_result;
+
+               typename RangeList<T>::List r = result.get ();
+
+               /* Work on all parts of the current result using this range *i */
+               for (typename RangeList<T>::List::const_iterator j = r.begin(); j != r.end(); ++j) {
+
+                       switch (coverage (j->from, j->to, i->from, i->to)) {
+                       case OverlapNone:
+                               /* The thing we're subtracting (*i) does not overlap this bit of the result (*j),
+                                  so pass it through.
+                               */
+                               new_result.add (*j);
+                               break;
+                       case OverlapInternal:
+                               /* Internal overlap of the thing we're subtracting (*i) from this bit of the result,
+                                  so we should end up with two bits of (*j) left over, from the start of (*j) to
+                                  the start of (*i), and from the end of (*i) to the end of (*j).
+                               */
+                               assert (j->from < i->from);
+                               assert (j->to > i->to);
+                               new_result.add (Range<T> (j->from, i->from - 1));
+                               new_result.add (Range<T> (i->to + 1, j->to));
+                               break;
+                       case OverlapStart:
+                               /* The bit we're subtracting (*i) overlaps the start of the bit of the result (*j),
+                                * so we keep only the part of of (*j) from after the end of (*i)
+                                */
+                               assert (i->to < j->to);
+                               new_result.add (Range<T> (i->to + 1, j->to));
+                               break;
+                       case OverlapEnd:
+                               /* The bit we're subtracting (*i) overlaps the end of the bit of the result (*j),
+                                * so we keep only the part of of (*j) from before the start of (*i)
+                                */
+                               assert (j->from < i->from);
+                               new_result.add (Range<T> (j->from, i->from - 1));
+                               break;
+                       case OverlapExternal:
+                               /* total overlap of the bit we're subtracting with the result bit, so the
+                                  result bit is completely removed; do nothing */
+                               break;
+                       }
+               }
+
+               new_result.coalesce ();
+               result = new_result;
+       }
+
+       return result;
+}
+
+}
+
+#endif
diff --git a/libs/evoral/evoral/Range.hpp b/libs/evoral/evoral/Range.hpp
deleted file mode 100644 (file)
index 71e02be..0000000
+++ /dev/null
@@ -1,298 +0,0 @@
-/*
- * Copyright (C) 2012 Carl Hetherington <carl@carlh.net>
- * Copyright (C) 2013-2016 Paul Davis <paul@linuxaudiosystems.com>
- * Copyright (C) 2014 Colin Fletcher <colin.m.fletcher@googlemail.com>
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-
-#ifndef EVORAL_RANGE_HPP
-#define EVORAL_RANGE_HPP
-
-#include <list>
-#include <assert.h>
-#include <iostream>
-#include "evoral/visibility.h"
-
-
-
-namespace Evoral {
-
-enum /*LIBEVORAL_API*/ OverlapType {
-       OverlapNone,      // no overlap
-       OverlapInternal,  // the overlap is 100% within the object
-       OverlapStart,     // overlap covers start, but ends within
-       OverlapEnd,       // overlap begins within and covers end
-       OverlapExternal   // overlap extends to (at least) begin+end
-};
-
-template<typename T>
-/*LIBEVORAL_API*/ OverlapType coverage (T sa, T ea, T sb, T eb) {
-       /* OverlapType returned reflects how the second (B)
-        * range overlaps the first (A).
-        *
-        * The diagram shows the OverlapType of each possible relative
-        * placement of A and B.
-        *
-        * Notes:
-        *    Internal: the start and end points cannot coincide
-        *    External: the start and end points can coincide
-        *    Start: end points can coincide
-        *    End: start points can coincide
-        *
-        * Internal disallows start and end point equality, and thus implies
-        * that there are two disjoint portions of A which do not overlap B.
-        *
-        * A:    |---|
-        * B starts before A
-        * B: |-|          None
-        * B: |--|         Start
-        * B: |----|       Start
-        * B: |------|     External
-        * B: |--------|   External
-        * B starts equal to A
-        * B:    |-|       Start
-        * B:    |---|     External
-        * B:    |----|    External
-        * B starts inside A
-        * B:     |-|      Internal
-        * B:     |--|     End
-        * B:     |---|    End
-        * B starts at end of A
-        * B:        |--|  End
-        * B starts after A
-        * B:         |-|  None
-        * A:    |---|
-        */
-
-
-       if (sa > ea) {
-               // seems we are sometimes called with negative length ranges
-               return OverlapNone;
-       }
-
-       if (sb > eb) {
-               // seems we are sometimes called with negative length ranges
-               return OverlapNone;
-       }
-
-       if (sb < sa) {  // B starts before A
-               if (eb < sa) {
-                       return OverlapNone;
-               } else if (eb == sa) {
-                       return OverlapStart;
-               } else { // eb > sa
-                       if (eb < ea) {
-                               return OverlapStart;
-                       } else if (eb == ea) {
-                               return OverlapExternal;
-                       } else {
-                               return OverlapExternal;
-                       }
-               }
-       } else if (sb == sa) { // B starts equal to A
-               if (eb < ea) {
-                       return OverlapStart;
-               } else if (eb == ea) {
-                       return OverlapExternal;
-               } else { // eb > ea
-                       return OverlapExternal;
-               }
-       } else { // sb > sa
-               if (eb < ea) {
-                       return OverlapInternal;
-               } else if (eb == ea) {
-                       return OverlapEnd;
-               } else { // eb > ea
-                       if (sb < ea) { // B starts inside A
-                               return OverlapEnd;
-                       } else if (sb == ea) { // B starts at end of A
-                               return OverlapEnd;
-                       } else { // sb > ea, B starts after A
-                               return OverlapNone;
-                       }
-               }
-       }
-
-       std::cerr << "unknown overlap type!" << sa << ", " << ea << "; " << sb << ", " << eb << std::endl;
-       assert(!"unknown overlap type!");
-       return OverlapNone;
-}
-
-/** Type to describe a time range */
-template<typename T>
-struct /*LIBEVORAL_API*/ Range {
-       Range (T f, T t) : from (f), to (t) {}
-       T from; ///< start of the range
-       T to;   ///< end of the range (inclusive: to lies inside the range)
-       bool empty() const { return from == to; }
-       T length() const { return to - from + 1; }
-
-       /** for a T, return a mapping of it into the range (used for
-        * looping). If the argument is earlier than or equal to the end of
-        * this range, do nothing.
-        */
-       T squish (T t) const {
-               if (t > to) {
-                       t = (from + ((t - from) % length()));
-               }
-               return t;
-       }
-};
-
-template<typename T>
-bool operator== (Range<T> a, Range<T> b) {
-       return a.from == b.from && a.to == b.to;
-}
-
-template<typename T>
-class /*LIBEVORAL_API*/ RangeList {
-public:
-       RangeList () : _dirty (false) {}
-
-       typedef std::list<Range<T> > List;
-
-       List const & get () {
-               coalesce ();
-               return _list;
-       }
-
-       void add (Range<T> const & range) {
-               _dirty = true;
-               _list.push_back (range);
-       }
-
-       bool empty () const {
-               return _list.empty ();
-       }
-
-       void coalesce () {
-               if (!_dirty) {
-                       return;
-               }
-
-       restart:
-               for (typename List::iterator i = _list.begin(); i != _list.end(); ++i) {
-                       for (typename List::iterator j = _list.begin(); j != _list.end(); ++j) {
-
-                               if (i == j) {
-                                       continue;
-                               }
-
-                               if (coverage (i->from, i->to, j->from, j->to) != OverlapNone) {
-                                       i->from = std::min (i->from, j->from);
-                                       i->to = std::max (i->to, j->to);
-                                       _list.erase (j);
-                                       goto restart;
-                               }
-                       }
-               }
-
-               _dirty = false;
-       }
-
-private:
-
-       List _list;
-       bool _dirty;
-};
-
-/** Type to describe the movement of a time range */
-template<typename T>
-struct /*LIBEVORAL_API*/ RangeMove {
-       RangeMove (T f, double l, T t) : from (f), length (l), to (t) {}
-       T         from;   ///< start of the range
-       double    length; ///< length of the range
-       T         to;     ///< new start of the range
-};
-
-/** Subtract the ranges in `sub' from that in `range',
- *  returning the result.
- */
-template<typename T>
-RangeList<T> subtract (Range<T> range, RangeList<T> sub)
-{
-       /* Start with the input range */
-       RangeList<T> result;
-       result.add (range);
-
-       if (sub.empty () || range.empty()) {
-               return result;
-       }
-
-       typename RangeList<T>::List s = sub.get ();
-
-       /* The basic idea here is to keep a list of the result ranges, and subtract
-          the bits of `sub' from them one by one.
-       */
-
-       for (typename RangeList<T>::List::const_iterator i = s.begin(); i != s.end(); ++i) {
-
-               /* Here's where we'll put the new current result after subtracting *i from it */
-               RangeList<T> new_result;
-
-               typename RangeList<T>::List r = result.get ();
-
-               /* Work on all parts of the current result using this range *i */
-               for (typename RangeList<T>::List::const_iterator j = r.begin(); j != r.end(); ++j) {
-
-                       switch (coverage (j->from, j->to, i->from, i->to)) {
-                       case OverlapNone:
-                               /* The thing we're subtracting (*i) does not overlap this bit of the result (*j),
-                                  so pass it through.
-                               */
-                               new_result.add (*j);
-                               break;
-                       case OverlapInternal:
-                               /* Internal overlap of the thing we're subtracting (*i) from this bit of the result,
-                                  so we should end up with two bits of (*j) left over, from the start of (*j) to
-                                  the start of (*i), and from the end of (*i) to the end of (*j).
-                               */
-                               assert (j->from < i->from);
-                               assert (j->to > i->to);
-                               new_result.add (Range<T> (j->from, i->from - 1));
-                               new_result.add (Range<T> (i->to + 1, j->to));
-                               break;
-                       case OverlapStart:
-                               /* The bit we're subtracting (*i) overlaps the start of the bit of the result (*j),
-                                * so we keep only the part of of (*j) from after the end of (*i)
-                                */
-                               assert (i->to < j->to);
-                               new_result.add (Range<T> (i->to + 1, j->to));
-                               break;
-                       case OverlapEnd:
-                               /* The bit we're subtracting (*i) overlaps the end of the bit of the result (*j),
-                                * so we keep only the part of of (*j) from before the start of (*i)
-                                */
-                               assert (j->from < i->from);
-                               new_result.add (Range<T> (j->from, i->from - 1));
-                               break;
-                       case OverlapExternal:
-                               /* total overlap of the bit we're subtracting with the result bit, so the
-                                  result bit is completely removed; do nothing */
-                               break;
-                       }
-               }
-
-               new_result.coalesce ();
-               result = new_result;
-       }
-
-       return result;
-}
-
-}
-
-#endif
diff --git a/libs/evoral/evoral/SMF.h b/libs/evoral/evoral/SMF.h
new file mode 100644 (file)
index 0000000..9ef03f5
--- /dev/null
@@ -0,0 +1,137 @@
+/*
+ * Copyright (C) 2008-2014 David Robillard <d@drobilla.net>
+ * Copyright (C) 2009-2017 Paul Davis <paul@linuxaudiosystems.com>
+ * Copyright (C) 2009 Hans Baier <hansfbaier@googlemail.com>
+ * Copyright (C) 2014-2016 Robin Gareus <robin@gareus.org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef EVORAL_SMF_HPP
+#define EVORAL_SMF_HPP
+
+#include <glibmm/threads.h>
+#include <set>
+
+#include "evoral/visibility.h"
+#include "evoral/types.h"
+
+struct smf_struct;
+struct smf_track_struct;
+struct smf_tempo_struct;
+typedef smf_struct smf_t;
+typedef smf_track_struct smf_track_t;
+typedef smf_tempo_struct smf_tempo_t;
+
+namespace Evoral {
+
+/** Standard Midi File.
+ * Currently only tempo-based time of a given PPQN is supported.
+ *
+ * For WRITING: this object specifically wraps a type0 file or a type1 file with only a
+ * single track. It has no support at this time for a type1 file with multiple
+ * tracks.
+ *
+ * For READING: this object can read a single arbitrary track from a type1
+ * file, or the single track of a type0 file. It has no support at this time
+ * for reading more than 1 track.
+ */
+class LIBEVORAL_API SMF {
+public:
+       class FileError : public std::exception {
+       public:
+               FileError (std::string const & n) : _file_name (n) {}
+               ~FileError () throw () {}
+               const char* what() const throw() { return "Unknown SMF error"; }
+               std::string file_name () const { return _file_name; }
+       private:
+               std::string _file_name;
+       };
+
+       SMF();
+       virtual ~SMF();
+
+       static bool test(const std::string& path);
+       int  open(const std::string& path, int track=1);
+       // XXX 19200 = 10 * Timecode::BBT_Time::ticks_per_beat
+       int  create(const std::string& path, int track=1, uint16_t ppqn=19200);
+       void close();
+
+       void seek_to_start() const;
+       int  seek_to_track(int track);
+
+       int read_event(uint32_t* delta_t, uint32_t* size, uint8_t** buf, event_id_t* note_id) const;
+
+       uint16_t num_tracks() const;
+       uint16_t ppqn()       const;
+       bool     is_empty()   const { return _empty; }
+
+       void begin_write();
+       void append_event_delta(uint32_t delta_t, uint32_t size, const uint8_t* buf, event_id_t note_id);
+       void end_write(std::string const &);
+
+       void flush() {};
+
+       double round_to_file_precision (double val) const;
+
+       bool is_type0 () const { return _type0; }
+       std::set<uint8_t> channels () const { return _type0channels; }
+       void track_names (std::vector<std::string>&) const;
+       void instrument_names (std::vector<std::string>&) const;
+
+       int num_tempos () const;
+
+       /* This is exactly modelled on smf_tempo_t */
+       struct Tempo {
+               size_t time_pulses;
+               double time_seconds;
+               int    microseconds_per_quarter_note;
+               int    numerator;
+               int    denominator;
+               int    clocks_per_click;
+               int    notes_per_note;
+
+               Tempo ()
+                       : time_pulses (0)
+                       , time_seconds (0)
+                       , microseconds_per_quarter_note (-1)
+                       , numerator (-1)
+                       , denominator (-1)
+                       , clocks_per_click (-1)
+                       , notes_per_note (-1) {}
+               Tempo (smf_tempo_t*);
+
+               double tempo() const {
+                       return 60.0 * (1000000.0 / (double) microseconds_per_quarter_note);
+               }
+       };
+
+       Tempo* tempo_at_smf_pulse (size_t smf_pulse) const;
+       Tempo* tempo_at_seconds (double seconds) const;
+       Tempo* nth_tempo (size_t n) const;
+
+  private:
+       smf_t*       _smf;
+       smf_track_t* _smf_track;
+       bool         _empty; ///< true iff file contains(non-empty) events
+       mutable Glib::Threads::Mutex _smf_lock;
+
+       bool              _type0;
+       std::set<uint8_t> _type0channels;
+};
+
+}; /* namespace Evoral */
+
+#endif /* EVORAL_SMF_HPP */
diff --git a/libs/evoral/evoral/SMF.hpp b/libs/evoral/evoral/SMF.hpp
deleted file mode 100644 (file)
index 4888ba6..0000000
+++ /dev/null
@@ -1,137 +0,0 @@
-/*
- * Copyright (C) 2008-2014 David Robillard <d@drobilla.net>
- * Copyright (C) 2009-2017 Paul Davis <paul@linuxaudiosystems.com>
- * Copyright (C) 2009 Hans Baier <hansfbaier@googlemail.com>
- * Copyright (C) 2014-2016 Robin Gareus <robin@gareus.org>
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-
-#ifndef EVORAL_SMF_HPP
-#define EVORAL_SMF_HPP
-
-#include <glibmm/threads.h>
-#include <set>
-
-#include "evoral/visibility.h"
-#include "evoral/types.hpp"
-
-struct smf_struct;
-struct smf_track_struct;
-struct smf_tempo_struct;
-typedef smf_struct smf_t;
-typedef smf_track_struct smf_track_t;
-typedef smf_tempo_struct smf_tempo_t;
-
-namespace Evoral {
-
-/** Standard Midi File.
- * Currently only tempo-based time of a given PPQN is supported.
- *
- * For WRITING: this object specifically wraps a type0 file or a type1 file with only a
- * single track. It has no support at this time for a type1 file with multiple
- * tracks.
- *
- * For READING: this object can read a single arbitrary track from a type1
- * file, or the single track of a type0 file. It has no support at this time
- * for reading more than 1 track.
- */
-class LIBEVORAL_API SMF {
-public:
-       class FileError : public std::exception {
-       public:
-               FileError (std::string const & n) : _file_name (n) {}
-               ~FileError () throw () {}
-               const char* what() const throw() { return "Unknown SMF error"; }
-               std::string file_name () const { return _file_name; }
-       private:
-               std::string _file_name;
-       };
-
-       SMF();
-       virtual ~SMF();
-
-       static bool test(const std::string& path);
-       int  open(const std::string& path, int track=1);
-       // XXX 19200 = 10 * Timecode::BBT_Time::ticks_per_beat
-       int  create(const std::string& path, int track=1, uint16_t ppqn=19200);
-       void close();
-
-       void seek_to_start() const;
-       int  seek_to_track(int track);
-
-       int read_event(uint32_t* delta_t, uint32_t* size, uint8_t** buf, event_id_t* note_id) const;
-
-       uint16_t num_tracks() const;
-       uint16_t ppqn()       const;
-       bool     is_empty()   const { return _empty; }
-
-       void begin_write();
-       void append_event_delta(uint32_t delta_t, uint32_t size, const uint8_t* buf, event_id_t note_id);
-       void end_write(std::string const &);
-
-       void flush() {};
-
-       double round_to_file_precision (double val) const;
-
-       bool is_type0 () const { return _type0; }
-       std::set<uint8_t> channels () const { return _type0channels; }
-       void track_names (std::vector<std::string>&) const;
-       void instrument_names (std::vector<std::string>&) const;
-
-       int num_tempos () const;
-
-       /* This is exactly modelled on smf_tempo_t */
-       struct Tempo {
-               size_t time_pulses;
-               double time_seconds;
-               int    microseconds_per_quarter_note;
-               int    numerator;
-               int    denominator;
-               int    clocks_per_click;
-               int    notes_per_note;
-
-               Tempo ()
-                       : time_pulses (0)
-                       , time_seconds (0)
-                       , microseconds_per_quarter_note (-1)
-                       , numerator (-1)
-                       , denominator (-1)
-                       , clocks_per_click (-1)
-                       , notes_per_note (-1) {}
-               Tempo (smf_tempo_t*);
-
-               double tempo() const {
-                       return 60.0 * (1000000.0 / (double) microseconds_per_quarter_note);
-               }
-       };
-
-       Tempo* tempo_at_smf_pulse (size_t smf_pulse) const;
-       Tempo* tempo_at_seconds (double seconds) const;
-       Tempo* nth_tempo (size_t n) const;
-
-  private:
-       smf_t*       _smf;
-       smf_track_t* _smf_track;
-       bool         _empty; ///< true iff file contains(non-empty) events
-       mutable Glib::Threads::Mutex _smf_lock;
-
-       bool              _type0;
-       std::set<uint8_t> _type0channels;
-};
-
-}; /* namespace Evoral */
-
-#endif /* EVORAL_SMF_HPP */
diff --git a/libs/evoral/evoral/SMFReader.h b/libs/evoral/evoral/SMFReader.h
new file mode 100644 (file)
index 0000000..81f9f6a
--- /dev/null
@@ -0,0 +1,88 @@
+/*
+ * Copyright (C) 2008-2011 David Robillard <d@drobilla.net>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef EVORAL_SMF_READER_HPP
+#define EVORAL_SMF_READER_HPP
+
+#include <exception>
+#include <stdexcept>
+#include <string>
+#include <inttypes.h>
+
+#include "evoral/visibility.h"
+
+namespace Evoral {
+
+
+/** Standard MIDI File (Type 0) Reader
+ *
+ * Currently this only reads SMF files with tempo-based timing.
+ */
+class LIBEVORAL_API SMFReader {
+public:
+       class PrematureEOF : public std::exception {
+               const char* what() const throw() { return "Unexpected end of file"; }
+       };
+       class CorruptFile : public std::exception {
+               const char* what() const throw() { return "Corrupted file"; }
+       };
+       class UnsupportedTime : public std::exception {
+               const char* what() const throw() { return "Unsupported time stamp type (SMPTE)"; }
+       };
+
+       SMFReader(const std::string& filename="");
+       ~SMFReader();
+
+       bool open(const std::string& filename) throw (std::logic_error, UnsupportedTime);
+
+       bool seek_to_track(unsigned track) throw (std::logic_error);
+
+       const std::string& filename() const { return _filename; };
+
+       uint16_t type()       const { return _type; }
+       uint16_t ppqn()       const { return _ppqn; }
+       uint16_t num_tracks() const { return _num_tracks; }
+
+       int read_event(size_t    buf_len,
+                      uint8_t*  buf,
+                      uint32_t* ev_size,
+                      uint32_t* ev_delta_time)
+               throw (std::logic_error, PrematureEOF, CorruptFile);
+
+       void close();
+
+       static uint32_t read_var_len(FILE* fd) throw (PrematureEOF);
+
+protected:
+       /** size of SMF header, including MTrk chunk header */
+       static const uint32_t HEADER_SIZE = 22;
+
+       std::string _filename;
+       FILE*       _fd;
+       uint16_t    _type;
+       uint16_t    _ppqn;
+       uint16_t    _num_tracks;
+       uint32_t    _track;
+       uint32_t    _track_size;
+};
+
+
+} // namespace Evoral
+
+#endif // EVORAL_SMF_READER_HPP
+
diff --git a/libs/evoral/evoral/SMFReader.hpp b/libs/evoral/evoral/SMFReader.hpp
deleted file mode 100644 (file)
index 81f9f6a..0000000
+++ /dev/null
@@ -1,88 +0,0 @@
-/*
- * Copyright (C) 2008-2011 David Robillard <d@drobilla.net>
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-
-#ifndef EVORAL_SMF_READER_HPP
-#define EVORAL_SMF_READER_HPP
-
-#include <exception>
-#include <stdexcept>
-#include <string>
-#include <inttypes.h>
-
-#include "evoral/visibility.h"
-
-namespace Evoral {
-
-
-/** Standard MIDI File (Type 0) Reader
- *
- * Currently this only reads SMF files with tempo-based timing.
- */
-class LIBEVORAL_API SMFReader {
-public:
-       class PrematureEOF : public std::exception {
-               const char* what() const throw() { return "Unexpected end of file"; }
-       };
-       class CorruptFile : public std::exception {
-               const char* what() const throw() { return "Corrupted file"; }
-       };
-       class UnsupportedTime : public std::exception {
-               const char* what() const throw() { return "Unsupported time stamp type (SMPTE)"; }
-       };
-
-       SMFReader(const std::string& filename="");
-       ~SMFReader();
-
-       bool open(const std::string& filename) throw (std::logic_error, UnsupportedTime);
-
-       bool seek_to_track(unsigned track) throw (std::logic_error);
-
-       const std::string& filename() const { return _filename; };
-
-       uint16_t type()       const { return _type; }
-       uint16_t ppqn()       const { return _ppqn; }
-       uint16_t num_tracks() const { return _num_tracks; }
-
-       int read_event(size_t    buf_len,
-                      uint8_t*  buf,
-                      uint32_t* ev_size,
-                      uint32_t* ev_delta_time)
-               throw (std::logic_error, PrematureEOF, CorruptFile);
-
-       void close();
-
-       static uint32_t read_var_len(FILE* fd) throw (PrematureEOF);
-
-protected:
-       /** size of SMF header, including MTrk chunk header */
-       static const uint32_t HEADER_SIZE = 22;
-
-       std::string _filename;
-       FILE*       _fd;
-       uint16_t    _type;
-       uint16_t    _ppqn;
-       uint16_t    _num_tracks;
-       uint32_t    _track;
-       uint32_t    _track_size;
-};
-
-
-} // namespace Evoral
-
-#endif // EVORAL_SMF_READER_HPP
-
diff --git a/libs/evoral/evoral/Sequence.h b/libs/evoral/evoral/Sequence.h
new file mode 100644 (file)
index 0000000..04864dd
--- /dev/null
@@ -0,0 +1,381 @@
+/*
+ * Copyright (C) 2008-2016 David Robillard <d@drobilla.net>
+ * Copyright (C) 2009-2012 Hans Baier <hansfbaier@googlemail.com>
+ * Copyright (C) 2009-2013 Paul Davis <paul@linuxaudiosystems.com>
+ * Copyright (C) 2010-2011 Carl Hetherington <carl@carlh.net>
+ * Copyright (C) 2013-2015 John Emmas <john@creativepost.co.uk>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef EVORAL_SEQUENCE_HPP
+#define EVORAL_SEQUENCE_HPP
+
+#include <vector>
+#include <queue>
+#include <set>
+#include <list>
+#include <utility>
+#include <boost/shared_ptr.hpp>
+#include <glibmm/threads.h>
+
+#include "evoral/visibility.h"
+#include "evoral/Note.h"
+#include "evoral/ControlSet.h"
+#include "evoral/ControlList.h"
+#include "evoral/PatchChange.h"
+
+namespace Evoral {
+
+class Parameter;
+class TypeMap;
+template<typename Time> class EventSink;
+template<typename Time> class Note;
+template<typename Time> class Event;
+
+/** An iterator over (the x axis of) a 2-d double coordinate space.
+ */
+class /*LIBEVORAL_API*/ ControlIterator {
+public:
+       ControlIterator(boost::shared_ptr<const ControlList> al, double ax, double ay)
+               : list(al)
+               , x(ax)
+               , y(ay)
+       {}
+
+       boost::shared_ptr<const ControlList> list;
+       double x;
+       double y;
+};
+
+
+/** This is a higher level view of events, with separate representations for
+ * notes (instead of just unassociated note on/off events) and controller data.
+ * Controller data is represented as a list of time-stamped float values. */
+template<typename Time>
+class LIBEVORAL_API Sequence : virtual public ControlSet {
+public:
+       Sequence(const TypeMap& type_map);
+       Sequence(const Sequence<Time>& other);
+
+protected:
+       struct WriteLockImpl {
+               WriteLockImpl(Glib::Threads::RWLock& s, Glib::Threads::Mutex& c)
+                       : sequence_lock(new Glib::Threads::RWLock::WriterLock(s))
+                       , control_lock(new Glib::Threads::Mutex::Lock(c)) { }
+               ~WriteLockImpl() {
+                       delete sequence_lock;
+                       delete control_lock;
+               }
+               Glib::Threads::RWLock::WriterLock* sequence_lock;
+               Glib::Threads::Mutex::Lock*        control_lock;
+       };
+
+public:
+
+       typedef typename boost::shared_ptr<Evoral::Note<Time> >       NotePtr;
+       typedef typename boost::weak_ptr<Evoral::Note<Time> >         WeakNotePtr;
+       typedef typename boost::shared_ptr<const Evoral::Note<Time> > constNotePtr;
+
+       typedef boost::shared_ptr<Glib::Threads::RWLock::ReaderLock> ReadLock;
+       typedef boost::shared_ptr<WriteLockImpl>                     WriteLock;
+
+       virtual ReadLock  read_lock() const { return ReadLock(new Glib::Threads::RWLock::ReaderLock(_lock)); }
+       virtual WriteLock write_lock()      { return WriteLock(new WriteLockImpl(_lock, _control_lock)); }
+
+       void clear();
+
+       bool percussive() const     { return _percussive; }
+       void set_percussive(bool p) { _percussive = p; }
+
+       void start_write();
+       bool writing() const { return _writing; }
+
+       enum StuckNoteOption {
+               Relax,
+               DeleteStuckNotes,
+               ResolveStuckNotes
+       };
+
+       void end_write (StuckNoteOption, Time when = Time());
+
+       void append(const Event<Time>& ev, Evoral::event_id_t evid);
+
+       const TypeMap& type_map() const { return _type_map; }
+
+       inline size_t n_notes() const { return _notes.size(); }
+       inline bool   empty()   const { return _notes.empty() && _sysexes.empty() && _patch_changes.empty() && ControlSet::controls_empty(); }
+
+       inline static bool note_time_comparator(const boost::shared_ptr< const Note<Time> >& a,
+                                               const boost::shared_ptr< const Note<Time> >& b) {
+               return a->time() < b->time();
+       }
+
+       struct NoteNumberComparator {
+               inline bool operator()(const boost::shared_ptr< const Note<Time> > a,
+                                      const boost::shared_ptr< const Note<Time> > b) const {
+                       return a->note() < b->note();
+               }
+       };
+
+       struct EarlierNoteComparator {
+               inline bool operator()(const boost::shared_ptr< const Note<Time> > a,
+                                      const boost::shared_ptr< const Note<Time> > b) const {
+                       return a->time() < b->time();
+               }
+       };
+
+#if 0 // NOT USED
+       struct LaterNoteComparator {
+               typedef const Note<Time>* value_type;
+               inline bool operator()(const boost::shared_ptr< const Note<Time> > a,
+                                      const boost::shared_ptr< const Note<Time> > b) const {
+                       return a->time() > b->time();
+               }
+       };
+#endif
+
+       struct LaterNoteEndComparator {
+               typedef const Note<Time>* value_type;
+               inline bool operator()(const boost::shared_ptr< const Note<Time> > a,
+                                      const boost::shared_ptr< const Note<Time> > b) const {
+                       return a->end_time().to_double() > b->end_time().to_double();
+               }
+       };
+
+       typedef std::multiset<NotePtr, EarlierNoteComparator> Notes;
+       inline       Notes& notes()       { return _notes; }
+       inline const Notes& notes() const { return _notes; }
+
+       enum NoteOperator {
+               PitchEqual,
+               PitchLessThan,
+               PitchLessThanOrEqual,
+               PitchGreater,
+               PitchGreaterThanOrEqual,
+               VelocityEqual,
+               VelocityLessThan,
+               VelocityLessThanOrEqual,
+               VelocityGreater,
+               VelocityGreaterThanOrEqual,
+       };
+
+       void get_notes (Notes&, NoteOperator, uint8_t val, int chan_mask = 0) const;
+
+       void remove_overlapping_notes ();
+       void trim_overlapping_notes ();
+       void remove_duplicate_notes ();
+
+       enum OverlapPitchResolution {
+               LastOnFirstOff,
+               FirstOnFirstOff
+       };
+
+       bool overlapping_pitches_accepted() const { return _overlapping_pitches_accepted; }
+       void overlapping_pitches_accepted(bool yn)  { _overlapping_pitches_accepted = yn; }
+       OverlapPitchResolution overlap_pitch_resolution() const { return _overlap_pitch_resolution; }
+       void set_overlap_pitch_resolution(OverlapPitchResolution opr);
+
+       void set_notes (const typename Sequence<Time>::Notes& n);
+
+       typedef boost::shared_ptr< Event<Time> > SysExPtr;
+       typedef boost::shared_ptr<const Event<Time> > constSysExPtr;
+
+       struct EarlierSysExComparator {
+               inline bool operator() (constSysExPtr a, constSysExPtr b) const {
+                       return a->time() < b->time();
+               }
+       };
+
+       typedef std::multiset<SysExPtr, EarlierSysExComparator> SysExes;
+       inline       SysExes& sysexes()       { return _sysexes; }
+       inline const SysExes& sysexes() const { return _sysexes; }
+
+       typedef boost::shared_ptr<PatchChange<Time> > PatchChangePtr;
+       typedef boost::shared_ptr<const PatchChange<Time> > constPatchChangePtr;
+
+       struct EarlierPatchChangeComparator {
+               inline bool operator() (constPatchChangePtr a, constPatchChangePtr b) const {
+                       return a->time() < b->time();
+               }
+       };
+
+       typedef std::multiset<PatchChangePtr, EarlierPatchChangeComparator> PatchChanges;
+       inline       PatchChanges& patch_changes ()       { return _patch_changes; }
+       inline const PatchChanges& patch_changes () const { return _patch_changes; }
+
+       void dump (std::ostream&) const;
+
+private:
+       typedef std::priority_queue<NotePtr, std::deque<NotePtr>, LaterNoteEndComparator> ActiveNotes;
+public:
+
+       /** Read iterator */
+       class LIBEVORAL_API const_iterator {
+       public:
+               const_iterator();
+               const_iterator(const Sequence<Time>&              seq,
+                              Time                               t,
+                              bool                               force_discrete,
+                              const std::set<Evoral::Parameter>& filtered,
+                              const std::set<WeakNotePtr>*       active_notes=NULL);
+
+               inline bool valid() const { return !_is_end && _event; }
+
+               void invalidate(std::set<WeakNotePtr>* notes);
+
+               const Event<Time>& operator*() const { return *_event;  }
+               const boost::shared_ptr< const Event<Time> > operator->() const { return _event; }
+
+               const const_iterator& operator++(); // prefix only
+
+               bool operator==(const const_iterator& other) const;
+               bool operator!=(const const_iterator& other) const { return ! operator==(other); }
+
+               const_iterator& operator=(const const_iterator& other);
+
+       private:
+               friend class Sequence<Time>;
+
+               Time choose_next(Time earliest_t);
+               void set_event();
+
+               typedef std::vector<ControlIterator> ControlIterators;
+               enum MIDIMessageType { NIL, NOTE_ON, NOTE_OFF, CONTROL, SYSEX, PATCH_CHANGE };
+
+               const Sequence<Time>*                 _seq;
+               boost::shared_ptr< Event<Time> >      _event;
+               mutable ActiveNotes                   _active_notes;
+               /** If the iterator is pointing at a patch change, this is the index of the
+                *  sub-message within that change.
+                */
+               int                                   _active_patch_change_message;
+               MIDIMessageType                       _type;
+               bool                                  _is_end;
+               typename Sequence::ReadLock           _lock;
+               typename Notes::const_iterator        _note_iter;
+               typename SysExes::const_iterator      _sysex_iter;
+               typename PatchChanges::const_iterator _patch_change_iter;
+               ControlIterators                      _control_iters;
+               ControlIterators::iterator            _control_iter;
+               bool                                  _force_discrete;
+       };
+
+       const_iterator begin (
+               Time                               t              = Time(),
+               bool                               force_discrete = false,
+               const std::set<Evoral::Parameter>& f              = std::set<Evoral::Parameter>(),
+               const std::set<WeakNotePtr>*       active_notes   = NULL) const {
+               return const_iterator (*this, t, force_discrete, f, active_notes);
+       }
+
+       const const_iterator& end() const { return _end_iter; }
+
+       // CONST iterator implementations (x3)
+       typename Notes::const_iterator note_lower_bound (Time t) const;
+       typename PatchChanges::const_iterator patch_change_lower_bound (Time t) const;
+       typename SysExes::const_iterator sysex_lower_bound (Time t) const;
+
+       // NON-CONST iterator implementations (x3)
+       typename Notes::iterator note_lower_bound (Time t);
+       typename PatchChanges::iterator patch_change_lower_bound (Time t);
+       typename SysExes::iterator sysex_lower_bound (Time t);
+
+       bool control_to_midi_event(boost::shared_ptr< Event<Time> >& ev,
+                                  const ControlIterator&            iter) const;
+
+       bool edited() const      { return _edited; }
+       void set_edited(bool yn) { _edited = yn; }
+
+       bool overlaps (const NotePtr& ev,
+                      const NotePtr& ignore_this_note) const;
+       bool contains (const NotePtr& ev) const;
+
+       bool add_note_unlocked (const NotePtr note, void* arg = 0);
+       void remove_note_unlocked(const constNotePtr note);
+
+       void add_patch_change_unlocked (const PatchChangePtr);
+       void remove_patch_change_unlocked (const constPatchChangePtr);
+
+       void add_sysex_unlocked (const SysExPtr);
+       void remove_sysex_unlocked (const SysExPtr);
+
+       uint8_t lowest_note()  const { return _lowest_note; }
+       uint8_t highest_note() const { return _highest_note; }
+
+
+protected:
+       bool                   _edited;
+       bool                   _overlapping_pitches_accepted;
+       OverlapPitchResolution _overlap_pitch_resolution;
+       mutable Glib::Threads::RWLock   _lock;
+       bool                   _writing;
+
+       virtual int resolve_overlaps_unlocked (const NotePtr, void* /* arg */ = 0) {
+               return 0;
+       }
+
+       typedef std::multiset<NotePtr, NoteNumberComparator>  Pitches;
+       inline       Pitches& pitches(uint8_t chan)       { return _pitches[chan&0xf]; }
+       inline const Pitches& pitches(uint8_t chan) const { return _pitches[chan&0xf]; }
+
+       virtual void control_list_marked_dirty ();
+
+private:
+       friend class const_iterator;
+
+       bool overlaps_unlocked (const NotePtr& ev, const NotePtr& ignore_this_note) const;
+       bool contains_unlocked (const NotePtr& ev) const;
+
+       void append_note_on_unlocked(const Event<Time>& event, Evoral::event_id_t);
+       void append_note_off_unlocked(const Event<Time>& event);
+       void append_control_unlocked(const Parameter& param, Time time, double value, Evoral::event_id_t);
+       void append_sysex_unlocked(const Event<Time>& ev, Evoral::event_id_t);
+       void append_patch_change_unlocked(const PatchChange<Time>&, Evoral::event_id_t);
+
+       void get_notes_by_pitch (Notes&, NoteOperator, uint8_t val, int chan_mask = 0) const;
+       void get_notes_by_velocity (Notes&, NoteOperator, uint8_t val, int chan_mask = 0) const;
+
+       const TypeMap& _type_map;
+
+       Notes        _notes;       // notes indexed by time
+       Pitches      _pitches[16]; // notes indexed by channel+pitch
+       SysExes      _sysexes;
+       PatchChanges _patch_changes;
+
+       typedef std::multiset<NotePtr, EarlierNoteComparator> WriteNotes;
+       WriteNotes _write_notes[16];
+
+       /** Current bank number on each channel so that we know what
+        *  to put in PatchChange events when program changes are
+        *  seen.
+        */
+       int _bank[16];
+
+       const   const_iterator _end_iter;
+       bool                   _percussive;
+
+       uint8_t _lowest_note;
+       uint8_t _highest_note;
+};
+
+
+} // namespace Evoral
+
+template<typename Time> /*LIBEVORAL_API*/ std::ostream& operator<<(std::ostream& o, const Evoral::Sequence<Time>& s) { s.dump (o); return o; }
+
+
+#endif // EVORAL_SEQUENCE_HPP
+
diff --git a/libs/evoral/evoral/Sequence.hpp b/libs/evoral/evoral/Sequence.hpp
deleted file mode 100644 (file)
index 2dbca34..0000000
+++ /dev/null
@@ -1,381 +0,0 @@
-/*
- * Copyright (C) 2008-2016 David Robillard <d@drobilla.net>
- * Copyright (C) 2009-2012 Hans Baier <hansfbaier@googlemail.com>
- * Copyright (C) 2009-2013 Paul Davis <paul@linuxaudiosystems.com>
- * Copyright (C) 2010-2011 Carl Hetherington <carl@carlh.net>
- * Copyright (C) 2013-2015 John Emmas <john@creativepost.co.uk>
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-
-#ifndef EVORAL_SEQUENCE_HPP
-#define EVORAL_SEQUENCE_HPP
-
-#include <vector>
-#include <queue>
-#include <set>
-#include <list>
-#include <utility>
-#include <boost/shared_ptr.hpp>
-#include <glibmm/threads.h>
-
-#include "evoral/visibility.h"
-#include "evoral/Note.hpp"
-#include "evoral/ControlSet.hpp"
-#include "evoral/ControlList.hpp"
-#include "evoral/PatchChange.hpp"
-
-namespace Evoral {
-
-class Parameter;
-class TypeMap;
-template<typename Time> class EventSink;
-template<typename Time> class Note;
-template<typename Time> class Event;
-
-/** An iterator over (the x axis of) a 2-d double coordinate space.
- */
-class /*LIBEVORAL_API*/ ControlIterator {
-public:
-       ControlIterator(boost::shared_ptr<const ControlList> al, double ax, double ay)
-               : list(al)
-               , x(ax)
-               , y(ay)
-       {}
-
-       boost::shared_ptr<const ControlList> list;
-       double x;
-       double y;
-};
-
-
-/** This is a higher level view of events, with separate representations for
- * notes (instead of just unassociated note on/off events) and controller data.
- * Controller data is represented as a list of time-stamped float values. */
-template<typename Time>
-class LIBEVORAL_API Sequence : virtual public ControlSet {
-public:
-       Sequence(const TypeMap& type_map);
-       Sequence(const Sequence<Time>& other);
-
-protected:
-       struct WriteLockImpl {
-               WriteLockImpl(Glib::Threads::RWLock& s, Glib::Threads::Mutex& c)
-                       : sequence_lock(new Glib::Threads::RWLock::WriterLock(s))
-                       , control_lock(new Glib::Threads::Mutex::Lock(c)) { }
-               ~WriteLockImpl() {
-                       delete sequence_lock;
-                       delete control_lock;
-               }
-               Glib::Threads::RWLock::WriterLock* sequence_lock;
-               Glib::Threads::Mutex::Lock*        control_lock;
-       };
-
-public:
-
-       typedef typename boost::shared_ptr<Evoral::Note<Time> >       NotePtr;
-       typedef typename boost::weak_ptr<Evoral::Note<Time> >         WeakNotePtr;
-       typedef typename boost::shared_ptr<const Evoral::Note<Time> > constNotePtr;
-
-       typedef boost::shared_ptr<Glib::Threads::RWLock::ReaderLock> ReadLock;
-       typedef boost::shared_ptr<WriteLockImpl>                     WriteLock;
-
-       virtual ReadLock  read_lock() const { return ReadLock(new Glib::Threads::RWLock::ReaderLock(_lock)); }
-       virtual WriteLock write_lock()      { return WriteLock(new WriteLockImpl(_lock, _control_lock)); }
-
-       void clear();
-
-       bool percussive() const     { return _percussive; }
-       void set_percussive(bool p) { _percussive = p; }
-
-       void start_write();
-       bool writing() const { return _writing; }
-
-       enum StuckNoteOption {
-               Relax,
-               DeleteStuckNotes,
-               ResolveStuckNotes
-       };
-
-       void end_write (StuckNoteOption, Time when = Time());
-
-       void append(const Event<Time>& ev, Evoral::event_id_t evid);
-
-       const TypeMap& type_map() const { return _type_map; }
-
-       inline size_t n_notes() const { return _notes.size(); }
-       inline bool   empty()   const { return _notes.empty() && _sysexes.empty() && _patch_changes.empty() && ControlSet::controls_empty(); }
-
-       inline static bool note_time_comparator(const boost::shared_ptr< const Note<Time> >& a,
-                                               const boost::shared_ptr< const Note<Time> >& b) {
-               return a->time() < b->time();
-       }
-
-       struct NoteNumberComparator {
-               inline bool operator()(const boost::shared_ptr< const Note<Time> > a,
-                                      const boost::shared_ptr< const Note<Time> > b) const {
-                       return a->note() < b->note();
-               }
-       };
-
-       struct EarlierNoteComparator {
-               inline bool operator()(const boost::shared_ptr< const Note<Time> > a,
-                                      const boost::shared_ptr< const Note<Time> > b) const {
-                       return a->time() < b->time();
-               }
-       };
-
-#if 0 // NOT USED
-       struct LaterNoteComparator {
-               typedef const Note<Time>* value_type;
-               inline bool operator()(const boost::shared_ptr< const Note<Time> > a,
-                                      const boost::shared_ptr< const Note<Time> > b) const {
-                       return a->time() > b->time();
-               }
-       };
-#endif
-
-       struct LaterNoteEndComparator {
-               typedef const Note<Time>* value_type;
-               inline bool operator()(const boost::shared_ptr< const Note<Time> > a,
-                                      const boost::shared_ptr< const Note<Time> > b) const {
-                       return a->end_time().to_double() > b->end_time().to_double();
-               }
-       };
-
-       typedef std::multiset<NotePtr, EarlierNoteComparator> Notes;
-       inline       Notes& notes()       { return _notes; }
-       inline const Notes& notes() const { return _notes; }
-
-       enum NoteOperator {
-               PitchEqual,
-               PitchLessThan,
-               PitchLessThanOrEqual,
-               PitchGreater,
-               PitchGreaterThanOrEqual,
-               VelocityEqual,
-               VelocityLessThan,
-               VelocityLessThanOrEqual,
-               VelocityGreater,
-               VelocityGreaterThanOrEqual,
-       };
-
-       void get_notes (Notes&, NoteOperator, uint8_t val, int chan_mask = 0) const;
-
-       void remove_overlapping_notes ();
-       void trim_overlapping_notes ();
-       void remove_duplicate_notes ();
-
-       enum OverlapPitchResolution {
-               LastOnFirstOff,
-               FirstOnFirstOff
-       };
-
-       bool overlapping_pitches_accepted() const { return _overlapping_pitches_accepted; }
-       void overlapping_pitches_accepted(bool yn)  { _overlapping_pitches_accepted = yn; }
-       OverlapPitchResolution overlap_pitch_resolution() const { return _overlap_pitch_resolution; }
-       void set_overlap_pitch_resolution(OverlapPitchResolution opr);
-
-       void set_notes (const typename Sequence<Time>::Notes& n);
-
-       typedef boost::shared_ptr< Event<Time> > SysExPtr;
-       typedef boost::shared_ptr<const Event<Time> > constSysExPtr;
-
-       struct EarlierSysExComparator {
-               inline bool operator() (constSysExPtr a, constSysExPtr b) const {
-                       return a->time() < b->time();
-               }
-       };
-
-       typedef std::multiset<SysExPtr, EarlierSysExComparator> SysExes;
-       inline       SysExes& sysexes()       { return _sysexes; }
-       inline const SysExes& sysexes() const { return _sysexes; }
-
-       typedef boost::shared_ptr<PatchChange<Time> > PatchChangePtr;
-       typedef boost::shared_ptr<const PatchChange<Time> > constPatchChangePtr;
-
-       struct EarlierPatchChangeComparator {
-               inline bool operator() (constPatchChangePtr a, constPatchChangePtr b) const {
-                       return a->time() < b->time();
-               }
-       };
-
-       typedef std::multiset<PatchChangePtr, EarlierPatchChangeComparator> PatchChanges;
-       inline       PatchChanges& patch_changes ()       { return _patch_changes; }
-       inline const PatchChanges& patch_changes () const { return _patch_changes; }
-
-       void dump (std::ostream&) const;
-
-private:
-       typedef std::priority_queue<NotePtr, std::deque<NotePtr>, LaterNoteEndComparator> ActiveNotes;
-public:
-
-       /** Read iterator */
-       class LIBEVORAL_API const_iterator {
-       public:
-               const_iterator();
-               const_iterator(const Sequence<Time>&              seq,
-                              Time                               t,
-                              bool                               force_discrete,
-                              const std::set<Evoral::Parameter>& filtered,
-                              const std::set<WeakNotePtr>*       active_notes=NULL);
-
-               inline bool valid() const { return !_is_end && _event; }
-
-               void invalidate(std::set<WeakNotePtr>* notes);
-
-               const Event<Time>& operator*() const { return *_event;  }
-               const boost::shared_ptr< const Event<Time> > operator->() const { return _event; }
-
-               const const_iterator& operator++(); // prefix only
-
-               bool operator==(const const_iterator& other) const;
-               bool operator!=(const const_iterator& other) const { return ! operator==(other); }
-
-               const_iterator& operator=(const const_iterator& other);
-
-       private:
-               friend class Sequence<Time>;
-
-               Time choose_next(Time earliest_t);
-               void set_event();
-
-               typedef std::vector<ControlIterator> ControlIterators;
-               enum MIDIMessageType { NIL, NOTE_ON, NOTE_OFF, CONTROL, SYSEX, PATCH_CHANGE };
-
-               const Sequence<Time>*                 _seq;
-               boost::shared_ptr< Event<Time> >      _event;
-               mutable ActiveNotes                   _active_notes;
-               /** If the iterator is pointing at a patch change, this is the index of the
-                *  sub-message within that change.
-                */
-               int                                   _active_patch_change_message;
-               MIDIMessageType                       _type;
-               bool                                  _is_end;
-               typename Sequence::ReadLock           _lock;
-               typename Notes::const_iterator        _note_iter;
-               typename SysExes::const_iterator      _sysex_iter;
-               typename PatchChanges::const_iterator _patch_change_iter;
-               ControlIterators                      _control_iters;
-               ControlIterators::iterator            _control_iter;
-               bool                                  _force_discrete;
-       };
-
-       const_iterator begin (
-               Time                               t              = Time(),
-               bool                               force_discrete = false,
-               const std::set<Evoral::Parameter>& f              = std::set<Evoral::Parameter>(),
-               const std::set<WeakNotePtr>*       active_notes   = NULL) const {
-               return const_iterator (*this, t, force_discrete, f, active_notes);
-       }
-
-       const const_iterator& end() const { return _end_iter; }
-
-       // CONST iterator implementations (x3)
-       typename Notes::const_iterator note_lower_bound (Time t) const;
-       typename PatchChanges::const_iterator patch_change_lower_bound (Time t) const;
-       typename SysExes::const_iterator sysex_lower_bound (Time t) const;
-
-       // NON-CONST iterator implementations (x3)
-       typename Notes::iterator note_lower_bound (Time t);
-       typename PatchChanges::iterator patch_change_lower_bound (Time t);
-       typename SysExes::iterator sysex_lower_bound (Time t);
-
-       bool control_to_midi_event(boost::shared_ptr< Event<Time> >& ev,
-                                  const ControlIterator&            iter) const;
-
-       bool edited() const      { return _edited; }
-       void set_edited(bool yn) { _edited = yn; }
-
-       bool overlaps (const NotePtr& ev,
-                      const NotePtr& ignore_this_note) const;
-       bool contains (const NotePtr& ev) const;
-
-       bool add_note_unlocked (const NotePtr note, void* arg = 0);
-       void remove_note_unlocked(const constNotePtr note);
-
-       void add_patch_change_unlocked (const PatchChangePtr);
-       void remove_patch_change_unlocked (const constPatchChangePtr);
-
-       void add_sysex_unlocked (const SysExPtr);
-       void remove_sysex_unlocked (const SysExPtr);
-
-       uint8_t lowest_note()  const { return _lowest_note; }
-       uint8_t highest_note() const { return _highest_note; }
-
-
-protected:
-       bool                   _edited;
-       bool                   _overlapping_pitches_accepted;
-       OverlapPitchResolution _overlap_pitch_resolution;
-       mutable Glib::Threads::RWLock   _lock;
-       bool                   _writing;
-
-       virtual int resolve_overlaps_unlocked (const NotePtr, void* /* arg */ = 0) {
-               return 0;
-       }
-
-       typedef std::multiset<NotePtr, NoteNumberComparator>  Pitches;
-       inline       Pitches& pitches(uint8_t chan)       { return _pitches[chan&0xf]; }
-       inline const Pitches& pitches(uint8_t chan) const { return _pitches[chan&0xf]; }
-
-       virtual void control_list_marked_dirty ();
-
-private:
-       friend class const_iterator;
-
-       bool overlaps_unlocked (const NotePtr& ev, const NotePtr& ignore_this_note) const;
-       bool contains_unlocked (const NotePtr& ev) const;
-
-       void append_note_on_unlocked(const Event<Time>& event, Evoral::event_id_t);
-       void append_note_off_unlocked(const Event<Time>& event);
-       void append_control_unlocked(const Parameter& param, Time time, double value, Evoral::event_id_t);
-       void append_sysex_unlocked(const Event<Time>& ev, Evoral::event_id_t);
-       void append_patch_change_unlocked(const PatchChange<Time>&, Evoral::event_id_t);
-
-       void get_notes_by_pitch (Notes&, NoteOperator, uint8_t val, int chan_mask = 0) const;
-       void get_notes_by_velocity (Notes&, NoteOperator, uint8_t val, int chan_mask = 0) const;
-
-       const TypeMap& _type_map;
-
-       Notes        _notes;       // notes indexed by time
-       Pitches      _pitches[16]; // notes indexed by channel+pitch
-       SysExes      _sysexes;
-       PatchChanges _patch_changes;
-
-       typedef std::multiset<NotePtr, EarlierNoteComparator> WriteNotes;
-       WriteNotes _write_notes[16];
-
-       /** Current bank number on each channel so that we know what
-        *  to put in PatchChange events when program changes are
-        *  seen.
-        */
-       int _bank[16];
-
-       const   const_iterator _end_iter;
-       bool                   _percussive;
-
-       uint8_t _lowest_note;
-       uint8_t _highest_note;
-};
-
-
-} // namespace Evoral
-
-template<typename Time> /*LIBEVORAL_API*/ std::ostream& operator<<(std::ostream& o, const Evoral::Sequence<Time>& s) { s.dump (o); return o; }
-
-
-#endif // EVORAL_SEQUENCE_HPP
-
diff --git a/libs/evoral/evoral/TimeConverter.h b/libs/evoral/evoral/TimeConverter.h
new file mode 100644 (file)
index 0000000..b0ef750
--- /dev/null
@@ -0,0 +1,81 @@
+/*
+ * Copyright (C) 2009-2014 David Robillard <d@drobilla.net>
+ * Copyright (C) 2010-2012 Carl Hetherington <carl@carlh.net>
+ * Copyright (C) 2011-2014 Paul Davis <paul@linuxaudiosystems.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef EVORAL_TIME_CONVERTER_HPP
+#define EVORAL_TIME_CONVERTER_HPP
+
+#include "evoral/visibility.h"
+
+namespace Evoral {
+
+/** A bidirectional converter between two different time units.
+ *
+ * Think of the conversion method names as if they are written in-between
+ * the two template parameters (i.e. "A <name> B").
+ *
+ * _origin_b should be the origin for conversion in the units of B.
+ * That is, there is some point in time _origin_b, such that:
+ *
+ *    to()   converts a time _origin_b + a into an offset from _origin_b in units of B.
+ *    from() converts a time _origin_b + b into an offset from _origin_b in units of A.
+ */
+template<typename A, typename B>
+class LIBEVORAL_TEMPLATE_API TimeConverter {
+public:
+       TimeConverter () : _origin_b (0) {}
+       TimeConverter (B ob) : _origin_b (ob) {}
+       virtual ~TimeConverter();
+
+       /** Convert A time to B time (A to B) */
+       virtual B to(A a) const = 0;
+
+       /** Convert B time to A time (A from B) */
+       virtual A from(B b) const = 0;
+
+       B origin_b () const {
+               return _origin_b;
+       }
+
+       void set_origin_b (B o) {
+               _origin_b = o;
+       }
+
+protected:
+       B _origin_b;
+};
+
+
+/** A stub TimeConverter that simple statically casts between types.
+ *  _origin_b has no bearing here, as there is no time conversion
+ *  going on.
+ */
+template<typename A, typename B>
+class LIBEVORAL_TEMPLATE_API IdentityConverter : public TimeConverter<A,B> {
+  public:
+       IdentityConverter() {}
+
+       B to(A a)   const;
+       A from(B b) const;
+};
+
+
+} // namespace Evoral
+
+#endif // EVORAL_TIME_CONVERTER_HPP
diff --git a/libs/evoral/evoral/TimeConverter.hpp b/libs/evoral/evoral/TimeConverter.hpp
deleted file mode 100644 (file)
index b0ef750..0000000
+++ /dev/null
@@ -1,81 +0,0 @@
-/*
- * Copyright (C) 2009-2014 David Robillard <d@drobilla.net>
- * Copyright (C) 2010-2012 Carl Hetherington <carl@carlh.net>
- * Copyright (C) 2011-2014 Paul Davis <paul@linuxaudiosystems.com>
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-
-#ifndef EVORAL_TIME_CONVERTER_HPP
-#define EVORAL_TIME_CONVERTER_HPP
-
-#include "evoral/visibility.h"
-
-namespace Evoral {
-
-/** A bidirectional converter between two different time units.
- *
- * Think of the conversion method names as if they are written in-between
- * the two template parameters (i.e. "A <name> B").
- *
- * _origin_b should be the origin for conversion in the units of B.
- * That is, there is some point in time _origin_b, such that:
- *
- *    to()   converts a time _origin_b + a into an offset from _origin_b in units of B.
- *    from() converts a time _origin_b + b into an offset from _origin_b in units of A.
- */
-template<typename A, typename B>
-class LIBEVORAL_TEMPLATE_API TimeConverter {
-public:
-       TimeConverter () : _origin_b (0) {}
-       TimeConverter (B ob) : _origin_b (ob) {}
-       virtual ~TimeConverter();
-
-       /** Convert A time to B time (A to B) */
-       virtual B to(A a) const = 0;
-
-       /** Convert B time to A time (A from B) */
-       virtual A from(B b) const = 0;
-
-       B origin_b () const {
-               return _origin_b;
-       }
-
-       void set_origin_b (B o) {
-               _origin_b = o;
-       }
-
-protected:
-       B _origin_b;
-};
-
-
-/** A stub TimeConverter that simple statically casts between types.
- *  _origin_b has no bearing here, as there is no time conversion
- *  going on.
- */
-template<typename A, typename B>
-class LIBEVORAL_TEMPLATE_API IdentityConverter : public TimeConverter<A,B> {
-  public:
-       IdentityConverter() {}
-
-       B to(A a)   const;
-       A from(B b) const;
-};
-
-
-} // namespace Evoral
-
-#endif // EVORAL_TIME_CONVERTER_HPP
diff --git a/libs/evoral/evoral/TypeMap.h b/libs/evoral/evoral/TypeMap.h
new file mode 100644 (file)
index 0000000..af9c8c3
--- /dev/null
@@ -0,0 +1,62 @@
+/*
+ * Copyright (C) 2008-2016 David Robillard <d@drobilla.net>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef EVORAL_TYPE_MAP_HPP
+#define EVORAL_TYPE_MAP_HPP
+
+#include <stdint.h>
+
+#include <string>
+
+#include "evoral/visibility.h"
+#include "evoral/types.h"
+
+namespace Evoral {
+
+class Parameter;
+class ParameterDescriptor;
+
+/** The applications passes one of these which provide the implementation
+ * with required information about event types in an opaque, type neutral way
+ */
+class /*LIBEVORAL_API*/ TypeMap {
+public:
+       virtual ~TypeMap() {}
+
+       /** Return true iff the type is a MIDI event.
+        * The contents of the event will be used for specific ID
+        */
+       virtual bool type_is_midi(uint32_t type) const = 0;
+
+       /** Return the MIDI type (ie status byte with channel 0) for a
+        * parameter, or 0 if parameter can not be expressed as a MIDI event
+        */
+       virtual uint8_t parameter_midi_type(const Parameter& param) const = 0;
+
+       /** The parameter type for the given MIDI event. */
+       virtual ParameterType midi_parameter_type(const uint8_t* buf, uint32_t len) const = 0;
+
+       /** Return the description of a parameter. */
+       virtual ParameterDescriptor descriptor(const Parameter& param) const = 0;
+
+       virtual std::string to_symbol(const Parameter& param) const = 0;
+};
+
+} // namespace Evoral
+
+#endif // EVORAL_TYPE_MAP_HPP
diff --git a/libs/evoral/evoral/TypeMap.hpp b/libs/evoral/evoral/TypeMap.hpp
deleted file mode 100644 (file)
index be2cc62..0000000
+++ /dev/null
@@ -1,62 +0,0 @@
-/*
- * Copyright (C) 2008-2016 David Robillard <d@drobilla.net>
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-
-#ifndef EVORAL_TYPE_MAP_HPP
-#define EVORAL_TYPE_MAP_HPP
-
-#include <stdint.h>
-
-#include <string>
-
-#include "evoral/visibility.h"
-#include "evoral/types.hpp"
-
-namespace Evoral {
-
-class Parameter;
-class ParameterDescriptor;
-
-/** The applications passes one of these which provide the implementation
- * with required information about event types in an opaque, type neutral way
- */
-class /*LIBEVORAL_API*/ TypeMap {
-public:
-       virtual ~TypeMap() {}
-
-       /** Return true iff the type is a MIDI event.
-        * The contents of the event will be used for specific ID
-        */
-       virtual bool type_is_midi(uint32_t type) const = 0;
-
-       /** Return the MIDI type (ie status byte with channel 0) for a
-        * parameter, or 0 if parameter can not be expressed as a MIDI event
-        */
-       virtual uint8_t parameter_midi_type(const Parameter& param) const = 0;
-
-       /** The parameter type for the given MIDI event. */
-       virtual ParameterType midi_parameter_type(const uint8_t* buf, uint32_t len) const = 0;
-
-       /** Return the description of a parameter. */
-       virtual ParameterDescriptor descriptor(const Parameter& param) const = 0;
-
-       virtual std::string to_symbol(const Parameter& param) const = 0;
-};
-
-} // namespace Evoral
-
-#endif // EVORAL_TYPE_MAP_HPP
diff --git a/libs/evoral/evoral/types.h b/libs/evoral/evoral/types.h
new file mode 100644 (file)
index 0000000..35eea56
--- /dev/null
@@ -0,0 +1,72 @@
+/*
+ * Copyright (C) 2008-2012 Carl Hetherington <carl@carlh.net>
+ * Copyright (C) 2008-2016 David Robillard <d@drobilla.net>
+ * Copyright (C) 2009-2015 Paul Davis <paul@linuxaudiosystems.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef EVORAL_TYPES_HPP
+#define EVORAL_TYPES_HPP
+
+#include <float.h>
+#include <math.h>
+#include <stdint.h>
+
+#include <iostream>
+#include <limits>
+#include <list>
+
+#include "evoral/visibility.h"
+#include "pbd/debug.h"
+
+namespace Evoral {
+
+/** ID of an event (note or other). This must be operable on by glib
+    atomic ops
+*/
+typedef int32_t event_id_t;
+
+/** Type of an event (opaque, mapped by application, e.g. MIDI).
+ *
+ * Event types are really an arbitrary integer provided by the type map, and it
+ * is safe to use values not in this enum, but this enum exists so the compiler
+ * can catch mistakes like setting the event type to a MIDI status byte.  Event
+ * types come from the type map and describe a format/protocol like MIDI, and
+ * must not be confused with the payload (such as a note on or CC change).
+ * There is a static value for MIDI as this type is handled specially by
+ * various parts of Evoral.
+ */
+enum EventType {
+       NO_EVENT,
+       MIDI_EVENT
+};
+
+/** Type of a parameter (opaque, mapped by application, e.g. gain) */
+typedef uint32_t ParameterType;
+
+class Beats;
+
+} // namespace Evoral
+
+namespace PBD {
+       namespace DEBUG {
+               LIBEVORAL_API extern DebugBits Sequence;
+               LIBEVORAL_API extern DebugBits Note;
+               LIBEVORAL_API extern DebugBits ControlList;
+       }
+}
+
+#endif // EVORAL_TYPES_HPP
diff --git a/libs/evoral/evoral/types.hpp b/libs/evoral/evoral/types.hpp
deleted file mode 100644 (file)
index 35eea56..0000000
+++ /dev/null
@@ -1,72 +0,0 @@
-/*
- * Copyright (C) 2008-2012 Carl Hetherington <carl@carlh.net>
- * Copyright (C) 2008-2016 David Robillard <d@drobilla.net>
- * Copyright (C) 2009-2015 Paul Davis <paul@linuxaudiosystems.com>
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-
-#ifndef EVORAL_TYPES_HPP
-#define EVORAL_TYPES_HPP
-
-#include <float.h>
-#include <math.h>
-#include <stdint.h>
-
-#include <iostream>
-#include <limits>
-#include <list>
-
-#include "evoral/visibility.h"
-#include "pbd/debug.h"
-
-namespace Evoral {
-
-/** ID of an event (note or other). This must be operable on by glib
-    atomic ops
-*/
-typedef int32_t event_id_t;
-
-/** Type of an event (opaque, mapped by application, e.g. MIDI).
- *
- * Event types are really an arbitrary integer provided by the type map, and it
- * is safe to use values not in this enum, but this enum exists so the compiler
- * can catch mistakes like setting the event type to a MIDI status byte.  Event
- * types come from the type map and describe a format/protocol like MIDI, and
- * must not be confused with the payload (such as a note on or CC change).
- * There is a static value for MIDI as this type is handled specially by
- * various parts of Evoral.
- */
-enum EventType {
-       NO_EVENT,
-       MIDI_EVENT
-};
-
-/** Type of a parameter (opaque, mapped by application, e.g. gain) */
-typedef uint32_t ParameterType;
-
-class Beats;
-
-} // namespace Evoral
-
-namespace PBD {
-       namespace DEBUG {
-               LIBEVORAL_API extern DebugBits Sequence;
-               LIBEVORAL_API extern DebugBits Note;
-               LIBEVORAL_API extern DebugBits ControlList;
-       }
-}
-
-#endif // EVORAL_TYPES_HPP
diff --git a/libs/evoral/src/Control.cc b/libs/evoral/src/Control.cc
new file mode 100644 (file)
index 0000000..0fd6449
--- /dev/null
@@ -0,0 +1,88 @@
+/*
+ * Copyright (C) 2008-2016 David Robillard <d@drobilla.net>
+ * Copyright (C) 2010-2014 Paul Davis <paul@linuxaudiosystems.com>
+ * Copyright (C) 2010 Carl Hetherington <carl@carlh.net>
+ * Copyright (C) 2015-2017 Robin Gareus <robin@gareus.org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include <iostream>
+
+#include "evoral/Control.h"
+#include "evoral/ControlList.h"
+#include "evoral/ParameterDescriptor.h"
+#include "evoral/TypeMap.h"
+
+namespace Evoral {
+
+Control::Control(const Parameter&               parameter,
+                 const ParameterDescriptor&     desc,
+                 boost::shared_ptr<ControlList> list)
+       : _parameter(parameter)
+       , _user_value(desc.normal)
+{
+       set_list (list);
+}
+
+
+/** Get the currently effective value (ie the one that corresponds to current output)
+ */
+double
+Control::get_double (bool from_list, double frame) const
+{
+       if (from_list) {
+               return _list->eval(frame);
+       } else {
+               return _user_value;
+       }
+}
+
+
+void
+Control::set_double (double value, double frame, bool to_list)
+{
+       _user_value = value;
+
+       /* if we're in a write pass, the automation watcher will determine the
+          values and add them to the list, so we we don't need to bother.
+       */
+
+       if (to_list && (!_list->in_write_pass() || _list->descriptor().toggled)) {
+               _list->add (frame, value, false);
+       }
+}
+
+
+void
+Control::set_list(boost::shared_ptr<ControlList> list)
+{
+       _list_marked_dirty_connection.disconnect ();
+
+       _list = list;
+
+       if (_list) {
+               _list->Dirty.connect_same_thread (_list_marked_dirty_connection, boost::bind (&Control::list_marked_dirty, this));
+       }
+}
+
+void
+Control::list_marked_dirty ()
+{
+       ListMarkedDirty (); /* EMIT SIGNAL */
+}
+
+} // namespace Evoral
+
diff --git a/libs/evoral/src/Control.cpp b/libs/evoral/src/Control.cpp
deleted file mode 100644 (file)
index 95b6e32..0000000
+++ /dev/null
@@ -1,88 +0,0 @@
-/*
- * Copyright (C) 2008-2016 David Robillard <d@drobilla.net>
- * Copyright (C) 2010-2014 Paul Davis <paul@linuxaudiosystems.com>
- * Copyright (C) 2010 Carl Hetherington <carl@carlh.net>
- * Copyright (C) 2015-2017 Robin Gareus <robin@gareus.org>
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-
-#include <iostream>
-
-#include "evoral/Control.hpp"
-#include "evoral/ControlList.hpp"
-#include "evoral/ParameterDescriptor.hpp"
-#include "evoral/TypeMap.hpp"
-
-namespace Evoral {
-
-Control::Control(const Parameter&               parameter,
-                 const ParameterDescriptor&     desc,
-                 boost::shared_ptr<ControlList> list)
-       : _parameter(parameter)
-       , _user_value(desc.normal)
-{
-       set_list (list);
-}
-
-
-/** Get the currently effective value (ie the one that corresponds to current output)
- */
-double
-Control::get_double (bool from_list, double frame) const
-{
-       if (from_list) {
-               return _list->eval(frame);
-       } else {
-               return _user_value;
-       }
-}
-
-
-void
-Control::set_double (double value, double frame, bool to_list)
-{
-       _user_value = value;
-
-       /* if we're in a write pass, the automation watcher will determine the
-          values and add them to the list, so we we don't need to bother.
-       */
-
-       if (to_list && (!_list->in_write_pass() || _list->descriptor().toggled)) {
-               _list->add (frame, value, false);
-       }
-}
-
-
-void
-Control::set_list(boost::shared_ptr<ControlList> list)
-{
-       _list_marked_dirty_connection.disconnect ();
-
-       _list = list;
-
-       if (_list) {
-               _list->Dirty.connect_same_thread (_list_marked_dirty_connection, boost::bind (&Control::list_marked_dirty, this));
-       }
-}
-
-void
-Control::list_marked_dirty ()
-{
-       ListMarkedDirty (); /* EMIT SIGNAL */
-}
-
-} // namespace Evoral
-
diff --git a/libs/evoral/src/ControlList.cc b/libs/evoral/src/ControlList.cc
new file mode 100644 (file)
index 0000000..897a689
--- /dev/null
@@ -0,0 +1,2076 @@
+/*
+ * Copyright (C) 2008-2009 Hans Baier <hansfbaier@googlemail.com>
+ * Copyright (C) 2008-2012 Carl Hetherington <carl@carlh.net>
+ * Copyright (C) 2008-2015 David Robillard <d@drobilla.net>
+ * Copyright (C) 2010-2017 Paul Davis <paul@linuxaudiosystems.com>
+ * Copyright (C) 2014-2018 Robin Gareus <robin@gareus.org>
+ * Copyright (C) 2014 Ben Loftis <ben@harrisonconsoles.com>
+ * Copyright (C) 2015 Nick Mainsbridge <mainsbridge@gmail.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include <cmath>
+
+#ifdef COMPILER_MSVC
+#include <float.h>
+
+// 'std::isnan()' is not available in MSVC.
+#define isnan_local(val) (bool)_isnan((double)val)
+#else
+#define isnan_local std::isnan
+#endif
+
+#define GUARD_POINT_DELTA 64
+
+#include <cassert>
+#include <cmath>
+#include <iostream>
+#include <utility>
+
+#include "evoral/ControlList.h"
+#include "evoral/Curve.h"
+#include "evoral/ParameterDescriptor.h"
+#include "evoral/TypeMap.h"
+#include "evoral/types.h"
+
+#include "pbd/control_math.h"
+#include "pbd/compose.h"
+#include "pbd/debug.h"
+
+using namespace std;
+using namespace PBD;
+
+namespace Evoral {
+
+inline bool event_time_less_than (ControlEvent* a, ControlEvent* b)
+{
+       return a->when < b->when;
+}
+
+ControlList::ControlList (const Parameter& id, const ParameterDescriptor& desc)
+       : _parameter(id)
+       , _desc(desc)
+       , _interpolation (default_interpolation ())
+       , _curve(0)
+{
+       _frozen = 0;
+       _changed_when_thawed = false;
+       _lookup_cache.left = -1;
+       _lookup_cache.range.first = _events.end();
+       _lookup_cache.range.second = _events.end();
+       _search_cache.left = -1;
+       _search_cache.first = _events.end();
+       _sort_pending = false;
+       new_write_pass = true;
+       _in_write_pass = false;
+       did_write_during_pass = false;
+       insert_position = -1;
+       most_recent_insert_iterator = _events.end();
+}
+
+ControlList::ControlList (const ControlList& other)
+       : _parameter(other._parameter)
+       , _desc(other._desc)
+       , _interpolation(other._interpolation)
+       , _curve(0)
+{
+       _frozen = 0;
+       _changed_when_thawed = false;
+       _lookup_cache.range.first = _events.end();
+       _lookup_cache.range.second = _events.end();
+       _search_cache.first = _events.end();
+       _sort_pending = false;
+       new_write_pass = true;
+       _in_write_pass = false;
+       did_write_during_pass = false;
+       insert_position = -1;
+       most_recent_insert_iterator = _events.end();
+
+       // XXX copy_events() emits Dirty, but this is just assignment copy/construction
+       copy_events (other);
+}
+
+ControlList::ControlList (const ControlList& other, double start, double end)
+       : _parameter(other._parameter)
+       , _desc(other._desc)
+       , _interpolation(other._interpolation)
+       , _curve(0)
+{
+       _frozen = 0;
+       _changed_when_thawed = false;
+       _lookup_cache.range.first = _events.end();
+       _lookup_cache.range.second = _events.end();
+       _search_cache.first = _events.end();
+       _sort_pending = false;
+
+       /* now grab the relevant points, and shift them back if necessary */
+
+       boost::shared_ptr<ControlList> section = const_cast<ControlList*>(&other)->copy (start, end);
+
+       if (!section->empty()) {
+               // XXX copy_events() emits Dirty, but this is just assignment copy/construction
+               copy_events (*(section.get()));
+       }
+
+       new_write_pass = true;
+       _in_write_pass = false;
+       did_write_during_pass = false;
+       insert_position = -1;
+       most_recent_insert_iterator = _events.end();
+
+       mark_dirty ();
+}
+
+ControlList::~ControlList()
+{
+       for (EventList::iterator x = _events.begin(); x != _events.end(); ++x) {
+               delete (*x);
+       }
+       _events.clear ();
+
+       delete _curve;
+}
+
+boost::shared_ptr<ControlList>
+ControlList::create(const Parameter& id, const ParameterDescriptor& desc)
+{
+       return boost::shared_ptr<ControlList>(new ControlList(id, desc));
+}
+
+bool
+ControlList::operator== (const ControlList& other)
+{
+       return _events == other._events;
+}
+
+ControlList&
+ControlList::operator= (const ControlList& other)
+{
+       if (this != &other) {
+               /* list should be frozen before assignment */
+               assert (_frozen > 0);
+               _changed_when_thawed = false;
+               _sort_pending = false;
+
+               insert_position = other.insert_position;
+               new_write_pass = true;
+               _in_write_pass = false;
+               did_write_during_pass = false;
+               insert_position = -1;
+
+               _parameter = other._parameter;
+               _desc = other._desc;
+               _interpolation = other._interpolation;
+
+               copy_events (other);
+       }
+
+       return *this;
+}
+
+void
+ControlList::copy_events (const ControlList& other)
+{
+       {
+               Glib::Threads::RWLock::WriterLock lm (_lock);
+               for (EventList::iterator x = _events.begin(); x != _events.end(); ++x) {
+                       delete (*x);
+               }
+               _events.clear ();
+               Glib::Threads::RWLock::ReaderLock olm (other._lock);
+               for (const_iterator i = other.begin(); i != other.end(); ++i) {
+                       _events.push_back (new ControlEvent ((*i)->when, (*i)->value));
+               }
+               unlocked_invalidate_insert_iterator ();
+               mark_dirty ();
+       }
+       maybe_signal_changed ();
+}
+
+void
+ControlList::create_curve()
+{
+       _curve = new Curve(*this);
+}
+
+void
+ControlList::destroy_curve()
+{
+       delete _curve;
+       _curve = NULL;
+}
+
+ControlList::InterpolationStyle
+ControlList::default_interpolation () const
+{
+       if (_desc.toggled) {
+               return Discrete;
+       } else if (_desc.logarithmic) {
+               return Logarithmic;
+       }
+       return Linear;
+}
+
+void
+ControlList::maybe_signal_changed ()
+{
+       mark_dirty ();
+
+       if (_frozen) {
+               _changed_when_thawed = true;
+       }
+}
+
+void
+ControlList::clear ()
+{
+       {
+               Glib::Threads::RWLock::WriterLock lm (_lock);
+               for (EventList::iterator x = _events.begin(); x != _events.end(); ++x) {
+                       delete (*x);
+               }
+               _events.clear ();
+               unlocked_invalidate_insert_iterator ();
+               mark_dirty ();
+       }
+
+       maybe_signal_changed ();
+}
+
+void
+ControlList::x_scale (double factor)
+{
+       Glib::Threads::RWLock::WriterLock lm (_lock);
+       _x_scale (factor);
+}
+
+bool
+ControlList::extend_to (double when)
+{
+       Glib::Threads::RWLock::WriterLock lm (_lock);
+       if (_events.empty() || _events.back()->when == when) {
+               return false;
+       }
+       double factor = when / _events.back()->when;
+       _x_scale (factor);
+       return true;
+}
+
+void
+ControlList::y_transform (boost::function<double(double)> callback)
+{
+       {
+               Glib::Threads::RWLock::WriterLock lm (_lock);
+               for (iterator i = _events.begin(); i != _events.end(); ++i) {
+                       (*i)->value = callback ((*i)->value);
+               }
+               mark_dirty ();
+       }
+       maybe_signal_changed ();
+}
+
+void
+ControlList::list_merge (ControlList const& other, boost::function<double(double, double)> callback)
+{
+       {
+               Glib::Threads::RWLock::WriterLock lm (_lock);
+               EventList nel;
+               /* First scale existing events, copy into a new list.
+                * The original list is needed later to interpolate
+                * for new events only present in the master list.
+                */
+               for (iterator i = _events.begin(); i != _events.end(); ++i) {
+                       float val = callback ((*i)->value, other.eval ((*i)->when));
+                       nel.push_back (new ControlEvent ((*i)->when , val));
+               }
+               /* Now add events which are only present in the master-list. */
+               const EventList& evl (other.events());
+               for (const_iterator i = evl.begin(); i != evl.end(); ++i) {
+                       bool found = false;
+                       // TODO: optimize, remember last matching iterator (lists are sorted)
+                       for (iterator j = _events.begin(); j != _events.end(); ++j) {
+                               if ((*i)->when == (*j)->when) {
+                                       found = true;
+                                       break;
+                               }
+                       }
+                       /* skip events that have already been merge in the first pass */
+                       if (found) {
+                               continue;
+                       }
+                       float val = callback (unlocked_eval ((*i)->when), (*i)->value);
+                       nel.push_back (new ControlEvent ((*i)->when, val));
+               }
+               nel.sort (event_time_less_than);
+
+               for (EventList::iterator x = _events.begin(); x != _events.end(); ++x) {
+                       delete (*x);
+               }
+               _events.clear ();
+               _events = nel;
+
+               unlocked_remove_duplicates ();
+               unlocked_invalidate_insert_iterator ();
+               mark_dirty ();
+       }
+       maybe_signal_changed ();
+}
+
+void
+ControlList::_x_scale (double factor)
+{
+       for (iterator i = _events.begin(); i != _events.end(); ++i) {
+               (*i)->when *= factor;
+       }
+
+       mark_dirty ();
+}
+
+struct ControlEventTimeComparator {
+       bool operator() (ControlEvent* a, ControlEvent* b) {
+               return a->when < b->when;
+       }
+};
+
+void
+ControlList::thin (double thinning_factor)
+{
+       if (thinning_factor == 0.0 || _desc.toggled) {
+               return;
+       }
+
+       assert (is_sorted ());
+
+       bool changed = false;
+
+       {
+               Glib::Threads::RWLock::WriterLock lm (_lock);
+
+               ControlEvent* prevprev = 0;
+               ControlEvent* cur = 0;
+               ControlEvent* prev = 0;
+               iterator pprev;
+               int counter = 0;
+
+               DEBUG_TRACE (DEBUG::ControlList, string_compose ("@%1 thin from %2 events\n", this, _events.size()));
+
+               for (iterator i = _events.begin(); i != _events.end(); ++i) {
+
+                       cur = *i;
+                       counter++;
+
+                       if (counter > 2) {
+
+                               /* compute the area of the triangle formed by 3 points
+                                */
+
+                               double area = fabs ((prevprev->when * (prev->value - cur->value)) +
+                                                   (prev->when * (cur->value - prevprev->value)) +
+                                                   (cur->when * (prevprev->value - prev->value)));
+
+                               if (area < thinning_factor) {
+                                       iterator tmp = pprev;
+
+                                       /* pprev will change to current
+                                          i is incremented to the next event
+                                          as we loop.
+                                       */
+
+                                       pprev = i;
+                                       _events.erase (tmp);
+                                       changed = true;
+                                       continue;
+                               }
+                       }
+
+                       prevprev = prev;
+                       prev = cur;
+                       pprev = i;
+               }
+
+               DEBUG_TRACE (DEBUG::ControlList, string_compose ("@%1 thin => %2 events\n", this, _events.size()));
+
+               if (changed) {
+                       unlocked_invalidate_insert_iterator ();
+                       mark_dirty ();
+               }
+       }
+
+       if (changed) {
+               maybe_signal_changed ();
+       }
+}
+
+void
+ControlList::fast_simple_add (double when, double value)
+{
+       Glib::Threads::RWLock::WriterLock lm (_lock);
+       /* to be used only for loading pre-sorted data from saved state */
+       _events.insert (_events.end(), new ControlEvent (when, value));
+
+       mark_dirty ();
+       if (_frozen) {
+               _sort_pending = true;
+       }
+}
+
+void
+ControlList::invalidate_insert_iterator ()
+{
+       Glib::Threads::RWLock::WriterLock lm (_lock);
+       unlocked_invalidate_insert_iterator ();
+}
+
+void
+ControlList::unlocked_invalidate_insert_iterator ()
+{
+       most_recent_insert_iterator = _events.end();
+}
+
+void
+ControlList::unlocked_remove_duplicates ()
+{
+       if (_events.size() < 2) {
+               return;
+       }
+       iterator i = _events.begin();
+       iterator prev = i++;
+       while (i != _events.end()) {
+               if ((*prev)->when == (*i)->when && (*prev)->value == (*i)->value) {
+                       i = _events.erase (i);
+               } else {
+                       ++prev;
+                       ++i;
+               }
+       }
+}
+
+void
+ControlList::start_write_pass (double when)
+{
+       Glib::Threads::RWLock::WriterLock lm (_lock);
+
+       DEBUG_TRACE (DEBUG::ControlList, string_compose ("%1: setup write pass @ %2\n", this, when));
+
+       insert_position = when;
+
+       /* leave the insert iterator invalid, so that we will do the lookup
+          of where it should be in a "lazy" way - deferring it until
+          we actually add the first point (which may never happen).
+       */
+
+       unlocked_invalidate_insert_iterator ();
+
+       /* except if we're already in an active write-pass.
+        *
+        * invalid iterator == end() the iterator is set to the correct
+        * position in ControlList::add IFF (_in_write_pass && new_write_pass)
+        */
+       if (_in_write_pass && !new_write_pass) {
+#if 1
+               add_guard_point (when, 0); // also sets most_recent_insert_iterator
+#else
+               const ControlEvent cp (when, 0.0);
+               most_recent_insert_iterator = lower_bound (_events.begin(), _events.end(), &cp, time_comparator);
+#endif
+       }
+}
+
+void
+ControlList::write_pass_finished (double /*when*/, double thinning_factor)
+{
+       DEBUG_TRACE (DEBUG::ControlList, "write pass finished\n");
+
+       if (did_write_during_pass) {
+               thin (thinning_factor);
+               did_write_during_pass = false;
+       }
+       new_write_pass = true;
+       _in_write_pass = false;
+}
+
+void
+ControlList::set_in_write_pass (bool yn, bool add_point, double when)
+{
+       DEBUG_TRACE (DEBUG::ControlList, string_compose ("now in write pass @ %1, add point ? %2\n", when, add_point));
+
+       _in_write_pass = yn;
+
+       if (yn && add_point) {
+               Glib::Threads::RWLock::WriterLock lm (_lock);
+               add_guard_point (when, 0);
+       }
+}
+
+void
+ControlList::add_guard_point (double when, double offset)
+{
+       // caller needs to hold writer-lock
+       if (offset < 0 && when < offset) {
+               return;
+       }
+       assert (offset <= 0);
+
+       if (offset != 0) {
+               /* check if there are points between when + offset .. when */
+               ControlEvent cp (when + offset, 0.0);
+               iterator s;
+               iterator e;
+               if ((s = lower_bound (_events.begin(), _events.end(), &cp, time_comparator)) != _events.end()) {
+                       cp.when = when;
+                       e = lower_bound (_events.begin(), _events.end(), &cp, time_comparator);
+                       if (s != e) {
+                               DEBUG_TRACE (DEBUG::ControlList, string_compose ("@%1 add_guard_point, none added, found event between %2 and %3\n", this, when - offset, when));
+                               return;
+                       }
+               }
+       }
+
+       /* don't do this again till the next write pass,
+        * unless we're not in a write-pass (transport stopped)
+        */
+       if (_in_write_pass && new_write_pass) {
+               WritePassStarted (); /* EMIT SIGNAL w/WriteLock */
+               new_write_pass = false;
+       }
+
+       when += offset;
+
+       ControlEvent cp (when, 0.0);
+       most_recent_insert_iterator = lower_bound (_events.begin(), _events.end(), &cp, time_comparator);
+
+       double eval_value = unlocked_eval (when);
+
+       if (most_recent_insert_iterator == _events.end()) {
+
+               DEBUG_TRACE (DEBUG::ControlList, string_compose ("@%1 insert iterator at end, adding eval-value there %2\n", this, eval_value));
+               _events.push_back (new ControlEvent (when, eval_value));
+               /* leave insert iterator at the end */
+
+       } else if ((*most_recent_insert_iterator)->when == when) {
+
+               DEBUG_TRACE (DEBUG::ControlList, string_compose ("@%1 insert iterator at existing point, setting eval-value there %2\n", this, eval_value));
+
+               /* most_recent_insert_iterator points to a control event
+                  already at the insert position, so there is
+                  nothing to do.
+
+                  ... except ...
+
+                  advance most_recent_insert_iterator so that the "real"
+                  insert occurs in the right place, since it
+                  points to the control event just inserted.
+               */
+
+               ++most_recent_insert_iterator;
+       } else {
+
+               /* insert a new control event at the right spot */
+
+               DEBUG_TRACE (DEBUG::ControlList, string_compose ("@%1 insert eval-value %2 just before iterator @ %3\n",
+                                                                this, eval_value, (*most_recent_insert_iterator)->when));
+
+               most_recent_insert_iterator = _events.insert (most_recent_insert_iterator, new ControlEvent (when, eval_value));
+
+               /* advance most_recent_insert_iterator so that the "real"
+                * insert occurs in the right place, since it
+                * points to the control event just inserted.
+                */
+
+               ++most_recent_insert_iterator;
+       }
+}
+
+bool
+ControlList::in_write_pass () const
+{
+       return _in_write_pass;
+}
+
+bool
+ControlList::editor_add (double when, double value, bool with_guard)
+{
+       /* this is for making changes from a graphical line editor */
+       {
+               Glib::Threads::RWLock::WriterLock lm (_lock);
+
+               ControlEvent cp (when, 0.0f);
+               iterator i = lower_bound (_events.begin(), _events.end(), &cp, time_comparator);
+
+               if (i != _events.end () && (*i)->when == when) {
+                       return false;
+               }
+
+               /* clamp new value to allowed range */
+               value = std::min ((double)_desc.upper, std::max ((double)_desc.lower, value));
+
+               if (_events.empty()) {
+
+                       /* as long as the point we're adding is not at zero,
+                        * add an "anchor" point there.
+                        */
+
+                       if (when >= 1) {
+                               _events.insert (_events.end(), new ControlEvent (0, value));
+                               DEBUG_TRACE (DEBUG::ControlList, string_compose ("@%1 added value %2 at zero\n", this, value));
+                       }
+               }
+
+               insert_position = when;
+               if (with_guard) {
+                       add_guard_point (when, -GUARD_POINT_DELTA);
+                       maybe_add_insert_guard (when);
+                       i = lower_bound (_events.begin(), _events.end(), &cp, time_comparator);
+               }
+
+               iterator result;
+               DEBUG_TRACE (DEBUG::ControlList, string_compose ("editor_add: actually add when= %1 value= %2\n", when, value));
+               result = _events.insert (i, new ControlEvent (when, value));
+
+               if (i == result) {
+                       return false;
+               }
+
+               mark_dirty ();
+       }
+       maybe_signal_changed ();
+
+       return true;
+}
+
+void
+ControlList::maybe_add_insert_guard (double when)
+{
+       // caller needs to hold writer-lock
+       if (most_recent_insert_iterator != _events.end()) {
+               if ((*most_recent_insert_iterator)->when - when > GUARD_POINT_DELTA) {
+                       /* Next control point is some distance from where our new point is
+                          going to go, so add a new point to avoid changing the shape of
+                          the line too much.  The insert iterator needs to point to the
+                          new control point so that our insert will happen correctly. */
+                       most_recent_insert_iterator = _events.insert ( most_recent_insert_iterator,
+                                       new ControlEvent (when + GUARD_POINT_DELTA, (*most_recent_insert_iterator)->value));
+
+                       DEBUG_TRACE (DEBUG::ControlList, string_compose ("@%1 added insert guard point @ %2 = %3\n",
+                                                                        this, when + GUARD_POINT_DELTA,
+                                                                        (*most_recent_insert_iterator)->value));
+               }
+       }
+}
+
+/** If we would just be adding to a straight line, move the previous point instead. */
+bool
+ControlList::maybe_insert_straight_line (double when, double value)
+{
+       // caller needs to hold writer-lock
+       if (_events.empty()) {
+               return false;
+       }
+
+       if (_events.back()->value == value) {
+               // Point b at the final point, which we know exists
+               EventList::iterator b = _events.end();
+               --b;
+               if (b == _events.begin()) {
+                       return false;  // No previous point
+               }
+
+               // Check the previous point's value
+               --b;
+               if ((*b)->value == value) {
+                       /* At least two points with the exact same value (straight
+                          line), just move the final point to the new time. */
+                       _events.back()->when = when;
+                       DEBUG_TRACE (DEBUG::ControlList, string_compose ("final value of %1 moved to %2\n", value, when));
+                       return true;
+               }
+       }
+       return false;
+}
+
+ControlList::iterator
+ControlList::erase_from_iterator_to (iterator iter, double when)
+{
+       // caller needs to hold writer-lock
+       while (iter != _events.end()) {
+               if ((*iter)->when < when) {
+                       DEBUG_TRACE (DEBUG::ControlList, string_compose ("@%1 erase existing @ %2\n", this, (*iter)->when));
+                       delete *iter;
+                       iter = _events.erase (iter);
+                       continue;
+               } else if ((*iter)->when >= when) {
+                       break;
+               }
+               ++iter;
+       }
+       return iter;
+}
+
+/* this is for making changes from some kind of user interface or
+ * control surface (GUI, MIDI, OSC etc)
+ */
+void
+ControlList::add (double when, double value, bool with_guards, bool with_initial)
+{
+       /* clamp new value to allowed range */
+       value = std::min ((double)_desc.upper, std::max ((double)_desc.lower, value));
+
+       DEBUG_TRACE (DEBUG::ControlList,
+                    string_compose ("@%1 add %2 at %3 guards = %4 write pass = %5 (new? %6) at end? %7\n",
+                                    this, value, when, with_guards, _in_write_pass, new_write_pass,
+                                    (most_recent_insert_iterator == _events.end())));
+       {
+               Glib::Threads::RWLock::WriterLock lm (_lock);
+               ControlEvent cp (when, 0.0f);
+               iterator insertion_point;
+
+               if (_events.empty() && with_initial) {
+
+                       /* empty: add an "anchor" point if the point we're adding past time 0 */
+
+                       if (when >= 1) {
+                               if (_desc.toggled) {
+                                       const double opp_val = ((value < 0.5) ? 1.0 : 0.0);
+                                       _events.insert (_events.end(), new ControlEvent (0, opp_val));
+                                       DEBUG_TRACE (DEBUG::ControlList, string_compose ("@%1 added toggled value %2 at zero\n", this, opp_val));
+
+                               } else {
+                                       _events.insert (_events.end(), new ControlEvent (0, value));
+                                       DEBUG_TRACE (DEBUG::ControlList, string_compose ("@%1 added default value %2 at zero\n", this, _desc.normal));
+                               }
+                       }
+               }
+
+               if (_in_write_pass && new_write_pass) {
+
+                       /* first write in a write pass: add guard point if requested */
+
+                       if (with_guards) {
+                               add_guard_point (insert_position, 0);
+                               did_write_during_pass = true;
+                       } else {
+                               /* not adding a guard, but we need to set iterator appropriately */
+                               const ControlEvent cp (when, 0.0);
+                               most_recent_insert_iterator = lower_bound (_events.begin(), _events.end(), &cp, time_comparator);
+                       }
+                       WritePassStarted (); /* EMIT SIGNAL w/WriteLock */
+                       new_write_pass = false;
+
+               } else if (_in_write_pass &&
+                          (most_recent_insert_iterator == _events.end() || when > (*most_recent_insert_iterator)->when)) {
+
+                       /* in write pass: erase from most recent insert to now */
+
+                       if (most_recent_insert_iterator != _events.end()) {
+                               /* advance to avoid deleting the last inserted point itself. */
+                               ++most_recent_insert_iterator;
+                       }
+
+                       if (with_guards) {
+                               most_recent_insert_iterator = erase_from_iterator_to (most_recent_insert_iterator, when + GUARD_POINT_DELTA);
+                               maybe_add_insert_guard (when);
+                       } else {
+                               most_recent_insert_iterator = erase_from_iterator_to(most_recent_insert_iterator, when);
+                       }
+
+               } else if (!_in_write_pass) {
+
+                       /* not in a write pass: figure out the iterator we should insert in front of */
+
+                       DEBUG_TRACE (DEBUG::ControlList, string_compose ("compute(b) MRI for position %1\n", when));
+                       ControlEvent cp (when, 0.0f);
+                       most_recent_insert_iterator = lower_bound (_events.begin(), _events.end(), &cp, time_comparator);
+               }
+
+               /* OK, now we're really ready to add a new point */
+
+               if (most_recent_insert_iterator == _events.end()) {
+                       DEBUG_TRACE (DEBUG::ControlList, string_compose ("@%1 appending new point at end\n", this));
+
+                       const bool done = maybe_insert_straight_line (when, value);
+                       if (!done) {
+                               _events.push_back (new ControlEvent (when, value));
+                               DEBUG_TRACE (DEBUG::ControlList, string_compose ("\tactually appended, size now %1\n", _events.size()));
+                       }
+
+                       most_recent_insert_iterator = _events.end();
+                       --most_recent_insert_iterator;
+
+               } else if ((*most_recent_insert_iterator)->when == when) {
+
+                       if ((*most_recent_insert_iterator)->value != value) {
+                               DEBUG_TRACE (DEBUG::ControlList, string_compose ("@%1 reset existing point to new value %2\n", this, value));
+
+                               /* only one point allowed per time point, so add a guard point
+                                * before it if needed then reset the value of the point.
+                                */
+
+                               (*most_recent_insert_iterator)->value = value;
+
+                               /* if we modified the final value, then its as
+                                * if we inserted a new point as far as the
+                                * next addition, so make sure we know that.
+                                */
+
+                               if (_events.back()->when == when) {
+                                       most_recent_insert_iterator = _events.end();
+                               }
+
+                       } else {
+                               DEBUG_TRACE (DEBUG::ControlList, string_compose ("@%1 same time %2, same value value %3\n", this, when, value));
+                       }
+
+               } else {
+                       DEBUG_TRACE (DEBUG::ControlList, string_compose ("@%1 insert new point at %2 at iterator at %3\n", this, when, (*most_recent_insert_iterator)->when));
+                       bool done = false;
+                       /* check for possible straight line here until maybe_insert_straight_line () handles the insert iterator properly*/
+                       if (most_recent_insert_iterator != _events.begin ()) {
+                               bool have_point2 = false;
+                               --most_recent_insert_iterator;
+                               const bool have_point1 = (*most_recent_insert_iterator)->value == value;
+
+                               if (most_recent_insert_iterator != _events.begin ()) {
+                                       --most_recent_insert_iterator;
+                                       have_point2 = (*most_recent_insert_iterator)->value == value;
+                                       ++most_recent_insert_iterator;
+                               }
+
+                               if (have_point1 && have_point2) {
+                                       DEBUG_TRACE (DEBUG::ControlList, string_compose ("@%1 no change: move existing at %3 to %2\n", this, when, (*most_recent_insert_iterator)->when));
+                                       (*most_recent_insert_iterator)->when = when;
+                                       done = true;
+                               } else {
+                                       ++most_recent_insert_iterator;
+                               }
+                       }
+
+                       /* if the transport is stopped, add guard points */
+                       if (!done && !_in_write_pass) {
+                               add_guard_point (when, -GUARD_POINT_DELTA);
+                               maybe_add_insert_guard (when);
+                       } else if (with_guards) {
+                               maybe_add_insert_guard (when);
+                       }
+
+                       if (!done) {
+                               EventList::iterator x = _events.insert (most_recent_insert_iterator, new ControlEvent (when, value));
+                               DEBUG_TRACE (DEBUG::ControlList, string_compose ("@%1 inserted new value before MRI, size now %2\n", this, _events.size()));
+                               most_recent_insert_iterator = x;
+                       }
+               }
+
+               mark_dirty ();
+       }
+
+       maybe_signal_changed ();
+}
+
+void
+ControlList::erase (iterator i)
+{
+       {
+               Glib::Threads::RWLock::WriterLock lm (_lock);
+               if (most_recent_insert_iterator == i) {
+                       unlocked_invalidate_insert_iterator ();
+               }
+               _events.erase (i);
+               mark_dirty ();
+       }
+       maybe_signal_changed ();
+}
+
+void
+ControlList::erase (iterator start, iterator end)
+{
+       {
+               Glib::Threads::RWLock::WriterLock lm (_lock);
+               _events.erase (start, end);
+               unlocked_invalidate_insert_iterator ();
+               mark_dirty ();
+       }
+       maybe_signal_changed ();
+}
+
+/** Erase the first event which matches the given time and value */
+void
+ControlList::erase (double when, double value)
+{
+       {
+               Glib::Threads::RWLock::WriterLock lm (_lock);
+
+               iterator i = begin ();
+               while (i != end() && ((*i)->when != when || (*i)->value != value)) {
+                       ++i;
+               }
+
+               if (i != end ()) {
+                       _events.erase (i);
+                       if (most_recent_insert_iterator == i) {
+                               unlocked_invalidate_insert_iterator ();
+                       }
+               }
+
+               mark_dirty ();
+       }
+
+       maybe_signal_changed ();
+}
+
+void
+ControlList::erase_range (double start, double endt)
+{
+       bool erased = false;
+
+       {
+               Glib::Threads::RWLock::WriterLock lm (_lock);
+               erased = erase_range_internal (start, endt, _events);
+
+               if (erased) {
+                       mark_dirty ();
+               }
+
+       }
+
+       if (erased) {
+               maybe_signal_changed ();
+       }
+}
+
+bool
+ControlList::erase_range_internal (double start, double endt, EventList & events)
+{
+       bool erased = false;
+       ControlEvent cp (start, 0.0f);
+       iterator s;
+       iterator e;
+
+       if ((s = lower_bound (events.begin(), events.end(), &cp, time_comparator)) != events.end()) {
+               cp.when = endt;
+               e = upper_bound (events.begin(), events.end(), &cp, time_comparator);
+               events.erase (s, e);
+               if (s != e) {
+                       unlocked_invalidate_insert_iterator ();
+                       erased = true;
+               }
+       }
+
+       return erased;
+}
+
+void
+ControlList::slide (iterator before, double distance)
+{
+       {
+               Glib::Threads::RWLock::WriterLock lm (_lock);
+
+               if (before == _events.end()) {
+                       return;
+               }
+
+               while (before != _events.end()) {
+                       (*before)->when += distance;
+                       ++before;
+               }
+
+               mark_dirty ();
+       }
+
+       maybe_signal_changed ();
+}
+
+void
+ControlList::shift (double pos, double frames)
+{
+       {
+               Glib::Threads::RWLock::WriterLock lm (_lock);
+               double v0, v1;
+               if (frames < 0) {
+                       /* Route::shift () with negative shift is used
+                        * for "remove time". The time [pos.. pos-frames] is removed.
+                        * and everyhing after, moved backwards.
+                        */
+                       v0 = unlocked_eval (pos);
+                       v1 = unlocked_eval (pos - frames);
+                       erase_range_internal (pos, pos - frames, _events);
+               } else {
+                       v0 = v1 = unlocked_eval (pos);
+               }
+
+               bool dst_guard_exists = false;
+
+               for (iterator i = _events.begin(); i != _events.end(); ++i) {
+                       if ((*i)->when == pos) {
+                               dst_guard_exists = true;
+                       }
+                       if ((*i)->when >= pos) {
+                               (*i)->when += frames;
+                       }
+               }
+
+               /* add guard-points to retain shape, if needed */
+               if (frames > 0) {
+                       ControlEvent cp (pos, 0.0);
+                       iterator s = lower_bound (_events.begin(), _events.end(), &cp, time_comparator);
+                       if (s != _events.end ()) {
+                               _events.insert (s, new ControlEvent (pos, v0));
+                       }
+                       pos += frames;
+               } else if (frames < 0 && pos > 0) {
+                       ControlEvent cp (pos - 1, 0.0);
+                       iterator s = lower_bound (_events.begin(), _events.end(), &cp, time_comparator);
+                       if (s != _events.end ()) {
+                               _events.insert (s, new ControlEvent (pos - 1, v0));
+                       }
+               }
+               if (!dst_guard_exists) {
+                       ControlEvent cp (pos, 0.0);
+                       iterator s = lower_bound (_events.begin(), _events.end(), &cp, time_comparator);
+                       _events.insert (s, new ControlEvent (pos, s == _events.end () ? v0 : v1));
+               }
+
+
+               mark_dirty ();
+       }
+
+       maybe_signal_changed ();
+}
+
+void
+ControlList::modify (iterator iter, double when, double val)
+{
+       /* note: we assume higher level logic is in place to avoid this
+        * reordering the time-order of control events in the list. ie. all
+        * points after *iter are later than when.
+        */
+
+       /* catch possible float/double rounding errors from higher levels */
+       val = std::min ((double)_desc.upper, std::max ((double)_desc.lower, val));
+
+       {
+               Glib::Threads::RWLock::WriterLock lm (_lock);
+
+               (*iter)->when = when;
+               (*iter)->value = val;
+               if (isnan_local (val)) {
+                       abort ();
+               }
+
+               if (!_frozen) {
+                       _events.sort (event_time_less_than);
+                       unlocked_remove_duplicates ();
+                       unlocked_invalidate_insert_iterator ();
+               } else {
+                       _sort_pending = true;
+               }
+
+               mark_dirty ();
+       }
+
+       maybe_signal_changed ();
+}
+
+std::pair<ControlList::iterator,ControlList::iterator>
+ControlList::control_points_adjacent (double xval)
+{
+       Glib::Threads::RWLock::ReaderLock lm (_lock);
+       iterator i;
+       ControlEvent cp (xval, 0.0f);
+       std::pair<iterator,iterator> ret;
+
+       ret.first = _events.end();
+       ret.second = _events.end();
+
+       for (i = lower_bound (_events.begin(), _events.end(), &cp, time_comparator); i != _events.end(); ++i) {
+
+               if (ret.first == _events.end()) {
+                       if ((*i)->when >= xval) {
+                               if (i != _events.begin()) {
+                                       ret.first = i;
+                                       --ret.first;
+                               } else {
+                                       return ret;
+                               }
+                       }
+               }
+
+               if ((*i)->when > xval) {
+                       ret.second = i;
+                       break;
+               }
+       }
+
+       return ret;
+}
+
+void
+ControlList::freeze ()
+{
+       _frozen++;
+}
+
+void
+ControlList::thaw ()
+{
+       assert(_frozen > 0);
+
+       if (--_frozen > 0) {
+               return;
+       }
+
+       {
+               Glib::Threads::RWLock::WriterLock lm (_lock);
+
+               if (_sort_pending) {
+                       _events.sort (event_time_less_than);
+                       unlocked_remove_duplicates ();
+                       unlocked_invalidate_insert_iterator ();
+                       _sort_pending = false;
+               }
+       }
+}
+
+void
+ControlList::mark_dirty () const
+{
+       _lookup_cache.left = -1;
+       _lookup_cache.range.first = _events.end();
+       _lookup_cache.range.second = _events.end();
+       _search_cache.left = -1;
+       _search_cache.first = _events.end();
+
+       if (_curve) {
+               _curve->mark_dirty();
+       }
+
+       Dirty (); /* EMIT SIGNAL */
+}
+
+void
+ControlList::truncate_end (double last_coordinate)
+{
+       {
+               Glib::Threads::RWLock::WriterLock lm (_lock);
+               ControlEvent cp (last_coordinate, 0);
+               ControlList::reverse_iterator i;
+               double last_val;
+
+               if (_events.empty()) {
+                       return;
+               }
+
+               if (last_coordinate == _events.back()->when) {
+                       return;
+               }
+
+               if (last_coordinate > _events.back()->when) {
+
+                       /* extending end:
+                        */
+
+                       iterator foo = _events.begin();
+                       bool lessthantwo;
+
+                       if (foo == _events.end()) {
+                               lessthantwo = true;
+                       } else if (++foo == _events.end()) {
+                               lessthantwo = true;
+                       } else {
+                               lessthantwo = false;
+                       }
+
+                       if (lessthantwo) {
+                               /* less than 2 points: add a new point */
+                               _events.push_back (new ControlEvent (last_coordinate, _events.back()->value));
+                       } else {
+
+                               /* more than 2 points: check to see if the last 2 values
+                                  are equal. if so, just move the position of the
+                                  last point. otherwise, add a new point.
+                               */
+
+                               iterator penultimate = _events.end();
+                               --penultimate; /* points at last point */
+                               --penultimate; /* points at the penultimate point */
+
+                               if (_events.back()->value == (*penultimate)->value) {
+                                       _events.back()->when = last_coordinate;
+                               } else {
+                                       _events.push_back (new ControlEvent (last_coordinate, _events.back()->value));
+                               }
+                       }
+
+               } else {
+
+                       /* shortening end */
+
+                       last_val = unlocked_eval (last_coordinate);
+                       last_val = max ((double) _desc.lower, last_val);
+                       last_val = min ((double) _desc.upper, last_val);
+
+                       i = _events.rbegin();
+
+                       /* make i point to the last control point */
+
+                       ++i;
+
+                       /* now go backwards, removing control points that are
+                          beyond the new last coordinate.
+                       */
+
+                       // FIXME: SLOW! (size() == O(n))
+
+                       uint32_t sz = _events.size();
+
+                       while (i != _events.rend() && sz > 2) {
+                               ControlList::reverse_iterator tmp;
+
+                               tmp = i;
+                               ++tmp;
+
+                               if ((*i)->when < last_coordinate) {
+                                       break;
+                               }
+
+                               _events.erase (i.base());
+                               --sz;
+
+                               i = tmp;
+                       }
+
+                       _events.back()->when = last_coordinate;
+                       _events.back()->value = last_val;
+               }
+
+               unlocked_invalidate_insert_iterator ();
+               mark_dirty();
+       }
+
+       maybe_signal_changed ();
+}
+
+void
+ControlList::truncate_start (double overall_length)
+{
+       {
+               Glib::Threads::RWLock::WriterLock lm (_lock);
+               iterator i;
+               double first_legal_value;
+               double first_legal_coordinate;
+
+               if (_events.empty()) {
+                       /* nothing to truncate */
+                       return;
+               } else if (overall_length == _events.back()->when) {
+                       /* no change in overall length */
+                       return;
+               }
+
+               if (overall_length > _events.back()->when) {
+
+                       /* growing at front: duplicate first point. shift all others */
+
+                       double shift = overall_length - _events.back()->when;
+                       uint32_t np;
+
+                       for (np = 0, i = _events.begin(); i != _events.end(); ++i, ++np) {
+                               (*i)->when += shift;
+                       }
+
+                       if (np < 2) {
+
+                               /* less than 2 points: add a new point */
+                               _events.push_front (new ControlEvent (0, _events.front()->value));
+
+                       } else {
+
+                               /* more than 2 points: check to see if the first 2 values
+                                  are equal. if so, just move the position of the
+                                  first point. otherwise, add a new point.
+                               */
+
+                               iterator second = _events.begin();
+                               ++second; /* points at the second point */
+
+                               if (_events.front()->value == (*second)->value) {
+                                       /* first segment is flat, just move start point back to zero */
+                                       _events.front()->when = 0;
+                               } else {
+                                       /* leave non-flat segment in place, add a new leading point. */
+                                       _events.push_front (new ControlEvent (0, _events.front()->value));
+                               }
+                       }
+
+               } else {
+
+                       /* shrinking at front */
+
+                       first_legal_coordinate = _events.back()->when - overall_length;
+                       first_legal_value = unlocked_eval (first_legal_coordinate);
+                       first_legal_value = max ((double)_desc.lower, first_legal_value);
+                       first_legal_value = min ((double)_desc.upper, first_legal_value);
+
+                       /* remove all events earlier than the new "front" */
+
+                       i = _events.begin();
+
+                       while (i != _events.end() && !_events.empty()) {
+                               ControlList::iterator tmp;
+
+                               tmp = i;
+                               ++tmp;
+
+                               if ((*i)->when > first_legal_coordinate) {
+                                       break;
+                               }
+
+                               _events.erase (i);
+
+                               i = tmp;
+                       }
+
+
+                       /* shift all remaining points left to keep their same
+                          relative position
+                       */
+
+                       for (i = _events.begin(); i != _events.end(); ++i) {
+                               (*i)->when -= first_legal_coordinate;
+                       }
+
+                       /* add a new point for the interpolated new value */
+
+                       _events.push_front (new ControlEvent (0, first_legal_value));
+               }
+
+               unlocked_invalidate_insert_iterator ();
+               mark_dirty();
+       }
+
+       maybe_signal_changed ();
+}
+
+double
+ControlList::unlocked_eval (double x) const
+{
+       pair<EventList::iterator,EventList::iterator> range;
+       int32_t npoints;
+       double lpos, upos;
+       double lval, uval;
+       double fraction;
+
+       const_iterator length_check_iter = _events.begin();
+       for (npoints = 0; npoints < 4; ++npoints, ++length_check_iter) {
+               if (length_check_iter == _events.end()) {
+                       break;
+               }
+       }
+
+       switch (npoints) {
+       case 0:
+               return _desc.normal;
+
+       case 1:
+               return _events.front()->value;
+
+       case 2:
+               if (x >= _events.back()->when) {
+                       return _events.back()->value;
+               } else if (x <= _events.front()->when) {
+                       return _events.front()->value;
+               }
+
+               lpos = _events.front()->when;
+               lval = _events.front()->value;
+               upos = _events.back()->when;
+               uval = _events.back()->value;
+
+               fraction = (double) (x - lpos) / (double) (upos - lpos);
+
+               switch (_interpolation) {
+                       case Discrete:
+                               return lval;
+                       case Logarithmic:
+                               return interpolate_logarithmic (lval, uval, fraction, _desc.lower, _desc.upper);
+                       case Exponential:
+                               return interpolate_gain (lval, uval, fraction, _desc.upper);
+                       case Curved:
+                               /* only used x-fade curves, never direct eval */
+                               assert (0);
+                       default: // Linear
+                               return interpolate_linear (lval, uval, fraction);
+               }
+
+       default:
+               if (x >= _events.back()->when) {
+                       return _events.back()->value;
+               } else if (x <= _events.front()->when) {
+                       return _events.front()->value;
+               }
+
+               return multipoint_eval (x);
+       }
+
+       abort(); /*NOTREACHED*/ /* stupid gcc */
+       return _desc.normal;
+}
+
+double
+ControlList::multipoint_eval (double x) const
+{
+       double upos, lpos;
+       double uval, lval;
+       double fraction;
+
+       /* "Stepped" lookup (no interpolation) */
+       /* FIXME: no cache.  significant? */
+       if (_interpolation == Discrete) {
+               const ControlEvent cp (x, 0);
+               EventList::const_iterator i = lower_bound (_events.begin(), _events.end(), &cp, time_comparator);
+
+               // shouldn't have made it to multipoint_eval
+               assert(i != _events.end());
+
+               if (i == _events.begin() || (*i)->when == x)
+                       return (*i)->value;
+               else
+                       return (*(--i))->value;
+       }
+
+       /* Only do the range lookup if x is in a different range than last time
+        * this was called (or if the lookup cache has been marked "dirty" (left<0) */
+       if ((_lookup_cache.left < 0) ||
+           ((_lookup_cache.left > x) ||
+            (_lookup_cache.range.first == _events.end()) ||
+            ((*_lookup_cache.range.second)->when < x))) {
+
+               const ControlEvent cp (x, 0);
+
+               _lookup_cache.range = equal_range (_events.begin(), _events.end(), &cp, time_comparator);
+       }
+
+       pair<const_iterator,const_iterator> range = _lookup_cache.range;
+
+       if (range.first == range.second) {
+
+               /* x does not exist within the list as a control point */
+
+               _lookup_cache.left = x;
+
+               if (range.first != _events.begin()) {
+                       --range.first;
+                       lpos = (*range.first)->when;
+                       lval = (*range.first)->value;
+               }  else {
+                       /* we're before the first point */
+                       // return _default_value;
+                       return _events.front()->value;
+               }
+
+               if (range.second == _events.end()) {
+                       /* we're after the last point */
+                       return _events.back()->value;
+               }
+
+               upos = (*range.second)->when;
+               uval = (*range.second)->value;
+
+               fraction = (double) (x - lpos) / (double) (upos - lpos);
+
+               switch (_interpolation) {
+                       case Logarithmic:
+                               return interpolate_logarithmic (lval, uval, fraction, _desc.lower, _desc.upper);
+                       case Exponential:
+                               return interpolate_gain (lval, uval, fraction, _desc.upper);
+                       case Discrete:
+                               /* should not reach here */
+                               assert (0);
+                       case Curved:
+                               /* only used x-fade curves, never direct eval */
+                               assert (0);
+                       default: // Linear
+                               return interpolate_linear (lval, uval, fraction);
+                               break;
+               }
+               assert (0);
+       }
+
+       /* x is a control point in the data */
+       _lookup_cache.left = -1;
+       return (*range.first)->value;
+}
+
+void
+ControlList::build_search_cache_if_necessary (double start) const
+{
+       if (_events.empty()) {
+               /* Empty, nothing to cache, move to end. */
+               _search_cache.first = _events.end();
+               _search_cache.left = 0;
+               return;
+       } else if ((_search_cache.left < 0) || (_search_cache.left > start)) {
+               /* Marked dirty (left < 0), or we're too far forward, re-search. */
+
+               const ControlEvent start_point (start, 0);
+
+               _search_cache.first = lower_bound (_events.begin(), _events.end(), &start_point, time_comparator);
+               _search_cache.left = start;
+       }
+
+       /* We now have a search cache that is not too far right, but it may be too
+          far left and need to be advanced. */
+
+       while (_search_cache.first != end() && (*_search_cache.first)->when < start) {
+               ++_search_cache.first;
+       }
+       _search_cache.left = start;
+}
+
+/** Get the earliest event after \a start using the current interpolation style.
+ *
+ * If an event is found, \a x and \a y are set to its coordinates.
+ *
+ * \param inclusive Include events with timestamp exactly equal to \a start
+ * \return true if event is found (and \a x and \a y are valid).
+ */
+bool
+ControlList::rt_safe_earliest_event (double start, double& x, double& y, bool inclusive) const
+{
+       // FIXME: It would be nice if this was unnecessary..
+       Glib::Threads::RWLock::ReaderLock lm(_lock, Glib::Threads::TRY_LOCK);
+       if (!lm.locked()) {
+               return false;
+       }
+
+       return rt_safe_earliest_event_unlocked (start, x, y, inclusive);
+}
+
+
+/** Get the earliest event after \a start using the current interpolation style.
+ *
+ * If an event is found, \a x and \a y are set to its coordinates.
+ *
+ * \param inclusive Include events with timestamp exactly equal to \a start
+ * \return true if event is found (and \a x and \a y are valid).
+ */
+bool
+ControlList::rt_safe_earliest_event_unlocked (double start, double& x, double& y, bool inclusive) const
+{
+       if (_interpolation == Discrete) {
+               return rt_safe_earliest_event_discrete_unlocked(start, x, y, inclusive);
+       } else {
+               return rt_safe_earliest_event_linear_unlocked(start, x, y, inclusive);
+       }
+}
+
+
+/** Get the earliest event after \a start without interpolation.
+ *
+ * If an event is found, \a x and \a y are set to its coordinates.
+ *
+ * \param inclusive Include events with timestamp exactly equal to \a start
+ * \return true if event is found (and \a x and \a y are valid).
+ */
+bool
+ControlList::rt_safe_earliest_event_discrete_unlocked (double start, double& x, double& y, bool inclusive) const
+{
+       build_search_cache_if_necessary (start);
+
+       if (_search_cache.first != _events.end()) {
+               const ControlEvent* const first = *_search_cache.first;
+
+               const bool past_start = (inclusive ? first->when >= start : first->when > start);
+
+               /* Earliest points is in range, return it */
+               if (past_start) {
+
+                       x = first->when;
+                       y = first->value;
+
+                       /* Move left of cache to this point
+                        * (Optimize for immediate call this cycle within range) */
+                       _search_cache.left = x;
+                       ++_search_cache.first;
+
+                       assert(x >= start);
+                       return true;
+
+               } else {
+                       return false;
+               }
+
+               /* No points in range */
+       } else {
+               return false;
+       }
+}
+
+/** Get the earliest time the line crosses an integer (Linear interpolation).
+ *
+ * If an event is found, \a x and \a y are set to its coordinates.
+ *
+ * \param inclusive Include events with timestamp exactly equal to \a start
+ * \return true if event is found (and \a x and \a y are valid).
+ */
+bool
+ControlList::rt_safe_earliest_event_linear_unlocked (double start, double& x, double& y, bool inclusive) const
+{
+       // cout << "earliest_event(start: " << start << ", x: " << x << ", y: " << y << ", inclusive: " << inclusive <<  ")" << endl;
+
+       const_iterator length_check_iter = _events.begin();
+       if (_events.empty()) { // 0 events
+               return false;
+       } else if (_events.end() == ++length_check_iter) { // 1 event
+               return rt_safe_earliest_event_discrete_unlocked (start, x, y, inclusive);
+       }
+
+       // Hack to avoid infinitely repeating the same event
+       build_search_cache_if_necessary (start);
+
+       if (_search_cache.first != _events.end()) {
+
+               const ControlEvent* first = NULL;
+               const ControlEvent* next = NULL;
+
+               if (_search_cache.first == _events.begin() || (*_search_cache.first)->when <= start) {
+                       /* Step is after first */
+                       first = *_search_cache.first;
+                       ++_search_cache.first;
+                       if (_search_cache.first == _events.end()) {
+                               return false;
+                       }
+                       next = *_search_cache.first;
+
+               } else {
+                       /* Step is before first */
+                       const_iterator prev = _search_cache.first;
+                       --prev;
+                       first = *prev;
+                       next = *_search_cache.first;
+               }
+
+               if (inclusive && first->when == start) {
+                       x = first->when;
+                       y = first->value;
+                       /* Move left of cache to this point
+                        * (Optimize for immediate call this cycle within range) */
+                       _search_cache.left = x;
+                       return true;
+               } else if (next->when < start || (!inclusive && next->when == start)) {
+                       /* "Next" is before the start, no points left. */
+                       return false;
+               }
+
+               if (fabs(first->value - next->value) <= 1) {
+                       if (next->when > start) {
+                               x = next->when;
+                               y = next->value;
+                               /* Move left of cache to this point
+                                * (Optimize for immediate call this cycle within range) */
+                               _search_cache.left = x;
+                               return true;
+                       } else {
+                               return false;
+                       }
+               }
+
+               const double slope = (next->value - first->value) / (double)(next->when - first->when);
+               //cerr << "start y: " << start_y << endl;
+
+               //y = first->value + (slope * fabs(start - first->when));
+               y = first->value;
+
+               if (first->value < next->value) // ramping up
+                       y = ceil(y);
+               else // ramping down
+                       y = floor(y);
+
+               x = first->when + (y - first->value) / (double)slope;
+
+               while ((inclusive && x < start) || (x <= start && y != next->value)) {
+
+                       if (first->value < next->value) // ramping up
+                               y += 1.0;
+                       else // ramping down
+                               y -= 1.0;
+
+                       x = first->when + (y - first->value) / (double)slope;
+               }
+
+               /*cerr << first->value << " @ " << first->when << " ... "
+                 << next->value << " @ " << next->when
+                 << " = " << y << " @ " << x << endl;*/
+
+               assert(    (y >= first->value && y <= next->value)
+                          || (y <= first->value && y >= next->value) );
+
+
+               const bool past_start = (inclusive ? x >= start : x > start);
+               if (past_start) {
+                       /* Move left of cache to this point
+                        * (Optimize for immediate call this cycle within range) */
+                       _search_cache.left = x;
+                       assert(inclusive ? x >= start : x > start);
+                       return true;
+               } else {
+                       if (inclusive) {
+                               x = next->when;
+                       } else {
+                               x = start;
+                       }
+                       _search_cache.left = x;
+                       return true;
+               }
+
+       } else {
+               /* No points in the future, so no steps (towards them) in the future */
+               return false;
+       }
+}
+
+
+/** @param start Start position in model coordinates.
+ *  @param end End position in model coordinates.
+ *  @param op 0 = cut, 1 = copy, 2 = clear.
+ */
+boost::shared_ptr<ControlList>
+ControlList::cut_copy_clear (double start, double end, int op)
+{
+       boost::shared_ptr<ControlList> nal = create (_parameter, _desc);
+       iterator s, e;
+       ControlEvent cp (start, 0.0);
+
+       {
+               Glib::Threads::RWLock::WriterLock lm (_lock);
+
+               /* first, determine s & e, two iterators that define the range of points
+                  affected by this operation
+               */
+
+               if ((s = lower_bound (_events.begin(), _events.end(), &cp, time_comparator)) == _events.end()) {
+                       return nal;
+               }
+
+               /* and the last that is at or after `end' */
+               cp.when = end;
+               e = upper_bound (_events.begin(), _events.end(), &cp, time_comparator);
+
+
+               /* if "start" isn't the location of an existing point,
+                  evaluate the curve to get a value for the start. Add a point to
+                  both the existing event list, and if its not a "clear" operation,
+                  to the copy ("nal") as well.
+
+                  Note that the time positions of the points in each list are different
+                  because we want the copy ("nal") to have a zero time reference.
+               */
+
+
+               /* before we begin any cut/clear operations, get the value of the curve
+                  at "end".
+               */
+
+               double end_value = unlocked_eval (end);
+
+               if ((*s)->when != start) {
+
+                       double val = unlocked_eval (start);
+
+                       if (op == 0) { // cut
+                               if (start > _events.front()->when) {
+                                       _events.insert (s, (new ControlEvent (start, val)));
+                               }
+                       }
+
+                       if (op != 2) { // ! clear
+                               nal->_events.push_back (new ControlEvent (0, val));
+                       }
+               }
+
+               for (iterator x = s; x != e; ) {
+
+                       /* adjust new points to be relative to start, which
+                          has been set to zero.
+                       */
+
+                       if (op != 2) {
+                               nal->_events.push_back (new ControlEvent ((*x)->when - start, (*x)->value));
+                       }
+
+                       if (op != 1) {
+                               x = _events.erase (x);
+                       } else {
+                               ++x;
+                       }
+               }
+
+               if (e == _events.end() || (*e)->when != end) {
+
+                       /* only add a boundary point if there is a point after "end"
+                        */
+
+                       if (op == 0 && (e != _events.end() && end < (*e)->when)) { // cut
+                               _events.insert (e, new ControlEvent (end, end_value));
+                       }
+
+                       if (op != 2 && (e != _events.end() && end < (*e)->when)) { // cut/copy
+                               nal->_events.push_back (new ControlEvent (end - start, end_value));
+                       }
+               }
+
+               unlocked_invalidate_insert_iterator ();
+               mark_dirty ();
+       }
+
+       if (op != 1) {
+               maybe_signal_changed ();
+       }
+
+       return nal;
+}
+
+
+boost::shared_ptr<ControlList>
+ControlList::cut (double start, double end)
+{
+       return cut_copy_clear (start, end, 0);
+}
+
+boost::shared_ptr<ControlList>
+ControlList::copy (double start, double end)
+{
+       return cut_copy_clear (start, end, 1);
+}
+
+void
+ControlList::clear (double start, double end)
+{
+       cut_copy_clear (start, end, 2);
+}
+
+/** @param pos Position in model coordinates */
+bool
+ControlList::paste (const ControlList& alist, double pos)
+{
+       if (alist._events.empty()) {
+               return false;
+       }
+
+       {
+               Glib::Threads::RWLock::WriterLock lm (_lock);
+               iterator where;
+               iterator prev;
+               double end = 0;
+               ControlEvent cp (pos, 0.0);
+
+               where = upper_bound (_events.begin(), _events.end(), &cp, time_comparator);
+
+               for (const_iterator i = alist.begin();i != alist.end(); ++i) {
+                       double value = (*i)->value;
+                       if (alist.parameter() != parameter()) {
+                               const ParameterDescriptor& src_desc = alist.descriptor();
+
+                               // This does not work for logscale and will probably also not do
+                               // the right thing for integer_step and sr_dependent parameters.
+                               //
+                               // TODO various flags from from ARDOUR::ParameterDescriptor
+                               // to Evoral::ParameterDescriptor
+
+                               value -= src_desc.lower;  // translate to 0-relative
+                               value /= (src_desc.upper - src_desc.lower);  // normalize range
+                               value *= (_desc.upper - _desc.lower);  // scale to our range
+                               value += _desc.lower;  // translate to our offset
+                               if (_desc.toggled) {
+                                       value = (value < 0.5) ? 0.0 : 1.0;
+                               }
+                               /* catch possible rounding errors */
+                               value = std::min ((double)_desc.upper, std::max ((double)_desc.lower, value));
+                       }
+                       _events.insert (where, new ControlEvent((*i)->when + pos, value));
+                       end = (*i)->when + pos;
+               }
+
+
+               /* move all  points after the insertion along the timeline by
+                  the correct amount.
+               */
+
+               while (where != _events.end()) {
+                       iterator tmp;
+                       if ((*where)->when <= end) {
+                               tmp = where;
+                               ++tmp;
+                               _events.erase(where);
+                               where = tmp;
+
+                       } else {
+                               break;
+                       }
+               }
+
+               unlocked_invalidate_insert_iterator ();
+               mark_dirty ();
+       }
+
+       maybe_signal_changed ();
+       return true;
+}
+
+/** Move automation around according to a list of region movements.
+ *  @param return true if anything was changed, otherwise false (ie nothing needed changing)
+ */
+bool
+ControlList::move_ranges (const list< RangeMove<double> >& movements)
+{
+       typedef list< RangeMove<double> > RangeMoveList;
+
+       {
+               Glib::Threads::RWLock::WriterLock lm (_lock);
+
+               /* a copy of the events list before we started moving stuff around */
+               EventList old_events = _events;
+
+               /* clear the source and destination ranges in the new list */
+               bool things_erased = false;
+               for (RangeMoveList::const_iterator i = movements.begin (); i != movements.end (); ++i) {
+
+                       if (erase_range_internal (i->from, i->from + i->length, _events)) {
+                               things_erased = true;
+                       }
+
+                       if (erase_range_internal (i->to, i->to + i->length, _events)) {
+                               things_erased = true;
+                       }
+               }
+
+               /* if nothing was erased, there is nothing to do */
+               if (!things_erased) {
+                       return false;
+               }
+
+               /* copy the events into the new list */
+               for (RangeMoveList::const_iterator i = movements.begin (); i != movements.end (); ++i) {
+                       iterator j = old_events.begin ();
+                       const double limit = i->from + i->length;
+                       const double dx    = i->to - i->from;
+                       while (j != old_events.end () && (*j)->when <= limit) {
+                               if ((*j)->when >= i->from) {
+                                       ControlEvent* ev = new ControlEvent (**j);
+                                       ev->when += dx;
+                                       _events.push_back (ev);
+                               }
+                               ++j;
+                       }
+               }
+
+               if (!_frozen) {
+                       _events.sort (event_time_less_than);
+                       unlocked_remove_duplicates ();
+                       unlocked_invalidate_insert_iterator ();
+               } else {
+                       _sort_pending = true;
+               }
+
+               mark_dirty ();
+       }
+
+       maybe_signal_changed ();
+       return true;
+}
+
+bool
+ControlList::set_interpolation (InterpolationStyle s)
+{
+       if (_interpolation == s) {
+               return true;
+       }
+
+       switch (s) {
+               case Logarithmic:
+                       if (_desc.lower * _desc.upper <= 0 || _desc.upper <= _desc.lower) {
+                               return false;
+                       }
+                       break;
+               case Exponential:
+                       if (_desc.lower != 0 || _desc.upper <= _desc.lower) {
+                               return false;
+                       }
+               default:
+                       break;
+       }
+
+       _interpolation = s;
+       InterpolationChanged (s); /* EMIT SIGNAL */
+       return true;
+}
+
+bool
+ControlList::operator!= (ControlList const & other) const
+{
+       if (_events.size() != other._events.size()) {
+               return true;
+       }
+
+       EventList::const_iterator i = _events.begin ();
+       EventList::const_iterator j = other._events.begin ();
+
+       while (i != _events.end() && (*i)->when == (*j)->when && (*i)->value == (*j)->value) {
+               ++i;
+               ++j;
+       }
+
+       if (i != _events.end ()) {
+               return true;
+       }
+
+       return (
+               _parameter != other._parameter ||
+               _interpolation != other._interpolation ||
+               _desc.lower != other._desc.lower ||
+               _desc.upper != other._desc.upper ||
+               _desc.normal != other._desc.normal
+               );
+}
+
+bool
+ControlList::is_sorted () const
+{
+       Glib::Threads::RWLock::ReaderLock lm (_lock);
+       if (_events.size () == 0) {
+               return true;
+       }
+       const_iterator i = _events.begin();
+       const_iterator n = i;
+       while (++n != _events.end ()) {
+               if (event_time_less_than(*n,*i)) {
+                       return false;
+               }
+               ++i;
+       }
+       return true;
+}
+
+void
+ControlList::dump (ostream& o)
+{
+       /* NOT LOCKED ... for debugging only */
+
+       for (EventList::iterator x = _events.begin(); x != _events.end(); ++x) {
+               o << (*x)->value << " @ " << (uint64_t) (*x)->when << endl;
+       }
+}
+
+} // namespace Evoral
+
diff --git a/libs/evoral/src/ControlList.cpp b/libs/evoral/src/ControlList.cpp
deleted file mode 100644 (file)
index e622a03..0000000
+++ /dev/null
@@ -1,2076 +0,0 @@
-/*
- * Copyright (C) 2008-2009 Hans Baier <hansfbaier@googlemail.com>
- * Copyright (C) 2008-2012 Carl Hetherington <carl@carlh.net>
- * Copyright (C) 2008-2015 David Robillard <d@drobilla.net>
- * Copyright (C) 2010-2017 Paul Davis <paul@linuxaudiosystems.com>
- * Copyright (C) 2014-2018 Robin Gareus <robin@gareus.org>
- * Copyright (C) 2014 Ben Loftis <ben@harrisonconsoles.com>
- * Copyright (C) 2015 Nick Mainsbridge <mainsbridge@gmail.com>
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-
-#include <cmath>
-
-#ifdef COMPILER_MSVC
-#include <float.h>
-
-// 'std::isnan()' is not available in MSVC.
-#define isnan_local(val) (bool)_isnan((double)val)
-#else
-#define isnan_local std::isnan
-#endif
-
-#define GUARD_POINT_DELTA 64
-
-#include <cassert>
-#include <cmath>
-#include <iostream>
-#include <utility>
-
-#include "evoral/ControlList.hpp"
-#include "evoral/Curve.hpp"
-#include "evoral/ParameterDescriptor.hpp"
-#include "evoral/TypeMap.hpp"
-#include "evoral/types.hpp"
-
-#include "pbd/control_math.h"
-#include "pbd/compose.h"
-#include "pbd/debug.h"
-
-using namespace std;
-using namespace PBD;
-
-namespace Evoral {
-
-inline bool event_time_less_than (ControlEvent* a, ControlEvent* b)
-{
-       return a->when < b->when;
-}
-
-ControlList::ControlList (const Parameter& id, const ParameterDescriptor& desc)
-       : _parameter(id)
-       , _desc(desc)
-       , _interpolation (default_interpolation ())
-       , _curve(0)
-{
-       _frozen = 0;
-       _changed_when_thawed = false;
-       _lookup_cache.left = -1;
-       _lookup_cache.range.first = _events.end();
-       _lookup_cache.range.second = _events.end();
-       _search_cache.left = -1;
-       _search_cache.first = _events.end();
-       _sort_pending = false;
-       new_write_pass = true;
-       _in_write_pass = false;
-       did_write_during_pass = false;
-       insert_position = -1;
-       most_recent_insert_iterator = _events.end();
-}
-
-ControlList::ControlList (const ControlList& other)
-       : _parameter(other._parameter)
-       , _desc(other._desc)
-       , _interpolation(other._interpolation)
-       , _curve(0)
-{
-       _frozen = 0;
-       _changed_when_thawed = false;
-       _lookup_cache.range.first = _events.end();
-       _lookup_cache.range.second = _events.end();
-       _search_cache.first = _events.end();
-       _sort_pending = false;
-       new_write_pass = true;
-       _in_write_pass = false;
-       did_write_during_pass = false;
-       insert_position = -1;
-       most_recent_insert_iterator = _events.end();
-
-       // XXX copy_events() emits Dirty, but this is just assignment copy/construction
-       copy_events (other);
-}
-
-ControlList::ControlList (const ControlList& other, double start, double end)
-       : _parameter(other._parameter)
-       , _desc(other._desc)
-       , _interpolation(other._interpolation)
-       , _curve(0)
-{
-       _frozen = 0;
-       _changed_when_thawed = false;
-       _lookup_cache.range.first = _events.end();
-       _lookup_cache.range.second = _events.end();
-       _search_cache.first = _events.end();
-       _sort_pending = false;
-
-       /* now grab the relevant points, and shift them back if necessary */
-
-       boost::shared_ptr<ControlList> section = const_cast<ControlList*>(&other)->copy (start, end);
-
-       if (!section->empty()) {
-               // XXX copy_events() emits Dirty, but this is just assignment copy/construction
-               copy_events (*(section.get()));
-       }
-
-       new_write_pass = true;
-       _in_write_pass = false;
-       did_write_during_pass = false;
-       insert_position = -1;
-       most_recent_insert_iterator = _events.end();
-
-       mark_dirty ();
-}
-
-ControlList::~ControlList()
-{
-       for (EventList::iterator x = _events.begin(); x != _events.end(); ++x) {
-               delete (*x);
-       }
-       _events.clear ();
-
-       delete _curve;
-}
-
-boost::shared_ptr<ControlList>
-ControlList::create(const Parameter& id, const ParameterDescriptor& desc)
-{
-       return boost::shared_ptr<ControlList>(new ControlList(id, desc));
-}
-
-bool
-ControlList::operator== (const ControlList& other)
-{
-       return _events == other._events;
-}
-
-ControlList&
-ControlList::operator= (const ControlList& other)
-{
-       if (this != &other) {
-               /* list should be frozen before assignment */
-               assert (_frozen > 0);
-               _changed_when_thawed = false;
-               _sort_pending = false;
-
-               insert_position = other.insert_position;
-               new_write_pass = true;
-               _in_write_pass = false;
-               did_write_during_pass = false;
-               insert_position = -1;
-
-               _parameter = other._parameter;
-               _desc = other._desc;
-               _interpolation = other._interpolation;
-
-               copy_events (other);
-       }
-
-       return *this;
-}
-
-void
-ControlList::copy_events (const ControlList& other)
-{
-       {
-               Glib::Threads::RWLock::WriterLock lm (_lock);
-               for (EventList::iterator x = _events.begin(); x != _events.end(); ++x) {
-                       delete (*x);
-               }
-               _events.clear ();
-               Glib::Threads::RWLock::ReaderLock olm (other._lock);
-               for (const_iterator i = other.begin(); i != other.end(); ++i) {
-                       _events.push_back (new ControlEvent ((*i)->when, (*i)->value));
-               }
-               unlocked_invalidate_insert_iterator ();
-               mark_dirty ();
-       }
-       maybe_signal_changed ();
-}
-
-void
-ControlList::create_curve()
-{
-       _curve = new Curve(*this);
-}
-
-void
-ControlList::destroy_curve()
-{
-       delete _curve;
-       _curve = NULL;
-}
-
-ControlList::InterpolationStyle
-ControlList::default_interpolation () const
-{
-       if (_desc.toggled) {
-               return Discrete;
-       } else if (_desc.logarithmic) {
-               return Logarithmic;
-       }
-       return Linear;
-}
-
-void
-ControlList::maybe_signal_changed ()
-{
-       mark_dirty ();
-
-       if (_frozen) {
-               _changed_when_thawed = true;
-       }
-}
-
-void
-ControlList::clear ()
-{
-       {
-               Glib::Threads::RWLock::WriterLock lm (_lock);
-               for (EventList::iterator x = _events.begin(); x != _events.end(); ++x) {
-                       delete (*x);
-               }
-               _events.clear ();
-               unlocked_invalidate_insert_iterator ();
-               mark_dirty ();
-       }
-
-       maybe_signal_changed ();
-}
-
-void
-ControlList::x_scale (double factor)
-{
-       Glib::Threads::RWLock::WriterLock lm (_lock);
-       _x_scale (factor);
-}
-
-bool
-ControlList::extend_to (double when)
-{
-       Glib::Threads::RWLock::WriterLock lm (_lock);
-       if (_events.empty() || _events.back()->when == when) {
-               return false;
-       }
-       double factor = when / _events.back()->when;
-       _x_scale (factor);
-       return true;
-}
-
-void
-ControlList::y_transform (boost::function<double(double)> callback)
-{
-       {
-               Glib::Threads::RWLock::WriterLock lm (_lock);
-               for (iterator i = _events.begin(); i != _events.end(); ++i) {
-                       (*i)->value = callback ((*i)->value);
-               }
-               mark_dirty ();
-       }
-       maybe_signal_changed ();
-}
-
-void
-ControlList::list_merge (ControlList const& other, boost::function<double(double, double)> callback)
-{
-       {
-               Glib::Threads::RWLock::WriterLock lm (_lock);
-               EventList nel;
-               /* First scale existing events, copy into a new list.
-                * The original list is needed later to interpolate
-                * for new events only present in the master list.
-                */
-               for (iterator i = _events.begin(); i != _events.end(); ++i) {
-                       float val = callback ((*i)->value, other.eval ((*i)->when));
-                       nel.push_back (new ControlEvent ((*i)->when , val));
-               }
-               /* Now add events which are only present in the master-list. */
-               const EventList& evl (other.events());
-               for (const_iterator i = evl.begin(); i != evl.end(); ++i) {
-                       bool found = false;
-                       // TODO: optimize, remember last matching iterator (lists are sorted)
-                       for (iterator j = _events.begin(); j != _events.end(); ++j) {
-                               if ((*i)->when == (*j)->when) {
-                                       found = true;
-                                       break;
-                               }
-                       }
-                       /* skip events that have already been merge in the first pass */
-                       if (found) {
-                               continue;
-                       }
-                       float val = callback (unlocked_eval ((*i)->when), (*i)->value);
-                       nel.push_back (new ControlEvent ((*i)->when, val));
-               }
-               nel.sort (event_time_less_than);
-
-               for (EventList::iterator x = _events.begin(); x != _events.end(); ++x) {
-                       delete (*x);
-               }
-               _events.clear ();
-               _events = nel;
-
-               unlocked_remove_duplicates ();
-               unlocked_invalidate_insert_iterator ();
-               mark_dirty ();
-       }
-       maybe_signal_changed ();
-}
-
-void
-ControlList::_x_scale (double factor)
-{
-       for (iterator i = _events.begin(); i != _events.end(); ++i) {
-               (*i)->when *= factor;
-       }
-
-       mark_dirty ();
-}
-
-struct ControlEventTimeComparator {
-       bool operator() (ControlEvent* a, ControlEvent* b) {
-               return a->when < b->when;
-       }
-};
-
-void
-ControlList::thin (double thinning_factor)
-{
-       if (thinning_factor == 0.0 || _desc.toggled) {
-               return;
-       }
-
-       assert (is_sorted ());
-
-       bool changed = false;
-
-       {
-               Glib::Threads::RWLock::WriterLock lm (_lock);
-
-               ControlEvent* prevprev = 0;
-               ControlEvent* cur = 0;
-               ControlEvent* prev = 0;
-               iterator pprev;
-               int counter = 0;
-
-               DEBUG_TRACE (DEBUG::ControlList, string_compose ("@%1 thin from %2 events\n", this, _events.size()));
-
-               for (iterator i = _events.begin(); i != _events.end(); ++i) {
-
-                       cur = *i;
-                       counter++;
-
-                       if (counter > 2) {
-
-                               /* compute the area of the triangle formed by 3 points
-                                */
-
-                               double area = fabs ((prevprev->when * (prev->value - cur->value)) +
-                                                   (prev->when * (cur->value - prevprev->value)) +
-                                                   (cur->when * (prevprev->value - prev->value)));
-
-                               if (area < thinning_factor) {
-                                       iterator tmp = pprev;
-
-                                       /* pprev will change to current
-                                          i is incremented to the next event
-                                          as we loop.
-                                       */
-
-                                       pprev = i;
-                                       _events.erase (tmp);
-                                       changed = true;
-                                       continue;
-                               }
-                       }
-
-                       prevprev = prev;
-                       prev = cur;
-                       pprev = i;
-               }
-
-               DEBUG_TRACE (DEBUG::ControlList, string_compose ("@%1 thin => %2 events\n", this, _events.size()));
-
-               if (changed) {
-                       unlocked_invalidate_insert_iterator ();
-                       mark_dirty ();
-               }
-       }
-
-       if (changed) {
-               maybe_signal_changed ();
-       }
-}
-
-void
-ControlList::fast_simple_add (double when, double value)
-{
-       Glib::Threads::RWLock::WriterLock lm (_lock);
-       /* to be used only for loading pre-sorted data from saved state */
-       _events.insert (_events.end(), new ControlEvent (when, value));
-
-       mark_dirty ();
-       if (_frozen) {
-               _sort_pending = true;
-       }
-}
-
-void
-ControlList::invalidate_insert_iterator ()
-{
-       Glib::Threads::RWLock::WriterLock lm (_lock);
-       unlocked_invalidate_insert_iterator ();
-}
-
-void
-ControlList::unlocked_invalidate_insert_iterator ()
-{
-       most_recent_insert_iterator = _events.end();
-}
-
-void
-ControlList::unlocked_remove_duplicates ()
-{
-       if (_events.size() < 2) {
-               return;
-       }
-       iterator i = _events.begin();
-       iterator prev = i++;
-       while (i != _events.end()) {
-               if ((*prev)->when == (*i)->when && (*prev)->value == (*i)->value) {
-                       i = _events.erase (i);
-               } else {
-                       ++prev;
-                       ++i;
-               }
-       }
-}
-
-void
-ControlList::start_write_pass (double when)
-{
-       Glib::Threads::RWLock::WriterLock lm (_lock);
-
-       DEBUG_TRACE (DEBUG::ControlList, string_compose ("%1: setup write pass @ %2\n", this, when));
-
-       insert_position = when;
-
-       /* leave the insert iterator invalid, so that we will do the lookup
-          of where it should be in a "lazy" way - deferring it until
-          we actually add the first point (which may never happen).
-       */
-
-       unlocked_invalidate_insert_iterator ();
-
-       /* except if we're already in an active write-pass.
-        *
-        * invalid iterator == end() the iterator is set to the correct
-        * position in ControlList::add IFF (_in_write_pass && new_write_pass)
-        */
-       if (_in_write_pass && !new_write_pass) {
-#if 1
-               add_guard_point (when, 0); // also sets most_recent_insert_iterator
-#else
-               const ControlEvent cp (when, 0.0);
-               most_recent_insert_iterator = lower_bound (_events.begin(), _events.end(), &cp, time_comparator);
-#endif
-       }
-}
-
-void
-ControlList::write_pass_finished (double /*when*/, double thinning_factor)
-{
-       DEBUG_TRACE (DEBUG::ControlList, "write pass finished\n");
-
-       if (did_write_during_pass) {
-               thin (thinning_factor);
-               did_write_during_pass = false;
-       }
-       new_write_pass = true;
-       _in_write_pass = false;
-}
-
-void
-ControlList::set_in_write_pass (bool yn, bool add_point, double when)
-{
-       DEBUG_TRACE (DEBUG::ControlList, string_compose ("now in write pass @ %1, add point ? %2\n", when, add_point));
-
-       _in_write_pass = yn;
-
-       if (yn && add_point) {
-               Glib::Threads::RWLock::WriterLock lm (_lock);
-               add_guard_point (when, 0);
-       }
-}
-
-void
-ControlList::add_guard_point (double when, double offset)
-{
-       // caller needs to hold writer-lock
-       if (offset < 0 && when < offset) {
-               return;
-       }
-       assert (offset <= 0);
-
-       if (offset != 0) {
-               /* check if there are points between when + offset .. when */
-               ControlEvent cp (when + offset, 0.0);
-               iterator s;
-               iterator e;
-               if ((s = lower_bound (_events.begin(), _events.end(), &cp, time_comparator)) != _events.end()) {
-                       cp.when = when;
-                       e = lower_bound (_events.begin(), _events.end(), &cp, time_comparator);
-                       if (s != e) {
-                               DEBUG_TRACE (DEBUG::ControlList, string_compose ("@%1 add_guard_point, none added, found event between %2 and %3\n", this, when - offset, when));
-                               return;
-                       }
-               }
-       }
-
-       /* don't do this again till the next write pass,
-        * unless we're not in a write-pass (transport stopped)
-        */
-       if (_in_write_pass && new_write_pass) {
-               WritePassStarted (); /* EMIT SIGNAL w/WriteLock */
-               new_write_pass = false;
-       }
-
-       when += offset;
-
-       ControlEvent cp (when, 0.0);
-       most_recent_insert_iterator = lower_bound (_events.begin(), _events.end(), &cp, time_comparator);
-
-       double eval_value = unlocked_eval (when);
-
-       if (most_recent_insert_iterator == _events.end()) {
-
-               DEBUG_TRACE (DEBUG::ControlList, string_compose ("@%1 insert iterator at end, adding eval-value there %2\n", this, eval_value));
-               _events.push_back (new ControlEvent (when, eval_value));
-               /* leave insert iterator at the end */
-
-       } else if ((*most_recent_insert_iterator)->when == when) {
-
-               DEBUG_TRACE (DEBUG::ControlList, string_compose ("@%1 insert iterator at existing point, setting eval-value there %2\n", this, eval_value));
-
-               /* most_recent_insert_iterator points to a control event
-                  already at the insert position, so there is
-                  nothing to do.
-
-                  ... except ...
-
-                  advance most_recent_insert_iterator so that the "real"
-                  insert occurs in the right place, since it
-                  points to the control event just inserted.
-               */
-
-               ++most_recent_insert_iterator;
-       } else {
-
-               /* insert a new control event at the right spot */
-
-               DEBUG_TRACE (DEBUG::ControlList, string_compose ("@%1 insert eval-value %2 just before iterator @ %3\n",
-                                                                this, eval_value, (*most_recent_insert_iterator)->when));
-
-               most_recent_insert_iterator = _events.insert (most_recent_insert_iterator, new ControlEvent (when, eval_value));
-
-               /* advance most_recent_insert_iterator so that the "real"
-                * insert occurs in the right place, since it
-                * points to the control event just inserted.
-                */
-
-               ++most_recent_insert_iterator;
-       }
-}
-
-bool
-ControlList::in_write_pass () const
-{
-       return _in_write_pass;
-}
-
-bool
-ControlList::editor_add (double when, double value, bool with_guard)
-{
-       /* this is for making changes from a graphical line editor */
-       {
-               Glib::Threads::RWLock::WriterLock lm (_lock);
-
-               ControlEvent cp (when, 0.0f);
-               iterator i = lower_bound (_events.begin(), _events.end(), &cp, time_comparator);
-
-               if (i != _events.end () && (*i)->when == when) {
-                       return false;
-               }
-
-               /* clamp new value to allowed range */
-               value = std::min ((double)_desc.upper, std::max ((double)_desc.lower, value));
-
-               if (_events.empty()) {
-
-                       /* as long as the point we're adding is not at zero,
-                        * add an "anchor" point there.
-                        */
-
-                       if (when >= 1) {
-                               _events.insert (_events.end(), new ControlEvent (0, value));
-                               DEBUG_TRACE (DEBUG::ControlList, string_compose ("@%1 added value %2 at zero\n", this, value));
-                       }
-               }
-
-               insert_position = when;
-               if (with_guard) {
-                       add_guard_point (when, -GUARD_POINT_DELTA);
-                       maybe_add_insert_guard (when);
-                       i = lower_bound (_events.begin(), _events.end(), &cp, time_comparator);
-               }
-
-               iterator result;
-               DEBUG_TRACE (DEBUG::ControlList, string_compose ("editor_add: actually add when= %1 value= %2\n", when, value));
-               result = _events.insert (i, new ControlEvent (when, value));
-
-               if (i == result) {
-                       return false;
-               }
-
-               mark_dirty ();
-       }
-       maybe_signal_changed ();
-
-       return true;
-}
-
-void
-ControlList::maybe_add_insert_guard (double when)
-{
-       // caller needs to hold writer-lock
-       if (most_recent_insert_iterator != _events.end()) {
-               if ((*most_recent_insert_iterator)->when - when > GUARD_POINT_DELTA) {
-                       /* Next control point is some distance from where our new point is
-                          going to go, so add a new point to avoid changing the shape of
-                          the line too much.  The insert iterator needs to point to the
-                          new control point so that our insert will happen correctly. */
-                       most_recent_insert_iterator = _events.insert ( most_recent_insert_iterator,
-                                       new ControlEvent (when + GUARD_POINT_DELTA, (*most_recent_insert_iterator)->value));
-
-                       DEBUG_TRACE (DEBUG::ControlList, string_compose ("@%1 added insert guard point @ %2 = %3\n",
-                                                                        this, when + GUARD_POINT_DELTA,
-                                                                        (*most_recent_insert_iterator)->value));
-               }
-       }
-}
-
-/** If we would just be adding to a straight line, move the previous point instead. */
-bool
-ControlList::maybe_insert_straight_line (double when, double value)
-{
-       // caller needs to hold writer-lock
-       if (_events.empty()) {
-               return false;
-       }
-
-       if (_events.back()->value == value) {
-               // Point b at the final point, which we know exists
-               EventList::iterator b = _events.end();
-               --b;
-               if (b == _events.begin()) {
-                       return false;  // No previous point
-               }
-
-               // Check the previous point's value
-               --b;
-               if ((*b)->value == value) {
-                       /* At least two points with the exact same value (straight
-                          line), just move the final point to the new time. */
-                       _events.back()->when = when;
-                       DEBUG_TRACE (DEBUG::ControlList, string_compose ("final value of %1 moved to %2\n", value, when));
-                       return true;
-               }
-       }
-       return false;
-}
-
-ControlList::iterator
-ControlList::erase_from_iterator_to (iterator iter, double when)
-{
-       // caller needs to hold writer-lock
-       while (iter != _events.end()) {
-               if ((*iter)->when < when) {
-                       DEBUG_TRACE (DEBUG::ControlList, string_compose ("@%1 erase existing @ %2\n", this, (*iter)->when));
-                       delete *iter;
-                       iter = _events.erase (iter);
-                       continue;
-               } else if ((*iter)->when >= when) {
-                       break;
-               }
-               ++iter;
-       }
-       return iter;
-}
-
-/* this is for making changes from some kind of user interface or
- * control surface (GUI, MIDI, OSC etc)
- */
-void
-ControlList::add (double when, double value, bool with_guards, bool with_initial)
-{
-       /* clamp new value to allowed range */
-       value = std::min ((double)_desc.upper, std::max ((double)_desc.lower, value));
-
-       DEBUG_TRACE (DEBUG::ControlList,
-                    string_compose ("@%1 add %2 at %3 guards = %4 write pass = %5 (new? %6) at end? %7\n",
-                                    this, value, when, with_guards, _in_write_pass, new_write_pass,
-                                    (most_recent_insert_iterator == _events.end())));
-       {
-               Glib::Threads::RWLock::WriterLock lm (_lock);
-               ControlEvent cp (when, 0.0f);
-               iterator insertion_point;
-
-               if (_events.empty() && with_initial) {
-
-                       /* empty: add an "anchor" point if the point we're adding past time 0 */
-
-                       if (when >= 1) {
-                               if (_desc.toggled) {
-                                       const double opp_val = ((value < 0.5) ? 1.0 : 0.0);
-                                       _events.insert (_events.end(), new ControlEvent (0, opp_val));
-                                       DEBUG_TRACE (DEBUG::ControlList, string_compose ("@%1 added toggled value %2 at zero\n", this, opp_val));
-
-                               } else {
-                                       _events.insert (_events.end(), new ControlEvent (0, value));
-                                       DEBUG_TRACE (DEBUG::ControlList, string_compose ("@%1 added default value %2 at zero\n", this, _desc.normal));
-                               }
-                       }
-               }
-
-               if (_in_write_pass && new_write_pass) {
-
-                       /* first write in a write pass: add guard point if requested */
-
-                       if (with_guards) {
-                               add_guard_point (insert_position, 0);
-                               did_write_during_pass = true;
-                       } else {
-                               /* not adding a guard, but we need to set iterator appropriately */
-                               const ControlEvent cp (when, 0.0);
-                               most_recent_insert_iterator = lower_bound (_events.begin(), _events.end(), &cp, time_comparator);
-                       }
-                       WritePassStarted (); /* EMIT SIGNAL w/WriteLock */
-                       new_write_pass = false;
-
-               } else if (_in_write_pass &&
-                          (most_recent_insert_iterator == _events.end() || when > (*most_recent_insert_iterator)->when)) {
-
-                       /* in write pass: erase from most recent insert to now */
-
-                       if (most_recent_insert_iterator != _events.end()) {
-                               /* advance to avoid deleting the last inserted point itself. */
-                               ++most_recent_insert_iterator;
-                       }
-
-                       if (with_guards) {
-                               most_recent_insert_iterator = erase_from_iterator_to (most_recent_insert_iterator, when + GUARD_POINT_DELTA);
-                               maybe_add_insert_guard (when);
-                       } else {
-                               most_recent_insert_iterator = erase_from_iterator_to(most_recent_insert_iterator, when);
-                       }
-
-               } else if (!_in_write_pass) {
-
-                       /* not in a write pass: figure out the iterator we should insert in front of */
-
-                       DEBUG_TRACE (DEBUG::ControlList, string_compose ("compute(b) MRI for position %1\n", when));
-                       ControlEvent cp (when, 0.0f);
-                       most_recent_insert_iterator = lower_bound (_events.begin(), _events.end(), &cp, time_comparator);
-               }
-
-               /* OK, now we're really ready to add a new point */
-
-               if (most_recent_insert_iterator == _events.end()) {
-                       DEBUG_TRACE (DEBUG::ControlList, string_compose ("@%1 appending new point at end\n", this));
-
-                       const bool done = maybe_insert_straight_line (when, value);
-                       if (!done) {
-                               _events.push_back (new ControlEvent (when, value));
-                               DEBUG_TRACE (DEBUG::ControlList, string_compose ("\tactually appended, size now %1\n", _events.size()));
-                       }
-
-                       most_recent_insert_iterator = _events.end();
-                       --most_recent_insert_iterator;
-
-               } else if ((*most_recent_insert_iterator)->when == when) {
-
-                       if ((*most_recent_insert_iterator)->value != value) {
-                               DEBUG_TRACE (DEBUG::ControlList, string_compose ("@%1 reset existing point to new value %2\n", this, value));
-
-                               /* only one point allowed per time point, so add a guard point
-                                * before it if needed then reset the value of the point.
-                                */
-
-                               (*most_recent_insert_iterator)->value = value;
-
-                               /* if we modified the final value, then its as
-                                * if we inserted a new point as far as the
-                                * next addition, so make sure we know that.
-                                */
-
-                               if (_events.back()->when == when) {
-                                       most_recent_insert_iterator = _events.end();
-                               }
-
-                       } else {
-                               DEBUG_TRACE (DEBUG::ControlList, string_compose ("@%1 same time %2, same value value %3\n", this, when, value));
-                       }
-
-               } else {
-                       DEBUG_TRACE (DEBUG::ControlList, string_compose ("@%1 insert new point at %2 at iterator at %3\n", this, when, (*most_recent_insert_iterator)->when));
-                       bool done = false;
-                       /* check for possible straight line here until maybe_insert_straight_line () handles the insert iterator properly*/
-                       if (most_recent_insert_iterator != _events.begin ()) {
-                               bool have_point2 = false;
-                               --most_recent_insert_iterator;
-                               const bool have_point1 = (*most_recent_insert_iterator)->value == value;
-
-                               if (most_recent_insert_iterator != _events.begin ()) {
-                                       --most_recent_insert_iterator;
-                                       have_point2 = (*most_recent_insert_iterator)->value == value;
-                                       ++most_recent_insert_iterator;
-                               }
-
-                               if (have_point1 && have_point2) {
-                                       DEBUG_TRACE (DEBUG::ControlList, string_compose ("@%1 no change: move existing at %3 to %2\n", this, when, (*most_recent_insert_iterator)->when));
-                                       (*most_recent_insert_iterator)->when = when;
-                                       done = true;
-                               } else {
-                                       ++most_recent_insert_iterator;
-                               }
-                       }
-
-                       /* if the transport is stopped, add guard points */
-                       if (!done && !_in_write_pass) {
-                               add_guard_point (when, -GUARD_POINT_DELTA);
-                               maybe_add_insert_guard (when);
-                       } else if (with_guards) {
-                               maybe_add_insert_guard (when);
-                       }
-
-                       if (!done) {
-                               EventList::iterator x = _events.insert (most_recent_insert_iterator, new ControlEvent (when, value));
-                               DEBUG_TRACE (DEBUG::ControlList, string_compose ("@%1 inserted new value before MRI, size now %2\n", this, _events.size()));
-                               most_recent_insert_iterator = x;
-                       }
-               }
-
-               mark_dirty ();
-       }
-
-       maybe_signal_changed ();
-}
-
-void
-ControlList::erase (iterator i)
-{
-       {
-               Glib::Threads::RWLock::WriterLock lm (_lock);
-               if (most_recent_insert_iterator == i) {
-                       unlocked_invalidate_insert_iterator ();
-               }
-               _events.erase (i);
-               mark_dirty ();
-       }
-       maybe_signal_changed ();
-}
-
-void
-ControlList::erase (iterator start, iterator end)
-{
-       {
-               Glib::Threads::RWLock::WriterLock lm (_lock);
-               _events.erase (start, end);
-               unlocked_invalidate_insert_iterator ();
-               mark_dirty ();
-       }
-       maybe_signal_changed ();
-}
-
-/** Erase the first event which matches the given time and value */
-void
-ControlList::erase (double when, double value)
-{
-       {
-               Glib::Threads::RWLock::WriterLock lm (_lock);
-
-               iterator i = begin ();
-               while (i != end() && ((*i)->when != when || (*i)->value != value)) {
-                       ++i;
-               }
-
-               if (i != end ()) {
-                       _events.erase (i);
-                       if (most_recent_insert_iterator == i) {
-                               unlocked_invalidate_insert_iterator ();
-                       }
-               }
-
-               mark_dirty ();
-       }
-
-       maybe_signal_changed ();
-}
-
-void
-ControlList::erase_range (double start, double endt)
-{
-       bool erased = false;
-
-       {
-               Glib::Threads::RWLock::WriterLock lm (_lock);
-               erased = erase_range_internal (start, endt, _events);
-
-               if (erased) {
-                       mark_dirty ();
-               }
-
-       }
-
-       if (erased) {
-               maybe_signal_changed ();
-       }
-}
-
-bool
-ControlList::erase_range_internal (double start, double endt, EventList & events)
-{
-       bool erased = false;
-       ControlEvent cp (start, 0.0f);
-       iterator s;
-       iterator e;
-
-       if ((s = lower_bound (events.begin(), events.end(), &cp, time_comparator)) != events.end()) {
-               cp.when = endt;
-               e = upper_bound (events.begin(), events.end(), &cp, time_comparator);
-               events.erase (s, e);
-               if (s != e) {
-                       unlocked_invalidate_insert_iterator ();
-                       erased = true;
-               }
-       }
-
-       return erased;
-}
-
-void
-ControlList::slide (iterator before, double distance)
-{
-       {
-               Glib::Threads::RWLock::WriterLock lm (_lock);
-
-               if (before == _events.end()) {
-                       return;
-               }
-
-               while (before != _events.end()) {
-                       (*before)->when += distance;
-                       ++before;
-               }
-
-               mark_dirty ();
-       }
-
-       maybe_signal_changed ();
-}
-
-void
-ControlList::shift (double pos, double frames)
-{
-       {
-               Glib::Threads::RWLock::WriterLock lm (_lock);
-               double v0, v1;
-               if (frames < 0) {
-                       /* Route::shift () with negative shift is used
-                        * for "remove time". The time [pos.. pos-frames] is removed.
-                        * and everyhing after, moved backwards.
-                        */
-                       v0 = unlocked_eval (pos);
-                       v1 = unlocked_eval (pos - frames);
-                       erase_range_internal (pos, pos - frames, _events);
-               } else {
-                       v0 = v1 = unlocked_eval (pos);
-               }
-
-               bool dst_guard_exists = false;
-
-               for (iterator i = _events.begin(); i != _events.end(); ++i) {
-                       if ((*i)->when == pos) {
-                               dst_guard_exists = true;
-                       }
-                       if ((*i)->when >= pos) {
-                               (*i)->when += frames;
-                       }
-               }
-
-               /* add guard-points to retain shape, if needed */
-               if (frames > 0) {
-                       ControlEvent cp (pos, 0.0);
-                       iterator s = lower_bound (_events.begin(), _events.end(), &cp, time_comparator);
-                       if (s != _events.end ()) {
-                               _events.insert (s, new ControlEvent (pos, v0));
-                       }
-                       pos += frames;
-               } else if (frames < 0 && pos > 0) {
-                       ControlEvent cp (pos - 1, 0.0);
-                       iterator s = lower_bound (_events.begin(), _events.end(), &cp, time_comparator);
-                       if (s != _events.end ()) {
-                               _events.insert (s, new ControlEvent (pos - 1, v0));
-                       }
-               }
-               if (!dst_guard_exists) {
-                       ControlEvent cp (pos, 0.0);
-                       iterator s = lower_bound (_events.begin(), _events.end(), &cp, time_comparator);
-                       _events.insert (s, new ControlEvent (pos, s == _events.end () ? v0 : v1));
-               }
-
-
-               mark_dirty ();
-       }
-
-       maybe_signal_changed ();
-}
-
-void
-ControlList::modify (iterator iter, double when, double val)
-{
-       /* note: we assume higher level logic is in place to avoid this
-        * reordering the time-order of control events in the list. ie. all
-        * points after *iter are later than when.
-        */
-
-       /* catch possible float/double rounding errors from higher levels */
-       val = std::min ((double)_desc.upper, std::max ((double)_desc.lower, val));
-
-       {
-               Glib::Threads::RWLock::WriterLock lm (_lock);
-
-               (*iter)->when = when;
-               (*iter)->value = val;
-               if (isnan_local (val)) {
-                       abort ();
-               }
-
-               if (!_frozen) {
-                       _events.sort (event_time_less_than);
-                       unlocked_remove_duplicates ();
-                       unlocked_invalidate_insert_iterator ();
-               } else {
-                       _sort_pending = true;
-               }
-
-               mark_dirty ();
-       }
-
-       maybe_signal_changed ();
-}
-
-std::pair<ControlList::iterator,ControlList::iterator>
-ControlList::control_points_adjacent (double xval)
-{
-       Glib::Threads::RWLock::ReaderLock lm (_lock);
-       iterator i;
-       ControlEvent cp (xval, 0.0f);
-       std::pair<iterator,iterator> ret;
-
-       ret.first = _events.end();
-       ret.second = _events.end();
-
-       for (i = lower_bound (_events.begin(), _events.end(), &cp, time_comparator); i != _events.end(); ++i) {
-
-               if (ret.first == _events.end()) {
-                       if ((*i)->when >= xval) {
-                               if (i != _events.begin()) {
-                                       ret.first = i;
-                                       --ret.first;
-                               } else {
-                                       return ret;
-                               }
-                       }
-               }
-
-               if ((*i)->when > xval) {
-                       ret.second = i;
-                       break;
-               }
-       }
-
-       return ret;
-}
-
-void
-ControlList::freeze ()
-{
-       _frozen++;
-}
-
-void
-ControlList::thaw ()
-{
-       assert(_frozen > 0);
-
-       if (--_frozen > 0) {
-               return;
-       }
-
-       {
-               Glib::Threads::RWLock::WriterLock lm (_lock);
-
-               if (_sort_pending) {
-                       _events.sort (event_time_less_than);
-                       unlocked_remove_duplicates ();
-                       unlocked_invalidate_insert_iterator ();
-                       _sort_pending = false;
-               }
-       }
-}
-
-void
-ControlList::mark_dirty () const
-{
-       _lookup_cache.left = -1;
-       _lookup_cache.range.first = _events.end();
-       _lookup_cache.range.second = _events.end();
-       _search_cache.left = -1;
-       _search_cache.first = _events.end();
-
-       if (_curve) {
-               _curve->mark_dirty();
-       }
-
-       Dirty (); /* EMIT SIGNAL */
-}
-
-void
-ControlList::truncate_end (double last_coordinate)
-{
-       {
-               Glib::Threads::RWLock::WriterLock lm (_lock);
-               ControlEvent cp (last_coordinate, 0);
-               ControlList::reverse_iterator i;
-               double last_val;
-
-               if (_events.empty()) {
-                       return;
-               }
-
-               if (last_coordinate == _events.back()->when) {
-                       return;
-               }
-
-               if (last_coordinate > _events.back()->when) {
-
-                       /* extending end:
-                        */
-
-                       iterator foo = _events.begin();
-                       bool lessthantwo;
-
-                       if (foo == _events.end()) {
-                               lessthantwo = true;
-                       } else if (++foo == _events.end()) {
-                               lessthantwo = true;
-                       } else {
-                               lessthantwo = false;
-                       }
-
-                       if (lessthantwo) {
-                               /* less than 2 points: add a new point */
-                               _events.push_back (new ControlEvent (last_coordinate, _events.back()->value));
-                       } else {
-
-                               /* more than 2 points: check to see if the last 2 values
-                                  are equal. if so, just move the position of the
-                                  last point. otherwise, add a new point.
-                               */
-
-                               iterator penultimate = _events.end();
-                               --penultimate; /* points at last point */
-                               --penultimate; /* points at the penultimate point */
-
-                               if (_events.back()->value == (*penultimate)->value) {
-                                       _events.back()->when = last_coordinate;
-                               } else {
-                                       _events.push_back (new ControlEvent (last_coordinate, _events.back()->value));
-                               }
-                       }
-
-               } else {
-
-                       /* shortening end */
-
-                       last_val = unlocked_eval (last_coordinate);
-                       last_val = max ((double) _desc.lower, last_val);
-                       last_val = min ((double) _desc.upper, last_val);
-
-                       i = _events.rbegin();
-
-                       /* make i point to the last control point */
-
-                       ++i;
-
-                       /* now go backwards, removing control points that are
-                          beyond the new last coordinate.
-                       */
-
-                       // FIXME: SLOW! (size() == O(n))
-
-                       uint32_t sz = _events.size();
-
-                       while (i != _events.rend() && sz > 2) {
-                               ControlList::reverse_iterator tmp;
-
-                               tmp = i;
-                               ++tmp;
-
-                               if ((*i)->when < last_coordinate) {
-                                       break;
-                               }
-
-                               _events.erase (i.base());
-                               --sz;
-
-                               i = tmp;
-                       }
-
-                       _events.back()->when = last_coordinate;
-                       _events.back()->value = last_val;
-               }
-
-               unlocked_invalidate_insert_iterator ();
-               mark_dirty();
-       }
-
-       maybe_signal_changed ();
-}
-
-void
-ControlList::truncate_start (double overall_length)
-{
-       {
-               Glib::Threads::RWLock::WriterLock lm (_lock);
-               iterator i;
-               double first_legal_value;
-               double first_legal_coordinate;
-
-               if (_events.empty()) {
-                       /* nothing to truncate */
-                       return;
-               } else if (overall_length == _events.back()->when) {
-                       /* no change in overall length */
-                       return;
-               }
-
-               if (overall_length > _events.back()->when) {
-
-                       /* growing at front: duplicate first point. shift all others */
-
-                       double shift = overall_length - _events.back()->when;
-                       uint32_t np;
-
-                       for (np = 0, i = _events.begin(); i != _events.end(); ++i, ++np) {
-                               (*i)->when += shift;
-                       }
-
-                       if (np < 2) {
-
-                               /* less than 2 points: add a new point */
-                               _events.push_front (new ControlEvent (0, _events.front()->value));
-
-                       } else {
-
-                               /* more than 2 points: check to see if the first 2 values
-                                  are equal. if so, just move the position of the
-                                  first point. otherwise, add a new point.
-                               */
-
-                               iterator second = _events.begin();
-                               ++second; /* points at the second point */
-
-                               if (_events.front()->value == (*second)->value) {
-                                       /* first segment is flat, just move start point back to zero */
-                                       _events.front()->when = 0;
-                               } else {
-                                       /* leave non-flat segment in place, add a new leading point. */
-                                       _events.push_front (new ControlEvent (0, _events.front()->value));
-                               }
-                       }
-
-               } else {
-
-                       /* shrinking at front */
-
-                       first_legal_coordinate = _events.back()->when - overall_length;
-                       first_legal_value = unlocked_eval (first_legal_coordinate);
-                       first_legal_value = max ((double)_desc.lower, first_legal_value);
-                       first_legal_value = min ((double)_desc.upper, first_legal_value);
-
-                       /* remove all events earlier than the new "front" */
-
-                       i = _events.begin();
-
-                       while (i != _events.end() && !_events.empty()) {
-                               ControlList::iterator tmp;
-
-                               tmp = i;
-                               ++tmp;
-
-                               if ((*i)->when > first_legal_coordinate) {
-                                       break;
-                               }
-
-                               _events.erase (i);
-
-                               i = tmp;
-                       }
-
-
-                       /* shift all remaining points left to keep their same
-                          relative position
-                       */
-
-                       for (i = _events.begin(); i != _events.end(); ++i) {
-                               (*i)->when -= first_legal_coordinate;
-                       }
-
-                       /* add a new point for the interpolated new value */
-
-                       _events.push_front (new ControlEvent (0, first_legal_value));
-               }
-
-               unlocked_invalidate_insert_iterator ();
-               mark_dirty();
-       }
-
-       maybe_signal_changed ();
-}
-
-double
-ControlList::unlocked_eval (double x) const
-{
-       pair<EventList::iterator,EventList::iterator> range;
-       int32_t npoints;
-       double lpos, upos;
-       double lval, uval;
-       double fraction;
-
-       const_iterator length_check_iter = _events.begin();
-       for (npoints = 0; npoints < 4; ++npoints, ++length_check_iter) {
-               if (length_check_iter == _events.end()) {
-                       break;
-               }
-       }
-
-       switch (npoints) {
-       case 0:
-               return _desc.normal;
-
-       case 1:
-               return _events.front()->value;
-
-       case 2:
-               if (x >= _events.back()->when) {
-                       return _events.back()->value;
-               } else if (x <= _events.front()->when) {
-                       return _events.front()->value;
-               }
-
-               lpos = _events.front()->when;
-               lval = _events.front()->value;
-               upos = _events.back()->when;
-               uval = _events.back()->value;
-
-               fraction = (double) (x - lpos) / (double) (upos - lpos);
-
-               switch (_interpolation) {
-                       case Discrete:
-                               return lval;
-                       case Logarithmic:
-                               return interpolate_logarithmic (lval, uval, fraction, _desc.lower, _desc.upper);
-                       case Exponential:
-                               return interpolate_gain (lval, uval, fraction, _desc.upper);
-                       case Curved:
-                               /* only used x-fade curves, never direct eval */
-                               assert (0);
-                       default: // Linear
-                               return interpolate_linear (lval, uval, fraction);
-               }
-
-       default:
-               if (x >= _events.back()->when) {
-                       return _events.back()->value;
-               } else if (x <= _events.front()->when) {
-                       return _events.front()->value;
-               }
-
-               return multipoint_eval (x);
-       }
-
-       abort(); /*NOTREACHED*/ /* stupid gcc */
-       return _desc.normal;
-}
-
-double
-ControlList::multipoint_eval (double x) const
-{
-       double upos, lpos;
-       double uval, lval;
-       double fraction;
-
-       /* "Stepped" lookup (no interpolation) */
-       /* FIXME: no cache.  significant? */
-       if (_interpolation == Discrete) {
-               const ControlEvent cp (x, 0);
-               EventList::const_iterator i = lower_bound (_events.begin(), _events.end(), &cp, time_comparator);
-
-               // shouldn't have made it to multipoint_eval
-               assert(i != _events.end());
-
-               if (i == _events.begin() || (*i)->when == x)
-                       return (*i)->value;
-               else
-                       return (*(--i))->value;
-       }
-
-       /* Only do the range lookup if x is in a different range than last time
-        * this was called (or if the lookup cache has been marked "dirty" (left<0) */
-       if ((_lookup_cache.left < 0) ||
-           ((_lookup_cache.left > x) ||
-            (_lookup_cache.range.first == _events.end()) ||
-            ((*_lookup_cache.range.second)->when < x))) {
-
-               const ControlEvent cp (x, 0);
-
-               _lookup_cache.range = equal_range (_events.begin(), _events.end(), &cp, time_comparator);
-       }
-
-       pair<const_iterator,const_iterator> range = _lookup_cache.range;
-
-       if (range.first == range.second) {
-
-               /* x does not exist within the list as a control point */
-
-               _lookup_cache.left = x;
-
-               if (range.first != _events.begin()) {
-                       --range.first;
-                       lpos = (*range.first)->when;
-                       lval = (*range.first)->value;
-               }  else {
-                       /* we're before the first point */
-                       // return _default_value;
-                       return _events.front()->value;
-               }
-
-               if (range.second == _events.end()) {
-                       /* we're after the last point */
-                       return _events.back()->value;
-               }
-
-               upos = (*range.second)->when;
-               uval = (*range.second)->value;
-
-               fraction = (double) (x - lpos) / (double) (upos - lpos);
-
-               switch (_interpolation) {
-                       case Logarithmic:
-                               return interpolate_logarithmic (lval, uval, fraction, _desc.lower, _desc.upper);
-                       case Exponential:
-                               return interpolate_gain (lval, uval, fraction, _desc.upper);
-                       case Discrete:
-                               /* should not reach here */
-                               assert (0);
-                       case Curved:
-                               /* only used x-fade curves, never direct eval */
-                               assert (0);
-                       default: // Linear
-                               return interpolate_linear (lval, uval, fraction);
-                               break;
-               }
-               assert (0);
-       }
-
-       /* x is a control point in the data */
-       _lookup_cache.left = -1;
-       return (*range.first)->value;
-}
-
-void
-ControlList::build_search_cache_if_necessary (double start) const
-{
-       if (_events.empty()) {
-               /* Empty, nothing to cache, move to end. */
-               _search_cache.first = _events.end();
-               _search_cache.left = 0;
-               return;
-       } else if ((_search_cache.left < 0) || (_search_cache.left > start)) {
-               /* Marked dirty (left < 0), or we're too far forward, re-search. */
-
-               const ControlEvent start_point (start, 0);
-
-               _search_cache.first = lower_bound (_events.begin(), _events.end(), &start_point, time_comparator);
-               _search_cache.left = start;
-       }
-
-       /* We now have a search cache that is not too far right, but it may be too
-          far left and need to be advanced. */
-
-       while (_search_cache.first != end() && (*_search_cache.first)->when < start) {
-               ++_search_cache.first;
-       }
-       _search_cache.left = start;
-}
-
-/** Get the earliest event after \a start using the current interpolation style.
- *
- * If an event is found, \a x and \a y are set to its coordinates.
- *
- * \param inclusive Include events with timestamp exactly equal to \a start
- * \return true if event is found (and \a x and \a y are valid).
- */
-bool
-ControlList::rt_safe_earliest_event (double start, double& x, double& y, bool inclusive) const
-{
-       // FIXME: It would be nice if this was unnecessary..
-       Glib::Threads::RWLock::ReaderLock lm(_lock, Glib::Threads::TRY_LOCK);
-       if (!lm.locked()) {
-               return false;
-       }
-
-       return rt_safe_earliest_event_unlocked (start, x, y, inclusive);
-}
-
-
-/** Get the earliest event after \a start using the current interpolation style.
- *
- * If an event is found, \a x and \a y are set to its coordinates.
- *
- * \param inclusive Include events with timestamp exactly equal to \a start
- * \return true if event is found (and \a x and \a y are valid).
- */
-bool
-ControlList::rt_safe_earliest_event_unlocked (double start, double& x, double& y, bool inclusive) const
-{
-       if (_interpolation == Discrete) {
-               return rt_safe_earliest_event_discrete_unlocked(start, x, y, inclusive);
-       } else {
-               return rt_safe_earliest_event_linear_unlocked(start, x, y, inclusive);
-       }
-}
-
-
-/** Get the earliest event after \a start without interpolation.
- *
- * If an event is found, \a x and \a y are set to its coordinates.
- *
- * \param inclusive Include events with timestamp exactly equal to \a start
- * \return true if event is found (and \a x and \a y are valid).
- */
-bool
-ControlList::rt_safe_earliest_event_discrete_unlocked (double start, double& x, double& y, bool inclusive) const
-{
-       build_search_cache_if_necessary (start);
-
-       if (_search_cache.first != _events.end()) {
-               const ControlEvent* const first = *_search_cache.first;
-
-               const bool past_start = (inclusive ? first->when >= start : first->when > start);
-
-               /* Earliest points is in range, return it */
-               if (past_start) {
-
-                       x = first->when;
-                       y = first->value;
-
-                       /* Move left of cache to this point
-                        * (Optimize for immediate call this cycle within range) */
-                       _search_cache.left = x;
-                       ++_search_cache.first;
-
-                       assert(x >= start);
-                       return true;
-
-               } else {
-                       return false;
-               }
-
-               /* No points in range */
-       } else {
-               return false;
-       }
-}
-
-/** Get the earliest time the line crosses an integer (Linear interpolation).
- *
- * If an event is found, \a x and \a y are set to its coordinates.
- *
- * \param inclusive Include events with timestamp exactly equal to \a start
- * \return true if event is found (and \a x and \a y are valid).
- */
-bool
-ControlList::rt_safe_earliest_event_linear_unlocked (double start, double& x, double& y, bool inclusive) const
-{
-       // cout << "earliest_event(start: " << start << ", x: " << x << ", y: " << y << ", inclusive: " << inclusive <<  ")" << endl;
-
-       const_iterator length_check_iter = _events.begin();
-       if (_events.empty()) { // 0 events
-               return false;
-       } else if (_events.end() == ++length_check_iter) { // 1 event
-               return rt_safe_earliest_event_discrete_unlocked (start, x, y, inclusive);
-       }
-
-       // Hack to avoid infinitely repeating the same event
-       build_search_cache_if_necessary (start);
-
-       if (_search_cache.first != _events.end()) {
-
-               const ControlEvent* first = NULL;
-               const ControlEvent* next = NULL;
-
-               if (_search_cache.first == _events.begin() || (*_search_cache.first)->when <= start) {
-                       /* Step is after first */
-                       first = *_search_cache.first;
-                       ++_search_cache.first;
-                       if (_search_cache.first == _events.end()) {
-                               return false;
-                       }
-                       next = *_search_cache.first;
-
-               } else {
-                       /* Step is before first */
-                       const_iterator prev = _search_cache.first;
-                       --prev;
-                       first = *prev;
-                       next = *_search_cache.first;
-               }
-
-               if (inclusive && first->when == start) {
-                       x = first->when;
-                       y = first->value;
-                       /* Move left of cache to this point
-                        * (Optimize for immediate call this cycle within range) */
-                       _search_cache.left = x;
-                       return true;
-               } else if (next->when < start || (!inclusive && next->when == start)) {
-                       /* "Next" is before the start, no points left. */
-                       return false;
-               }
-
-               if (fabs(first->value - next->value) <= 1) {
-                       if (next->when > start) {
-                               x = next->when;
-                               y = next->value;
-                               /* Move left of cache to this point
-                                * (Optimize for immediate call this cycle within range) */
-                               _search_cache.left = x;
-                               return true;
-                       } else {
-                               return false;
-                       }
-               }
-
-               const double slope = (next->value - first->value) / (double)(next->when - first->when);
-               //cerr << "start y: " << start_y << endl;
-
-               //y = first->value + (slope * fabs(start - first->when));
-               y = first->value;
-
-               if (first->value < next->value) // ramping up
-                       y = ceil(y);
-               else // ramping down
-                       y = floor(y);
-
-               x = first->when + (y - first->value) / (double)slope;
-
-               while ((inclusive && x < start) || (x <= start && y != next->value)) {
-
-                       if (first->value < next->value) // ramping up
-                               y += 1.0;
-                       else // ramping down
-                               y -= 1.0;
-
-                       x = first->when + (y - first->value) / (double)slope;
-               }
-
-               /*cerr << first->value << " @ " << first->when << " ... "
-                 << next->value << " @ " << next->when
-                 << " = " << y << " @ " << x << endl;*/
-
-               assert(    (y >= first->value && y <= next->value)
-                          || (y <= first->value && y >= next->value) );
-
-
-               const bool past_start = (inclusive ? x >= start : x > start);
-               if (past_start) {
-                       /* Move left of cache to this point
-                        * (Optimize for immediate call this cycle within range) */
-                       _search_cache.left = x;
-                       assert(inclusive ? x >= start : x > start);
-                       return true;
-               } else {
-                       if (inclusive) {
-                               x = next->when;
-                       } else {
-                               x = start;
-                       }
-                       _search_cache.left = x;
-                       return true;
-               }
-
-       } else {
-               /* No points in the future, so no steps (towards them) in the future */
-               return false;
-       }
-}
-
-
-/** @param start Start position in model coordinates.
- *  @param end End position in model coordinates.
- *  @param op 0 = cut, 1 = copy, 2 = clear.
- */
-boost::shared_ptr<ControlList>
-ControlList::cut_copy_clear (double start, double end, int op)
-{
-       boost::shared_ptr<ControlList> nal = create (_parameter, _desc);
-       iterator s, e;
-       ControlEvent cp (start, 0.0);
-
-       {
-               Glib::Threads::RWLock::WriterLock lm (_lock);
-
-               /* first, determine s & e, two iterators that define the range of points
-                  affected by this operation
-               */
-
-               if ((s = lower_bound (_events.begin(), _events.end(), &cp, time_comparator)) == _events.end()) {
-                       return nal;
-               }
-
-               /* and the last that is at or after `end' */
-               cp.when = end;
-               e = upper_bound (_events.begin(), _events.end(), &cp, time_comparator);
-
-
-               /* if "start" isn't the location of an existing point,
-                  evaluate the curve to get a value for the start. Add a point to
-                  both the existing event list, and if its not a "clear" operation,
-                  to the copy ("nal") as well.
-
-                  Note that the time positions of the points in each list are different
-                  because we want the copy ("nal") to have a zero time reference.
-               */
-
-
-               /* before we begin any cut/clear operations, get the value of the curve
-                  at "end".
-               */
-
-               double end_value = unlocked_eval (end);
-
-               if ((*s)->when != start) {
-
-                       double val = unlocked_eval (start);
-
-                       if (op == 0) { // cut
-                               if (start > _events.front()->when) {
-                                       _events.insert (s, (new ControlEvent (start, val)));
-                               }
-                       }
-
-                       if (op != 2) { // ! clear
-                               nal->_events.push_back (new ControlEvent (0, val));
-                       }
-               }
-
-               for (iterator x = s; x != e; ) {
-
-                       /* adjust new points to be relative to start, which
-                          has been set to zero.
-                       */
-
-                       if (op != 2) {
-                               nal->_events.push_back (new ControlEvent ((*x)->when - start, (*x)->value));
-                       }
-
-                       if (op != 1) {
-                               x = _events.erase (x);
-                       } else {
-                               ++x;
-                       }
-               }
-
-               if (e == _events.end() || (*e)->when != end) {
-
-                       /* only add a boundary point if there is a point after "end"
-                        */
-
-                       if (op == 0 && (e != _events.end() && end < (*e)->when)) { // cut
-                               _events.insert (e, new ControlEvent (end, end_value));
-                       }
-
-                       if (op != 2 && (e != _events.end() && end < (*e)->when)) { // cut/copy
-                               nal->_events.push_back (new ControlEvent (end - start, end_value));
-                       }
-               }
-
-               unlocked_invalidate_insert_iterator ();
-               mark_dirty ();
-       }
-
-       if (op != 1) {
-               maybe_signal_changed ();
-       }
-
-       return nal;
-}
-
-
-boost::shared_ptr<ControlList>
-ControlList::cut (double start, double end)
-{
-       return cut_copy_clear (start, end, 0);
-}
-
-boost::shared_ptr<ControlList>
-ControlList::copy (double start, double end)
-{
-       return cut_copy_clear (start, end, 1);
-}
-
-void
-ControlList::clear (double start, double end)
-{
-       cut_copy_clear (start, end, 2);
-}
-
-/** @param pos Position in model coordinates */
-bool
-ControlList::paste (const ControlList& alist, double pos)
-{
-       if (alist._events.empty()) {
-               return false;
-       }
-
-       {
-               Glib::Threads::RWLock::WriterLock lm (_lock);
-               iterator where;
-               iterator prev;
-               double end = 0;
-               ControlEvent cp (pos, 0.0);
-
-               where = upper_bound (_events.begin(), _events.end(), &cp, time_comparator);
-
-               for (const_iterator i = alist.begin();i != alist.end(); ++i) {
-                       double value = (*i)->value;
-                       if (alist.parameter() != parameter()) {
-                               const ParameterDescriptor& src_desc = alist.descriptor();
-
-                               // This does not work for logscale and will probably also not do
-                               // the right thing for integer_step and sr_dependent parameters.
-                               //
-                               // TODO various flags from from ARDOUR::ParameterDescriptor
-                               // to Evoral::ParameterDescriptor
-
-                               value -= src_desc.lower;  // translate to 0-relative
-                               value /= (src_desc.upper - src_desc.lower);  // normalize range
-                               value *= (_desc.upper - _desc.lower);  // scale to our range
-                               value += _desc.lower;  // translate to our offset
-                               if (_desc.toggled) {
-                                       value = (value < 0.5) ? 0.0 : 1.0;
-                               }
-                               /* catch possible rounding errors */
-                               value = std::min ((double)_desc.upper, std::max ((double)_desc.lower, value));
-                       }
-                       _events.insert (where, new ControlEvent((*i)->when + pos, value));
-                       end = (*i)->when + pos;
-               }
-
-
-               /* move all  points after the insertion along the timeline by
-                  the correct amount.
-               */
-
-               while (where != _events.end()) {
-                       iterator tmp;
-                       if ((*where)->when <= end) {
-                               tmp = where;
-                               ++tmp;
-                               _events.erase(where);
-                               where = tmp;
-
-                       } else {
-                               break;
-                       }
-               }
-
-               unlocked_invalidate_insert_iterator ();
-               mark_dirty ();
-       }
-
-       maybe_signal_changed ();
-       return true;
-}
-
-/** Move automation around according to a list of region movements.
- *  @param return true if anything was changed, otherwise false (ie nothing needed changing)
- */
-bool
-ControlList::move_ranges (const list< RangeMove<double> >& movements)
-{
-       typedef list< RangeMove<double> > RangeMoveList;
-
-       {
-               Glib::Threads::RWLock::WriterLock lm (_lock);
-
-               /* a copy of the events list before we started moving stuff around */
-               EventList old_events = _events;
-
-               /* clear the source and destination ranges in the new list */
-               bool things_erased = false;
-               for (RangeMoveList::const_iterator i = movements.begin (); i != movements.end (); ++i) {
-
-                       if (erase_range_internal (i->from, i->from + i->length, _events)) {
-                               things_erased = true;
-                       }
-
-                       if (erase_range_internal (i->to, i->to + i->length, _events)) {
-                               things_erased = true;
-                       }
-               }
-
-               /* if nothing was erased, there is nothing to do */
-               if (!things_erased) {
-                       return false;
-               }
-
-               /* copy the events into the new list */
-               for (RangeMoveList::const_iterator i = movements.begin (); i != movements.end (); ++i) {
-                       iterator j = old_events.begin ();
-                       const double limit = i->from + i->length;
-                       const double dx    = i->to - i->from;
-                       while (j != old_events.end () && (*j)->when <= limit) {
-                               if ((*j)->when >= i->from) {
-                                       ControlEvent* ev = new ControlEvent (**j);
-                                       ev->when += dx;
-                                       _events.push_back (ev);
-                               }
-                               ++j;
-                       }
-               }
-
-               if (!_frozen) {
-                       _events.sort (event_time_less_than);
-                       unlocked_remove_duplicates ();
-                       unlocked_invalidate_insert_iterator ();
-               } else {
-                       _sort_pending = true;
-               }
-
-               mark_dirty ();
-       }
-
-       maybe_signal_changed ();
-       return true;
-}
-
-bool
-ControlList::set_interpolation (InterpolationStyle s)
-{
-       if (_interpolation == s) {
-               return true;
-       }
-
-       switch (s) {
-               case Logarithmic:
-                       if (_desc.lower * _desc.upper <= 0 || _desc.upper <= _desc.lower) {
-                               return false;
-                       }
-                       break;
-               case Exponential:
-                       if (_desc.lower != 0 || _desc.upper <= _desc.lower) {
-                               return false;
-                       }
-               default:
-                       break;
-       }
-
-       _interpolation = s;
-       InterpolationChanged (s); /* EMIT SIGNAL */
-       return true;
-}
-
-bool
-ControlList::operator!= (ControlList const & other) const
-{
-       if (_events.size() != other._events.size()) {
-               return true;
-       }
-
-       EventList::const_iterator i = _events.begin ();
-       EventList::const_iterator j = other._events.begin ();
-
-       while (i != _events.end() && (*i)->when == (*j)->when && (*i)->value == (*j)->value) {
-               ++i;
-               ++j;
-       }
-
-       if (i != _events.end ()) {
-               return true;
-       }
-
-       return (
-               _parameter != other._parameter ||
-               _interpolation != other._interpolation ||
-               _desc.lower != other._desc.lower ||
-               _desc.upper != other._desc.upper ||
-               _desc.normal != other._desc.normal
-               );
-}
-
-bool
-ControlList::is_sorted () const
-{
-       Glib::Threads::RWLock::ReaderLock lm (_lock);
-       if (_events.size () == 0) {
-               return true;
-       }
-       const_iterator i = _events.begin();
-       const_iterator n = i;
-       while (++n != _events.end ()) {
-               if (event_time_less_than(*n,*i)) {
-                       return false;
-               }
-               ++i;
-       }
-       return true;
-}
-
-void
-ControlList::dump (ostream& o)
-{
-       /* NOT LOCKED ... for debugging only */
-
-       for (EventList::iterator x = _events.begin(); x != _events.end(); ++x) {
-               o << (*x)->value << " @ " << (uint64_t) (*x)->when << endl;
-       }
-}
-
-} // namespace Evoral
-
diff --git a/libs/evoral/src/ControlSet.cc b/libs/evoral/src/ControlSet.cc
new file mode 100644 (file)
index 0000000..c909618
--- /dev/null
@@ -0,0 +1,115 @@
+/*
+ * Copyright (C) 2008-2014 David Robillard <d@drobilla.net>
+ * Copyright (C) 2010-2018 Paul Davis <paul@linuxaudiosystems.com>
+ * Copyright (C) 2010 Carl Hetherington <carl@carlh.net>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include <iostream>
+#include <limits>
+#include "evoral/ControlSet.h"
+#include "evoral/ControlList.h"
+#include "evoral/Control.h"
+#include "evoral/Event.h"
+
+using namespace std;
+
+namespace Evoral {
+
+
+ControlSet::ControlSet()
+{
+}
+
+ControlSet::ControlSet (const ControlSet&)
+       : noncopyable ()
+{
+       /* derived class must copy controls */
+}
+
+void
+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));
+
+       if (ac->list()) {
+               ac->list()->InterpolationChanged.connect_same_thread (
+                       _list_connections,
+                       boost::bind (&ControlSet::control_list_interpolation_changed,
+                                    this, ac->parameter(), _1));
+       }
+}
+
+void
+ControlSet::what_has_data (set<Parameter>& s) const
+{
+       Glib::Threads::Mutex::Lock lm (_control_lock);
+
+       for (Controls::const_iterator li = _controls.begin(); li != _controls.end(); ++li) {
+               if (li->second->list() && !li->second->list()->empty()) {
+                       s.insert (li->first);
+               }
+       }
+}
+
+/** If a control for the given parameter does not exist and \a create_if_missing is true,
+ * a control will be created, added to this set, and returned.
+ * If \a create_if_missing is false this function may return null.
+ */
+boost::shared_ptr<Control>
+ControlSet::control (const Parameter& parameter, bool create_if_missing)
+{
+       Controls::iterator i = _controls.find(parameter);
+
+       if (i != _controls.end()) {
+               return i->second;
+
+       } else if (create_if_missing) {
+               boost::shared_ptr<Control> ac(control_factory(parameter));
+               add_control(ac);
+               return ac;
+
+       } else {
+               return boost::shared_ptr<Control>();
+       }
+}
+
+void
+ControlSet::clear_controls ()
+{
+       Glib::Threads::Mutex::Lock lm (_control_lock);
+
+       _control_connections.drop_connections ();
+       _list_connections.drop_connections ();
+
+       for (Controls::iterator li = _controls.begin(); li != _controls.end(); ++li) {
+               if (li->second->list()) {
+                       li->second->list()->clear();
+               }
+       }
+}
+
+} // namespace Evoral
+
+/* No good place for this so just put it here */
+
+std::ostream&
+std::operator<< (std::ostream & str, Evoral::Parameter const & p)
+{
+       return str << p.type() << '-' << p.id() << '-' << (int) p.channel();
+}
diff --git a/libs/evoral/src/ControlSet.cpp b/libs/evoral/src/ControlSet.cpp
deleted file mode 100644 (file)
index 132b48b..0000000
+++ /dev/null
@@ -1,115 +0,0 @@
-/*
- * Copyright (C) 2008-2014 David Robillard <d@drobilla.net>
- * Copyright (C) 2010-2018 Paul Davis <paul@linuxaudiosystems.com>
- * Copyright (C) 2010 Carl Hetherington <carl@carlh.net>
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-
-#include <iostream>
-#include <limits>
-#include "evoral/ControlSet.hpp"
-#include "evoral/ControlList.hpp"
-#include "evoral/Control.hpp"
-#include "evoral/Event.hpp"
-
-using namespace std;
-
-namespace Evoral {
-
-
-ControlSet::ControlSet()
-{
-}
-
-ControlSet::ControlSet (const ControlSet&)
-       : noncopyable ()
-{
-       /* derived class must copy controls */
-}
-
-void
-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));
-
-       if (ac->list()) {
-               ac->list()->InterpolationChanged.connect_same_thread (
-                       _list_connections,
-                       boost::bind (&ControlSet::control_list_interpolation_changed,
-                                    this, ac->parameter(), _1));
-       }
-}
-
-void
-ControlSet::what_has_data (set<Parameter>& s) const
-{
-       Glib::Threads::Mutex::Lock lm (_control_lock);
-
-       for (Controls::const_iterator li = _controls.begin(); li != _controls.end(); ++li) {
-               if (li->second->list() && !li->second->list()->empty()) {
-                       s.insert (li->first);
-               }
-       }
-}
-
-/** If a control for the given parameter does not exist and \a create_if_missing is true,
- * a control will be created, added to this set, and returned.
- * If \a create_if_missing is false this function may return null.
- */
-boost::shared_ptr<Control>
-ControlSet::control (const Parameter& parameter, bool create_if_missing)
-{
-       Controls::iterator i = _controls.find(parameter);
-
-       if (i != _controls.end()) {
-               return i->second;
-
-       } else if (create_if_missing) {
-               boost::shared_ptr<Control> ac(control_factory(parameter));
-               add_control(ac);
-               return ac;
-
-       } else {
-               return boost::shared_ptr<Control>();
-       }
-}
-
-void
-ControlSet::clear_controls ()
-{
-       Glib::Threads::Mutex::Lock lm (_control_lock);
-
-       _control_connections.drop_connections ();
-       _list_connections.drop_connections ();
-
-       for (Controls::iterator li = _controls.begin(); li != _controls.end(); ++li) {
-               if (li->second->list()) {
-                       li->second->list()->clear();
-               }
-       }
-}
-
-} // namespace Evoral
-
-/* No good place for this so just put it here */
-
-std::ostream&
-std::operator<< (std::ostream & str, Evoral::Parameter const & p)
-{
-       return str << p.type() << '-' << p.id() << '-' << (int) p.channel();
-}
diff --git a/libs/evoral/src/Curve.cc b/libs/evoral/src/Curve.cc
new file mode 100644 (file)
index 0000000..37e6407
--- /dev/null
@@ -0,0 +1,458 @@
+/*
+ * Copyright (C) 2008-2013 Paul Davis <paul@linuxaudiosystems.com>
+ * Copyright (C) 2008-2016 David Robillard <d@drobilla.net>
+ * Copyright (C) 2010-2012 Carl Hetherington <carl@carlh.net>
+ * Copyright (C) 2012-2018 Robin Gareus <robin@gareus.org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include <iostream>
+#include <float.h>
+#include <cmath>
+#include <climits>
+#include <cfloat>
+#include <cmath>
+#include <vector>
+
+#include <glibmm/threads.h>
+
+#include "pbd/control_math.h"
+
+#include "evoral/Curve.h"
+#include "evoral/ControlList.h"
+
+using namespace std;
+using namespace sigc;
+
+namespace Evoral {
+
+
+Curve::Curve (const ControlList& cl)
+       : _dirty (true)
+       , _list (cl)
+{
+}
+
+void
+Curve::solve () const
+{
+       uint32_t npoints;
+
+       if (!_dirty) {
+               return;
+       }
+
+       if ((npoints = _list.events().size()) > 2) {
+
+               /* Compute coefficients needed to efficiently compute a constrained spline
+                  curve. See "Constrained Cubic Spline Interpolation" by CJC Kruger
+                  (www.korf.co.uk/spline.pdf) for more details.
+               */
+
+               vector<double> x(npoints);
+               vector<double> y(npoints);
+               uint32_t i;
+               ControlList::EventList::const_iterator xx;
+
+               for (i = 0, xx = _list.events().begin(); xx != _list.events().end(); ++xx, ++i) {
+                       x[i] = (double) (*xx)->when;
+                       y[i] = (double) (*xx)->value;
+               }
+
+               double lp0, lp1, fpone;
+
+               lp0 = (x[1] - x[0])/(y[1] - y[0]);
+               lp1 = (x[2] - x[1])/(y[2] - y[1]);
+
+               if (lp0*lp1 < 0) {
+                       fpone = 0;
+               } else {
+                       fpone = 2 / (lp1 + lp0);
+               }
+
+               double fplast = 0;
+
+               for (i = 0, xx = _list.events().begin(); xx != _list.events().end(); ++xx, ++i) {
+
+                       double xdelta;   /* gcc is wrong about possible uninitialized use */
+                       double xdelta2;  /* ditto */
+                       double ydelta;   /* ditto */
+                       double fppL, fppR;
+                       double fpi;
+
+                       if (i > 0) {
+                               xdelta = x[i] - x[i-1];
+                               xdelta2 = xdelta * xdelta;
+                               ydelta = y[i] - y[i-1];
+                       }
+
+                       /* compute (constrained) first derivatives */
+
+                       if (i == 0) {
+
+                               /* first segment */
+
+                               fplast = ((3 * (y[1] - y[0]) / (2 * (x[1] - x[0]))) - (fpone * 0.5));
+
+                               /* we don't store coefficients for i = 0 */
+
+                               continue;
+
+                       } else if (i == npoints - 1) {
+
+                               /* last segment */
+
+                               fpi = ((3 * ydelta) / (2 * xdelta)) - (fplast * 0.5);
+
+                       } else {
+
+                               /* all other segments */
+
+                               double slope_before = ((x[i+1] - x[i]) / (y[i+1] - y[i]));
+                               double slope_after = (xdelta / ydelta);
+
+                               if (slope_after * slope_before < 0.0) {
+                                       /* slope changed sign */
+                                       fpi = 0.0;
+                               } else {
+                                       fpi = 2 / (slope_before + slope_after);
+                               }
+                       }
+
+                       /* compute second derivative for either side of control point `i' */
+
+                       fppL = (((-2 * (fpi + (2 * fplast))) / (xdelta))) +
+                               ((6 * ydelta) / xdelta2);
+
+                       fppR = (2 * ((2 * fpi) + fplast) / xdelta) -
+                               ((6 * ydelta) / xdelta2);
+
+                       /* compute polynomial coefficients */
+
+                       double b, c, d;
+
+                       d = (fppR - fppL) / (6 * xdelta);
+                       c = ((x[i] * fppL) - (x[i-1] * fppR))/(2 * xdelta);
+
+                       double xim12, xim13;
+                       double xi2, xi3;
+
+                       xim12 = x[i-1] * x[i-1];  /* "x[i-1] squared" */
+                       xim13 = xim12 * x[i-1];   /* "x[i-1] cubed" */
+                       xi2 = x[i] * x[i];        /* "x[i] squared" */
+                       xi3 = xi2 * x[i];         /* "x[i] cubed" */
+
+                       b = (ydelta - (c * (xi2 - xim12)) - (d * (xi3 - xim13))) / xdelta;
+
+                       /* store */
+
+                       (*xx)->create_coeffs();
+                       (*xx)->coeff[0] = y[i-1] - (b * x[i-1]) - (c * xim12) - (d * xim13);
+                       (*xx)->coeff[1] = b;
+                       (*xx)->coeff[2] = c;
+                       (*xx)->coeff[3] = d;
+
+                       fplast = fpi;
+               }
+
+       }
+
+       _dirty = false;
+}
+
+bool
+Curve::rt_safe_get_vector (double x0, double x1, float *vec, int32_t veclen) const
+{
+       Glib::Threads::RWLock::ReaderLock lm(_list.lock(), Glib::Threads::TRY_LOCK);
+
+       if (!lm.locked()) {
+               return false;
+       } else {
+               _get_vector (x0, x1, vec, veclen);
+               return true;
+       }
+}
+
+void
+Curve::get_vector (double x0, double x1, float *vec, int32_t veclen) const
+{
+       Glib::Threads::RWLock::ReaderLock lm(_list.lock());
+       _get_vector (x0, x1, vec, veclen);
+}
+
+void
+Curve::_get_vector (double x0, double x1, float *vec, int32_t veclen) const
+{
+       double rx, lx, hx, max_x, min_x;
+       int32_t i;
+       int32_t original_veclen;
+       int32_t npoints;
+
+       if (veclen == 0) {
+               return;
+       }
+
+       if ((npoints = _list.events().size()) == 0) {
+               /* no events in list, so just fill the entire array with the default value */
+               for (int32_t i = 0; i < veclen; ++i) {
+                       vec[i] = _list.descriptor().normal;
+               }
+               return;
+       }
+
+       if (npoints == 1) {
+               for (int32_t i = 0; i < veclen; ++i) {
+                       vec[i] = _list.events().front()->value;
+               }
+               return;
+       }
+
+       /* events is now known not to be empty */
+
+       max_x = _list.events().back()->when;
+       min_x = _list.events().front()->when;
+
+       if (x0 > max_x) {
+               /* totally past the end - just fill the entire array with the final value */
+               for (int32_t i = 0; i < veclen; ++i) {
+                       vec[i] = _list.events().back()->value;
+               }
+               return;
+       }
+
+       if (x1 < min_x) {
+               /* totally before the first event - fill the entire array with
+                * the initial value.
+                */
+               for (int32_t i = 0; i < veclen; ++i) {
+                       vec[i] = _list.events().front()->value;
+               }
+               return;
+       }
+
+       original_veclen = veclen;
+
+       if (x0 < min_x) {
+
+               /* fill some beginning section of the array with the
+                  initial (used to be default) value
+               */
+
+               double frac = (min_x - x0) / (x1 - x0);
+               int64_t fill_len = (int64_t) floor (veclen * frac);
+
+               fill_len = min (fill_len, (int64_t)veclen);
+
+               for (i = 0; i < fill_len; ++i) {
+                       vec[i] = _list.events().front()->value;
+               }
+
+               veclen -= fill_len;
+               vec += fill_len;
+       }
+
+       if (veclen && x1 > max_x) {
+
+               /* fill some end section of the array with the default or final value */
+
+               double frac = (x1 - max_x) / (x1 - x0);
+               int64_t fill_len = (int64_t) floor (original_veclen * frac);
+               float val;
+
+               fill_len = min (fill_len, (int64_t)veclen);
+               val = _list.events().back()->value;
+
+               for (i = veclen - fill_len; i < veclen; ++i) {
+                       vec[i] = val;
+               }
+
+               veclen -= fill_len;
+       }
+
+       lx = max (min_x, x0);
+       hx = min (max_x, x1);
+
+       if (npoints == 2) {
+
+               const double lpos = _list.events().front()->when;
+               const double lval = _list.events().front()->value;
+               const double upos = _list.events().back()->when;
+               const double uval = _list.events().back()->value;
+
+               /* dx that we are using */
+               if (veclen > 1) {
+                       const double dx_num = hx - lx;
+                       const double dx_den = veclen - 1;
+                       const double lower = _list.descriptor().lower;
+                       const double upper = _list.descriptor().upper;
+
+                       /* gradient of the line */
+                       const double m_num = uval - lval;
+                       const double m_den = upos - lpos;
+                       /* y intercept of the line */
+                       const double c = uval - (m_num * upos / m_den);
+
+                       switch (_list.interpolation()) {
+                               case ControlList::Logarithmic:
+                                       for (int i = 0; i < veclen; ++i) {
+                                               const double fraction = (lx - lpos + i * dx_num / dx_den) / m_den;
+                                               vec[i] = interpolate_logarithmic (lval, uval, fraction, lower, upper);
+                                       }
+                                       break;
+                               case ControlList::Exponential:
+                                       for (int i = 0; i < veclen; ++i) {
+                                               const double fraction = (lx - lpos + i * dx_num / dx_den) / m_den;
+                                               vec[i] = interpolate_gain (lval, uval, fraction, upper);
+                                       }
+                                       break;
+                               case ControlList::Discrete:
+                                       // any discrete vector curves somewhere?
+                                       assert (0);
+                               case ControlList::Curved:
+                                       /* no 2 point spline */
+                                       /* fallthrough */
+                               default: // Linear:
+                                       for (int i = 0; i < veclen; ++i) {
+                                               vec[i] = (lx * (m_num / m_den) + m_num * i * dx_num / (m_den * dx_den)) + c;
+                                       }
+                                       break;
+                       }
+               } else {
+                       double fraction = (lx - lpos) / (upos - lpos);
+                       switch (_list.interpolation()) {
+                               case ControlList::Logarithmic:
+                                       vec[0] = interpolate_logarithmic (lval, uval, fraction, _list.descriptor().lower, _list.descriptor().upper);
+                                       break;
+                               case ControlList::Exponential:
+                                       vec[0] = interpolate_gain (lval, uval, fraction, _list.descriptor().upper);
+                                       break;
+                               case ControlList::Discrete:
+                                       // any discrete vector curves somewhere?
+                                       assert (0);
+                               case ControlList::Curved:
+                                       /* no 2 point spline */
+                                       /* fallthrough */
+                               default: // Linear:
+                                       vec[0] = interpolate_linear (lval, uval, fraction);
+                                       break;
+                       }
+               }
+
+               return;
+       }
+
+       if (_dirty) {
+               solve ();
+       }
+
+       rx = lx;
+
+       double dx = 0;
+       if (veclen > 1) {
+               dx = (hx - lx) / (veclen - 1);
+       }
+
+       for (i = 0; i < veclen; ++i, rx += dx) {
+               vec[i] = multipoint_eval (rx);
+       }
+}
+
+double
+Curve::multipoint_eval (double x) const
+{
+       pair<ControlList::EventList::const_iterator,ControlList::EventList::const_iterator> range;
+
+       ControlList::LookupCache& lookup_cache = _list.lookup_cache();
+
+       if ((lookup_cache.left < 0) ||
+           ((lookup_cache.left > x) ||
+            (lookup_cache.range.first == _list.events().end()) ||
+            ((*lookup_cache.range.second)->when < x))) {
+
+               ControlEvent cp (x, 0.0);
+
+               lookup_cache.range = equal_range (_list.events().begin(), _list.events().end(), &cp, ControlList::time_comparator);
+       }
+
+       range = lookup_cache.range;
+
+       /* EITHER
+
+          a) x is an existing control point, so first == existing point, second == next point
+
+          OR
+
+          b) x is between control points, so range is empty (first == second, points to where
+              to insert x)
+
+       */
+
+       if (range.first == range.second) {
+
+               /* x does not exist within the list as a control point */
+
+               lookup_cache.left = x;
+
+               if (range.first == _list.events().begin()) {
+                       /* we're before the first point */
+                       // return default_value;
+                       return _list.events().front()->value;
+               }
+
+               if (range.second == _list.events().end()) {
+                       /* we're after the last point */
+                       return _list.events().back()->value;
+               }
+
+               ControlEvent* after = (*range.second);
+               range.second--;
+               ControlEvent* before = (*range.second);
+
+               double vdelta = after->value - before->value;
+
+               if (vdelta == 0.0) {
+                       return before->value;
+               }
+
+               double tdelta = x - before->when;
+               double trange = after->when - before->when;
+
+               switch (_list.interpolation()) {
+                       case ControlList::Discrete:
+                               return before->value;
+                       case ControlList::Logarithmic:
+                               return interpolate_logarithmic (before->value, after->value, tdelta / trange, _list.descriptor().lower, _list.descriptor().upper);
+                       case ControlList::Exponential:
+                               return interpolate_gain (before->value, after->value, tdelta / trange, _list.descriptor().upper);
+                       case ControlList::Curved:
+                               if (after->coeff) {
+                                       ControlEvent* ev = after;
+                                       double x2 = x * x;
+                                       return ev->coeff[0] + (ev->coeff[1] * x) + (ev->coeff[2] * x2) + (ev->coeff[3] * x2 * x);
+                               }
+                               /* fallthrough */
+                       case ControlList::Linear:
+                               return before->value + (vdelta * (tdelta / trange));
+               }
+       }
+
+       /* x is a control point in the data */
+       /* invalidate the cached range because its not usable */
+       lookup_cache.left = -1;
+       return (*range.first)->value;
+}
+
+} // namespace Evoral
diff --git a/libs/evoral/src/Curve.cpp b/libs/evoral/src/Curve.cpp
deleted file mode 100644 (file)
index c4a2706..0000000
+++ /dev/null
@@ -1,458 +0,0 @@
-/*
- * Copyright (C) 2008-2013 Paul Davis <paul@linuxaudiosystems.com>
- * Copyright (C) 2008-2016 David Robillard <d@drobilla.net>
- * Copyright (C) 2010-2012 Carl Hetherington <carl@carlh.net>
- * Copyright (C) 2012-2018 Robin Gareus <robin@gareus.org>
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-
-#include <iostream>
-#include <float.h>
-#include <cmath>
-#include <climits>
-#include <cfloat>
-#include <cmath>
-#include <vector>
-
-#include <glibmm/threads.h>
-
-#include "pbd/control_math.h"
-
-#include "evoral/Curve.hpp"
-#include "evoral/ControlList.hpp"
-
-using namespace std;
-using namespace sigc;
-
-namespace Evoral {
-
-
-Curve::Curve (const ControlList& cl)
-       : _dirty (true)
-       , _list (cl)
-{
-}
-
-void
-Curve::solve () const
-{
-       uint32_t npoints;
-
-       if (!_dirty) {
-               return;
-       }
-
-       if ((npoints = _list.events().size()) > 2) {
-
-               /* Compute coefficients needed to efficiently compute a constrained spline
-                  curve. See "Constrained Cubic Spline Interpolation" by CJC Kruger
-                  (www.korf.co.uk/spline.pdf) for more details.
-               */
-
-               vector<double> x(npoints);
-               vector<double> y(npoints);
-               uint32_t i;
-               ControlList::EventList::const_iterator xx;
-
-               for (i = 0, xx = _list.events().begin(); xx != _list.events().end(); ++xx, ++i) {
-                       x[i] = (double) (*xx)->when;
-                       y[i] = (double) (*xx)->value;
-               }
-
-               double lp0, lp1, fpone;
-
-               lp0 = (x[1] - x[0])/(y[1] - y[0]);
-               lp1 = (x[2] - x[1])/(y[2] - y[1]);
-
-               if (lp0*lp1 < 0) {
-                       fpone = 0;
-               } else {
-                       fpone = 2 / (lp1 + lp0);
-               }
-
-               double fplast = 0;
-
-               for (i = 0, xx = _list.events().begin(); xx != _list.events().end(); ++xx, ++i) {
-
-                       double xdelta;   /* gcc is wrong about possible uninitialized use */
-                       double xdelta2;  /* ditto */
-                       double ydelta;   /* ditto */
-                       double fppL, fppR;
-                       double fpi;
-
-                       if (i > 0) {
-                               xdelta = x[i] - x[i-1];
-                               xdelta2 = xdelta * xdelta;
-                               ydelta = y[i] - y[i-1];
-                       }
-
-                       /* compute (constrained) first derivatives */
-
-                       if (i == 0) {
-
-                               /* first segment */
-
-                               fplast = ((3 * (y[1] - y[0]) / (2 * (x[1] - x[0]))) - (fpone * 0.5));
-
-                               /* we don't store coefficients for i = 0 */
-
-                               continue;
-
-                       } else if (i == npoints - 1) {
-
-                               /* last segment */
-
-                               fpi = ((3 * ydelta) / (2 * xdelta)) - (fplast * 0.5);
-
-                       } else {
-
-                               /* all other segments */
-
-                               double slope_before = ((x[i+1] - x[i]) / (y[i+1] - y[i]));
-                               double slope_after = (xdelta / ydelta);
-
-                               if (slope_after * slope_before < 0.0) {
-                                       /* slope changed sign */
-                                       fpi = 0.0;
-                               } else {
-                                       fpi = 2 / (slope_before + slope_after);
-                               }
-                       }
-
-                       /* compute second derivative for either side of control point `i' */
-
-                       fppL = (((-2 * (fpi + (2 * fplast))) / (xdelta))) +
-                               ((6 * ydelta) / xdelta2);
-
-                       fppR = (2 * ((2 * fpi) + fplast) / xdelta) -
-                               ((6 * ydelta) / xdelta2);
-
-                       /* compute polynomial coefficients */
-
-                       double b, c, d;
-
-                       d = (fppR - fppL) / (6 * xdelta);
-                       c = ((x[i] * fppL) - (x[i-1] * fppR))/(2 * xdelta);
-
-                       double xim12, xim13;
-                       double xi2, xi3;
-
-                       xim12 = x[i-1] * x[i-1];  /* "x[i-1] squared" */
-                       xim13 = xim12 * x[i-1];   /* "x[i-1] cubed" */
-                       xi2 = x[i] * x[i];        /* "x[i] squared" */
-                       xi3 = xi2 * x[i];         /* "x[i] cubed" */
-
-                       b = (ydelta - (c * (xi2 - xim12)) - (d * (xi3 - xim13))) / xdelta;
-
-                       /* store */
-
-                       (*xx)->create_coeffs();
-                       (*xx)->coeff[0] = y[i-1] - (b * x[i-1]) - (c * xim12) - (d * xim13);
-                       (*xx)->coeff[1] = b;
-                       (*xx)->coeff[2] = c;
-                       (*xx)->coeff[3] = d;
-
-                       fplast = fpi;
-               }
-
-       }
-
-       _dirty = false;
-}
-
-bool
-Curve::rt_safe_get_vector (double x0, double x1, float *vec, int32_t veclen) const
-{
-       Glib::Threads::RWLock::ReaderLock lm(_list.lock(), Glib::Threads::TRY_LOCK);
-
-       if (!lm.locked()) {
-               return false;
-       } else {
-               _get_vector (x0, x1, vec, veclen);
-               return true;
-       }
-}
-
-void
-Curve::get_vector (double x0, double x1, float *vec, int32_t veclen) const
-{
-       Glib::Threads::RWLock::ReaderLock lm(_list.lock());
-       _get_vector (x0, x1, vec, veclen);
-}
-
-void
-Curve::_get_vector (double x0, double x1, float *vec, int32_t veclen) const
-{
-       double rx, lx, hx, max_x, min_x;
-       int32_t i;
-       int32_t original_veclen;
-       int32_t npoints;
-
-       if (veclen == 0) {
-               return;
-       }
-
-       if ((npoints = _list.events().size()) == 0) {
-               /* no events in list, so just fill the entire array with the default value */
-               for (int32_t i = 0; i < veclen; ++i) {
-                       vec[i] = _list.descriptor().normal;
-               }
-               return;
-       }
-
-       if (npoints == 1) {
-               for (int32_t i = 0; i < veclen; ++i) {
-                       vec[i] = _list.events().front()->value;
-               }
-               return;
-       }
-
-       /* events is now known not to be empty */
-
-       max_x = _list.events().back()->when;
-       min_x = _list.events().front()->when;
-
-       if (x0 > max_x) {
-               /* totally past the end - just fill the entire array with the final value */
-               for (int32_t i = 0; i < veclen; ++i) {
-                       vec[i] = _list.events().back()->value;
-               }
-               return;
-       }
-
-       if (x1 < min_x) {
-               /* totally before the first event - fill the entire array with
-                * the initial value.
-                */
-               for (int32_t i = 0; i < veclen; ++i) {
-                       vec[i] = _list.events().front()->value;
-               }
-               return;
-       }
-
-       original_veclen = veclen;
-
-       if (x0 < min_x) {
-
-               /* fill some beginning section of the array with the
-                  initial (used to be default) value
-               */
-
-               double frac = (min_x - x0) / (x1 - x0);
-               int64_t fill_len = (int64_t) floor (veclen * frac);
-
-               fill_len = min (fill_len, (int64_t)veclen);
-
-               for (i = 0; i < fill_len; ++i) {
-                       vec[i] = _list.events().front()->value;
-               }
-
-               veclen -= fill_len;
-               vec += fill_len;
-       }
-
-       if (veclen && x1 > max_x) {
-
-               /* fill some end section of the array with the default or final value */
-
-               double frac = (x1 - max_x) / (x1 - x0);
-               int64_t fill_len = (int64_t) floor (original_veclen * frac);
-               float val;
-
-               fill_len = min (fill_len, (int64_t)veclen);
-               val = _list.events().back()->value;
-
-               for (i = veclen - fill_len; i < veclen; ++i) {
-                       vec[i] = val;
-               }
-
-               veclen -= fill_len;
-       }
-
-       lx = max (min_x, x0);
-       hx = min (max_x, x1);
-
-       if (npoints == 2) {
-
-               const double lpos = _list.events().front()->when;
-               const double lval = _list.events().front()->value;
-               const double upos = _list.events().back()->when;
-               const double uval = _list.events().back()->value;
-
-               /* dx that we are using */
-               if (veclen > 1) {
-                       const double dx_num = hx - lx;
-                       const double dx_den = veclen - 1;
-                       const double lower = _list.descriptor().lower;
-                       const double upper = _list.descriptor().upper;
-
-                       /* gradient of the line */
-                       const double m_num = uval - lval;
-                       const double m_den = upos - lpos;
-                       /* y intercept of the line */
-                       const double c = uval - (m_num * upos / m_den);
-
-                       switch (_list.interpolation()) {
-                               case ControlList::Logarithmic:
-                                       for (int i = 0; i < veclen; ++i) {
-                                               const double fraction = (lx - lpos + i * dx_num / dx_den) / m_den;
-                                               vec[i] = interpolate_logarithmic (lval, uval, fraction, lower, upper);
-                                       }
-                                       break;
-                               case ControlList::Exponential:
-                                       for (int i = 0; i < veclen; ++i) {
-                                               const double fraction = (lx - lpos + i * dx_num / dx_den) / m_den;
-                                               vec[i] = interpolate_gain (lval, uval, fraction, upper);
-                                       }
-                                       break;
-                               case ControlList::Discrete:
-                                       // any discrete vector curves somewhere?
-                                       assert (0);
-                               case ControlList::Curved:
-                                       /* no 2 point spline */
-                                       /* fallthrough */
-                               default: // Linear:
-                                       for (int i = 0; i < veclen; ++i) {
-                                               vec[i] = (lx * (m_num / m_den) + m_num * i * dx_num / (m_den * dx_den)) + c;
-                                       }
-                                       break;
-                       }
-               } else {
-                       double fraction = (lx - lpos) / (upos - lpos);
-                       switch (_list.interpolation()) {
-                               case ControlList::Logarithmic:
-                                       vec[0] = interpolate_logarithmic (lval, uval, fraction, _list.descriptor().lower, _list.descriptor().upper);
-                                       break;
-                               case ControlList::Exponential:
-                                       vec[0] = interpolate_gain (lval, uval, fraction, _list.descriptor().upper);
-                                       break;
-                               case ControlList::Discrete:
-                                       // any discrete vector curves somewhere?
-                                       assert (0);
-                               case ControlList::Curved:
-                                       /* no 2 point spline */
-                                       /* fallthrough */
-                               default: // Linear:
-                                       vec[0] = interpolate_linear (lval, uval, fraction);
-                                       break;
-                       }
-               }
-
-               return;
-       }
-
-       if (_dirty) {
-               solve ();
-       }
-
-       rx = lx;
-
-       double dx = 0;
-       if (veclen > 1) {
-               dx = (hx - lx) / (veclen - 1);
-       }
-
-       for (i = 0; i < veclen; ++i, rx += dx) {
-               vec[i] = multipoint_eval (rx);
-       }
-}
-
-double
-Curve::multipoint_eval (double x) const
-{
-       pair<ControlList::EventList::const_iterator,ControlList::EventList::const_iterator> range;
-
-       ControlList::LookupCache& lookup_cache = _list.lookup_cache();
-
-       if ((lookup_cache.left < 0) ||
-           ((lookup_cache.left > x) ||
-            (lookup_cache.range.first == _list.events().end()) ||
-            ((*lookup_cache.range.second)->when < x))) {
-
-               ControlEvent cp (x, 0.0);
-
-               lookup_cache.range = equal_range (_list.events().begin(), _list.events().end(), &cp, ControlList::time_comparator);
-       }
-
-       range = lookup_cache.range;
-
-       /* EITHER
-
-          a) x is an existing control point, so first == existing point, second == next point
-
-          OR
-
-          b) x is between control points, so range is empty (first == second, points to where
-              to insert x)
-
-       */
-
-       if (range.first == range.second) {
-
-               /* x does not exist within the list as a control point */
-
-               lookup_cache.left = x;
-
-               if (range.first == _list.events().begin()) {
-                       /* we're before the first point */
-                       // return default_value;
-                       return _list.events().front()->value;
-               }
-
-               if (range.second == _list.events().end()) {
-                       /* we're after the last point */
-                       return _list.events().back()->value;
-               }
-
-               ControlEvent* after = (*range.second);
-               range.second--;
-               ControlEvent* before = (*range.second);
-
-               double vdelta = after->value - before->value;
-
-               if (vdelta == 0.0) {
-                       return before->value;
-               }
-
-               double tdelta = x - before->when;
-               double trange = after->when - before->when;
-
-               switch (_list.interpolation()) {
-                       case ControlList::Discrete:
-                               return before->value;
-                       case ControlList::Logarithmic:
-                               return interpolate_logarithmic (before->value, after->value, tdelta / trange, _list.descriptor().lower, _list.descriptor().upper);
-                       case ControlList::Exponential:
-                               return interpolate_gain (before->value, after->value, tdelta / trange, _list.descriptor().upper);
-                       case ControlList::Curved:
-                               if (after->coeff) {
-                                       ControlEvent* ev = after;
-                                       double x2 = x * x;
-                                       return ev->coeff[0] + (ev->coeff[1] * x) + (ev->coeff[2] * x2) + (ev->coeff[3] * x2 * x);
-                               }
-                               /* fallthrough */
-                       case ControlList::Linear:
-                               return before->value + (vdelta * (tdelta / trange));
-               }
-       }
-
-       /* x is a control point in the data */
-       /* invalidate the cached range because its not usable */
-       lookup_cache.left = -1;
-       return (*range.first)->value;
-}
-
-} // namespace Evoral
diff --git a/libs/evoral/src/Event.cc b/libs/evoral/src/Event.cc
new file mode 100644 (file)
index 0000000..a2c1cdd
--- /dev/null
@@ -0,0 +1,173 @@
+/*
+ * Copyright (C) 2008-2016 David Robillard <d@drobilla.net>
+ * Copyright (C) 2009-2017 Paul Davis <paul@linuxaudiosystems.com>
+ * Copyright (C) 2010 Carl Hetherington <carl@carlh.net>
+ * Copyright (C) 2014-2015 Robin Gareus <robin@gareus.org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include <glib.h>
+
+#include "temporal/beats.h"
+#include "evoral/Event.h"
+
+namespace Evoral {
+
+static event_id_t _event_id_counter = 0;
+
+event_id_t
+event_id_counter()
+{
+       return g_atomic_int_get (&_event_id_counter);
+}
+
+void
+init_event_id_counter(event_id_t n)
+{
+       g_atomic_int_set (&_event_id_counter, n);
+}
+
+event_id_t
+next_event_id ()
+{
+       /* TODO: handle 31bit overflow , event_id_t is an int32_t,
+        * and libsmf only supports loading uint32_t vlq's, see smf_extract_vlq()
+        *
+        * event-IDs only have to be unique per .mid file.
+        * Previously (Ardour 4.2ish) Ardour re-generated those IDs when loading the
+        * file but that lead to .mid files being modified on every load/save.
+        *
+        * current user-record: is event-counter="276390506" (just abov 2^28)
+        */
+       return g_atomic_int_add (&_event_id_counter, 1);
+}
+
+#ifdef EVORAL_EVENT_ALLOC
+
+template<typename Timestamp>
+Event<Timestamp>::Event(EventType type, Timestamp time, uint32_t size, uint8_t* buf, bool alloc)
+       : _type(type)
+       , _time(time)
+       , _size(size)
+       , _buf(buf)
+       , _id(-1)
+       , _owns_buf(alloc)
+{
+       if (alloc) {
+               _buf = (uint8_t*)malloc(_size);
+               if (buf) {
+                       memcpy(_buf, buf, _size);
+               } else {
+                       memset(_buf, 0, _size);
+               }
+       }
+}
+
+template<typename Timestamp>
+Event<Timestamp>::Event(EventType      type,
+                        Timestamp      time,
+                        uint32_t       size,
+                        const uint8_t* buf)
+       : _type(type)
+       , _time(time)
+       , _size(size)
+       , _buf((uint8_t*)malloc(size))
+       , _id(-1)
+       , _owns_buf(true)
+{
+       memcpy(_buf, buf, _size);
+}
+
+template<typename Timestamp>
+Event<Timestamp>::Event(const Event& copy, bool owns_buf)
+       : _type(copy._type)
+       , _time(copy._time)
+       , _size(copy._size)
+       , _buf(copy._buf)
+       , _id (next_event_id ())
+       , _owns_buf(owns_buf)
+{
+       if (owns_buf) {
+               _buf = (uint8_t*)malloc(_size);
+               if (copy._buf) {
+                       memcpy(_buf, copy._buf, _size);
+               } else {
+                       memset(_buf, 0, _size);
+               }
+       }
+}
+
+template<typename Timestamp>
+Event<Timestamp>::~Event() {
+       if (_owns_buf) {
+               free(_buf);
+       }
+}
+
+template<typename Timestamp>
+void
+Event<Timestamp>::assign(const Event& other)
+{
+       _id = other._id;
+       _type = other._type;
+       _time = other._time;
+       _owns_buf = other._owns_buf;
+       if (_owns_buf) {
+               if (other._buf) {
+                       if (other._size > _size) {
+                               _buf = (uint8_t*)::realloc(_buf, other._size);
+                       }
+                       memcpy(_buf, other._buf, other._size);
+               } else {
+                       free(_buf);
+                       _buf = NULL;
+               }
+       } else {
+               _buf = other._buf;
+       }
+
+       _size = other._size;
+}
+
+template<typename Timestamp>
+void
+Event<Timestamp>::set (const uint8_t* buf, uint32_t size, Timestamp t)
+{
+       if (_owns_buf) {
+               if (_size < size) {
+                       _buf = (uint8_t*) ::realloc(_buf, size);
+               }
+               memcpy (_buf, buf, size);
+       } else {
+               /* XXX this is really dangerous given the
+                  const-ness of buf. The API should really
+                  intervene here.
+               */
+               _buf = const_cast<uint8_t*> (buf);
+       }
+
+       _time = t;
+       _size = size;
+}
+
+#endif // EVORAL_EVENT_ALLOC
+
+template class Event<Temporal::Beats>;
+template class Event<double>;
+template class Event<int64_t>;
+
+} // namespace Evoral
+
diff --git a/libs/evoral/src/Event.cpp b/libs/evoral/src/Event.cpp
deleted file mode 100644 (file)
index 91496f3..0000000
+++ /dev/null
@@ -1,173 +0,0 @@
-/*
- * Copyright (C) 2008-2016 David Robillard <d@drobilla.net>
- * Copyright (C) 2009-2017 Paul Davis <paul@linuxaudiosystems.com>
- * Copyright (C) 2010 Carl Hetherington <carl@carlh.net>
- * Copyright (C) 2014-2015 Robin Gareus <robin@gareus.org>
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-
-#include <glib.h>
-
-#include "temporal/beats.h"
-#include "evoral/Event.hpp"
-
-namespace Evoral {
-
-static event_id_t _event_id_counter = 0;
-
-event_id_t
-event_id_counter()
-{
-       return g_atomic_int_get (&_event_id_counter);
-}
-
-void
-init_event_id_counter(event_id_t n)
-{
-       g_atomic_int_set (&_event_id_counter, n);
-}
-
-event_id_t
-next_event_id ()
-{
-       /* TODO: handle 31bit overflow , event_id_t is an int32_t,
-        * and libsmf only supports loading uint32_t vlq's, see smf_extract_vlq()
-        *
-        * event-IDs only have to be unique per .mid file.
-        * Previously (Ardour 4.2ish) Ardour re-generated those IDs when loading the
-        * file but that lead to .mid files being modified on every load/save.
-        *
-        * current user-record: is event-counter="276390506" (just abov 2^28)
-        */
-       return g_atomic_int_add (&_event_id_counter, 1);
-}
-
-#ifdef EVORAL_EVENT_ALLOC
-
-template<typename Timestamp>
-Event<Timestamp>::Event(EventType type, Timestamp time, uint32_t size, uint8_t* buf, bool alloc)
-       : _type(type)
-       , _time(time)
-       , _size(size)
-       , _buf(buf)
-       , _id(-1)
-       , _owns_buf(alloc)
-{
-       if (alloc) {
-               _buf = (uint8_t*)malloc(_size);
-               if (buf) {
-                       memcpy(_buf, buf, _size);
-               } else {
-                       memset(_buf, 0, _size);
-               }
-       }
-}
-
-template<typename Timestamp>
-Event<Timestamp>::Event(EventType      type,
-                        Timestamp      time,
-                        uint32_t       size,
-                        const uint8_t* buf)
-       : _type(type)
-       , _time(time)
-       , _size(size)
-       , _buf((uint8_t*)malloc(size))
-       , _id(-1)
-       , _owns_buf(true)
-{
-       memcpy(_buf, buf, _size);
-}
-
-template<typename Timestamp>
-Event<Timestamp>::Event(const Event& copy, bool owns_buf)
-       : _type(copy._type)
-       , _time(copy._time)
-       , _size(copy._size)
-       , _buf(copy._buf)
-       , _id (next_event_id ())
-       , _owns_buf(owns_buf)
-{
-       if (owns_buf) {
-               _buf = (uint8_t*)malloc(_size);
-               if (copy._buf) {
-                       memcpy(_buf, copy._buf, _size);
-               } else {
-                       memset(_buf, 0, _size);
-               }
-       }
-}
-
-template<typename Timestamp>
-Event<Timestamp>::~Event() {
-       if (_owns_buf) {
-               free(_buf);
-       }
-}
-
-template<typename Timestamp>
-void
-Event<Timestamp>::assign(const Event& other)
-{
-       _id = other._id;
-       _type = other._type;
-       _time = other._time;
-       _owns_buf = other._owns_buf;
-       if (_owns_buf) {
-               if (other._buf) {
-                       if (other._size > _size) {
-                               _buf = (uint8_t*)::realloc(_buf, other._size);
-                       }
-                       memcpy(_buf, other._buf, other._size);
-               } else {
-                       free(_buf);
-                       _buf = NULL;
-               }
-       } else {
-               _buf = other._buf;
-       }
-
-       _size = other._size;
-}
-
-template<typename Timestamp>
-void
-Event<Timestamp>::set (const uint8_t* buf, uint32_t size, Timestamp t)
-{
-       if (_owns_buf) {
-               if (_size < size) {
-                       _buf = (uint8_t*) ::realloc(_buf, size);
-               }
-               memcpy (_buf, buf, size);
-       } else {
-               /* XXX this is really dangerous given the
-                  const-ness of buf. The API should really
-                  intervene here.
-               */
-               _buf = const_cast<uint8_t*> (buf);
-       }
-
-       _time = t;
-       _size = size;
-}
-
-#endif // EVORAL_EVENT_ALLOC
-
-template class Event<Temporal::Beats>;
-template class Event<double>;
-template class Event<int64_t>;
-
-} // namespace Evoral
-
diff --git a/libs/evoral/src/Note.cc b/libs/evoral/src/Note.cc
new file mode 100644 (file)
index 0000000..a703a85
--- /dev/null
@@ -0,0 +1,97 @@
+/*
+ * Copyright (C) 2008-2016 David Robillard <d@drobilla.net>
+ * Copyright (C) 2009-2017 Paul Davis <paul@linuxaudiosystems.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include <cassert>
+#include <iostream>
+#include <limits>
+#include <glib.h>
+#ifndef COMPILER_MSVC
+#include "evoral/Note.h"
+#endif
+
+#include "temporal/beats.h"
+
+namespace Evoral {
+
+template<typename Time>
+Note<Time>::Note(uint8_t chan, Time t, Time l, uint8_t n, uint8_t v)
+       : _on_event (MIDI_EVENT, t, 3, NULL, true)
+       , _off_event (MIDI_EVENT, t + l, 3, NULL, true)
+{
+       assert(chan < 16);
+
+       _on_event.buffer()[0] = MIDI_CMD_NOTE_ON + chan;
+       _on_event.buffer()[1] = n;
+       _on_event.buffer()[2] = v;
+
+       _off_event.buffer()[0] = MIDI_CMD_NOTE_OFF + chan;
+       _off_event.buffer()[1] = n;
+       _off_event.buffer()[2] = 0x40;
+
+       assert(time() == t);
+       assert(length() == l);
+       assert(note() == n);
+       assert(velocity() == v);
+       assert(_on_event.channel() == _off_event.channel());
+       assert(channel() == chan);
+}
+
+
+template<typename Time>
+Note<Time>::Note(const Note<Time>& copy)
+       : _on_event(copy._on_event, true)
+       , _off_event(copy._off_event, true)
+{
+       assert(_on_event.buffer());
+       assert(_off_event.buffer());
+       /*
+         assert(copy._on_event.size == 3);
+         _on_event.buffer = _on_event_buffer;
+         memcpy(_on_event_buffer, copy._on_event_buffer, 3);
+
+         assert(copy._off_event.size == 3);
+         _off_event.buffer = _off_event_buffer;
+         memcpy(_off_event_buffer, copy._off_event_buffer, 3);
+       */
+
+       assert(time() == copy.time());
+       assert(end_time() == copy.end_time());
+       assert(length() == copy.length());
+       assert(note() == copy.note());
+       assert(velocity() == copy.velocity());
+       assert(_on_event.channel() == _off_event.channel());
+       assert(channel() == copy.channel());
+}
+
+template<typename Time>
+Note<Time>::~Note()
+{
+}
+
+template<typename Time> void
+Note<Time>::set_id (event_id_t id)
+{
+       _on_event.set_id (id);
+       _off_event.set_id (id);
+}
+
+template class Note<Temporal::Beats>;
+
+} // namespace Evoral
+
diff --git a/libs/evoral/src/Note.cpp b/libs/evoral/src/Note.cpp
deleted file mode 100644 (file)
index 00d0220..0000000
+++ /dev/null
@@ -1,97 +0,0 @@
-/*
- * Copyright (C) 2008-2016 David Robillard <d@drobilla.net>
- * Copyright (C) 2009-2017 Paul Davis <paul@linuxaudiosystems.com>
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-
-#include <cassert>
-#include <iostream>
-#include <limits>
-#include <glib.h>
-#ifndef COMPILER_MSVC
-#include "evoral/Note.hpp"
-#endif
-
-#include "temporal/beats.h"
-
-namespace Evoral {
-
-template<typename Time>
-Note<Time>::Note(uint8_t chan, Time t, Time l, uint8_t n, uint8_t v)
-       : _on_event (MIDI_EVENT, t, 3, NULL, true)
-       , _off_event (MIDI_EVENT, t + l, 3, NULL, true)
-{
-       assert(chan < 16);
-
-       _on_event.buffer()[0] = MIDI_CMD_NOTE_ON + chan;
-       _on_event.buffer()[1] = n;
-       _on_event.buffer()[2] = v;
-
-       _off_event.buffer()[0] = MIDI_CMD_NOTE_OFF + chan;
-       _off_event.buffer()[1] = n;
-       _off_event.buffer()[2] = 0x40;
-
-       assert(time() == t);
-       assert(length() == l);
-       assert(note() == n);
-       assert(velocity() == v);
-       assert(_on_event.channel() == _off_event.channel());
-       assert(channel() == chan);
-}
-
-
-template<typename Time>
-Note<Time>::Note(const Note<Time>& copy)
-       : _on_event(copy._on_event, true)
-       , _off_event(copy._off_event, true)
-{
-       assert(_on_event.buffer());
-       assert(_off_event.buffer());
-       /*
-         assert(copy._on_event.size == 3);
-         _on_event.buffer = _on_event_buffer;
-         memcpy(_on_event_buffer, copy._on_event_buffer, 3);
-
-         assert(copy._off_event.size == 3);
-         _off_event.buffer = _off_event_buffer;
-         memcpy(_off_event_buffer, copy._off_event_buffer, 3);
-       */
-
-       assert(time() == copy.time());
-       assert(end_time() == copy.end_time());
-       assert(length() == copy.length());
-       assert(note() == copy.note());
-       assert(velocity() == copy.velocity());
-       assert(_on_event.channel() == _off_event.channel());
-       assert(channel() == copy.channel());
-}
-
-template<typename Time>
-Note<Time>::~Note()
-{
-}
-
-template<typename Time> void
-Note<Time>::set_id (event_id_t id)
-{
-       _on_event.set_id (id);
-       _off_event.set_id (id);
-}
-
-template class Note<Temporal::Beats>;
-
-} // namespace Evoral
-
diff --git a/libs/evoral/src/SMF.cc b/libs/evoral/src/SMF.cc
new file mode 100644 (file)
index 0000000..aebc41e
--- /dev/null
@@ -0,0 +1,603 @@
+/*
+ * Copyright (C) 2008-2015 David Robillard <d@drobilla.net>
+ * Copyright (C) 2009-2018 Paul Davis <paul@linuxaudiosystems.com>
+ * Copyright (C) 2009 Hans Baier <hansfbaier@googlemail.com>
+ * Copyright (C) 2010-2012 Carl Hetherington <carl@carlh.net>
+ * Copyright (C) 2014-2018 Robin Gareus <robin@gareus.org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include <algorithm>
+#include <cassert>
+#include <cmath>
+#include <iostream>
+#include <stdint.h>
+
+#include <glib/gstdio.h>
+
+#include "libsmf/smf.h"
+
+#include "evoral/Event.h"
+#include "evoral/SMF.h"
+#include "evoral/midi_util.h"
+
+#ifdef COMPILER_MSVC
+extern double round(double x);
+#endif
+
+using namespace std;
+
+namespace Evoral {
+
+SMF::SMF()
+       : _smf (0)
+       , _smf_track (0)
+       , _empty (true)
+       , _type0 (false)
+       {};
+
+SMF::~SMF()
+{
+       close ();
+}
+
+uint16_t
+SMF::num_tracks() const
+{
+       Glib::Threads::Mutex::Lock lm (_smf_lock);
+       return _smf ? _smf->number_of_tracks : 0;
+}
+
+uint16_t
+SMF::ppqn() const
+{
+       Glib::Threads::Mutex::Lock lm (_smf_lock);
+       return _smf->ppqn;
+}
+
+/** Seek to the specified track (1-based indexing)
+ * \return 0 on success
+ */
+int
+SMF::seek_to_track(int track)
+{
+       Glib::Threads::Mutex::Lock lm (_smf_lock);
+       _smf_track = smf_get_track_by_number(_smf, track);
+       if (_smf_track != NULL) {
+               _smf_track->next_event_number = (_smf_track->number_of_events == 0) ? 0 : 1;
+               return 0;
+       } else {
+               return -1;
+       }
+}
+
+/** Attempt to open the SMF file just to see if it is valid.
+ *
+ * \return  true on success
+ *          false on failure
+ */
+bool
+SMF::test(const std::string& path)
+{
+       FILE* f = g_fopen(path.c_str(), "r");
+       if (f == 0) {
+               return false;
+       }
+
+       smf_t* test_smf = smf_load(f);
+       fclose(f);
+
+       if (!test_smf) {
+               return false;
+       }
+       if (test_smf) {
+               smf_delete(test_smf);
+       }
+       return true;
+}
+
+/** Attempt to open the SMF file for reading and/or writing.
+ *
+ * \return  0 on success
+ *         -1 if the file can not be opened or created
+ *         -2 if the file exists but specified track does not exist
+ */
+int
+SMF::open(const std::string& path, int track)
+{
+       Glib::Threads::Mutex::Lock lm (_smf_lock);
+
+       _type0 = false;
+       _type0channels.clear ();
+
+       assert(track >= 1);
+       if (_smf) {
+               smf_delete(_smf);
+       }
+
+       FILE* f = g_fopen(path.c_str(), "r");
+       if (f == 0) {
+               return -1;
+       } else if ((_smf = smf_load(f)) == 0) {
+               fclose(f);
+               return -1;
+       } else if ((_smf_track = smf_get_track_by_number(_smf, track)) == 0) {
+               fclose(f);
+               return -2;
+       }
+
+       //cerr << "Track " << track << " # events: " << _smf_track->number_of_events << endl;
+       if (_smf_track->number_of_events == 0) {
+               _smf_track->next_event_number = 0;
+               _empty = true;
+       } else {
+               _smf_track->next_event_number = 1;
+               _empty = false;
+       }
+
+       fclose(f);
+
+       lm.release ();
+       if (_smf->format == 0 && _smf->number_of_tracks == 1 && !_empty) {
+               // type-0 file: scan file for # of used channels.
+               int ret;
+               uint32_t delta_t = 0;
+               uint32_t size    = 0;
+               uint8_t* buf     = NULL;
+               event_id_t event_id = 0;
+               seek_to_start();
+               while ((ret = read_event (&delta_t, &size, &buf, &event_id)) >= 0) {
+                       if (ret == 0) {
+                               continue;
+                       }
+                       if (size == 0) {
+                               break;
+                       }
+                       uint8_t type = buf[0] & 0xf0;
+                       uint8_t chan = buf[0] & 0x0f;
+                       if (type < 0x80 || type > 0xE0) {
+                               continue;
+                       }
+                       _type0channels.insert(chan);
+               }
+               _type0 = true;
+               seek_to_start();
+       }
+       return 0;
+}
+
+
+/** Attempt to create a new SMF file for reading and/or writing.
+ *
+ * \return  0 on success
+ *         -1 if the file can not be created
+ *         -2 if the track can not be created
+ */
+int
+SMF::create(const std::string& path, int track, uint16_t ppqn)
+{
+       Glib::Threads::Mutex::Lock lm (_smf_lock);
+
+       assert(track >= 1);
+       if (_smf) {
+               smf_delete(_smf);
+       }
+
+       _smf = smf_new();
+
+       if (_smf == NULL) {
+               return -1;
+       }
+
+       if (smf_set_ppqn(_smf, ppqn) != 0) {
+               return -1;
+       }
+
+       for (int i = 0; i < track; ++i) {
+               _smf_track = smf_track_new();
+               if (!_smf_track) {
+                       return -2;
+               }
+               smf_add_track(_smf, _smf_track);
+       }
+
+       _smf_track = smf_get_track_by_number(_smf, track);
+       if (!_smf_track)
+               return -2;
+
+       _smf_track->next_event_number = 0;
+
+       {
+               /* put a stub file on disk */
+
+               FILE* f = g_fopen (path.c_str(), "w+");
+               if (f == 0) {
+                       return -1;
+               }
+
+               if (smf_save (_smf, f)) {
+                       fclose (f);
+                       return -1;
+               }
+               fclose (f);
+       }
+
+       _empty = true;
+       _type0 = false;
+       _type0channels.clear ();
+
+       return 0;
+}
+
+void
+SMF::close()
+{
+       Glib::Threads::Mutex::Lock lm (_smf_lock);
+
+       if (_smf) {
+               smf_delete(_smf);
+               _smf = 0;
+               _smf_track = 0;
+               _type0 = false;
+               _type0channels.clear ();
+       }
+}
+
+void
+SMF::seek_to_start() const
+{
+       Glib::Threads::Mutex::Lock lm (_smf_lock);
+       if (_smf_track) {
+               _smf_track->next_event_number = std::min(_smf_track->number_of_events, (size_t)1);
+       } else {
+               cerr << "WARNING: SMF seek_to_start() with no track" << endl;
+       }
+}
+
+/** Read an event from the current position in file.
+ *
+ * File position MUST be at the beginning of a delta time, or this will die very messily.
+ * ev.buffer must be of size ev.size, and large enough for the event.  The returned event
+ * will have it's time field set to it's delta time, in SMF tempo-based ticks, using the
+ * rate given by ppqn() (it is the caller's responsibility to calculate a real time).
+ *
+ * \a buf must be a pointer to a buffer allocated with malloc, or a pointer to NULL.
+ * \a size must be the capacity of \a buf.  If it is not large enough, \a buf will
+ * be reallocated and *size will be set to the new size of buf.
+ *
+ * if the event is a meta-event and is an Evoral Note ID, then \a note_id will be set
+ * to the value of the NoteID; otherwise, meta-events will set \a note_id to -1.
+ *
+ * \return event length (including status byte) on success, 0 if event was
+ * a meta event, or -1 on EOF (or end of track).
+ */
+int
+SMF::read_event(uint32_t* delta_t, uint32_t* size, uint8_t** buf, event_id_t* note_id) const
+{
+       Glib::Threads::Mutex::Lock lm (_smf_lock);
+
+       smf_event_t* event;
+
+       assert(delta_t);
+       assert(size);
+       assert(buf);
+       assert(note_id);
+
+       if ((event = smf_track_get_next_event(_smf_track)) != NULL) {
+
+               *delta_t = event->delta_time_pulses;
+
+               if (smf_event_is_metadata(event)) {
+                       *note_id = -1; // "no note id in this meta-event */
+
+                       if (event->midi_buffer[1] == 0x7f) { // Sequencer-specific
+
+                               uint32_t evsize;
+                               uint32_t lenlen;
+
+                               if (smf_extract_vlq (&event->midi_buffer[2], event->midi_buffer_length-2, &evsize, &lenlen) == 0) {
+
+                                       if (event->midi_buffer[2+lenlen] == 0x99 &&  // Evoral
+                                           event->midi_buffer[3+lenlen] == 0x1) { // Evoral Note ID
+
+                                               uint32_t id;
+                                               uint32_t idlen;
+
+                                               if (smf_extract_vlq (&event->midi_buffer[4+lenlen], event->midi_buffer_length-(4+lenlen), &id, &idlen) == 0) {
+                                                       *note_id = id;
+                                               }
+                                       }
+                               }
+                       }
+                       return 0; /* this is a meta-event */
+               }
+
+               int event_size = event->midi_buffer_length;
+               assert(event_size > 0);
+
+               // Make sure we have enough scratch buffer
+               if (*size < (unsigned)event_size) {
+                       *buf = (uint8_t*)realloc(*buf, event_size);
+               }
+               assert (*buf);
+               memcpy(*buf, event->midi_buffer, size_t(event_size));
+               *size = event_size;
+               if (((*buf)[0] & 0xF0) == 0x90 && (*buf)[2] == 0) {
+                       /* normalize note on with velocity 0 to proper note off */
+                       (*buf)[0] = 0x80 | ((*buf)[0] & 0x0F);  /* note off */
+                       (*buf)[2] = 0x40;  /* default velocity */
+               }
+
+               if (!midi_event_is_valid(*buf, *size)) {
+                       cerr << "WARNING: SMF ignoring illegal MIDI event" << endl;
+                       *size = 0;
+                       return -1;
+               }
+
+               /* printf("SMF::read_event @ %u: ", *delta_t);
+                  for (size_t i = 0; i < *size; ++i) {
+                  printf("%X ", (*buf)[i]);
+                  } printf("\n") */
+
+               return event_size;
+       } else {
+               return -1;
+       }
+}
+
+void
+SMF::append_event_delta(uint32_t delta_t, uint32_t size, const uint8_t* buf, event_id_t note_id)
+{
+       Glib::Threads::Mutex::Lock lm (_smf_lock);
+
+       if (size == 0) {
+               return;
+       }
+
+       /* printf("SMF::append_event_delta @ %u:", delta_t);
+          for (size_t i = 0; i < size; ++i) {
+          printf("%X ", buf[i]);
+          } printf("\n"); */
+
+       switch (buf[0]) {
+       case 0xf1:
+       case 0xf2:
+       case 0xf3:
+       case 0xf4:
+       case 0xf5:
+       case 0xf6:
+       case 0xf8:
+       case 0xf9:
+       case 0xfa:
+       case 0xfb:
+       case 0xfc:
+       case 0xfd:
+       case 0xfe:
+       case 0xff:
+               /* System Real Time or System Common event: not valid in SMF
+                */
+               return;
+       }
+
+       if (!midi_event_is_valid(buf, size)) {
+               cerr << "WARNING: SMF ignoring illegal MIDI event" << endl;
+               return;
+       }
+
+       smf_event_t* event;
+
+       /* XXX july 2010: currently only store event ID's for notes, program changes and bank changes
+        */
+
+       uint8_t const c = buf[0] & 0xf0;
+       bool const store_id = (
+               c == MIDI_CMD_NOTE_ON ||
+               c == MIDI_CMD_NOTE_OFF ||
+               c == MIDI_CMD_NOTE_PRESSURE ||
+               c == MIDI_CMD_PGM_CHANGE ||
+               (c == MIDI_CMD_CONTROL && (buf[1] == MIDI_CTL_MSB_BANK || buf[1] == MIDI_CTL_LSB_BANK))
+                              );
+
+       if (store_id && note_id >= 0) {
+               int idlen;
+               int lenlen;
+               uint8_t idbuf[16];
+               uint8_t lenbuf[16];
+
+               event = smf_event_new ();
+               assert(event != NULL);
+
+               /* generate VLQ representation of note ID */
+               idlen = smf_format_vlq (idbuf, sizeof(idbuf), note_id);
+
+               /* generate VLQ representation of meta event length,
+                  which is the idlen + 2 bytes (Evoral type ID plus Note ID type)
+               */
+
+               lenlen = smf_format_vlq (lenbuf, sizeof(lenbuf), idlen+2);
+
+               event->midi_buffer_length = 2 + lenlen + 2 + idlen;
+               /* this should be allocated by malloc(3) because libsmf will
+                  call free(3) on it
+               */
+               event->midi_buffer = (uint8_t*) malloc (sizeof(uint8_t) * event->midi_buffer_length);
+
+               event->midi_buffer[0] = 0xff; // Meta-event
+               event->midi_buffer[1] = 0x7f; // Sequencer-specific
+               memcpy (&event->midi_buffer[2], lenbuf, lenlen);
+               event->midi_buffer[2+lenlen] = 0x99; // Evoral type ID
+               event->midi_buffer[3+lenlen] = 0x1;  // Evoral type Note ID
+               memcpy (&event->midi_buffer[4+lenlen], idbuf, idlen);
+
+               assert(_smf_track);
+               smf_track_add_event_delta_pulses(_smf_track, event, 0);
+       }
+
+       event = smf_event_new_from_pointer(buf, size);
+       assert(event != NULL);
+
+       assert(_smf_track);
+       smf_track_add_event_delta_pulses(_smf_track, event, delta_t);
+       _empty = false;
+}
+
+void
+SMF::begin_write()
+{
+       Glib::Threads::Mutex::Lock lm (_smf_lock);
+
+       assert(_smf_track);
+       smf_track_delete(_smf_track);
+
+       _smf_track = smf_track_new();
+       assert(_smf_track);
+
+       smf_add_track(_smf, _smf_track);
+       assert(_smf->number_of_tracks == 1);
+}
+
+void
+SMF::end_write(string const & path)
+{
+       Glib::Threads::Mutex::Lock lm (_smf_lock);
+
+       if (!_smf) {
+               return;
+       }
+
+       FILE* f = g_fopen (path.c_str(), "w+");
+       if (f == 0) {
+               throw FileError (path);
+       }
+
+       if (smf_save(_smf, f) != 0) {
+               fclose(f);
+               throw FileError (path);
+       }
+
+       fclose(f);
+}
+
+double
+SMF::round_to_file_precision (double val) const
+{
+       double div = ppqn();
+
+       return round (val * div) / div;
+}
+
+void
+SMF::track_names(vector<string>& names) const
+{
+       if (!_smf) {
+               return;
+       }
+
+       names.clear ();
+
+       Glib::Threads::Mutex::Lock lm (_smf_lock);
+
+       for (uint16_t n = 0; n < _smf->number_of_tracks; ++n) {
+               smf_track_t* trk = smf_get_track_by_number (_smf, n+1);
+               if (!trk) {
+                       names.push_back (string());
+               } else {
+                       if (trk->name) {
+                               names.push_back (trk->name);
+                       } else {
+                               names.push_back (string());
+                       }
+               }
+       }
+}
+
+void
+SMF::instrument_names(vector<string>& names) const
+{
+       if (!_smf) {
+               return;
+       }
+
+       names.clear ();
+
+       Glib::Threads::Mutex::Lock lm (_smf_lock);
+
+       for (uint16_t n = 0; n < _smf->number_of_tracks; ++n) {
+               smf_track_t* trk = smf_get_track_by_number (_smf, n+1);
+               if (!trk) {
+                       names.push_back (string());
+               } else {
+                       if (trk->instrument) {
+                               names.push_back (trk->instrument);
+                       } else {
+                               names.push_back (string());
+                       }
+               }
+       }
+}
+
+SMF::Tempo::Tempo (smf_tempo_t* smft)
+       : time_pulses (smft->time_pulses)
+       , time_seconds (smft->time_seconds)
+       , microseconds_per_quarter_note (smft->microseconds_per_quarter_note)
+       , numerator (smft->numerator)
+       , denominator (smft->denominator)
+       , clocks_per_click (smft->clocks_per_click)
+       , notes_per_note (smft->notes_per_note)
+{
+}
+
+int
+SMF::num_tempos () const
+{
+       assert (_smf);
+       return smf_get_tempo_count (_smf);
+}
+
+SMF::Tempo*
+SMF::tempo_at_smf_pulse (size_t smf_pulse) const
+{
+       smf_tempo_t* t = smf_get_tempo_by_seconds (_smf, smf_pulse);
+       if (!t) {
+               return 0;
+       }
+       return new Tempo (t);
+}
+
+SMF::Tempo*
+SMF::tempo_at_seconds (double seconds) const
+{
+       smf_tempo_t* t = smf_get_tempo_by_seconds (_smf, seconds);
+       if (!t) {
+               return 0;
+       }
+       return new Tempo (t);
+}
+
+SMF::Tempo*
+SMF::nth_tempo (size_t n) const
+{
+       assert (_smf);
+
+       smf_tempo_t* t = smf_get_tempo_by_number (_smf, n);
+       if (!t) {
+               return 0;
+       }
+
+       return new Tempo (t);
+}
+
+} // namespace Evoral
diff --git a/libs/evoral/src/SMF.cpp b/libs/evoral/src/SMF.cpp
deleted file mode 100644 (file)
index 8297429..0000000
+++ /dev/null
@@ -1,603 +0,0 @@
-/*
- * Copyright (C) 2008-2015 David Robillard <d@drobilla.net>
- * Copyright (C) 2009-2018 Paul Davis <paul@linuxaudiosystems.com>
- * Copyright (C) 2009 Hans Baier <hansfbaier@googlemail.com>
- * Copyright (C) 2010-2012 Carl Hetherington <carl@carlh.net>
- * Copyright (C) 2014-2018 Robin Gareus <robin@gareus.org>
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-
-#include <algorithm>
-#include <cassert>
-#include <cmath>
-#include <iostream>
-#include <stdint.h>
-
-#include <glib/gstdio.h>
-
-#include "libsmf/smf.h"
-
-#include "evoral/Event.hpp"
-#include "evoral/SMF.hpp"
-#include "evoral/midi_util.h"
-
-#ifdef COMPILER_MSVC
-extern double round(double x);
-#endif
-
-using namespace std;
-
-namespace Evoral {
-
-SMF::SMF()
-       : _smf (0)
-       , _smf_track (0)
-       , _empty (true)
-       , _type0 (false)
-       {};
-
-SMF::~SMF()
-{
-       close ();
-}
-
-uint16_t
-SMF::num_tracks() const
-{
-       Glib::Threads::Mutex::Lock lm (_smf_lock);
-       return _smf ? _smf->number_of_tracks : 0;
-}
-
-uint16_t
-SMF::ppqn() const
-{
-       Glib::Threads::Mutex::Lock lm (_smf_lock);
-       return _smf->ppqn;
-}
-
-/** Seek to the specified track (1-based indexing)
- * \return 0 on success
- */
-int
-SMF::seek_to_track(int track)
-{
-       Glib::Threads::Mutex::Lock lm (_smf_lock);
-       _smf_track = smf_get_track_by_number(_smf, track);
-       if (_smf_track != NULL) {
-               _smf_track->next_event_number = (_smf_track->number_of_events == 0) ? 0 : 1;
-               return 0;
-       } else {
-               return -1;
-       }
-}
-
-/** Attempt to open the SMF file just to see if it is valid.
- *
- * \return  true on success
- *          false on failure
- */
-bool
-SMF::test(const std::string& path)
-{
-       FILE* f = g_fopen(path.c_str(), "r");
-       if (f == 0) {
-               return false;
-       }
-
-       smf_t* test_smf = smf_load(f);
-       fclose(f);
-
-       if (!test_smf) {
-               return false;
-       }
-       if (test_smf) {
-               smf_delete(test_smf);
-       }
-       return true;
-}
-
-/** Attempt to open the SMF file for reading and/or writing.
- *
- * \return  0 on success
- *         -1 if the file can not be opened or created
- *         -2 if the file exists but specified track does not exist
- */
-int
-SMF::open(const std::string& path, int track)
-{
-       Glib::Threads::Mutex::Lock lm (_smf_lock);
-
-       _type0 = false;
-       _type0channels.clear ();
-
-       assert(track >= 1);
-       if (_smf) {
-               smf_delete(_smf);
-       }
-
-       FILE* f = g_fopen(path.c_str(), "r");
-       if (f == 0) {
-               return -1;
-       } else if ((_smf = smf_load(f)) == 0) {
-               fclose(f);
-               return -1;
-       } else if ((_smf_track = smf_get_track_by_number(_smf, track)) == 0) {
-               fclose(f);
-               return -2;
-       }
-
-       //cerr << "Track " << track << " # events: " << _smf_track->number_of_events << endl;
-       if (_smf_track->number_of_events == 0) {
-               _smf_track->next_event_number = 0;
-               _empty = true;
-       } else {
-               _smf_track->next_event_number = 1;
-               _empty = false;
-       }
-
-       fclose(f);
-
-       lm.release ();
-       if (_smf->format == 0 && _smf->number_of_tracks == 1 && !_empty) {
-               // type-0 file: scan file for # of used channels.
-               int ret;
-               uint32_t delta_t = 0;
-               uint32_t size    = 0;
-               uint8_t* buf     = NULL;
-               event_id_t event_id = 0;
-               seek_to_start();
-               while ((ret = read_event (&delta_t, &size, &buf, &event_id)) >= 0) {
-                       if (ret == 0) {
-                               continue;
-                       }
-                       if (size == 0) {
-                               break;
-                       }
-                       uint8_t type = buf[0] & 0xf0;
-                       uint8_t chan = buf[0] & 0x0f;
-                       if (type < 0x80 || type > 0xE0) {
-                               continue;
-                       }
-                       _type0channels.insert(chan);
-               }
-               _type0 = true;
-               seek_to_start();
-       }
-       return 0;
-}
-
-
-/** Attempt to create a new SMF file for reading and/or writing.
- *
- * \return  0 on success
- *         -1 if the file can not be created
- *         -2 if the track can not be created
- */
-int
-SMF::create(const std::string& path, int track, uint16_t ppqn)
-{
-       Glib::Threads::Mutex::Lock lm (_smf_lock);
-
-       assert(track >= 1);
-       if (_smf) {
-               smf_delete(_smf);
-       }
-
-       _smf = smf_new();
-
-       if (_smf == NULL) {
-               return -1;
-       }
-
-       if (smf_set_ppqn(_smf, ppqn) != 0) {
-               return -1;
-       }
-
-       for (int i = 0; i < track; ++i) {
-               _smf_track = smf_track_new();
-               if (!_smf_track) {
-                       return -2;
-               }
-               smf_add_track(_smf, _smf_track);
-       }
-
-       _smf_track = smf_get_track_by_number(_smf, track);
-       if (!_smf_track)
-               return -2;
-
-       _smf_track->next_event_number = 0;
-
-       {
-               /* put a stub file on disk */
-
-               FILE* f = g_fopen (path.c_str(), "w+");
-               if (f == 0) {
-                       return -1;
-               }
-
-               if (smf_save (_smf, f)) {
-                       fclose (f);
-                       return -1;
-               }
-               fclose (f);
-       }
-
-       _empty = true;
-       _type0 = false;
-       _type0channels.clear ();
-
-       return 0;
-}
-
-void
-SMF::close()
-{
-       Glib::Threads::Mutex::Lock lm (_smf_lock);
-
-       if (_smf) {
-               smf_delete(_smf);
-               _smf = 0;
-               _smf_track = 0;
-               _type0 = false;
-               _type0channels.clear ();
-       }
-}
-
-void
-SMF::seek_to_start() const
-{
-       Glib::Threads::Mutex::Lock lm (_smf_lock);
-       if (_smf_track) {
-               _smf_track->next_event_number = std::min(_smf_track->number_of_events, (size_t)1);
-       } else {
-               cerr << "WARNING: SMF seek_to_start() with no track" << endl;
-       }
-}
-
-/** Read an event from the current position in file.
- *
- * File position MUST be at the beginning of a delta time, or this will die very messily.
- * ev.buffer must be of size ev.size, and large enough for the event.  The returned event
- * will have it's time field set to it's delta time, in SMF tempo-based ticks, using the
- * rate given by ppqn() (it is the caller's responsibility to calculate a real time).
- *
- * \a buf must be a pointer to a buffer allocated with malloc, or a pointer to NULL.
- * \a size must be the capacity of \a buf.  If it is not large enough, \a buf will
- * be reallocated and *size will be set to the new size of buf.
- *
- * if the event is a meta-event and is an Evoral Note ID, then \a note_id will be set
- * to the value of the NoteID; otherwise, meta-events will set \a note_id to -1.
- *
- * \return event length (including status byte) on success, 0 if event was
- * a meta event, or -1 on EOF (or end of track).
- */
-int
-SMF::read_event(uint32_t* delta_t, uint32_t* size, uint8_t** buf, event_id_t* note_id) const
-{
-       Glib::Threads::Mutex::Lock lm (_smf_lock);
-
-       smf_event_t* event;
-
-       assert(delta_t);
-       assert(size);
-       assert(buf);
-       assert(note_id);
-
-       if ((event = smf_track_get_next_event(_smf_track)) != NULL) {
-
-               *delta_t = event->delta_time_pulses;
-
-               if (smf_event_is_metadata(event)) {
-                       *note_id = -1; // "no note id in this meta-event */
-
-                       if (event->midi_buffer[1] == 0x7f) { // Sequencer-specific
-
-                               uint32_t evsize;
-                               uint32_t lenlen;
-
-                               if (smf_extract_vlq (&event->midi_buffer[2], event->midi_buffer_length-2, &evsize, &lenlen) == 0) {
-
-                                       if (event->midi_buffer[2+lenlen] == 0x99 &&  // Evoral
-                                           event->midi_buffer[3+lenlen] == 0x1) { // Evoral Note ID
-
-                                               uint32_t id;
-                                               uint32_t idlen;
-
-                                               if (smf_extract_vlq (&event->midi_buffer[4+lenlen], event->midi_buffer_length-(4+lenlen), &id, &idlen) == 0) {
-                                                       *note_id = id;
-                                               }
-                                       }
-                               }
-                       }
-                       return 0; /* this is a meta-event */
-               }
-
-               int event_size = event->midi_buffer_length;
-               assert(event_size > 0);
-
-               // Make sure we have enough scratch buffer
-               if (*size < (unsigned)event_size) {
-                       *buf = (uint8_t*)realloc(*buf, event_size);
-               }
-               assert (*buf);
-               memcpy(*buf, event->midi_buffer, size_t(event_size));
-               *size = event_size;
-               if (((*buf)[0] & 0xF0) == 0x90 && (*buf)[2] == 0) {
-                       /* normalize note on with velocity 0 to proper note off */
-                       (*buf)[0] = 0x80 | ((*buf)[0] & 0x0F);  /* note off */
-                       (*buf)[2] = 0x40;  /* default velocity */
-               }
-
-               if (!midi_event_is_valid(*buf, *size)) {
-                       cerr << "WARNING: SMF ignoring illegal MIDI event" << endl;
-                       *size = 0;
-                       return -1;
-               }
-
-               /* printf("SMF::read_event @ %u: ", *delta_t);
-                  for (size_t i = 0; i < *size; ++i) {
-                  printf("%X ", (*buf)[i]);
-                  } printf("\n") */
-
-               return event_size;
-       } else {
-               return -1;
-       }
-}
-
-void
-SMF::append_event_delta(uint32_t delta_t, uint32_t size, const uint8_t* buf, event_id_t note_id)
-{
-       Glib::Threads::Mutex::Lock lm (_smf_lock);
-
-       if (size == 0) {
-               return;
-       }
-
-       /* printf("SMF::append_event_delta @ %u:", delta_t);
-          for (size_t i = 0; i < size; ++i) {
-          printf("%X ", buf[i]);
-          } printf("\n"); */
-
-       switch (buf[0]) {
-       case 0xf1:
-       case 0xf2:
-       case 0xf3:
-       case 0xf4:
-       case 0xf5:
-       case 0xf6:
-       case 0xf8:
-       case 0xf9:
-       case 0xfa:
-       case 0xfb:
-       case 0xfc:
-       case 0xfd:
-       case 0xfe:
-       case 0xff:
-               /* System Real Time or System Common event: not valid in SMF
-                */
-               return;
-       }
-
-       if (!midi_event_is_valid(buf, size)) {
-               cerr << "WARNING: SMF ignoring illegal MIDI event" << endl;
-               return;
-       }
-
-       smf_event_t* event;
-
-       /* XXX july 2010: currently only store event ID's for notes, program changes and bank changes
-        */
-
-       uint8_t const c = buf[0] & 0xf0;
-       bool const store_id = (
-               c == MIDI_CMD_NOTE_ON ||
-               c == MIDI_CMD_NOTE_OFF ||
-               c == MIDI_CMD_NOTE_PRESSURE ||
-               c == MIDI_CMD_PGM_CHANGE ||
-               (c == MIDI_CMD_CONTROL && (buf[1] == MIDI_CTL_MSB_BANK || buf[1] == MIDI_CTL_LSB_BANK))
-                              );
-
-       if (store_id && note_id >= 0) {
-               int idlen;
-               int lenlen;
-               uint8_t idbuf[16];
-               uint8_t lenbuf[16];
-
-               event = smf_event_new ();
-               assert(event != NULL);
-
-               /* generate VLQ representation of note ID */
-               idlen = smf_format_vlq (idbuf, sizeof(idbuf), note_id);
-
-               /* generate VLQ representation of meta event length,
-                  which is the idlen + 2 bytes (Evoral type ID plus Note ID type)
-               */
-
-               lenlen = smf_format_vlq (lenbuf, sizeof(lenbuf), idlen+2);
-
-               event->midi_buffer_length = 2 + lenlen + 2 + idlen;
-               /* this should be allocated by malloc(3) because libsmf will
-                  call free(3) on it
-               */
-               event->midi_buffer = (uint8_t*) malloc (sizeof(uint8_t) * event->midi_buffer_length);
-
-               event->midi_buffer[0] = 0xff; // Meta-event
-               event->midi_buffer[1] = 0x7f; // Sequencer-specific
-               memcpy (&event->midi_buffer[2], lenbuf, lenlen);
-               event->midi_buffer[2+lenlen] = 0x99; // Evoral type ID
-               event->midi_buffer[3+lenlen] = 0x1;  // Evoral type Note ID
-               memcpy (&event->midi_buffer[4+lenlen], idbuf, idlen);
-
-               assert(_smf_track);
-               smf_track_add_event_delta_pulses(_smf_track, event, 0);
-       }
-
-       event = smf_event_new_from_pointer(buf, size);
-       assert(event != NULL);
-
-       assert(_smf_track);
-       smf_track_add_event_delta_pulses(_smf_track, event, delta_t);
-       _empty = false;
-}
-
-void
-SMF::begin_write()
-{
-       Glib::Threads::Mutex::Lock lm (_smf_lock);
-
-       assert(_smf_track);
-       smf_track_delete(_smf_track);
-
-       _smf_track = smf_track_new();
-       assert(_smf_track);
-
-       smf_add_track(_smf, _smf_track);
-       assert(_smf->number_of_tracks == 1);
-}
-
-void
-SMF::end_write(string const & path)
-{
-       Glib::Threads::Mutex::Lock lm (_smf_lock);
-
-       if (!_smf) {
-               return;
-       }
-
-       FILE* f = g_fopen (path.c_str(), "w+");
-       if (f == 0) {
-               throw FileError (path);
-       }
-
-       if (smf_save(_smf, f) != 0) {
-               fclose(f);
-               throw FileError (path);
-       }
-
-       fclose(f);
-}
-
-double
-SMF::round_to_file_precision (double val) const
-{
-       double div = ppqn();
-
-       return round (val * div) / div;
-}
-
-void
-SMF::track_names(vector<string>& names) const
-{
-       if (!_smf) {
-               return;
-       }
-
-       names.clear ();
-
-       Glib::Threads::Mutex::Lock lm (_smf_lock);
-
-       for (uint16_t n = 0; n < _smf->number_of_tracks; ++n) {
-               smf_track_t* trk = smf_get_track_by_number (_smf, n+1);
-               if (!trk) {
-                       names.push_back (string());
-               } else {
-                       if (trk->name) {
-                               names.push_back (trk->name);
-                       } else {
-                               names.push_back (string());
-                       }
-               }
-       }
-}
-
-void
-SMF::instrument_names(vector<string>& names) const
-{
-       if (!_smf) {
-               return;
-       }
-
-       names.clear ();
-
-       Glib::Threads::Mutex::Lock lm (_smf_lock);
-
-       for (uint16_t n = 0; n < _smf->number_of_tracks; ++n) {
-               smf_track_t* trk = smf_get_track_by_number (_smf, n+1);
-               if (!trk) {
-                       names.push_back (string());
-               } else {
-                       if (trk->instrument) {
-                               names.push_back (trk->instrument);
-                       } else {
-                               names.push_back (string());
-                       }
-               }
-       }
-}
-
-SMF::Tempo::Tempo (smf_tempo_t* smft)
-       : time_pulses (smft->time_pulses)
-       , time_seconds (smft->time_seconds)
-       , microseconds_per_quarter_note (smft->microseconds_per_quarter_note)
-       , numerator (smft->numerator)
-       , denominator (smft->denominator)
-       , clocks_per_click (smft->clocks_per_click)
-       , notes_per_note (smft->notes_per_note)
-{
-}
-
-int
-SMF::num_tempos () const
-{
-       assert (_smf);
-       return smf_get_tempo_count (_smf);
-}
-
-SMF::Tempo*
-SMF::tempo_at_smf_pulse (size_t smf_pulse) const
-{
-       smf_tempo_t* t = smf_get_tempo_by_seconds (_smf, smf_pulse);
-       if (!t) {
-               return 0;
-       }
-       return new Tempo (t);
-}
-
-SMF::Tempo*
-SMF::tempo_at_seconds (double seconds) const
-{
-       smf_tempo_t* t = smf_get_tempo_by_seconds (_smf, seconds);
-       if (!t) {
-               return 0;
-       }
-       return new Tempo (t);
-}
-
-SMF::Tempo*
-SMF::nth_tempo (size_t n) const
-{
-       assert (_smf);
-
-       smf_tempo_t* t = smf_get_tempo_by_number (_smf, n);
-       if (!t) {
-               return 0;
-       }
-
-       return new Tempo (t);
-}
-
-} // namespace Evoral
diff --git a/libs/evoral/src/SMFReader.cc b/libs/evoral/src/SMFReader.cc
new file mode 100644 (file)
index 0000000..601cd7a
--- /dev/null
@@ -0,0 +1,283 @@
+/*
+ * Copyright (C) 2008-2011 David Robillard <d@drobilla.net>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include <cstring>
+#include <cstdio>
+#include <cassert>
+#include <iostream>
+#include <glibmm/miscutils.h>
+
+#include "evoral/midi_util.h"
+#include "evoral/SMFReader.h"
+
+using namespace std;
+
+namespace Evoral {
+
+
+SMFReader::SMFReader(const string& filename)
+       : _fd(NULL)
+       , _ppqn(0)
+       , _track(0)
+       , _track_size(0)
+{
+       if (filename.length() > 0) {
+               open(filename);
+       }
+}
+
+
+SMFReader::~SMFReader()
+{
+       if (_fd)
+               close();
+}
+
+
+bool
+SMFReader::open(const string& filename) throw (logic_error, UnsupportedTime)
+{
+       if (_fd)
+               throw logic_error("Attempt to start new read while write in progress.");
+
+       cout << "Opening SMF file " << filename << " for reading." << endl;
+
+       _fd = fopen(filename.c_str(), "r+");
+
+       if (_fd) {
+               // Read type (bytes 8..9)
+               fseek(_fd, 0, SEEK_SET);
+               char mthd[5];
+               mthd[4] = '\0';
+               fread(mthd, 1, 4, _fd);
+               if (strcmp(mthd, "MThd")) {
+                       cerr << filename << " is not an SMF file, aborting." << endl;
+                       fclose(_fd);
+                       _fd = NULL;
+                       return false;
+               }
+
+               // Read type (bytes 8..9)
+               fseek(_fd, 8, SEEK_SET);
+               uint16_t type_be = 0;
+               fread(&type_be, 2, 1, _fd);
+               _type = GUINT16_FROM_BE(type_be);
+
+               // Read number of tracks (bytes 10..11)
+               uint16_t num_tracks_be = 0;
+               fread(&num_tracks_be, 2, 1, _fd);
+               _num_tracks = GUINT16_FROM_BE(num_tracks_be);
+
+               // Read PPQN (bytes 12..13)
+               uint16_t ppqn_be = 0;
+               fread(&ppqn_be, 2, 1, _fd);
+               _ppqn = GUINT16_FROM_BE(ppqn_be);
+
+               // TODO: Absolute (SMPTE seconds) time support
+               if ((_ppqn & 0x8000) != 0)
+                       throw UnsupportedTime();
+
+               seek_to_track(1);
+
+               return true;
+       } else {
+               return false;
+       }
+}
+
+
+/** Seek to the start of a given track, starting from 1.
+ * Returns true if specified track was found.
+ */
+bool
+SMFReader::seek_to_track(unsigned track) throw (std::logic_error)
+{
+       if (track == 0)
+               throw logic_error("Seek to track 0 out of range (must be >= 1)");
+
+       if (!_fd)
+               throw logic_error("Attempt to seek to track on unopened SMF file.");
+
+       unsigned track_pos = 0;
+
+       fseek(_fd, 14, SEEK_SET);
+       char id[5];
+       id[4] = '\0';
+       uint32_t chunk_size = 0;
+
+       while (!feof(_fd)) {
+               fread(id, 1, 4, _fd);
+
+               if (!strcmp(id, "MTrk")) {
+                       ++track_pos;
+               } else {
+                       std::cerr << "Unknown chunk ID " << id << endl;
+               }
+
+               uint32_t chunk_size_be;
+               fread(&chunk_size_be, 4, 1, _fd);
+               chunk_size = GUINT32_FROM_BE(chunk_size_be);
+
+               if (track_pos == track)
+                       break;
+
+               fseek(_fd, chunk_size, SEEK_CUR);
+       }
+
+       if (!feof(_fd) && track_pos == track) {
+               _track = track;
+               _track_size = chunk_size;
+               return true;
+       } else {
+               return false;
+       }
+}
+
+
+/** Read an event from the current position in file.
+ *
+ * File position MUST be at the beginning of a delta time, or this will die very messily.
+ * ev.buffer must be of size ev.size, and large enough for the event.  The returned event
+ * will have it's time field set to it's delta time (so it's the caller's responsibility
+ * to keep track of delta time, even for ignored events).
+ *
+ * Returns event length (including status byte) on success, 0 if event was
+ * skipped (eg a meta event), or -1 on EOF (or end of track).
+ *
+ * If @a buf is not large enough to hold the event, 0 will be returned, but ev_size
+ * set to the actual size of the event.
+ */
+int
+SMFReader::read_event(size_t    buf_len,
+                      uint8_t*  buf,
+                      uint32_t* ev_size,
+                      uint32_t* delta_time)
+               throw (std::logic_error, PrematureEOF, CorruptFile)
+{
+       if (_track == 0)
+               throw logic_error("Attempt to read from unopened SMF file");
+
+       if (!_fd || feof(_fd)) {
+               return -1;
+       }
+
+       assert(buf_len > 0);
+       assert(buf);
+       assert(ev_size);
+       assert(delta_time);
+
+       // Running status state
+       static uint8_t  last_status = 0;
+       static uint32_t last_size   = 0;
+
+       *delta_time = read_var_len(_fd);
+       int status = fgetc(_fd);
+       if (status == EOF)
+               throw PrematureEOF();
+       else if (status > 0xFF)
+               throw CorruptFile();
+
+       if (status < 0x80) {
+               if (last_status == 0)
+                       throw CorruptFile();
+               status = last_status;
+               *ev_size = last_size;
+               fseek(_fd, -1, SEEK_CUR);
+       } else {
+               last_status = status;
+               *ev_size = midi_event_size(status);
+               last_size = *ev_size;
+       }
+
+       buf[0] = (uint8_t)status;
+
+       if (status == 0xFF) {
+               *ev_size = 0;
+               if (feof(_fd))
+                       throw PrematureEOF();
+               uint8_t type = fgetc(_fd);
+               const uint32_t size = read_var_len(_fd);
+               /*cerr.flags(ios::hex);
+               cerr << "SMF - meta 0x" << (int)type << ", size = ";
+               cerr.flags(ios::dec);
+               cerr << size << endl;*/
+
+               if ((uint8_t)type == 0x2F) {
+                       return -1; // we hit the logical EOF anyway...
+               } else {
+                       fseek(_fd, size, SEEK_CUR);
+                       return 0;
+               }
+       }
+
+       if (*ev_size > buf_len || *ev_size == 0 || feof(_fd)) {
+               //cerr << "SMF - Skipping event" << endl;
+               // Skip event, return 0
+               fseek(_fd, *ev_size - 1, SEEK_CUR);
+               return 0;
+       } else {
+               // Read event, return size
+               if (ferror(_fd))
+                       throw CorruptFile();
+
+               fread(buf+1, 1, *ev_size - 1, _fd);
+
+               if ((buf[0] & 0xF0) == 0x90 && buf[2] == 0) {
+                       buf[0] = (0x80 | (buf[0] & 0x0F));
+                       buf[2] = 0x40;
+               }
+
+               return *ev_size;
+       }
+}
+
+
+void
+SMFReader::close()
+{
+       if (_fd)
+               fclose(_fd);
+
+       _fd = NULL;
+}
+
+
+uint32_t
+SMFReader::read_var_len(FILE* fd) throw (PrematureEOF)
+{
+       if (feof(fd))
+               throw PrematureEOF();
+
+       uint32_t value;
+       uint8_t  c;
+
+       if ( (value = getc(fd)) & 0x80 ) {
+               value &= 0x7F;
+               do {
+                       if (feof(fd))
+                               throw PrematureEOF();
+                       value = (value << 7) + ((c = getc(fd)) & 0x7F);
+               } while (c & 0x80);
+       }
+
+       return value;
+}
+
+
+} // namespace Evoral
+
diff --git a/libs/evoral/src/SMFReader.cpp b/libs/evoral/src/SMFReader.cpp
deleted file mode 100644 (file)
index bbd6e6c..0000000
+++ /dev/null
@@ -1,283 +0,0 @@
-/*
- * Copyright (C) 2008-2011 David Robillard <d@drobilla.net>
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-
-#include <cstring>
-#include <cstdio>
-#include <cassert>
-#include <iostream>
-#include <glibmm/miscutils.h>
-
-#include "evoral/midi_util.h"
-#include "evoral/SMFReader.hpp"
-
-using namespace std;
-
-namespace Evoral {
-
-
-SMFReader::SMFReader(const string& filename)
-       : _fd(NULL)
-       , _ppqn(0)
-       , _track(0)
-       , _track_size(0)
-{
-       if (filename.length() > 0) {
-               open(filename);
-       }
-}
-
-
-SMFReader::~SMFReader()
-{
-       if (_fd)
-               close();
-}
-
-
-bool
-SMFReader::open(const string& filename) throw (logic_error, UnsupportedTime)
-{
-       if (_fd)
-               throw logic_error("Attempt to start new read while write in progress.");
-
-       cout << "Opening SMF file " << filename << " for reading." << endl;
-
-       _fd = fopen(filename.c_str(), "r+");
-
-       if (_fd) {
-               // Read type (bytes 8..9)
-               fseek(_fd, 0, SEEK_SET);
-               char mthd[5];
-               mthd[4] = '\0';
-               fread(mthd, 1, 4, _fd);
-               if (strcmp(mthd, "MThd")) {
-                       cerr << filename << " is not an SMF file, aborting." << endl;
-                       fclose(_fd);
-                       _fd = NULL;
-                       return false;
-               }
-
-               // Read type (bytes 8..9)
-               fseek(_fd, 8, SEEK_SET);
-               uint16_t type_be = 0;
-               fread(&type_be, 2, 1, _fd);
-               _type = GUINT16_FROM_BE(type_be);
-
-               // Read number of tracks (bytes 10..11)
-               uint16_t num_tracks_be = 0;
-               fread(&num_tracks_be, 2, 1, _fd);
-               _num_tracks = GUINT16_FROM_BE(num_tracks_be);
-
-               // Read PPQN (bytes 12..13)
-               uint16_t ppqn_be = 0;
-               fread(&ppqn_be, 2, 1, _fd);
-               _ppqn = GUINT16_FROM_BE(ppqn_be);
-
-               // TODO: Absolute (SMPTE seconds) time support
-               if ((_ppqn & 0x8000) != 0)
-                       throw UnsupportedTime();
-
-               seek_to_track(1);
-
-               return true;
-       } else {
-               return false;
-       }
-}
-
-
-/** Seek to the start of a given track, starting from 1.
- * Returns true if specified track was found.
- */
-bool
-SMFReader::seek_to_track(unsigned track) throw (std::logic_error)
-{
-       if (track == 0)
-               throw logic_error("Seek to track 0 out of range (must be >= 1)");
-
-       if (!_fd)
-               throw logic_error("Attempt to seek to track on unopened SMF file.");
-
-       unsigned track_pos = 0;
-
-       fseek(_fd, 14, SEEK_SET);
-       char id[5];
-       id[4] = '\0';
-       uint32_t chunk_size = 0;
-
-       while (!feof(_fd)) {
-               fread(id, 1, 4, _fd);
-
-               if (!strcmp(id, "MTrk")) {
-                       ++track_pos;
-               } else {
-                       std::cerr << "Unknown chunk ID " << id << endl;
-               }
-
-               uint32_t chunk_size_be;
-               fread(&chunk_size_be, 4, 1, _fd);
-               chunk_size = GUINT32_FROM_BE(chunk_size_be);
-
-               if (track_pos == track)
-                       break;
-
-               fseek(_fd, chunk_size, SEEK_CUR);
-       }
-
-       if (!feof(_fd) && track_pos == track) {
-               _track = track;
-               _track_size = chunk_size;
-               return true;
-       } else {
-               return false;
-       }
-}
-
-
-/** Read an event from the current position in file.
- *
- * File position MUST be at the beginning of a delta time, or this will die very messily.
- * ev.buffer must be of size ev.size, and large enough for the event.  The returned event
- * will have it's time field set to it's delta time (so it's the caller's responsibility
- * to keep track of delta time, even for ignored events).
- *
- * Returns event length (including status byte) on success, 0 if event was
- * skipped (eg a meta event), or -1 on EOF (or end of track).
- *
- * If @a buf is not large enough to hold the event, 0 will be returned, but ev_size
- * set to the actual size of the event.
- */
-int
-SMFReader::read_event(size_t    buf_len,
-                      uint8_t*  buf,
-                      uint32_t* ev_size,
-                      uint32_t* delta_time)
-               throw (std::logic_error, PrematureEOF, CorruptFile)
-{
-       if (_track == 0)
-               throw logic_error("Attempt to read from unopened SMF file");
-
-       if (!_fd || feof(_fd)) {
-               return -1;
-       }
-
-       assert(buf_len > 0);
-       assert(buf);
-       assert(ev_size);
-       assert(delta_time);
-
-       // Running status state
-       static uint8_t  last_status = 0;
-       static uint32_t last_size   = 0;
-
-       *delta_time = read_var_len(_fd);
-       int status = fgetc(_fd);
-       if (status == EOF)
-               throw PrematureEOF();
-       else if (status > 0xFF)
-               throw CorruptFile();
-
-       if (status < 0x80) {
-               if (last_status == 0)
-                       throw CorruptFile();
-               status = last_status;
-               *ev_size = last_size;
-               fseek(_fd, -1, SEEK_CUR);
-       } else {
-               last_status = status;
-               *ev_size = midi_event_size(status);
-               last_size = *ev_size;
-       }
-
-       buf[0] = (uint8_t)status;
-
-       if (status == 0xFF) {
-               *ev_size = 0;
-               if (feof(_fd))
-                       throw PrematureEOF();
-               uint8_t type = fgetc(_fd);
-               const uint32_t size = read_var_len(_fd);
-               /*cerr.flags(ios::hex);
-               cerr << "SMF - meta 0x" << (int)type << ", size = ";
-               cerr.flags(ios::dec);
-               cerr << size << endl;*/
-
-               if ((uint8_t)type == 0x2F) {
-                       return -1; // we hit the logical EOF anyway...
-               } else {
-                       fseek(_fd, size, SEEK_CUR);
-                       return 0;
-               }
-       }
-
-       if (*ev_size > buf_len || *ev_size == 0 || feof(_fd)) {
-               //cerr << "SMF - Skipping event" << endl;
-               // Skip event, return 0
-               fseek(_fd, *ev_size - 1, SEEK_CUR);
-               return 0;
-       } else {
-               // Read event, return size
-               if (ferror(_fd))
-                       throw CorruptFile();
-
-               fread(buf+1, 1, *ev_size - 1, _fd);
-
-               if ((buf[0] & 0xF0) == 0x90 && buf[2] == 0) {
-                       buf[0] = (0x80 | (buf[0] & 0x0F));
-                       buf[2] = 0x40;
-               }
-
-               return *ev_size;
-       }
-}
-
-
-void
-SMFReader::close()
-{
-       if (_fd)
-               fclose(_fd);
-
-       _fd = NULL;
-}
-
-
-uint32_t
-SMFReader::read_var_len(FILE* fd) throw (PrematureEOF)
-{
-       if (feof(fd))
-               throw PrematureEOF();
-
-       uint32_t value;
-       uint8_t  c;
-
-       if ( (value = getc(fd)) & 0x80 ) {
-               value &= 0x7F;
-               do {
-                       if (feof(fd))
-                               throw PrematureEOF();
-                       value = (value << 7) + ((c = getc(fd)) & 0x7F);
-               } while (c & 0x80);
-       }
-
-       return value;
-}
-
-
-} // namespace Evoral
-
diff --git a/libs/evoral/src/Sequence.cc b/libs/evoral/src/Sequence.cc
new file mode 100644 (file)
index 0000000..a1f166c
--- /dev/null
@@ -0,0 +1,1422 @@
+/*
+ * Copyright (C) 2008-2012 Hans Baier <hansfbaier@googlemail.com>
+ * Copyright (C) 2008-2016 David Robillard <d@drobilla.net>
+ * Copyright (C) 2008-2017 Paul Davis <paul@linuxaudiosystems.com>
+ * Copyright (C) 2009-2011 Carl Hetherington <carl@carlh.net>
+ * Copyright (C) 2014-2019 Robin Gareus <robin@gareus.org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include <algorithm>
+#include <cmath>
+#include <iostream>
+#include <limits>
+#include <stdexcept>
+#include <stdint.h>
+#include <cstdio>
+
+#if __clang__
+#include "evoral/Note.h"
+#endif
+
+#include "pbd/compose.h"
+#include "pbd/error.h"
+
+#include "temporal/beats.h"
+
+#include "evoral/Control.h"
+#include "evoral/ControlList.h"
+#include "evoral/ControlSet.h"
+#include "evoral/EventSink.h"
+#include "evoral/ParameterDescriptor.h"
+#include "evoral/Sequence.h"
+#include "evoral/TypeMap.h"
+#include "evoral/midi_util.h"
+
+#include "pbd/i18n.h"
+
+using namespace std;
+using namespace PBD;
+
+/** Minimum time between MIDI outputs from a single interpolated controller,
+    expressed in beats.  This is to limit the rate at which MIDI messages
+    are generated.  It is only applied to interpolated controllers.
+
+    XXX: This is a hack.  The time should probably be expressed in
+    seconds rather than beats, and should be configurable etc. etc.
+*/
+static double const time_between_interpolated_controller_outputs = 1.0 / 256;
+
+namespace Evoral {
+
+// Read iterator (const_iterator)
+
+template<typename Time>
+Sequence<Time>::const_iterator::const_iterator()
+       : _seq(NULL)
+       , _event(boost::shared_ptr< Event<Time> >(new Event<Time>()))
+       , _active_patch_change_message (NO_EVENT)
+       , _type(NIL)
+       , _is_end(true)
+       , _control_iter(_control_iters.end())
+       , _force_discrete(false)
+{
+}
+
+/** @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,
+                                               bool                               force_discrete,
+                                               const std::set<Evoral::Parameter>& filtered,
+                                               const std::set<WeakNotePtr>*       active_notes)
+       : _seq(&seq)
+       , _active_patch_change_message (0)
+       , _type(NIL)
+       , _is_end((t == DBL_MAX) || seq.empty())
+       , _note_iter(seq.notes().end())
+       , _sysex_iter(seq.sysexes().end())
+       , _patch_change_iter(seq.patch_changes().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));
+
+       if (_is_end) {
+               return;
+       }
+
+       _lock = seq.read_lock();
+
+       // Add currently active notes, if given
+       if (active_notes) {
+               for (typename std::set<WeakNotePtr>::const_iterator i = active_notes->begin();
+                    i != active_notes->end(); ++i) {
+                       NotePtr note = i->lock();
+                       if (note && note->time() <= t && note->end_time() > t) {
+                               _active_notes.push(note);
+                       }
+               }
+       }
+
+       // Find first note which begins at or after t
+       _note_iter = seq.note_lower_bound(t);
+
+       // Find first sysex event at or after t
+       for (typename Sequence<Time>::SysExes::const_iterator i = seq.sysexes().begin();
+            i != seq.sysexes().end(); ++i) {
+               if ((*i)->time() >= t) {
+                       _sysex_iter = i;
+                       break;
+               }
+       }
+       assert(_sysex_iter == seq.sysexes().end() || (*_sysex_iter)->time() >= t);
+
+       // Find first patch event at or after t
+       for (typename Sequence<Time>::PatchChanges::const_iterator i = seq.patch_changes().begin(); i != seq.patch_changes().end(); ++i) {
+               if ((*i)->time() >= t) {
+                       _patch_change_iter = i;
+                       break;
+               }
+       }
+       assert (_patch_change_iter == seq.patch_changes().end() || (*_patch_change_iter)->time() >= t);
+
+       // Find first control event after t
+       _control_iters.reserve(seq._controls.size());
+       bool   found                  = false;
+       size_t earliest_control_index = 0;
+       double earliest_control_x     = DBL_MAX;
+       for (Controls::const_iterator i = seq._controls.begin(); i != seq._controls.end(); ++i) {
+
+               if (filtered.find (i->first) != filtered.end()) {
+                       /* this parameter is filtered, so don't bother setting up an iterator for it */
+                       continue;
+               }
+
+               DEBUG_TRACE (DEBUG::Sequence, string_compose ("Iterator: control: %1\n", seq._type_map.to_symbol(i->first)));
+               double x, y;
+               bool ret;
+               if (_force_discrete || i->second->list()->interpolation() == ControlList::Discrete) {
+                       ret = i->second->list()->rt_safe_earliest_event_discrete_unlocked (t.to_double(), x, y, true);
+               } else {
+                       ret = i->second->list()->rt_safe_earliest_event_unlocked(t.to_double(), 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));
+                       continue;
+               }
+
+               assert(x >= 0);
+
+               const ParameterDescriptor& desc = seq.type_map().descriptor(i->first);
+               if (y < desc.lower || y > desc.upper) {
+                       cerr << "ERROR: Controller value " << y
+                            << " out of range [" << desc.lower << "," << desc.upper
+                            << "], event ignored" << endl;
+                       continue;
+               }
+
+               DEBUG_TRACE (DEBUG::Sequence, string_compose ("Iterator: CC %1 added (%2, %3)\n", i->first.id(), x, y));
+
+               const ControlIterator new_iter(i->second->list(), x, y);
+               _control_iters.push_back(new_iter);
+
+               // Found a new earliest_control
+               if (x < earliest_control_x) {
+                       earliest_control_x     = x;
+                       earliest_control_index = _control_iters.size() - 1;
+                       found                  = true;
+               }
+       }
+
+       if (found) {
+               _control_iter = _control_iters.begin() + earliest_control_index;
+               assert(_control_iter != _control_iters.end());
+               assert(_control_iter->list);
+       } else {
+               _control_iter = _control_iters.end();
+       }
+
+       // Choose the earliest event overall to point to
+       choose_next(t);
+
+       // Allocate a new event for storing the current event in MIDI format
+       _event = boost::shared_ptr< Event<Time> >(
+               new Event<Time>(NO_EVENT, Time(), 4, NULL, true));
+
+       // Set event from chosen sub-iterator
+       set_event();
+
+       if (_is_end) {
+               DEBUG_TRACE(DEBUG::Sequence,
+                           string_compose("Starting at end @ %1\n", t));
+       } else {
+               DEBUG_TRACE(DEBUG::Sequence,
+                           string_compose("Starting at type 0x%1 : 0x%2 @ %3\n",
+                                          (int)_event->event_type(),
+                                          (int)_event->buffer()[0],
+                                          _event->time()));
+       }
+}
+
+template<typename Time>
+void
+Sequence<Time>::const_iterator::invalidate(std::set< boost::weak_ptr< Note<Time> > >* notes)
+{
+       while (!_active_notes.empty()) {
+               if (notes) {
+                       notes->insert(_active_notes.top());
+               }
+               _active_notes.pop();
+       }
+       _type = NIL;
+       _is_end = true;
+       if (_seq) {
+               _note_iter = _seq->notes().end();
+               _sysex_iter = _seq->sysexes().end();
+               _patch_change_iter = _seq->patch_changes().end();
+               _active_patch_change_message = 0;
+       }
+       _control_iters.clear();
+       _control_iter = _control_iters.end();
+       _lock.reset();
+}
+
+template<typename Time>
+Time
+Sequence<Time>::const_iterator::choose_next(Time earliest_t)
+{
+       _type = NIL;
+
+       // Next earliest note on, if any
+       if (_note_iter != _seq->notes().end()) {
+               _type      = NOTE_ON;
+               earliest_t = (*_note_iter)->time();
+       }
+
+       /* Use the next earliest patch change iff it is earlier or coincident with the note-on.
+        * A patch-change with the same time-stamp applies to the concurrent note-on */
+       if (_patch_change_iter != _seq->patch_changes().end()) {
+               if (_type == NIL || (*_patch_change_iter)->time() <= earliest_t) {
+                       _type      = PATCH_CHANGE;
+                       earliest_t = (*_patch_change_iter)->time();
+               }
+       }
+
+       /* Use the next earliest controller iff it's earlier or coincident with the note-on
+        * or patch-change. Bank-select (CC0, CC32) needs to be sent before the PGM. */
+       if (_control_iter != _control_iters.end() &&
+           _control_iter->list && _control_iter->x != DBL_MAX) {
+               if (_type == NIL || _control_iter->x <= earliest_t.to_double()) {
+                       _type      = CONTROL;
+                       earliest_t = Time(_control_iter->x);
+               }
+       }
+
+       /* .. but prefer to send any Note-off first */
+       if ((!_active_notes.empty())) {
+               if (_type == NIL || _active_notes.top()->end_time().to_double() <= earliest_t.to_double()) {
+                       _type      = NOTE_OFF;
+                       earliest_t = _active_notes.top()->end_time();
+               }
+       }
+
+       /* SysEx is last, always sent after any other concurrent 3 byte event */
+       if (_sysex_iter != _seq->sysexes().end()) {
+               if (_type == NIL || (*_sysex_iter)->time() < earliest_t) {
+                       _type      = SYSEX;
+                       earliest_t = (*_sysex_iter)->time();
+               }
+       }
+
+       return earliest_t;
+}
+
+template<typename Time>
+void
+Sequence<Time>::const_iterator::set_event()
+{
+       switch (_type) {
+       case NOTE_ON:
+               DEBUG_TRACE(DEBUG::Sequence, "iterator = note on\n");
+               _event->assign ((*_note_iter)->on_event());
+               _active_notes.push(*_note_iter);
+               break;
+       case NOTE_OFF:
+               DEBUG_TRACE(DEBUG::Sequence, "iterator = note off\n");
+               assert(!_active_notes.empty());
+               _event->assign (_active_notes.top()->off_event());
+               // We don't pop the active note until we increment past it
+               break;
+       case SYSEX:
+               DEBUG_TRACE(DEBUG::Sequence, "iterator = sysex\n");
+               _event->assign (*(*_sysex_iter));
+               break;
+       case CONTROL:
+               DEBUG_TRACE(DEBUG::Sequence, "iterator = control\n");
+               _seq->control_to_midi_event(_event, *_control_iter);
+               break;
+       case PATCH_CHANGE:
+               DEBUG_TRACE(DEBUG::Sequence, "iterator = program change\n");
+               _event->assign ((*_patch_change_iter)->message (_active_patch_change_message));
+               break;
+       default:
+               _is_end = true;
+               break;
+       }
+
+       if (_type == NIL || !_event || _event->size() == 0) {
+               DEBUG_TRACE(DEBUG::Sequence, "iterator = end\n");
+               _type   = NIL;
+               _is_end = true;
+       } else {
+               assert(midi_event_is_valid(_event->buffer(), _event->size()));
+       }
+}
+
+template<typename Time>
+const typename Sequence<Time>::const_iterator&
+Sequence<Time>::const_iterator::operator++()
+{
+       if (_is_end) {
+               throw std::logic_error("Attempt to iterate past end of Sequence");
+       }
+
+       assert(_event && _event->buffer() && _event->size() > 0);
+
+       const Event<Time>& ev = *_event.get();
+
+       if (!(     ev.is_note()
+                  || ev.is_cc()
+                  || ev.is_pgm_change()
+                  || ev.is_pitch_bender()
+                  || ev.is_channel_pressure()
+                  || ev.is_poly_pressure()
+                  || ev.is_sysex()) ) {
+               cerr << "WARNING: Unknown event (type " << _type << "): " << hex
+                    << int(ev.buffer()[0]) << int(ev.buffer()[1]) << int(ev.buffer()[2]) << endl;
+       }
+
+       double x   = 0.0;
+       double y   = 0.0;
+       bool   ret = false;
+
+       // Increment past current event
+       switch (_type) {
+       case NOTE_ON:
+               ++_note_iter;
+               break;
+       case NOTE_OFF:
+               _active_notes.pop();
+               break;
+       case CONTROL:
+               // Increment current controller iterator
+               if (_force_discrete || _control_iter->list->interpolation() == ControlList::Discrete) {
+                       ret = _control_iter->list->rt_safe_earliest_event_discrete_unlocked (
+                               _control_iter->x, x, y, false);
+               } else {
+                       ret = _control_iter->list->rt_safe_earliest_event_linear_unlocked (
+                               _control_iter->x + time_between_interpolated_controller_outputs, x, y, false);
+               }
+               assert(!ret || x > _control_iter->x);
+               if (ret) {
+                       _control_iter->x = x;
+                       _control_iter->y = y;
+               } else {
+                       _control_iter->list.reset();
+                       _control_iter->x = DBL_MAX;
+                       _control_iter->y = DBL_MAX;
+               }
+
+               // Find the controller with the next earliest event time
+               _control_iter = _control_iters.begin();
+               for (ControlIterators::iterator i = _control_iters.begin();
+                    i != _control_iters.end(); ++i) {
+                       if (i->x < _control_iter->x) {
+                               _control_iter = i;
+                       }
+               }
+               break;
+       case SYSEX:
+               ++_sysex_iter;
+               break;
+       case PATCH_CHANGE:
+               ++_active_patch_change_message;
+               if (_active_patch_change_message == (*_patch_change_iter)->messages()) {
+                       ++_patch_change_iter;
+                       _active_patch_change_message = 0;
+               }
+               break;
+       default:
+               assert(false);
+       }
+
+       // Choose the earliest event overall to point to
+       choose_next(std::numeric_limits<Time>::max());
+
+       // Set event from chosen sub-iterator
+       set_event();
+
+       assert(_is_end || (_event->size() > 0 && _event->buffer() && _event->buffer()[0] != '\0'));
+
+       return *this;
+}
+
+template<typename Time>
+bool
+Sequence<Time>::const_iterator::operator==(const const_iterator& other) const
+{
+       if (_seq != other._seq) {
+               return false;
+       } else if (_is_end || other._is_end) {
+               return (_is_end == other._is_end);
+       } else if (_type != other._type) {
+               return false;
+       } else {
+               return (_event == other._event);
+       }
+}
+
+template<typename Time>
+typename Sequence<Time>::const_iterator&
+Sequence<Time>::const_iterator::operator=(const const_iterator& other)
+{
+       _seq           = other._seq;
+       _event         = other._event;
+       _active_notes  = other._active_notes;
+       _type          = other._type;
+       _is_end        = other._is_end;
+       _note_iter     = other._note_iter;
+       _sysex_iter    = other._sysex_iter;
+       _patch_change_iter = other._patch_change_iter;
+       _control_iters = other._control_iters;
+       _force_discrete = other._force_discrete;
+       _active_patch_change_message = other._active_patch_change_message;
+
+       if (other._lock) {
+               _lock = _seq->read_lock();
+       } else {
+               _lock.reset();
+       }
+
+       if (other._control_iter == other._control_iters.end()) {
+               _control_iter = _control_iters.end();
+       } else {
+               const size_t index = other._control_iter - other._control_iters.begin();
+               _control_iter  = _control_iters.begin() + index;
+       }
+
+       return *this;
+}
+
+// Sequence
+
+template<typename Time>
+Sequence<Time>::Sequence(const TypeMap& type_map)
+       : _edited(false)
+       , _overlapping_pitches_accepted (true)
+       , _overlap_pitch_resolution (FirstOnFirstOff)
+       , _writing(false)
+       , _type_map(type_map)
+       , _end_iter(*this, std::numeric_limits<Time>::max(), false, std::set<Evoral::Parameter> ())
+       , _percussive(false)
+       , _lowest_note(127)
+       , _highest_note(0)
+{
+       DEBUG_TRACE (DEBUG::Sequence, string_compose ("Sequence constructed: %1\n", this));
+       assert(_end_iter._is_end);
+       assert( ! _end_iter._lock);
+
+       for (int i = 0; i < 16; ++i) {
+               _bank[i] = 0;
+       }
+}
+
+template<typename Time>
+Sequence<Time>::Sequence(const Sequence<Time>& other)
+       : ControlSet (other)
+       , _edited(false)
+       , _overlapping_pitches_accepted (other._overlapping_pitches_accepted)
+       , _overlap_pitch_resolution (other._overlap_pitch_resolution)
+       , _writing(false)
+       , _type_map(other._type_map)
+       , _end_iter(*this, std::numeric_limits<Time>::max(), false, std::set<Evoral::Parameter> ())
+       , _percussive(other._percussive)
+       , _lowest_note(other._lowest_note)
+       , _highest_note(other._highest_note)
+{
+       for (typename Notes::const_iterator i = other._notes.begin(); i != other._notes.end(); ++i) {
+               NotePtr n (new Note<Time> (**i));
+               _notes.insert (n);
+       }
+
+       for (typename SysExes::const_iterator i = other._sysexes.begin(); i != other._sysexes.end(); ++i) {
+               boost::shared_ptr<Event<Time> > n (new Event<Time> (**i, true));
+               _sysexes.insert (n);
+       }
+
+       for (typename PatchChanges::const_iterator i = other._patch_changes.begin(); i != other._patch_changes.end(); ++i) {
+               PatchChangePtr n (new PatchChange<Time> (**i));
+               _patch_changes.insert (n);
+       }
+
+       for (int i = 0; i < 16; ++i) {
+               _bank[i] = other._bank[i];
+       }
+
+       DEBUG_TRACE (DEBUG::Sequence, string_compose ("Sequence copied: %1\n", this));
+       assert(_end_iter._is_end);
+       assert(! _end_iter._lock);
+}
+
+/** Write the controller event pointed to by `iter` to `ev`.
+ * The buffer of `ev` will be allocated or resized as necessary.
+ * \return true on success
+ */
+template<typename Time>
+bool
+Sequence<Time>::control_to_midi_event(
+       boost::shared_ptr< Event<Time> >& ev,
+       const ControlIterator&            iter) const
+{
+       assert(iter.list.get());
+
+       // initialize the event pointer with a new event, if necessary
+       if (!ev) {
+               ev = boost::shared_ptr< Event<Time> >(new Event<Time>(NO_EVENT, Time(), 3, NULL, true));
+       }
+
+       const uint8_t midi_type = _type_map.parameter_midi_type(iter.list->parameter());
+       ev->set_event_type(MIDI_EVENT);
+       ev->set_id(-1);
+       switch (midi_type) {
+       case MIDI_CMD_CONTROL:
+               assert(iter.list.get());
+               assert(iter.list->parameter().channel() < 16);
+               assert(iter.list->parameter().id() <= INT8_MAX);
+               assert(iter.y <= INT8_MAX);
+
+               ev->set_time(Time(iter.x));
+               ev->realloc(3);
+               ev->buffer()[0] = MIDI_CMD_CONTROL + iter.list->parameter().channel();
+               ev->buffer()[1] = (uint8_t)iter.list->parameter().id();
+               ev->buffer()[2] = (uint8_t)iter.y;
+               break;
+
+       case MIDI_CMD_PGM_CHANGE:
+               assert(iter.list.get());
+               assert(iter.list->parameter().channel() < 16);
+               assert(iter.y <= INT8_MAX);
+
+               ev->set_time(Time(iter.x));
+               ev->realloc(2);
+               ev->buffer()[0] = MIDI_CMD_PGM_CHANGE + iter.list->parameter().channel();
+               ev->buffer()[1] = (uint8_t)iter.y;
+               break;
+
+       case MIDI_CMD_BENDER:
+               assert(iter.list.get());
+               assert(iter.list->parameter().channel() < 16);
+               assert(iter.y < (1<<14));
+
+               ev->set_time(Time(iter.x));
+               ev->realloc(3);
+               ev->buffer()[0] = MIDI_CMD_BENDER + iter.list->parameter().channel();
+               ev->buffer()[1] = uint16_t(iter.y) & 0x7F; // LSB
+               ev->buffer()[2] = (uint16_t(iter.y) >> 7) & 0x7F; // MSB
+               break;
+
+       case MIDI_CMD_NOTE_PRESSURE:
+               assert(iter.list.get());
+               assert(iter.list->parameter().channel() < 16);
+               assert(iter.list->parameter().id() <= INT8_MAX);
+               assert(iter.y <= INT8_MAX);
+
+               ev->set_time(Time(iter.x));
+               ev->realloc(3);
+               ev->buffer()[0] = MIDI_CMD_NOTE_PRESSURE + iter.list->parameter().channel();
+               ev->buffer()[1] = (uint8_t)iter.list->parameter().id();
+               ev->buffer()[2] = (uint8_t)iter.y;
+               break;
+
+       case MIDI_CMD_CHANNEL_PRESSURE:
+               assert(iter.list.get());
+               assert(iter.list->parameter().channel() < 16);
+               assert(iter.y <= INT8_MAX);
+
+               ev->set_time(Time(iter.x));
+               ev->realloc(2);
+               ev->buffer()[0] = MIDI_CMD_CHANNEL_PRESSURE + iter.list->parameter().channel();
+               ev->buffer()[1] = (uint8_t)iter.y;
+               break;
+
+       default:
+               return false;
+       }
+
+       return true;
+}
+
+/** Clear all events from the model.
+ */
+template<typename Time>
+void
+Sequence<Time>::clear()
+{
+       WriteLock lock(write_lock());
+       _notes.clear();
+       for (Controls::iterator li = _controls.begin(); li != _controls.end(); ++li)
+               li->second->list()->clear();
+}
+
+/** Begin a write of events to the model.
+ *
+ * If \a mode is Sustained, complete notes with length are constructed as note
+ * on/off events are received.  Otherwise (Percussive), only note on events are
+ * stored; note off events are discarded entirely and all contained notes will
+ * have length 0.
+ */
+template<typename Time>
+void
+Sequence<Time>::start_write()
+{
+       DEBUG_TRACE (DEBUG::Sequence, string_compose ("%1 : start_write (percussive = %2)\n", this, _percussive));
+       WriteLock lock(write_lock());
+       _writing = true;
+       for (int i = 0; i < 16; ++i) {
+               _write_notes[i].clear();
+       }
+}
+
+/** Finish a write of events to the model.
+ *
+ * If \a delete_stuck is true and the current mode is Sustained, note on events
+ * that were never resolved with a corresonding note off will be deleted.
+ * Otherwise they will remain as notes with length 0.
+ */
+template<typename Time>
+void
+Sequence<Time>::end_write (StuckNoteOption option, Time when)
+{
+       WriteLock lock(write_lock());
+
+       if (!_writing) {
+               return;
+       }
+
+       DEBUG_TRACE (DEBUG::Sequence, string_compose ("%1 : end_write (%2 notes) delete stuck option %3 @ %4\n", this, _notes.size(), option, when));
+
+       for (typename Notes::iterator n = _notes.begin(); n != _notes.end() ;) {
+               typename Notes::iterator next = n;
+               ++next;
+
+               if (!(*n)->length()) {
+                       switch (option) {
+                       case Relax:
+                               break;
+                       case DeleteStuckNotes:
+                               cerr << "WARNING: Stuck note lost: " << (*n)->note() << endl;
+                               _notes.erase(n);
+                               break;
+                       case ResolveStuckNotes:
+                               if (when <= (*n)->time()) {
+                                       cerr << "WARNING: Stuck note resolution - end time @ "
+                                            << when << " is before note on: " << (**n) << endl;
+                                       _notes.erase (*n);
+                               } else {
+                                       (*n)->set_length (when - (*n)->time());
+                                       cerr << "WARNING: resolved note-on with no note-off to generate " << (**n) << endl;
+                               }
+                               break;
+                       }
+               }
+
+               n = next;
+       }
+
+       for (int i = 0; i < 16; ++i) {
+               _write_notes[i].clear();
+       }
+
+       _writing = false;
+}
+
+
+template<typename Time>
+bool
+Sequence<Time>::add_note_unlocked(const NotePtr note, void* arg)
+{
+       /* This is the core method to add notes to a Sequence
+        */
+
+       DEBUG_TRACE (DEBUG::Sequence, string_compose ("%1 add note %2 @ %3 dur %4\n", this, (int)note->note(), note->time(), note->length()));
+
+       if (resolve_overlaps_unlocked (note, arg)) {
+               DEBUG_TRACE (DEBUG::Sequence, string_compose ("%1 DISALLOWED: note %2 @ %3\n", this, (int)note->note(), note->time()));
+               return false;
+       }
+
+       if (note->id() < 0) {
+               note->set_id (Evoral::next_event_id());
+       }
+
+       if (note->note() < _lowest_note)
+               _lowest_note = note->note();
+       if (note->note() > _highest_note)
+               _highest_note = note->note();
+
+       _notes.insert (note);
+       _pitches[note->channel()].insert (note);
+
+       _edited = true;
+
+       return true;
+}
+
+template<typename Time>
+void
+Sequence<Time>::remove_note_unlocked(const constNotePtr note)
+{
+       bool erased = false;
+       bool id_matched = false;
+
+       DEBUG_TRACE (DEBUG::Sequence, string_compose ("%1 remove note #%2 %3 @ %4\n", this, note->id(), (int)note->note(), note->time()));
+
+       /* first try searching for the note using the time index, which is
+        * faster since the container is "indexed" by time. (technically, this
+        * means that lower_bound() can do a binary search rather than linear)
+        *
+        * this may not work, for reasons explained below.
+        */
+
+       typename Sequence<Time>::Notes::iterator i;
+
+       for (i = note_lower_bound(note->time()); i != _notes.end() && (*i)->time() == note->time(); ++i) {
+
+               if (*i == note) {
+
+                       DEBUG_TRACE (DEBUG::Sequence, string_compose ("%1\terasing note #%2 %3 @ %4\n", this, (*i)->id(), (int)(*i)->note(), (*i)->time()));
+                       _notes.erase (i);
+
+                       if (note->note() == _lowest_note || note->note() == _highest_note) {
+
+                               _lowest_note = 127;
+                               _highest_note = 0;
+
+                               for (typename Sequence<Time>::Notes::iterator ii = _notes.begin(); ii != _notes.end(); ++ii) {
+                                       if ((*ii)->note() < _lowest_note)
+                                               _lowest_note = (*ii)->note();
+                                       if ((*ii)->note() > _highest_note)
+                                               _highest_note = (*ii)->note();
+                               }
+                       }
+
+                       erased = true;
+                       break;
+               }
+       }
+
+       DEBUG_TRACE (DEBUG::Sequence, string_compose ("%1\ttime-based lookup did not find note #%2 %3 @ %4\n", this, note->id(), (int)note->note(), note->time()));
+
+       if (!erased) {
+
+               /* if the note's time property was changed in tandem with some
+                * other property as the next operation after it was added to
+                * the sequence, then at the point where we call this to undo
+                * the add, the note we are targetting currently has a
+                * different time property than the one we we passed via
+                * the argument.
+                *
+                * in this scenario, we have no choice other than to linear
+                * search the list of notes and find the note by ID.
+                */
+
+               for (i = _notes.begin(); i != _notes.end(); ++i) {
+
+                       if ((*i)->id() == note->id()) {
+
+                               DEBUG_TRACE (DEBUG::Sequence, string_compose ("%1\tID-based pass, erasing note #%2 %3 @ %4\n", this, (*i)->id(), (int)(*i)->note(), (*i)->time()));
+                               _notes.erase (i);
+
+                               if (note->note() == _lowest_note || note->note() == _highest_note) {
+
+                                       _lowest_note = 127;
+                                       _highest_note = 0;
+
+                                       for (typename Sequence<Time>::Notes::iterator ii = _notes.begin(); ii != _notes.end(); ++ii) {
+                                               if ((*ii)->note() < _lowest_note)
+                                                       _lowest_note = (*ii)->note();
+                                               if ((*ii)->note() > _highest_note)
+                                                       _highest_note = (*ii)->note();
+                                       }
+                               }
+
+                               erased = true;
+                               id_matched = true;
+                               break;
+                       }
+               }
+       }
+
+       if (erased) {
+
+               Pitches& p (pitches (note->channel()));
+
+               typename Pitches::iterator j;
+
+               /* if we had to ID-match above, we can't expect to find it in
+                * pitches via note comparison either. so do another linear
+                * search to locate it. otherwise, we can use the note index
+                * to potentially speed things up.
+                */
+
+               if (id_matched) {
+
+                       for (j = p.begin(); j != p.end(); ++j) {
+                               if ((*j)->id() == note->id()) {
+                                       p.erase (j);
+                                       break;
+                               }
+                       }
+
+               } else {
+
+                       /* Now find the same note in the "pitches" list (which indexes
+                        * notes by channel+time. We care only about its note number
+                        * so the search_note has all other properties unset.
+                        */
+
+                       NotePtr search_note (new Note<Time>(0, Time(), Time(), note->note(), 0));
+
+                       for (j = p.lower_bound (search_note); j != p.end() && (*j)->note() == note->note(); ++j) {
+
+                               if ((*j) == note) {
+                                       DEBUG_TRACE (DEBUG::Sequence, string_compose ("%1\terasing pitch %2 @ %3\n", this, (int)(*j)->note(), (*j)->time()));
+                                       p.erase (j);
+                                       break;
+                               }
+                       }
+               }
+
+               if (j == p.end()) {
+                       warning << string_compose ("erased note %1 not found in pitches for channel %2", *note, (int) note->channel()) << endmsg;
+               }
+
+               _edited = true;
+
+       } else {
+               cerr << "Unable to find note to erase matching " << *note.get() << endmsg;
+       }
+}
+
+template<typename Time>
+void
+Sequence<Time>::remove_patch_change_unlocked (const constPatchChangePtr p)
+{
+       typename Sequence<Time>::PatchChanges::iterator i = patch_change_lower_bound (p->time ());
+
+       while (i != _patch_changes.end() && ((*i)->time() == p->time())) {
+
+               typename Sequence<Time>::PatchChanges::iterator tmp = i;
+               ++tmp;
+
+               if (**i == *p) {
+                       _patch_changes.erase (i);
+               }
+
+               i = tmp;
+       }
+}
+
+template<typename Time>
+void
+Sequence<Time>::remove_sysex_unlocked (const SysExPtr sysex)
+{
+       typename Sequence<Time>::SysExes::iterator i = sysex_lower_bound (sysex->time ());
+       while (i != _sysexes.end() && (*i)->time() == sysex->time()) {
+
+               typename Sequence<Time>::SysExes::iterator tmp = i;
+               ++tmp;
+
+               if (*i == sysex) {
+                       _sysexes.erase (i);
+               }
+
+               i = tmp;
+       }
+}
+
+/** Append \a ev to model.  NOT realtime safe.
+ *
+ * The timestamp of event is expected to be relative to
+ * the start of this model (t=0) and MUST be monotonically increasing
+ * and MUST be >= the latest event currently in the model.
+ */
+template<typename Time>
+void
+Sequence<Time>::append(const Event<Time>& ev, event_id_t evid)
+{
+       WriteLock lock(write_lock());
+
+       assert(_notes.empty() || ev.time() >= (*_notes.rbegin())->time());
+       assert(_writing);
+
+       if (!midi_event_is_valid(ev.buffer(), ev.size())) {
+               cerr << "WARNING: Sequence ignoring illegal MIDI event" << endl;
+               return;
+       }
+
+       if (ev.is_note_on() && ev.velocity() > 0) {
+               append_note_on_unlocked (ev, evid);
+       } else if (ev.is_note_off() || (ev.is_note_on() && ev.velocity() == 0)) {
+               /* XXX note: event ID is discarded because we merge the on+off events into
+                  a single note object
+               */
+               append_note_off_unlocked (ev);
+       } else if (ev.is_sysex()) {
+               append_sysex_unlocked(ev, evid);
+       } else if (ev.is_cc() && (ev.cc_number() == MIDI_CTL_MSB_BANK || ev.cc_number() == MIDI_CTL_LSB_BANK)) {
+               /* note bank numbers in our _bank[] array, so that we can write an event when the program change arrives */
+               if (ev.cc_number() == MIDI_CTL_MSB_BANK) {
+                       _bank[ev.channel()] &= ~(0x7f << 7);
+                       _bank[ev.channel()] |= ev.cc_value() << 7;
+               } else {
+                       _bank[ev.channel()] &= ~0x7f;
+                       _bank[ev.channel()] |= ev.cc_value();
+               }
+       } else if (ev.is_cc()) {
+               const ParameterType ptype = _type_map.midi_parameter_type(ev.buffer(), ev.size());
+               append_control_unlocked(
+                       Parameter(ptype, ev.channel(), ev.cc_number()),
+                       ev.time(), ev.cc_value(), evid);
+       } else if (ev.is_pgm_change()) {
+               /* write a patch change with this program change and any previously set-up bank number */
+               append_patch_change_unlocked (
+                       PatchChange<Time> (ev.time(), ev.channel(),
+                                          ev.pgm_number(), _bank[ev.channel()]), evid);
+       } else if (ev.is_pitch_bender()) {
+               const ParameterType ptype = _type_map.midi_parameter_type(ev.buffer(), ev.size());
+               append_control_unlocked(
+                       Parameter(ptype, ev.channel()),
+                       ev.time(), double ((0x7F & ev.pitch_bender_msb()) << 7
+                                          | (0x7F & ev.pitch_bender_lsb())),
+                       evid);
+       } else if (ev.is_poly_pressure()) {
+               const ParameterType ptype = _type_map.midi_parameter_type(ev.buffer(), ev.size());
+               append_control_unlocked (Parameter (ptype, ev.channel(), ev.poly_note()), ev.time(), ev.poly_pressure(), evid);
+       } else if (ev.is_channel_pressure()) {
+               const ParameterType ptype = _type_map.midi_parameter_type(ev.buffer(), ev.size());
+               append_control_unlocked(
+                       Parameter(ptype, ev.channel()),
+                       ev.time(), ev.channel_pressure(), evid);
+       } else if (!_type_map.type_is_midi(ev.event_type())) {
+               printf("WARNING: Sequence: Unknown event type %X: ", ev.event_type());
+               for (size_t i=0; i < ev.size(); ++i) {
+                       printf("%X ", ev.buffer()[i]);
+               }
+               printf("\n");
+       } else {
+               printf("WARNING: Sequence: Unknown MIDI event type %X\n", ev.type());
+       }
+
+       _edited = true;
+}
+
+template<typename Time>
+void
+Sequence<Time>::append_note_on_unlocked (const Event<Time>& ev, event_id_t evid)
+{
+       DEBUG_TRACE (DEBUG::Sequence, string_compose ("%1 c=%2 note %3 on @ %4 v=%5\n", this,
+                                                     (int)ev.channel(), (int)ev.note(),
+                                                     ev.time(), (int)ev.velocity()));
+       assert(_writing);
+
+       if (ev.note() > 127) {
+               error << string_compose (_("invalid note on number (%1) ignored"), (int) ev.note()) << endmsg;
+               return;
+       } else if (ev.channel() >= 16) {
+               error << string_compose (_("invalid note on channel (%1) ignored"), (int) ev.channel()) << endmsg;
+               return;
+       } else if (ev.velocity() == 0) {
+               // Note on with velocity 0 handled as note off by caller
+               error << string_compose (_("invalid note on velocity (%1) ignored"), (int) ev.velocity()) << endmsg;
+               return;
+       }
+
+       NotePtr note(new Note<Time>(ev.channel(), ev.time(), Time(), ev.note(), ev.velocity()));
+       note->set_id (evid);
+
+       add_note_unlocked (note);
+
+       DEBUG_TRACE (DEBUG::Sequence, string_compose ("Appending active note on %1 channel %2\n",
+                                                     (unsigned)(uint8_t)note->note(), note->channel()));
+       _write_notes[note->channel()].insert (note);
+
+}
+
+template<typename Time>
+void
+Sequence<Time>::append_note_off_unlocked (const Event<Time>& ev)
+{
+       DEBUG_TRACE (DEBUG::Sequence, string_compose ("%1 c=%2 note %3 OFF @ %4 v=%5\n",
+                                                     this, (int)ev.channel(),
+                                                     (int)ev.note(), ev.time(), (int)ev.velocity()));
+       assert(_writing);
+
+       if (ev.note() > 127) {
+               error << string_compose (_("invalid note off number (%1) ignored"), (int) ev.note()) << endmsg;
+               return;
+       } else if (ev.channel() >= 16) {
+               error << string_compose (_("invalid note off channel (%1) ignored"), (int) ev.channel()) << endmsg;
+               return;
+       }
+
+       _edited = true;
+
+       bool resolved = false;
+
+       /* _write_notes is sorted earliest-latest, so this will find the first matching note (FIFO) that
+          matches this note (by pitch & channel). the MIDI specification doesn't provide any guidance
+          whether to use FIFO or LIFO for this matching process, so SMF is fundamentally a lossy
+          format.
+       */
+
+       /* XXX use _overlap_pitch_resolution to determine FIFO/LIFO ... */
+
+       for (typename WriteNotes::iterator n = _write_notes[ev.channel()].begin(); n != _write_notes[ev.channel()].end(); ) {
+
+               typename WriteNotes::iterator tmp = n;
+               ++tmp;
+
+               NotePtr nn = *n;
+               if (ev.note() == nn->note() && nn->channel() == ev.channel()) {
+                       assert(ev.time() >= nn->time());
+
+                       nn->set_length (ev.time() - nn->time());
+                       nn->set_off_velocity (ev.velocity());
+
+                       _write_notes[ev.channel()].erase(n);
+                       DEBUG_TRACE (DEBUG::Sequence, string_compose ("resolved note @ %2 length: %1\n", nn->length(), nn->time()));
+                       resolved = true;
+                       break;
+               }
+
+               n = tmp;
+       }
+
+       if (!resolved) {
+               cerr << this << " spurious note off chan " << (int)ev.channel()
+                    << ", note " << (int)ev.note() << " @ " << ev.time() << endl;
+       }
+}
+
+template<typename Time>
+void
+Sequence<Time>::append_control_unlocked(const Parameter& param, Time time, double value, event_id_t /* evid */)
+{
+       DEBUG_TRACE (DEBUG::Sequence, string_compose ("%1 %2 @ %3 = %4 # controls: %5\n",
+                                                     this, _type_map.to_symbol(param), time, value, _controls.size()));
+       boost::shared_ptr<Control> c = control(param, true);
+       c->list()->add (time.to_double(), value, true, false);
+       /* XXX control events should use IDs */
+}
+
+template<typename Time>
+void
+Sequence<Time>::append_sysex_unlocked(const Event<Time>& ev, event_id_t /* evid */)
+{
+#ifdef DEBUG_SEQUENCE
+       cerr << this << " SysEx @ " << ev.time() << " \t= \t [ " << hex;
+       for (size_t i=0; i < ev.size(); ++i) {
+               cerr << int(ev.buffer()[i]) << " ";
+       } cerr << "]" << endl;
+#endif
+
+       boost::shared_ptr< Event<Time> > event(new Event<Time>(ev, true));
+       /* XXX sysex events should use IDs */
+       _sysexes.insert(event);
+}
+
+template<typename Time>
+void
+Sequence<Time>::append_patch_change_unlocked (const PatchChange<Time>& ev, event_id_t id)
+{
+       PatchChangePtr p (new PatchChange<Time> (ev));
+
+       if (p->id() < 0) {
+               p->set_id (id);
+       }
+
+       _patch_changes.insert (p);
+}
+
+template<typename Time>
+void
+Sequence<Time>::add_patch_change_unlocked (PatchChangePtr p)
+{
+       if (p->id () < 0) {
+               p->set_id (Evoral::next_event_id ());
+       }
+
+       _patch_changes.insert (p);
+}
+
+template<typename Time>
+void
+Sequence<Time>::add_sysex_unlocked (SysExPtr s)
+{
+       if (s->id () < 0) {
+               s->set_id (Evoral::next_event_id ());
+       }
+
+       _sysexes.insert (s);
+}
+
+template<typename Time>
+bool
+Sequence<Time>::contains (const NotePtr& note) const
+{
+       ReadLock lock (read_lock());
+       return contains_unlocked (note);
+}
+
+template<typename Time>
+bool
+Sequence<Time>::contains_unlocked (const NotePtr& note) const
+{
+       const Pitches& p (pitches (note->channel()));
+       NotePtr search_note(new Note<Time>(0, Time(), Time(), note->note()));
+
+       for (typename Pitches::const_iterator i = p.lower_bound (search_note);
+            i != p.end() && (*i)->note() == note->note(); ++i) {
+
+               if (**i == *note) {
+                       return true;
+               }
+       }
+
+       return false;
+}
+
+template<typename Time>
+bool
+Sequence<Time>::overlaps (const NotePtr& note, const NotePtr& without) const
+{
+       ReadLock lock (read_lock());
+       return overlaps_unlocked (note, without);
+}
+
+template<typename Time>
+bool
+Sequence<Time>::overlaps_unlocked (const NotePtr& note, const NotePtr& without) const
+{
+       Time sa = note->time();
+       Time ea  = note->end_time();
+
+       const Pitches& p (pitches (note->channel()));
+       NotePtr search_note(new Note<Time>(0, Time(), Time(), note->note()));
+
+       for (typename Pitches::const_iterator i = p.lower_bound (search_note);
+            i != p.end() && (*i)->note() == note->note(); ++i) {
+
+               if (without && (**i) == *without) {
+                       continue;
+               }
+
+               Time sb = (*i)->time();
+               Time eb = (*i)->end_time();
+
+               if (((sb > sa) && (eb <= ea)) ||
+                   ((eb >= sa) && (eb <= ea)) ||
+                   ((sb > sa) && (sb <= ea)) ||
+                   ((sa >= sb) && (sa <= eb) && (ea <= eb))) {
+                       return true;
+               }
+       }
+
+       return false;
+}
+
+template<typename Time>
+void
+Sequence<Time>::set_notes (const typename Sequence<Time>::Notes& n)
+{
+       _notes = n;
+}
+
+// CONST iterator implementations (x3)
+
+/** Return the earliest note with time >= t */
+template<typename Time>
+typename Sequence<Time>::Notes::const_iterator
+Sequence<Time>::note_lower_bound (Time t) const
+{
+       NotePtr search_note(new Note<Time>(0, t, Time(), 0, 0));
+       typename Sequence<Time>::Notes::const_iterator i = _notes.lower_bound(search_note);
+       assert(i == _notes.end() || (*i)->time() >= t);
+       return i;
+}
+
+/** Return the earliest patch change with time >= t */
+template<typename Time>
+typename Sequence<Time>::PatchChanges::const_iterator
+Sequence<Time>::patch_change_lower_bound (Time t) const
+{
+       PatchChangePtr search (new PatchChange<Time> (t, 0, 0, 0));
+       typename Sequence<Time>::PatchChanges::const_iterator i = _patch_changes.lower_bound (search);
+       assert (i == _patch_changes.end() || (*i)->time() >= t);
+       return i;
+}
+
+/** Return the earliest sysex with time >= t */
+template<typename Time>
+typename Sequence<Time>::SysExes::const_iterator
+Sequence<Time>::sysex_lower_bound (Time t) const
+{
+       SysExPtr search (new Event<Time> (NO_EVENT, t));
+       typename Sequence<Time>::SysExes::const_iterator i = _sysexes.lower_bound (search);
+       assert (i == _sysexes.end() || (*i)->time() >= t);
+       return i;
+}
+
+// NON-CONST iterator implementations (x3)
+
+/** Return the earliest note with time >= t */
+template<typename Time>
+typename Sequence<Time>::Notes::iterator
+Sequence<Time>::note_lower_bound (Time t)
+{
+       NotePtr search_note(new Note<Time>(0, t, Time(), 0, 0));
+       typename Sequence<Time>::Notes::iterator i = _notes.lower_bound(search_note);
+       assert(i == _notes.end() || (*i)->time() >= t);
+       return i;
+}
+
+/** Return the earliest patch change with time >= t */
+template<typename Time>
+typename Sequence<Time>::PatchChanges::iterator
+Sequence<Time>::patch_change_lower_bound (Time t)
+{
+       PatchChangePtr search (new PatchChange<Time> (t, 0, 0, 0));
+       typename Sequence<Time>::PatchChanges::iterator i = _patch_changes.lower_bound (search);
+       assert (i == _patch_changes.end() || (*i)->time() >= t);
+       return i;
+}
+
+/** Return the earliest sysex with time >= t */
+template<typename Time>
+typename Sequence<Time>::SysExes::iterator
+Sequence<Time>::sysex_lower_bound (Time t)
+{
+       SysExPtr search (new Event<Time> (NO_EVENT, t));
+       typename Sequence<Time>::SysExes::iterator i = _sysexes.lower_bound (search);
+       assert (i == _sysexes.end() || (*i)->time() >= t);
+       return i;
+}
+
+template<typename Time>
+void
+Sequence<Time>::get_notes (Notes& n, NoteOperator op, uint8_t val, int chan_mask) const
+{
+       switch (op) {
+       case PitchEqual:
+       case PitchLessThan:
+       case PitchLessThanOrEqual:
+       case PitchGreater:
+       case PitchGreaterThanOrEqual:
+               get_notes_by_pitch (n, op, val, chan_mask);
+               break;
+
+       case VelocityEqual:
+       case VelocityLessThan:
+       case VelocityLessThanOrEqual:
+       case VelocityGreater:
+       case VelocityGreaterThanOrEqual:
+               get_notes_by_velocity (n, op, val, chan_mask);
+               break;
+       }
+}
+
+template<typename Time>
+void
+Sequence<Time>::get_notes_by_pitch (Notes& n, NoteOperator op, uint8_t val, int chan_mask) const
+{
+       for (uint8_t c = 0; c < 16; ++c) {
+
+               if (chan_mask != 0 && !((1<<c) & chan_mask)) {
+                       continue;
+               }
+
+               const Pitches& p (pitches (c));
+               NotePtr search_note(new Note<Time>(0, Time(), Time(), val, 0));
+               typename Pitches::const_iterator i;
+               switch (op) {
+               case PitchEqual:
+                       i = p.lower_bound (search_note);
+                       while (i != p.end() && (*i)->note() == val) {
+                               n.insert (*i);
+                       }
+                       break;
+               case PitchLessThan:
+                       i = p.upper_bound (search_note);
+                       while (i != p.end() && (*i)->note() < val) {
+                               n.insert (*i);
+                       }
+                       break;
+               case PitchLessThanOrEqual:
+                       i = p.upper_bound (search_note);
+                       while (i != p.end() && (*i)->note() <= val) {
+                               n.insert (*i);
+                       }
+                       break;
+               case PitchGreater:
+                       i = p.lower_bound (search_note);
+                       while (i != p.end() && (*i)->note() > val) {
+                               n.insert (*i);
+                       }
+                       break;
+               case PitchGreaterThanOrEqual:
+                       i = p.lower_bound (search_note);
+                       while (i != p.end() && (*i)->note() >= val) {
+                               n.insert (*i);
+                       }
+                       break;
+
+               default:
+                       //fatal << string_compose (_("programming error: %1 %2", X_("get_notes_by_pitch() called with illegal operator"), op)) << endmsg;
+                       abort(); /* NOTREACHED*/
+               }
+       }
+}
+
+template<typename Time>
+void
+Sequence<Time>::get_notes_by_velocity (Notes& n, NoteOperator op, uint8_t val, int chan_mask) const
+{
+       ReadLock lock (read_lock());
+
+       for (typename Notes::const_iterator i = _notes.begin(); i != _notes.end(); ++i) {
+
+               if (chan_mask != 0 && !((1<<((*i)->channel())) & chan_mask)) {
+                       continue;
+               }
+
+               switch (op) {
+               case VelocityEqual:
+                       if ((*i)->velocity() == val) {
+                               n.insert (*i);
+                       }
+                       break;
+               case VelocityLessThan:
+                       if ((*i)->velocity() < val) {
+                               n.insert (*i);
+                       }
+                       break;
+               case VelocityLessThanOrEqual:
+                       if ((*i)->velocity() <= val) {
+                               n.insert (*i);
+                       }
+                       break;
+               case VelocityGreater:
+                       if ((*i)->velocity() > val) {
+                               n.insert (*i);
+                       }
+                       break;
+               case VelocityGreaterThanOrEqual:
+                       if ((*i)->velocity() >= val) {
+                               n.insert (*i);
+                       }
+                       break;
+               default:
+                       // fatal << string_compose (_("programming error: %1 %2", X_("get_notes_by_velocity() called with illegal operator"), op)) << endmsg;
+                       abort(); /* NOTREACHED*/
+
+               }
+       }
+}
+
+template<typename Time>
+void
+Sequence<Time>::set_overlap_pitch_resolution (OverlapPitchResolution opr)
+{
+       _overlap_pitch_resolution = opr;
+
+       /* XXX todo: clean up existing overlaps in source data? */
+}
+
+template<typename Time>
+void
+Sequence<Time>::control_list_marked_dirty ()
+{
+       set_edited (true);
+}
+
+template<typename Time>
+void
+Sequence<Time>::dump (ostream& str) const
+{
+       typename Sequence<Time>::const_iterator i;
+       str << "+++ dump\n";
+       for (i = begin(); i != end(); ++i) {
+               str << *i << endl;
+       }
+       str << "--- dump\n";
+}
+
+template class Sequence<Temporal::Beats>;
+
+} // namespace Evoral
diff --git a/libs/evoral/src/Sequence.cpp b/libs/evoral/src/Sequence.cpp
deleted file mode 100644 (file)
index 65b70bb..0000000
+++ /dev/null
@@ -1,1422 +0,0 @@
-/*
- * Copyright (C) 2008-2012 Hans Baier <hansfbaier@googlemail.com>
- * Copyright (C) 2008-2016 David Robillard <d@drobilla.net>
- * Copyright (C) 2008-2017 Paul Davis <paul@linuxaudiosystems.com>
- * Copyright (C) 2009-2011 Carl Hetherington <carl@carlh.net>
- * Copyright (C) 2014-2019 Robin Gareus <robin@gareus.org>
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-
-#include <algorithm>
-#include <cmath>
-#include <iostream>
-#include <limits>
-#include <stdexcept>
-#include <stdint.h>
-#include <cstdio>
-
-#if __clang__
-#include "evoral/Note.hpp"
-#endif
-
-#include "pbd/compose.h"
-#include "pbd/error.h"
-
-#include "temporal/beats.h"
-
-#include "evoral/Control.hpp"
-#include "evoral/ControlList.hpp"
-#include "evoral/ControlSet.hpp"
-#include "evoral/EventSink.hpp"
-#include "evoral/ParameterDescriptor.hpp"
-#include "evoral/Sequence.hpp"
-#include "evoral/TypeMap.hpp"
-#include "evoral/midi_util.h"
-
-#include "pbd/i18n.h"
-
-using namespace std;
-using namespace PBD;
-
-/** Minimum time between MIDI outputs from a single interpolated controller,
-    expressed in beats.  This is to limit the rate at which MIDI messages
-    are generated.  It is only applied to interpolated controllers.
-
-    XXX: This is a hack.  The time should probably be expressed in
-    seconds rather than beats, and should be configurable etc. etc.
-*/
-static double const time_between_interpolated_controller_outputs = 1.0 / 256;
-
-namespace Evoral {
-
-// Read iterator (const_iterator)
-
-template<typename Time>
-Sequence<Time>::const_iterator::const_iterator()
-       : _seq(NULL)
-       , _event(boost::shared_ptr< Event<Time> >(new Event<Time>()))
-       , _active_patch_change_message (NO_EVENT)
-       , _type(NIL)
-       , _is_end(true)
-       , _control_iter(_control_iters.end())
-       , _force_discrete(false)
-{
-}
-
-/** @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,
-                                               bool                               force_discrete,
-                                               const std::set<Evoral::Parameter>& filtered,
-                                               const std::set<WeakNotePtr>*       active_notes)
-       : _seq(&seq)
-       , _active_patch_change_message (0)
-       , _type(NIL)
-       , _is_end((t == DBL_MAX) || seq.empty())
-       , _note_iter(seq.notes().end())
-       , _sysex_iter(seq.sysexes().end())
-       , _patch_change_iter(seq.patch_changes().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));
-
-       if (_is_end) {
-               return;
-       }
-
-       _lock = seq.read_lock();
-
-       // Add currently active notes, if given
-       if (active_notes) {
-               for (typename std::set<WeakNotePtr>::const_iterator i = active_notes->begin();
-                    i != active_notes->end(); ++i) {
-                       NotePtr note = i->lock();
-                       if (note && note->time() <= t && note->end_time() > t) {
-                               _active_notes.push(note);
-                       }
-               }
-       }
-
-       // Find first note which begins at or after t
-       _note_iter = seq.note_lower_bound(t);
-
-       // Find first sysex event at or after t
-       for (typename Sequence<Time>::SysExes::const_iterator i = seq.sysexes().begin();
-            i != seq.sysexes().end(); ++i) {
-               if ((*i)->time() >= t) {
-                       _sysex_iter = i;
-                       break;
-               }
-       }
-       assert(_sysex_iter == seq.sysexes().end() || (*_sysex_iter)->time() >= t);
-
-       // Find first patch event at or after t
-       for (typename Sequence<Time>::PatchChanges::const_iterator i = seq.patch_changes().begin(); i != seq.patch_changes().end(); ++i) {
-               if ((*i)->time() >= t) {
-                       _patch_change_iter = i;
-                       break;
-               }
-       }
-       assert (_patch_change_iter == seq.patch_changes().end() || (*_patch_change_iter)->time() >= t);
-
-       // Find first control event after t
-       _control_iters.reserve(seq._controls.size());
-       bool   found                  = false;
-       size_t earliest_control_index = 0;
-       double earliest_control_x     = DBL_MAX;
-       for (Controls::const_iterator i = seq._controls.begin(); i != seq._controls.end(); ++i) {
-
-               if (filtered.find (i->first) != filtered.end()) {
-                       /* this parameter is filtered, so don't bother setting up an iterator for it */
-                       continue;
-               }
-
-               DEBUG_TRACE (DEBUG::Sequence, string_compose ("Iterator: control: %1\n", seq._type_map.to_symbol(i->first)));
-               double x, y;
-               bool ret;
-               if (_force_discrete || i->second->list()->interpolation() == ControlList::Discrete) {
-                       ret = i->second->list()->rt_safe_earliest_event_discrete_unlocked (t.to_double(), x, y, true);
-               } else {
-                       ret = i->second->list()->rt_safe_earliest_event_unlocked(t.to_double(), 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));
-                       continue;
-               }
-
-               assert(x >= 0);
-
-               const ParameterDescriptor& desc = seq.type_map().descriptor(i->first);
-               if (y < desc.lower || y > desc.upper) {
-                       cerr << "ERROR: Controller value " << y
-                            << " out of range [" << desc.lower << "," << desc.upper
-                            << "], event ignored" << endl;
-                       continue;
-               }
-
-               DEBUG_TRACE (DEBUG::Sequence, string_compose ("Iterator: CC %1 added (%2, %3)\n", i->first.id(), x, y));
-
-               const ControlIterator new_iter(i->second->list(), x, y);
-               _control_iters.push_back(new_iter);
-
-               // Found a new earliest_control
-               if (x < earliest_control_x) {
-                       earliest_control_x     = x;
-                       earliest_control_index = _control_iters.size() - 1;
-                       found                  = true;
-               }
-       }
-
-       if (found) {
-               _control_iter = _control_iters.begin() + earliest_control_index;
-               assert(_control_iter != _control_iters.end());
-               assert(_control_iter->list);
-       } else {
-               _control_iter = _control_iters.end();
-       }
-
-       // Choose the earliest event overall to point to
-       choose_next(t);
-
-       // Allocate a new event for storing the current event in MIDI format
-       _event = boost::shared_ptr< Event<Time> >(
-               new Event<Time>(NO_EVENT, Time(), 4, NULL, true));
-
-       // Set event from chosen sub-iterator
-       set_event();
-
-       if (_is_end) {
-               DEBUG_TRACE(DEBUG::Sequence,
-                           string_compose("Starting at end @ %1\n", t));
-       } else {
-               DEBUG_TRACE(DEBUG::Sequence,
-                           string_compose("Starting at type 0x%1 : 0x%2 @ %3\n",
-                                          (int)_event->event_type(),
-                                          (int)_event->buffer()[0],
-                                          _event->time()));
-       }
-}
-
-template<typename Time>
-void
-Sequence<Time>::const_iterator::invalidate(std::set< boost::weak_ptr< Note<Time> > >* notes)
-{
-       while (!_active_notes.empty()) {
-               if (notes) {
-                       notes->insert(_active_notes.top());
-               }
-               _active_notes.pop();
-       }
-       _type = NIL;
-       _is_end = true;
-       if (_seq) {
-               _note_iter = _seq->notes().end();
-               _sysex_iter = _seq->sysexes().end();
-               _patch_change_iter = _seq->patch_changes().end();
-               _active_patch_change_message = 0;
-       }
-       _control_iters.clear();
-       _control_iter = _control_iters.end();
-       _lock.reset();
-}
-
-template<typename Time>
-Time
-Sequence<Time>::const_iterator::choose_next(Time earliest_t)
-{
-       _type = NIL;
-
-       // Next earliest note on, if any
-       if (_note_iter != _seq->notes().end()) {
-               _type      = NOTE_ON;
-               earliest_t = (*_note_iter)->time();
-       }
-
-       /* Use the next earliest patch change iff it is earlier or coincident with the note-on.
-        * A patch-change with the same time-stamp applies to the concurrent note-on */
-       if (_patch_change_iter != _seq->patch_changes().end()) {
-               if (_type == NIL || (*_patch_change_iter)->time() <= earliest_t) {
-                       _type      = PATCH_CHANGE;
-                       earliest_t = (*_patch_change_iter)->time();
-               }
-       }
-
-       /* Use the next earliest controller iff it's earlier or coincident with the note-on
-        * or patch-change. Bank-select (CC0, CC32) needs to be sent before the PGM. */
-       if (_control_iter != _control_iters.end() &&
-           _control_iter->list && _control_iter->x != DBL_MAX) {
-               if (_type == NIL || _control_iter->x <= earliest_t.to_double()) {
-                       _type      = CONTROL;
-                       earliest_t = Time(_control_iter->x);
-               }
-       }
-
-       /* .. but prefer to send any Note-off first */
-       if ((!_active_notes.empty())) {
-               if (_type == NIL || _active_notes.top()->end_time().to_double() <= earliest_t.to_double()) {
-                       _type      = NOTE_OFF;
-                       earliest_t = _active_notes.top()->end_time();
-               }
-       }
-
-       /* SysEx is last, always sent after any other concurrent 3 byte event */
-       if (_sysex_iter != _seq->sysexes().end()) {
-               if (_type == NIL || (*_sysex_iter)->time() < earliest_t) {
-                       _type      = SYSEX;
-                       earliest_t = (*_sysex_iter)->time();
-               }
-       }
-
-       return earliest_t;
-}
-
-template<typename Time>
-void
-Sequence<Time>::const_iterator::set_event()
-{
-       switch (_type) {
-       case NOTE_ON:
-               DEBUG_TRACE(DEBUG::Sequence, "iterator = note on\n");
-               _event->assign ((*_note_iter)->on_event());
-               _active_notes.push(*_note_iter);
-               break;
-       case NOTE_OFF:
-               DEBUG_TRACE(DEBUG::Sequence, "iterator = note off\n");
-               assert(!_active_notes.empty());
-               _event->assign (_active_notes.top()->off_event());
-               // We don't pop the active note until we increment past it
-               break;
-       case SYSEX:
-               DEBUG_TRACE(DEBUG::Sequence, "iterator = sysex\n");
-               _event->assign (*(*_sysex_iter));
-               break;
-       case CONTROL:
-               DEBUG_TRACE(DEBUG::Sequence, "iterator = control\n");
-               _seq->control_to_midi_event(_event, *_control_iter);
-               break;
-       case PATCH_CHANGE:
-               DEBUG_TRACE(DEBUG::Sequence, "iterator = program change\n");
-               _event->assign ((*_patch_change_iter)->message (_active_patch_change_message));
-               break;
-       default:
-               _is_end = true;
-               break;
-       }
-
-       if (_type == NIL || !_event || _event->size() == 0) {
-               DEBUG_TRACE(DEBUG::Sequence, "iterator = end\n");
-               _type   = NIL;
-               _is_end = true;
-       } else {
-               assert(midi_event_is_valid(_event->buffer(), _event->size()));
-       }
-}
-
-template<typename Time>
-const typename Sequence<Time>::const_iterator&
-Sequence<Time>::const_iterator::operator++()
-{
-       if (_is_end) {
-               throw std::logic_error("Attempt to iterate past end of Sequence");
-       }
-
-       assert(_event && _event->buffer() && _event->size() > 0);
-
-       const Event<Time>& ev = *_event.get();
-
-       if (!(     ev.is_note()
-                  || ev.is_cc()
-                  || ev.is_pgm_change()
-                  || ev.is_pitch_bender()
-                  || ev.is_channel_pressure()
-                  || ev.is_poly_pressure()
-                  || ev.is_sysex()) ) {
-               cerr << "WARNING: Unknown event (type " << _type << "): " << hex
-                    << int(ev.buffer()[0]) << int(ev.buffer()[1]) << int(ev.buffer()[2]) << endl;
-       }
-
-       double x   = 0.0;
-       double y   = 0.0;
-       bool   ret = false;
-
-       // Increment past current event
-       switch (_type) {
-       case NOTE_ON:
-               ++_note_iter;
-               break;
-       case NOTE_OFF:
-               _active_notes.pop();
-               break;
-       case CONTROL:
-               // Increment current controller iterator
-               if (_force_discrete || _control_iter->list->interpolation() == ControlList::Discrete) {
-                       ret = _control_iter->list->rt_safe_earliest_event_discrete_unlocked (
-                               _control_iter->x, x, y, false);
-               } else {
-                       ret = _control_iter->list->rt_safe_earliest_event_linear_unlocked (
-                               _control_iter->x + time_between_interpolated_controller_outputs, x, y, false);
-               }
-               assert(!ret || x > _control_iter->x);
-               if (ret) {
-                       _control_iter->x = x;
-                       _control_iter->y = y;
-               } else {
-                       _control_iter->list.reset();
-                       _control_iter->x = DBL_MAX;
-                       _control_iter->y = DBL_MAX;
-               }
-
-               // Find the controller with the next earliest event time
-               _control_iter = _control_iters.begin();
-               for (ControlIterators::iterator i = _control_iters.begin();
-                    i != _control_iters.end(); ++i) {
-                       if (i->x < _control_iter->x) {
-                               _control_iter = i;
-                       }
-               }
-               break;
-       case SYSEX:
-               ++_sysex_iter;
-               break;
-       case PATCH_CHANGE:
-               ++_active_patch_change_message;
-               if (_active_patch_change_message == (*_patch_change_iter)->messages()) {
-                       ++_patch_change_iter;
-                       _active_patch_change_message = 0;
-               }
-               break;
-       default:
-               assert(false);
-       }
-
-       // Choose the earliest event overall to point to
-       choose_next(std::numeric_limits<Time>::max());
-
-       // Set event from chosen sub-iterator
-       set_event();
-
-       assert(_is_end || (_event->size() > 0 && _event->buffer() && _event->buffer()[0] != '\0'));
-
-       return *this;
-}
-
-template<typename Time>
-bool
-Sequence<Time>::const_iterator::operator==(const const_iterator& other) const
-{
-       if (_seq != other._seq) {
-               return false;
-       } else if (_is_end || other._is_end) {
-               return (_is_end == other._is_end);
-       } else if (_type != other._type) {
-               return false;
-       } else {
-               return (_event == other._event);
-       }
-}
-
-template<typename Time>
-typename Sequence<Time>::const_iterator&
-Sequence<Time>::const_iterator::operator=(const const_iterator& other)
-{
-       _seq           = other._seq;
-       _event         = other._event;
-       _active_notes  = other._active_notes;
-       _type          = other._type;
-       _is_end        = other._is_end;
-       _note_iter     = other._note_iter;
-       _sysex_iter    = other._sysex_iter;
-       _patch_change_iter = other._patch_change_iter;
-       _control_iters = other._control_iters;
-       _force_discrete = other._force_discrete;
-       _active_patch_change_message = other._active_patch_change_message;
-
-       if (other._lock) {
-               _lock = _seq->read_lock();
-       } else {
-               _lock.reset();
-       }
-
-       if (other._control_iter == other._control_iters.end()) {
-               _control_iter = _control_iters.end();
-       } else {
-               const size_t index = other._control_iter - other._control_iters.begin();
-               _control_iter  = _control_iters.begin() + index;
-       }
-
-       return *this;
-}
-
-// Sequence
-
-template<typename Time>
-Sequence<Time>::Sequence(const TypeMap& type_map)
-       : _edited(false)
-       , _overlapping_pitches_accepted (true)
-       , _overlap_pitch_resolution (FirstOnFirstOff)
-       , _writing(false)
-       , _type_map(type_map)
-       , _end_iter(*this, std::numeric_limits<Time>::max(), false, std::set<Evoral::Parameter> ())
-       , _percussive(false)
-       , _lowest_note(127)
-       , _highest_note(0)
-{
-       DEBUG_TRACE (DEBUG::Sequence, string_compose ("Sequence constructed: %1\n", this));
-       assert(_end_iter._is_end);
-       assert( ! _end_iter._lock);
-
-       for (int i = 0; i < 16; ++i) {
-               _bank[i] = 0;
-       }
-}
-
-template<typename Time>
-Sequence<Time>::Sequence(const Sequence<Time>& other)
-       : ControlSet (other)
-       , _edited(false)
-       , _overlapping_pitches_accepted (other._overlapping_pitches_accepted)
-       , _overlap_pitch_resolution (other._overlap_pitch_resolution)
-       , _writing(false)
-       , _type_map(other._type_map)
-       , _end_iter(*this, std::numeric_limits<Time>::max(), false, std::set<Evoral::Parameter> ())
-       , _percussive(other._percussive)
-       , _lowest_note(other._lowest_note)
-       , _highest_note(other._highest_note)
-{
-       for (typename Notes::const_iterator i = other._notes.begin(); i != other._notes.end(); ++i) {
-               NotePtr n (new Note<Time> (**i));
-               _notes.insert (n);
-       }
-
-       for (typename SysExes::const_iterator i = other._sysexes.begin(); i != other._sysexes.end(); ++i) {
-               boost::shared_ptr<Event<Time> > n (new Event<Time> (**i, true));
-               _sysexes.insert (n);
-       }
-
-       for (typename PatchChanges::const_iterator i = other._patch_changes.begin(); i != other._patch_changes.end(); ++i) {
-               PatchChangePtr n (new PatchChange<Time> (**i));
-               _patch_changes.insert (n);
-       }
-
-       for (int i = 0; i < 16; ++i) {
-               _bank[i] = other._bank[i];
-       }
-
-       DEBUG_TRACE (DEBUG::Sequence, string_compose ("Sequence copied: %1\n", this));
-       assert(_end_iter._is_end);
-       assert(! _end_iter._lock);
-}
-
-/** Write the controller event pointed to by `iter` to `ev`.
- * The buffer of `ev` will be allocated or resized as necessary.
- * \return true on success
- */
-template<typename Time>
-bool
-Sequence<Time>::control_to_midi_event(
-       boost::shared_ptr< Event<Time> >& ev,
-       const ControlIterator&            iter) const
-{
-       assert(iter.list.get());
-
-       // initialize the event pointer with a new event, if necessary
-       if (!ev) {
-               ev = boost::shared_ptr< Event<Time> >(new Event<Time>(NO_EVENT, Time(), 3, NULL, true));
-       }
-
-       const uint8_t midi_type = _type_map.parameter_midi_type(iter.list->parameter());
-       ev->set_event_type(MIDI_EVENT);
-       ev->set_id(-1);
-       switch (midi_type) {
-       case MIDI_CMD_CONTROL:
-               assert(iter.list.get());
-               assert(iter.list->parameter().channel() < 16);
-               assert(iter.list->parameter().id() <= INT8_MAX);
-               assert(iter.y <= INT8_MAX);
-
-               ev->set_time(Time(iter.x));
-               ev->realloc(3);
-               ev->buffer()[0] = MIDI_CMD_CONTROL + iter.list->parameter().channel();
-               ev->buffer()[1] = (uint8_t)iter.list->parameter().id();
-               ev->buffer()[2] = (uint8_t)iter.y;
-               break;
-
-       case MIDI_CMD_PGM_CHANGE:
-               assert(iter.list.get());
-               assert(iter.list->parameter().channel() < 16);
-               assert(iter.y <= INT8_MAX);
-
-               ev->set_time(Time(iter.x));
-               ev->realloc(2);
-               ev->buffer()[0] = MIDI_CMD_PGM_CHANGE + iter.list->parameter().channel();
-               ev->buffer()[1] = (uint8_t)iter.y;
-               break;
-
-       case MIDI_CMD_BENDER:
-               assert(iter.list.get());
-               assert(iter.list->parameter().channel() < 16);
-               assert(iter.y < (1<<14));
-
-               ev->set_time(Time(iter.x));
-               ev->realloc(3);
-               ev->buffer()[0] = MIDI_CMD_BENDER + iter.list->parameter().channel();
-               ev->buffer()[1] = uint16_t(iter.y) & 0x7F; // LSB
-               ev->buffer()[2] = (uint16_t(iter.y) >> 7) & 0x7F; // MSB
-               break;
-
-       case MIDI_CMD_NOTE_PRESSURE:
-               assert(iter.list.get());
-               assert(iter.list->parameter().channel() < 16);
-               assert(iter.list->parameter().id() <= INT8_MAX);
-               assert(iter.y <= INT8_MAX);
-
-               ev->set_time(Time(iter.x));
-               ev->realloc(3);
-               ev->buffer()[0] = MIDI_CMD_NOTE_PRESSURE + iter.list->parameter().channel();
-               ev->buffer()[1] = (uint8_t)iter.list->parameter().id();
-               ev->buffer()[2] = (uint8_t)iter.y;
-               break;
-
-       case MIDI_CMD_CHANNEL_PRESSURE:
-               assert(iter.list.get());
-               assert(iter.list->parameter().channel() < 16);
-               assert(iter.y <= INT8_MAX);
-
-               ev->set_time(Time(iter.x));
-               ev->realloc(2);
-               ev->buffer()[0] = MIDI_CMD_CHANNEL_PRESSURE + iter.list->parameter().channel();
-               ev->buffer()[1] = (uint8_t)iter.y;
-               break;
-
-       default:
-               return false;
-       }
-
-       return true;
-}
-
-/** Clear all events from the model.
- */
-template<typename Time>
-void
-Sequence<Time>::clear()
-{
-       WriteLock lock(write_lock());
-       _notes.clear();
-       for (Controls::iterator li = _controls.begin(); li != _controls.end(); ++li)
-               li->second->list()->clear();
-}
-
-/** Begin a write of events to the model.
- *
- * If \a mode is Sustained, complete notes with length are constructed as note
- * on/off events are received.  Otherwise (Percussive), only note on events are
- * stored; note off events are discarded entirely and all contained notes will
- * have length 0.
- */
-template<typename Time>
-void
-Sequence<Time>::start_write()
-{
-       DEBUG_TRACE (DEBUG::Sequence, string_compose ("%1 : start_write (percussive = %2)\n", this, _percussive));
-       WriteLock lock(write_lock());
-       _writing = true;
-       for (int i = 0; i < 16; ++i) {
-               _write_notes[i].clear();
-       }
-}
-
-/** Finish a write of events to the model.
- *
- * If \a delete_stuck is true and the current mode is Sustained, note on events
- * that were never resolved with a corresonding note off will be deleted.
- * Otherwise they will remain as notes with length 0.
- */
-template<typename Time>
-void
-Sequence<Time>::end_write (StuckNoteOption option, Time when)
-{
-       WriteLock lock(write_lock());
-
-       if (!_writing) {
-               return;
-       }
-
-       DEBUG_TRACE (DEBUG::Sequence, string_compose ("%1 : end_write (%2 notes) delete stuck option %3 @ %4\n", this, _notes.size(), option, when));
-
-       for (typename Notes::iterator n = _notes.begin(); n != _notes.end() ;) {
-               typename Notes::iterator next = n;
-               ++next;
-
-               if (!(*n)->length()) {
-                       switch (option) {
-                       case Relax:
-                               break;
-                       case DeleteStuckNotes:
-                               cerr << "WARNING: Stuck note lost: " << (*n)->note() << endl;
-                               _notes.erase(n);
-                               break;
-                       case ResolveStuckNotes:
-                               if (when <= (*n)->time()) {
-                                       cerr << "WARNING: Stuck note resolution - end time @ "
-                                            << when << " is before note on: " << (**n) << endl;
-                                       _notes.erase (*n);
-                               } else {
-                                       (*n)->set_length (when - (*n)->time());
-                                       cerr << "WARNING: resolved note-on with no note-off to generate " << (**n) << endl;
-                               }
-                               break;
-                       }
-               }
-
-               n = next;
-       }
-
-       for (int i = 0; i < 16; ++i) {
-               _write_notes[i].clear();
-       }
-
-       _writing = false;
-}
-
-
-template<typename Time>
-bool
-Sequence<Time>::add_note_unlocked(const NotePtr note, void* arg)
-{
-       /* This is the core method to add notes to a Sequence
-        */
-
-       DEBUG_TRACE (DEBUG::Sequence, string_compose ("%1 add note %2 @ %3 dur %4\n", this, (int)note->note(), note->time(), note->length()));
-
-       if (resolve_overlaps_unlocked (note, arg)) {
-               DEBUG_TRACE (DEBUG::Sequence, string_compose ("%1 DISALLOWED: note %2 @ %3\n", this, (int)note->note(), note->time()));
-               return false;
-       }
-
-       if (note->id() < 0) {
-               note->set_id (Evoral::next_event_id());
-       }
-
-       if (note->note() < _lowest_note)
-               _lowest_note = note->note();
-       if (note->note() > _highest_note)
-               _highest_note = note->note();
-
-       _notes.insert (note);
-       _pitches[note->channel()].insert (note);
-
-       _edited = true;
-
-       return true;
-}
-
-template<typename Time>
-void
-Sequence<Time>::remove_note_unlocked(const constNotePtr note)
-{
-       bool erased = false;
-       bool id_matched = false;
-
-       DEBUG_TRACE (DEBUG::Sequence, string_compose ("%1 remove note #%2 %3 @ %4\n", this, note->id(), (int)note->note(), note->time()));
-
-       /* first try searching for the note using the time index, which is
-        * faster since the container is "indexed" by time. (technically, this
-        * means that lower_bound() can do a binary search rather than linear)
-        *
-        * this may not work, for reasons explained below.
-        */
-
-       typename Sequence<Time>::Notes::iterator i;
-
-       for (i = note_lower_bound(note->time()); i != _notes.end() && (*i)->time() == note->time(); ++i) {
-
-               if (*i == note) {
-
-                       DEBUG_TRACE (DEBUG::Sequence, string_compose ("%1\terasing note #%2 %3 @ %4\n", this, (*i)->id(), (int)(*i)->note(), (*i)->time()));
-                       _notes.erase (i);
-
-                       if (note->note() == _lowest_note || note->note() == _highest_note) {
-
-                               _lowest_note = 127;
-                               _highest_note = 0;
-
-                               for (typename Sequence<Time>::Notes::iterator ii = _notes.begin(); ii != _notes.end(); ++ii) {
-                                       if ((*ii)->note() < _lowest_note)
-                                               _lowest_note = (*ii)->note();
-                                       if ((*ii)->note() > _highest_note)
-                                               _highest_note = (*ii)->note();
-                               }
-                       }
-
-                       erased = true;
-                       break;
-               }
-       }
-
-       DEBUG_TRACE (DEBUG::Sequence, string_compose ("%1\ttime-based lookup did not find note #%2 %3 @ %4\n", this, note->id(), (int)note->note(), note->time()));
-
-       if (!erased) {
-
-               /* if the note's time property was changed in tandem with some
-                * other property as the next operation after it was added to
-                * the sequence, then at the point where we call this to undo
-                * the add, the note we are targetting currently has a
-                * different time property than the one we we passed via
-                * the argument.
-                *
-                * in this scenario, we have no choice other than to linear
-                * search the list of notes and find the note by ID.
-                */
-
-               for (i = _notes.begin(); i != _notes.end(); ++i) {
-
-                       if ((*i)->id() == note->id()) {
-
-                               DEBUG_TRACE (DEBUG::Sequence, string_compose ("%1\tID-based pass, erasing note #%2 %3 @ %4\n", this, (*i)->id(), (int)(*i)->note(), (*i)->time()));
-                               _notes.erase (i);
-
-                               if (note->note() == _lowest_note || note->note() == _highest_note) {
-
-                                       _lowest_note = 127;
-                                       _highest_note = 0;
-
-                                       for (typename Sequence<Time>::Notes::iterator ii = _notes.begin(); ii != _notes.end(); ++ii) {
-                                               if ((*ii)->note() < _lowest_note)
-                                                       _lowest_note = (*ii)->note();
-                                               if ((*ii)->note() > _highest_note)
-                                                       _highest_note = (*ii)->note();
-                                       }
-                               }
-
-                               erased = true;
-                               id_matched = true;
-                               break;
-                       }
-               }
-       }
-
-       if (erased) {
-
-               Pitches& p (pitches (note->channel()));
-
-               typename Pitches::iterator j;
-
-               /* if we had to ID-match above, we can't expect to find it in
-                * pitches via note comparison either. so do another linear
-                * search to locate it. otherwise, we can use the note index
-                * to potentially speed things up.
-                */
-
-               if (id_matched) {
-
-                       for (j = p.begin(); j != p.end(); ++j) {
-                               if ((*j)->id() == note->id()) {
-                                       p.erase (j);
-                                       break;
-                               }
-                       }
-
-               } else {
-
-                       /* Now find the same note in the "pitches" list (which indexes
-                        * notes by channel+time. We care only about its note number
-                        * so the search_note has all other properties unset.
-                        */
-
-                       NotePtr search_note (new Note<Time>(0, Time(), Time(), note->note(), 0));
-
-                       for (j = p.lower_bound (search_note); j != p.end() && (*j)->note() == note->note(); ++j) {
-
-                               if ((*j) == note) {
-                                       DEBUG_TRACE (DEBUG::Sequence, string_compose ("%1\terasing pitch %2 @ %3\n", this, (int)(*j)->note(), (*j)->time()));
-                                       p.erase (j);
-                                       break;
-                               }
-                       }
-               }
-
-               if (j == p.end()) {
-                       warning << string_compose ("erased note %1 not found in pitches for channel %2", *note, (int) note->channel()) << endmsg;
-               }
-
-               _edited = true;
-
-       } else {
-               cerr << "Unable to find note to erase matching " << *note.get() << endmsg;
-       }
-}
-
-template<typename Time>
-void
-Sequence<Time>::remove_patch_change_unlocked (const constPatchChangePtr p)
-{
-       typename Sequence<Time>::PatchChanges::iterator i = patch_change_lower_bound (p->time ());
-
-       while (i != _patch_changes.end() && ((*i)->time() == p->time())) {
-
-               typename Sequence<Time>::PatchChanges::iterator tmp = i;
-               ++tmp;
-
-               if (**i == *p) {
-                       _patch_changes.erase (i);
-               }
-
-               i = tmp;
-       }
-}
-
-template<typename Time>
-void
-Sequence<Time>::remove_sysex_unlocked (const SysExPtr sysex)
-{
-       typename Sequence<Time>::SysExes::iterator i = sysex_lower_bound (sysex->time ());
-       while (i != _sysexes.end() && (*i)->time() == sysex->time()) {
-
-               typename Sequence<Time>::SysExes::iterator tmp = i;
-               ++tmp;
-
-               if (*i == sysex) {
-                       _sysexes.erase (i);
-               }
-
-               i = tmp;
-       }
-}
-
-/** Append \a ev to model.  NOT realtime safe.
- *
- * The timestamp of event is expected to be relative to
- * the start of this model (t=0) and MUST be monotonically increasing
- * and MUST be >= the latest event currently in the model.
- */
-template<typename Time>
-void
-Sequence<Time>::append(const Event<Time>& ev, event_id_t evid)
-{
-       WriteLock lock(write_lock());
-
-       assert(_notes.empty() || ev.time() >= (*_notes.rbegin())->time());
-       assert(_writing);
-
-       if (!midi_event_is_valid(ev.buffer(), ev.size())) {
-               cerr << "WARNING: Sequence ignoring illegal MIDI event" << endl;
-               return;
-       }
-
-       if (ev.is_note_on() && ev.velocity() > 0) {
-               append_note_on_unlocked (ev, evid);
-       } else if (ev.is_note_off() || (ev.is_note_on() && ev.velocity() == 0)) {
-               /* XXX note: event ID is discarded because we merge the on+off events into
-                  a single note object
-               */
-               append_note_off_unlocked (ev);
-       } else if (ev.is_sysex()) {
-               append_sysex_unlocked(ev, evid);
-       } else if (ev.is_cc() && (ev.cc_number() == MIDI_CTL_MSB_BANK || ev.cc_number() == MIDI_CTL_LSB_BANK)) {
-               /* note bank numbers in our _bank[] array, so that we can write an event when the program change arrives */
-               if (ev.cc_number() == MIDI_CTL_MSB_BANK) {
-                       _bank[ev.channel()] &= ~(0x7f << 7);
-                       _bank[ev.channel()] |= ev.cc_value() << 7;
-               } else {
-                       _bank[ev.channel()] &= ~0x7f;
-                       _bank[ev.channel()] |= ev.cc_value();
-               }
-       } else if (ev.is_cc()) {
-               const ParameterType ptype = _type_map.midi_parameter_type(ev.buffer(), ev.size());
-               append_control_unlocked(
-                       Parameter(ptype, ev.channel(), ev.cc_number()),
-                       ev.time(), ev.cc_value(), evid);
-       } else if (ev.is_pgm_change()) {
-               /* write a patch change with this program change and any previously set-up bank number */
-               append_patch_change_unlocked (
-                       PatchChange<Time> (ev.time(), ev.channel(),
-                                          ev.pgm_number(), _bank[ev.channel()]), evid);
-       } else if (ev.is_pitch_bender()) {
-               const ParameterType ptype = _type_map.midi_parameter_type(ev.buffer(), ev.size());
-               append_control_unlocked(
-                       Parameter(ptype, ev.channel()),
-                       ev.time(), double ((0x7F & ev.pitch_bender_msb()) << 7
-                                          | (0x7F & ev.pitch_bender_lsb())),
-                       evid);
-       } else if (ev.is_poly_pressure()) {
-               const ParameterType ptype = _type_map.midi_parameter_type(ev.buffer(), ev.size());
-               append_control_unlocked (Parameter (ptype, ev.channel(), ev.poly_note()), ev.time(), ev.poly_pressure(), evid);
-       } else if (ev.is_channel_pressure()) {
-               const ParameterType ptype = _type_map.midi_parameter_type(ev.buffer(), ev.size());
-               append_control_unlocked(
-                       Parameter(ptype, ev.channel()),
-                       ev.time(), ev.channel_pressure(), evid);
-       } else if (!_type_map.type_is_midi(ev.event_type())) {
-               printf("WARNING: Sequence: Unknown event type %X: ", ev.event_type());
-               for (size_t i=0; i < ev.size(); ++i) {
-                       printf("%X ", ev.buffer()[i]);
-               }
-               printf("\n");
-       } else {
-               printf("WARNING: Sequence: Unknown MIDI event type %X\n", ev.type());
-       }
-
-       _edited = true;
-}
-
-template<typename Time>
-void
-Sequence<Time>::append_note_on_unlocked (const Event<Time>& ev, event_id_t evid)
-{
-       DEBUG_TRACE (DEBUG::Sequence, string_compose ("%1 c=%2 note %3 on @ %4 v=%5\n", this,
-                                                     (int)ev.channel(), (int)ev.note(),
-                                                     ev.time(), (int)ev.velocity()));
-       assert(_writing);
-
-       if (ev.note() > 127) {
-               error << string_compose (_("invalid note on number (%1) ignored"), (int) ev.note()) << endmsg;
-               return;
-       } else if (ev.channel() >= 16) {
-               error << string_compose (_("invalid note on channel (%1) ignored"), (int) ev.channel()) << endmsg;
-               return;
-       } else if (ev.velocity() == 0) {
-               // Note on with velocity 0 handled as note off by caller
-               error << string_compose (_("invalid note on velocity (%1) ignored"), (int) ev.velocity()) << endmsg;
-               return;
-       }
-
-       NotePtr note(new Note<Time>(ev.channel(), ev.time(), Time(), ev.note(), ev.velocity()));
-       note->set_id (evid);
-
-       add_note_unlocked (note);
-
-       DEBUG_TRACE (DEBUG::Sequence, string_compose ("Appending active note on %1 channel %2\n",
-                                                     (unsigned)(uint8_t)note->note(), note->channel()));
-       _write_notes[note->channel()].insert (note);
-
-}
-
-template<typename Time>
-void
-Sequence<Time>::append_note_off_unlocked (const Event<Time>& ev)
-{
-       DEBUG_TRACE (DEBUG::Sequence, string_compose ("%1 c=%2 note %3 OFF @ %4 v=%5\n",
-                                                     this, (int)ev.channel(),
-                                                     (int)ev.note(), ev.time(), (int)ev.velocity()));
-       assert(_writing);
-
-       if (ev.note() > 127) {
-               error << string_compose (_("invalid note off number (%1) ignored"), (int) ev.note()) << endmsg;
-               return;
-       } else if (ev.channel() >= 16) {
-               error << string_compose (_("invalid note off channel (%1) ignored"), (int) ev.channel()) << endmsg;
-               return;
-       }
-
-       _edited = true;
-
-       bool resolved = false;
-
-       /* _write_notes is sorted earliest-latest, so this will find the first matching note (FIFO) that
-          matches this note (by pitch & channel). the MIDI specification doesn't provide any guidance
-          whether to use FIFO or LIFO for this matching process, so SMF is fundamentally a lossy
-          format.
-       */
-
-       /* XXX use _overlap_pitch_resolution to determine FIFO/LIFO ... */
-
-       for (typename WriteNotes::iterator n = _write_notes[ev.channel()].begin(); n != _write_notes[ev.channel()].end(); ) {
-
-               typename WriteNotes::iterator tmp = n;
-               ++tmp;
-
-               NotePtr nn = *n;
-               if (ev.note() == nn->note() && nn->channel() == ev.channel()) {
-                       assert(ev.time() >= nn->time());
-
-                       nn->set_length (ev.time() - nn->time());
-                       nn->set_off_velocity (ev.velocity());
-
-                       _write_notes[ev.channel()].erase(n);
-                       DEBUG_TRACE (DEBUG::Sequence, string_compose ("resolved note @ %2 length: %1\n", nn->length(), nn->time()));
-                       resolved = true;
-                       break;
-               }
-
-               n = tmp;
-       }
-
-       if (!resolved) {
-               cerr << this << " spurious note off chan " << (int)ev.channel()
-                    << ", note " << (int)ev.note() << " @ " << ev.time() << endl;
-       }
-}
-
-template<typename Time>
-void
-Sequence<Time>::append_control_unlocked(const Parameter& param, Time time, double value, event_id_t /* evid */)
-{
-       DEBUG_TRACE (DEBUG::Sequence, string_compose ("%1 %2 @ %3 = %4 # controls: %5\n",
-                                                     this, _type_map.to_symbol(param), time, value, _controls.size()));
-       boost::shared_ptr<Control> c = control(param, true);
-       c->list()->add (time.to_double(), value, true, false);
-       /* XXX control events should use IDs */
-}
-
-template<typename Time>
-void
-Sequence<Time>::append_sysex_unlocked(const Event<Time>& ev, event_id_t /* evid */)
-{
-#ifdef DEBUG_SEQUENCE
-       cerr << this << " SysEx @ " << ev.time() << " \t= \t [ " << hex;
-       for (size_t i=0; i < ev.size(); ++i) {
-               cerr << int(ev.buffer()[i]) << " ";
-       } cerr << "]" << endl;
-#endif
-
-       boost::shared_ptr< Event<Time> > event(new Event<Time>(ev, true));
-       /* XXX sysex events should use IDs */
-       _sysexes.insert(event);
-}
-
-template<typename Time>
-void
-Sequence<Time>::append_patch_change_unlocked (const PatchChange<Time>& ev, event_id_t id)
-{
-       PatchChangePtr p (new PatchChange<Time> (ev));
-
-       if (p->id() < 0) {
-               p->set_id (id);
-       }
-
-       _patch_changes.insert (p);
-}
-
-template<typename Time>
-void
-Sequence<Time>::add_patch_change_unlocked (PatchChangePtr p)
-{
-       if (p->id () < 0) {
-               p->set_id (Evoral::next_event_id ());
-       }
-
-       _patch_changes.insert (p);
-}
-
-template<typename Time>
-void
-Sequence<Time>::add_sysex_unlocked (SysExPtr s)
-{
-       if (s->id () < 0) {
-               s->set_id (Evoral::next_event_id ());
-       }
-
-       _sysexes.insert (s);
-}
-
-template<typename Time>
-bool
-Sequence<Time>::contains (const NotePtr& note) const
-{
-       ReadLock lock (read_lock());
-       return contains_unlocked (note);
-}
-
-template<typename Time>
-bool
-Sequence<Time>::contains_unlocked (const NotePtr& note) const
-{
-       const Pitches& p (pitches (note->channel()));
-       NotePtr search_note(new Note<Time>(0, Time(), Time(), note->note()));
-
-       for (typename Pitches::const_iterator i = p.lower_bound (search_note);
-            i != p.end() && (*i)->note() == note->note(); ++i) {
-
-               if (**i == *note) {
-                       return true;
-               }
-       }
-
-       return false;
-}
-
-template<typename Time>
-bool
-Sequence<Time>::overlaps (const NotePtr& note, const NotePtr& without) const
-{
-       ReadLock lock (read_lock());
-       return overlaps_unlocked (note, without);
-}
-
-template<typename Time>
-bool
-Sequence<Time>::overlaps_unlocked (const NotePtr& note, const NotePtr& without) const
-{
-       Time sa = note->time();
-       Time ea  = note->end_time();
-
-       const Pitches& p (pitches (note->channel()));
-       NotePtr search_note(new Note<Time>(0, Time(), Time(), note->note()));
-
-       for (typename Pitches::const_iterator i = p.lower_bound (search_note);
-            i != p.end() && (*i)->note() == note->note(); ++i) {
-
-               if (without && (**i) == *without) {
-                       continue;
-               }
-
-               Time sb = (*i)->time();
-               Time eb = (*i)->end_time();
-
-               if (((sb > sa) && (eb <= ea)) ||
-                   ((eb >= sa) && (eb <= ea)) ||
-                   ((sb > sa) && (sb <= ea)) ||
-                   ((sa >= sb) && (sa <= eb) && (ea <= eb))) {
-                       return true;
-               }
-       }
-
-       return false;
-}
-
-template<typename Time>
-void
-Sequence<Time>::set_notes (const typename Sequence<Time>::Notes& n)
-{
-       _notes = n;
-}
-
-// CONST iterator implementations (x3)
-
-/** Return the earliest note with time >= t */
-template<typename Time>
-typename Sequence<Time>::Notes::const_iterator
-Sequence<Time>::note_lower_bound (Time t) const
-{
-       NotePtr search_note(new Note<Time>(0, t, Time(), 0, 0));
-       typename Sequence<Time>::Notes::const_iterator i = _notes.lower_bound(search_note);
-       assert(i == _notes.end() || (*i)->time() >= t);
-       return i;
-}
-
-/** Return the earliest patch change with time >= t */
-template<typename Time>
-typename Sequence<Time>::PatchChanges::const_iterator
-Sequence<Time>::patch_change_lower_bound (Time t) const
-{
-       PatchChangePtr search (new PatchChange<Time> (t, 0, 0, 0));
-       typename Sequence<Time>::PatchChanges::const_iterator i = _patch_changes.lower_bound (search);
-       assert (i == _patch_changes.end() || (*i)->time() >= t);
-       return i;
-}
-
-/** Return the earliest sysex with time >= t */
-template<typename Time>
-typename Sequence<Time>::SysExes::const_iterator
-Sequence<Time>::sysex_lower_bound (Time t) const
-{
-       SysExPtr search (new Event<Time> (NO_EVENT, t));
-       typename Sequence<Time>::SysExes::const_iterator i = _sysexes.lower_bound (search);
-       assert (i == _sysexes.end() || (*i)->time() >= t);
-       return i;
-}
-
-// NON-CONST iterator implementations (x3)
-
-/** Return the earliest note with time >= t */
-template<typename Time>
-typename Sequence<Time>::Notes::iterator
-Sequence<Time>::note_lower_bound (Time t)
-{
-       NotePtr search_note(new Note<Time>(0, t, Time(), 0, 0));
-       typename Sequence<Time>::Notes::iterator i = _notes.lower_bound(search_note);
-       assert(i == _notes.end() || (*i)->time() >= t);
-       return i;
-}
-
-/** Return the earliest patch change with time >= t */
-template<typename Time>
-typename Sequence<Time>::PatchChanges::iterator
-Sequence<Time>::patch_change_lower_bound (Time t)
-{
-       PatchChangePtr search (new PatchChange<Time> (t, 0, 0, 0));
-       typename Sequence<Time>::PatchChanges::iterator i = _patch_changes.lower_bound (search);
-       assert (i == _patch_changes.end() || (*i)->time() >= t);
-       return i;
-}
-
-/** Return the earliest sysex with time >= t */
-template<typename Time>
-typename Sequence<Time>::SysExes::iterator
-Sequence<Time>::sysex_lower_bound (Time t)
-{
-       SysExPtr search (new Event<Time> (NO_EVENT, t));
-       typename Sequence<Time>::SysExes::iterator i = _sysexes.lower_bound (search);
-       assert (i == _sysexes.end() || (*i)->time() >= t);
-       return i;
-}
-
-template<typename Time>
-void
-Sequence<Time>::get_notes (Notes& n, NoteOperator op, uint8_t val, int chan_mask) const
-{
-       switch (op) {
-       case PitchEqual:
-       case PitchLessThan:
-       case PitchLessThanOrEqual:
-       case PitchGreater:
-       case PitchGreaterThanOrEqual:
-               get_notes_by_pitch (n, op, val, chan_mask);
-               break;
-
-       case VelocityEqual:
-       case VelocityLessThan:
-       case VelocityLessThanOrEqual:
-       case VelocityGreater:
-       case VelocityGreaterThanOrEqual:
-               get_notes_by_velocity (n, op, val, chan_mask);
-               break;
-       }
-}
-
-template<typename Time>
-void
-Sequence<Time>::get_notes_by_pitch (Notes& n, NoteOperator op, uint8_t val, int chan_mask) const
-{
-       for (uint8_t c = 0; c < 16; ++c) {
-
-               if (chan_mask != 0 && !((1<<c) & chan_mask)) {
-                       continue;
-               }
-
-               const Pitches& p (pitches (c));
-               NotePtr search_note(new Note<Time>(0, Time(), Time(), val, 0));
-               typename Pitches::const_iterator i;
-               switch (op) {
-               case PitchEqual:
-                       i = p.lower_bound (search_note);
-                       while (i != p.end() && (*i)->note() == val) {
-                               n.insert (*i);
-                       }
-                       break;
-               case PitchLessThan:
-                       i = p.upper_bound (search_note);
-                       while (i != p.end() && (*i)->note() < val) {
-                               n.insert (*i);
-                       }
-                       break;
-               case PitchLessThanOrEqual:
-                       i = p.upper_bound (search_note);
-                       while (i != p.end() && (*i)->note() <= val) {
-                               n.insert (*i);
-                       }
-                       break;
-               case PitchGreater:
-                       i = p.lower_bound (search_note);
-                       while (i != p.end() && (*i)->note() > val) {
-                               n.insert (*i);
-                       }
-                       break;
-               case PitchGreaterThanOrEqual:
-                       i = p.lower_bound (search_note);
-                       while (i != p.end() && (*i)->note() >= val) {
-                               n.insert (*i);
-                       }
-                       break;
-
-               default:
-                       //fatal << string_compose (_("programming error: %1 %2", X_("get_notes_by_pitch() called with illegal operator"), op)) << endmsg;
-                       abort(); /* NOTREACHED*/
-               }
-       }
-}
-
-template<typename Time>
-void
-Sequence<Time>::get_notes_by_velocity (Notes& n, NoteOperator op, uint8_t val, int chan_mask) const
-{
-       ReadLock lock (read_lock());
-
-       for (typename Notes::const_iterator i = _notes.begin(); i != _notes.end(); ++i) {
-
-               if (chan_mask != 0 && !((1<<((*i)->channel())) & chan_mask)) {
-                       continue;
-               }
-
-               switch (op) {
-               case VelocityEqual:
-                       if ((*i)->velocity() == val) {
-                               n.insert (*i);
-                       }
-                       break;
-               case VelocityLessThan:
-                       if ((*i)->velocity() < val) {
-                               n.insert (*i);
-                       }
-                       break;
-               case VelocityLessThanOrEqual:
-                       if ((*i)->velocity() <= val) {
-                               n.insert (*i);
-                       }
-                       break;
-               case VelocityGreater:
-                       if ((*i)->velocity() > val) {
-                               n.insert (*i);
-                       }
-                       break;
-               case VelocityGreaterThanOrEqual:
-                       if ((*i)->velocity() >= val) {
-                               n.insert (*i);
-                       }
-                       break;
-               default:
-                       // fatal << string_compose (_("programming error: %1 %2", X_("get_notes_by_velocity() called with illegal operator"), op)) << endmsg;
-                       abort(); /* NOTREACHED*/
-
-               }
-       }
-}
-
-template<typename Time>
-void
-Sequence<Time>::set_overlap_pitch_resolution (OverlapPitchResolution opr)
-{
-       _overlap_pitch_resolution = opr;
-
-       /* XXX todo: clean up existing overlaps in source data? */
-}
-
-template<typename Time>
-void
-Sequence<Time>::control_list_marked_dirty ()
-{
-       set_edited (true);
-}
-
-template<typename Time>
-void
-Sequence<Time>::dump (ostream& str) const
-{
-       typename Sequence<Time>::const_iterator i;
-       str << "+++ dump\n";
-       for (i = begin(); i != end(); ++i) {
-               str << *i << endl;
-       }
-       str << "--- dump\n";
-}
-
-template class Sequence<Temporal::Beats>;
-
-} // namespace Evoral
diff --git a/libs/evoral/src/TimeConverter.cc b/libs/evoral/src/TimeConverter.cc
new file mode 100644 (file)
index 0000000..d34be44
--- /dev/null
@@ -0,0 +1,51 @@
+/*
+ * Copyright (C) 2014-2015 David Robillard <d@drobilla.net>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include <stdint.h>
+
+#include "temporal/beats.h"
+#include "temporal/types.h"
+
+#include "evoral/TimeConverter.h"
+#include "evoral/types.h"
+
+namespace Evoral {
+
+template<typename A, typename B>
+TimeConverter<A,B>::~TimeConverter()
+{}
+
+template<typename A, typename B>
+B
+IdentityConverter<A,B>::to(A a) const
+{
+       return static_cast<B>(a);
+}
+
+template<typename A, typename B>
+A
+IdentityConverter<A,B>::from(B b) const
+{
+       return static_cast<A>(b);
+}
+
+template class IdentityConverter<double, Temporal::samplepos_t>;
+template class TimeConverter<double, Temporal::samplepos_t>;
+template class TimeConverter<Temporal::Beats, Temporal::samplepos_t>;
+
+} // namespace Evoral
diff --git a/libs/evoral/src/TimeConverter.cpp b/libs/evoral/src/TimeConverter.cpp
deleted file mode 100644 (file)
index 7d6dfd5..0000000
+++ /dev/null
@@ -1,51 +0,0 @@
-/*
- * Copyright (C) 2014-2015 David Robillard <d@drobilla.net>
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-
-#include <stdint.h>
-
-#include "temporal/beats.h"
-#include "temporal/types.h"
-
-#include "evoral/TimeConverter.hpp"
-#include "evoral/types.hpp"
-
-namespace Evoral {
-
-template<typename A, typename B>
-TimeConverter<A,B>::~TimeConverter()
-{}
-
-template<typename A, typename B>
-B
-IdentityConverter<A,B>::to(A a) const
-{
-       return static_cast<B>(a);
-}
-
-template<typename A, typename B>
-A
-IdentityConverter<A,B>::from(B b) const
-{
-       return static_cast<A>(b);
-}
-
-template class IdentityConverter<double, Temporal::samplepos_t>;
-template class TimeConverter<double, Temporal::samplepos_t>;
-template class TimeConverter<Temporal::Beats, Temporal::samplepos_t>;
-
-} // namespace Evoral
diff --git a/libs/evoral/src/debug.cc b/libs/evoral/src/debug.cc
new file mode 100644 (file)
index 0000000..e6ed3aa
--- /dev/null
@@ -0,0 +1,6 @@
+#include "evoral/types.h"
+
+PBD::DebugBits PBD::DEBUG::Sequence = PBD::new_debug_bit ("sequence");
+PBD::DebugBits PBD::DEBUG::Note = PBD::new_debug_bit ("note");
+PBD::DebugBits PBD::DEBUG::ControlList = PBD::new_debug_bit ("controllist");
+
diff --git a/libs/evoral/src/debug.cpp b/libs/evoral/src/debug.cpp
deleted file mode 100644 (file)
index 785c33b..0000000
+++ /dev/null
@@ -1,6 +0,0 @@
-#include "evoral/types.hpp"
-
-PBD::DebugBits PBD::DEBUG::Sequence = PBD::new_debug_bit ("sequence");
-PBD::DebugBits PBD::DEBUG::Note = PBD::new_debug_bit ("note");
-PBD::DebugBits PBD::DEBUG::ControlList = PBD::new_debug_bit ("controllist");
-
diff --git a/libs/evoral/test/BeatsTest.cc b/libs/evoral/test/BeatsTest.cc
new file mode 100644 (file)
index 0000000..07eab1f
--- /dev/null
@@ -0,0 +1,171 @@
+#include <stdlib.h>
+
+#include "BeatsTest.hpp"
+#include "temporal/beats.h"
+
+CPPUNIT_TEST_SUITE_REGISTRATION(BeatsTest);
+
+using namespace Evoral;
+using namespace Temporal;
+
+static const double delta = 1.5 / (double)Beats::PPQN;
+
+void
+BeatsTest::createTest()
+{
+       const Beats a(1, 2);
+       CPPUNIT_ASSERT_EQUAL(1, a.get_beats());
+       CPPUNIT_ASSERT_EQUAL(2, a.get_ticks());
+       CPPUNIT_ASSERT_DOUBLES_EQUAL(1 + 2 / (double)Beats::PPQN, a.to_double(), delta);
+
+       const Beats b(1.5);
+       CPPUNIT_ASSERT_EQUAL(1, b.get_beats());
+       CPPUNIT_ASSERT_EQUAL(Beats::PPQN / 2, b.get_ticks());
+       CPPUNIT_ASSERT_DOUBLES_EQUAL(1.5, b.to_double(), delta);
+
+       const Beats c = Beats::beats(6);
+       CPPUNIT_ASSERT_EQUAL(6, c.get_beats());
+       CPPUNIT_ASSERT_EQUAL(0, c.get_ticks());
+
+       const Beats d = Beats::ticks(7);
+       CPPUNIT_ASSERT_EQUAL(0, d.get_beats());
+       CPPUNIT_ASSERT_EQUAL(7, d.get_ticks());
+
+       Beats e(8, 9);
+       e = d;
+       CPPUNIT_ASSERT_EQUAL(d, e);
+
+
+       // const Beats diff = n2 - n1;
+       // CPPUNIT_ASSERT_EQUAL(-44, diff.get_beats());
+       // CPPUNIT_ASSERT_EQUAL(44 / Beats::PPQN, diff.get_ticks());
+       // CPPUNIT_ASSERT_DOUBLES_EQUAL(diff.to_double(), -44.44, delta);
+}
+
+void
+BeatsTest::addTest()
+{
+       const Beats a(1, 2);
+       const Beats b(3, 4);
+
+       // Positive + positive
+       const Beats c = a + b;
+       CPPUNIT_ASSERT_EQUAL(4, c.get_beats());
+       CPPUNIT_ASSERT_EQUAL(6, c.get_ticks());
+
+       const Beats n1(-12.34);
+       CPPUNIT_ASSERT_DOUBLES_EQUAL(-12.34, n1.to_double(), delta);
+
+       const Beats n2(-56.78);
+       CPPUNIT_ASSERT_DOUBLES_EQUAL(-56.78, n2.to_double(), delta);
+
+       // Positive + negative
+       const Beats p1(1.0);
+       const Beats p_n = p1 + n1;
+       CPPUNIT_ASSERT_EQUAL(-11, p_n.get_beats());
+       CPPUNIT_ASSERT_EQUAL((int32_t)(Beats::PPQN * -0.34), p_n.get_ticks());
+       CPPUNIT_ASSERT_DOUBLES_EQUAL(-11.34, p_n.to_double(), delta);
+
+       // Negative + positive
+       const Beats n_p = n1 + p1;
+       CPPUNIT_ASSERT_EQUAL(-11, n_p.get_beats());
+       CPPUNIT_ASSERT_EQUAL((int32_t)(Beats::PPQN * -0.34), n_p.get_ticks());
+       CPPUNIT_ASSERT_DOUBLES_EQUAL(-11.34, n_p.to_double(), delta);
+
+       // Negative + negative
+       const Beats sum = n1 + n2;
+       CPPUNIT_ASSERT_EQUAL(-69, sum.get_beats());
+       //CPPUNIT_ASSERT_EQUAL((int32_t)(Beats::PPQN * -0.12), n_p.get_ticks());
+       CPPUNIT_ASSERT_DOUBLES_EQUAL(-69.12, sum.to_double(), delta);
+}
+
+void
+BeatsTest::subtractTest()
+{
+       const Beats a(1, 2);
+       const Beats b(3, 4);
+
+       // Positive - positive
+       const Beats c = b - a;
+       CPPUNIT_ASSERT_EQUAL(2, c.get_beats());
+       CPPUNIT_ASSERT_EQUAL(2, c.get_ticks());
+
+       const Beats n1(-12.34);
+       CPPUNIT_ASSERT_DOUBLES_EQUAL(-12.34, n1.to_double(), delta);
+
+       const Beats n2(-56.78);
+       CPPUNIT_ASSERT_DOUBLES_EQUAL(-56.78, n2.to_double(), delta);
+
+       // Positive - negative
+       const Beats p1(1.0);
+       const Beats p_n = p1 - n1;
+       CPPUNIT_ASSERT_EQUAL(13, p_n.get_beats());
+       CPPUNIT_ASSERT_EQUAL((int32_t)(Beats::PPQN * 0.34), p_n.get_ticks());
+       CPPUNIT_ASSERT_DOUBLES_EQUAL(13.34, p_n.to_double(), delta);
+
+       // Negative - positive
+       const Beats n_p = n1 - p1;
+       CPPUNIT_ASSERT_EQUAL(-13, n_p.get_beats());
+       CPPUNIT_ASSERT_EQUAL((int32_t)(Beats::PPQN * -0.34), n_p.get_ticks());
+       CPPUNIT_ASSERT_DOUBLES_EQUAL(-13.34, n_p.to_double(), delta);
+
+       // Negative - negative
+       const Beats diff = n1 - n2;
+       CPPUNIT_ASSERT_EQUAL(44, diff.get_beats());
+       CPPUNIT_ASSERT_EQUAL((int32_t)lrint(Beats::PPQN * 0.44), diff.get_ticks());
+       CPPUNIT_ASSERT_DOUBLES_EQUAL(44.44, diff.to_double(), delta);
+}
+
+void
+BeatsTest::multiplyTest()
+{
+       CPPUNIT_ASSERT_DOUBLES_EQUAL(3.0, (Beats(1.5) * 2.0).to_double(), delta);
+       CPPUNIT_ASSERT_DOUBLES_EQUAL(-10.0, (Beats(5.0) * -2.0).to_double(), delta);
+       CPPUNIT_ASSERT_DOUBLES_EQUAL(-10.0, (Beats(-5.0) * 2.0).to_double(), delta);
+}
+
+void
+BeatsTest::roundTest()
+{
+       Beats a(1, 1);
+
+       // Round a up
+       const Beats au = a.round_up_to_beat();
+       CPPUNIT_ASSERT_EQUAL(au.get_beats(), 2);
+       CPPUNIT_ASSERT_EQUAL(au.get_ticks(), 0);
+
+       // Round a down
+       const Beats ad = a.round_down_to_beat();
+       CPPUNIT_ASSERT_EQUAL(ad.get_beats(), 1);
+       CPPUNIT_ASSERT_EQUAL(ad.get_ticks(), 0);
+
+       // Round result down again
+       const Beats add = ad.round_down_to_beat();
+       CPPUNIT_ASSERT_EQUAL(ad, add);
+
+       // Round result up
+       const Beats adu = ad.round_up_to_beat();
+       CPPUNIT_ASSERT_EQUAL(ad, adu);
+
+       // Snap to 1.5
+       const Beats snapped = a.snap_to(Beats(1.5));
+       CPPUNIT_ASSERT_EQUAL(snapped.get_beats(), 1);
+       CPPUNIT_ASSERT_EQUAL(snapped.get_ticks(), Beats::PPQN / 2);
+}
+
+void
+BeatsTest::convertTest()
+{
+       const Beats a = Beats::ticks_at_rate(72000, 48000);
+       CPPUNIT_ASSERT_DOUBLES_EQUAL(1, a.get_beats(), delta);
+       CPPUNIT_ASSERT_DOUBLES_EQUAL(Beats::PPQN / 2, a.get_ticks(), delta);
+       CPPUNIT_ASSERT_DOUBLES_EQUAL(1.5, a.to_double(), delta);
+
+       const Beats b = Beats::ticks_at_rate(8, 48000);
+       CPPUNIT_ASSERT_DOUBLES_EQUAL(0, b.get_beats(), delta);
+       CPPUNIT_ASSERT_DOUBLES_EQUAL(Beats::PPQN * 8 / 48000, b.get_ticks(), delta);
+       CPPUNIT_ASSERT_DOUBLES_EQUAL((8 / 48000.0), b.to_double(), delta);
+
+       CPPUNIT_ASSERT_EQUAL(int64_t(1.5 * Beats::PPQN), a.to_ticks());
+       CPPUNIT_ASSERT_EQUAL(int64_t(1.5 * 192), a.to_ticks(192));
+}
diff --git a/libs/evoral/test/BeatsTest.cpp b/libs/evoral/test/BeatsTest.cpp
deleted file mode 100644 (file)
index 07eab1f..0000000
+++ /dev/null
@@ -1,171 +0,0 @@
-#include <stdlib.h>
-
-#include "BeatsTest.hpp"
-#include "temporal/beats.h"
-
-CPPUNIT_TEST_SUITE_REGISTRATION(BeatsTest);
-
-using namespace Evoral;
-using namespace Temporal;
-
-static const double delta = 1.5 / (double)Beats::PPQN;
-
-void
-BeatsTest::createTest()
-{
-       const Beats a(1, 2);
-       CPPUNIT_ASSERT_EQUAL(1, a.get_beats());
-       CPPUNIT_ASSERT_EQUAL(2, a.get_ticks());
-       CPPUNIT_ASSERT_DOUBLES_EQUAL(1 + 2 / (double)Beats::PPQN, a.to_double(), delta);
-
-       const Beats b(1.5);
-       CPPUNIT_ASSERT_EQUAL(1, b.get_beats());
-       CPPUNIT_ASSERT_EQUAL(Beats::PPQN / 2, b.get_ticks());
-       CPPUNIT_ASSERT_DOUBLES_EQUAL(1.5, b.to_double(), delta);
-
-       const Beats c = Beats::beats(6);
-       CPPUNIT_ASSERT_EQUAL(6, c.get_beats());
-       CPPUNIT_ASSERT_EQUAL(0, c.get_ticks());
-
-       const Beats d = Beats::ticks(7);
-       CPPUNIT_ASSERT_EQUAL(0, d.get_beats());
-       CPPUNIT_ASSERT_EQUAL(7, d.get_ticks());
-
-       Beats e(8, 9);
-       e = d;
-       CPPUNIT_ASSERT_EQUAL(d, e);
-
-
-       // const Beats diff = n2 - n1;
-       // CPPUNIT_ASSERT_EQUAL(-44, diff.get_beats());
-       // CPPUNIT_ASSERT_EQUAL(44 / Beats::PPQN, diff.get_ticks());
-       // CPPUNIT_ASSERT_DOUBLES_EQUAL(diff.to_double(), -44.44, delta);
-}
-
-void
-BeatsTest::addTest()
-{
-       const Beats a(1, 2);
-       const Beats b(3, 4);
-
-       // Positive + positive
-       const Beats c = a + b;
-       CPPUNIT_ASSERT_EQUAL(4, c.get_beats());
-       CPPUNIT_ASSERT_EQUAL(6, c.get_ticks());
-
-       const Beats n1(-12.34);
-       CPPUNIT_ASSERT_DOUBLES_EQUAL(-12.34, n1.to_double(), delta);
-
-       const Beats n2(-56.78);
-       CPPUNIT_ASSERT_DOUBLES_EQUAL(-56.78, n2.to_double(), delta);
-
-       // Positive + negative
-       const Beats p1(1.0);
-       const Beats p_n = p1 + n1;
-       CPPUNIT_ASSERT_EQUAL(-11, p_n.get_beats());
-       CPPUNIT_ASSERT_EQUAL((int32_t)(Beats::PPQN * -0.34), p_n.get_ticks());
-       CPPUNIT_ASSERT_DOUBLES_EQUAL(-11.34, p_n.to_double(), delta);
-
-       // Negative + positive
-       const Beats n_p = n1 + p1;
-       CPPUNIT_ASSERT_EQUAL(-11, n_p.get_beats());
-       CPPUNIT_ASSERT_EQUAL((int32_t)(Beats::PPQN * -0.34), n_p.get_ticks());
-       CPPUNIT_ASSERT_DOUBLES_EQUAL(-11.34, n_p.to_double(), delta);
-
-       // Negative + negative
-       const Beats sum = n1 + n2;
-       CPPUNIT_ASSERT_EQUAL(-69, sum.get_beats());
-       //CPPUNIT_ASSERT_EQUAL((int32_t)(Beats::PPQN * -0.12), n_p.get_ticks());
-       CPPUNIT_ASSERT_DOUBLES_EQUAL(-69.12, sum.to_double(), delta);
-}
-
-void
-BeatsTest::subtractTest()
-{
-       const Beats a(1, 2);
-       const Beats b(3, 4);
-
-       // Positive - positive
-       const Beats c = b - a;
-       CPPUNIT_ASSERT_EQUAL(2, c.get_beats());
-       CPPUNIT_ASSERT_EQUAL(2, c.get_ticks());
-
-       const Beats n1(-12.34);
-       CPPUNIT_ASSERT_DOUBLES_EQUAL(-12.34, n1.to_double(), delta);
-
-       const Beats n2(-56.78);
-       CPPUNIT_ASSERT_DOUBLES_EQUAL(-56.78, n2.to_double(), delta);
-
-       // Positive - negative
-       const Beats p1(1.0);
-       const Beats p_n = p1 - n1;
-       CPPUNIT_ASSERT_EQUAL(13, p_n.get_beats());
-       CPPUNIT_ASSERT_EQUAL((int32_t)(Beats::PPQN * 0.34), p_n.get_ticks());
-       CPPUNIT_ASSERT_DOUBLES_EQUAL(13.34, p_n.to_double(), delta);
-
-       // Negative - positive
-       const Beats n_p = n1 - p1;
-       CPPUNIT_ASSERT_EQUAL(-13, n_p.get_beats());
-       CPPUNIT_ASSERT_EQUAL((int32_t)(Beats::PPQN * -0.34), n_p.get_ticks());
-       CPPUNIT_ASSERT_DOUBLES_EQUAL(-13.34, n_p.to_double(), delta);
-
-       // Negative - negative
-       const Beats diff = n1 - n2;
-       CPPUNIT_ASSERT_EQUAL(44, diff.get_beats());
-       CPPUNIT_ASSERT_EQUAL((int32_t)lrint(Beats::PPQN * 0.44), diff.get_ticks());
-       CPPUNIT_ASSERT_DOUBLES_EQUAL(44.44, diff.to_double(), delta);
-}
-
-void
-BeatsTest::multiplyTest()
-{
-       CPPUNIT_ASSERT_DOUBLES_EQUAL(3.0, (Beats(1.5) * 2.0).to_double(), delta);
-       CPPUNIT_ASSERT_DOUBLES_EQUAL(-10.0, (Beats(5.0) * -2.0).to_double(), delta);
-       CPPUNIT_ASSERT_DOUBLES_EQUAL(-10.0, (Beats(-5.0) * 2.0).to_double(), delta);
-}
-
-void
-BeatsTest::roundTest()
-{
-       Beats a(1, 1);
-
-       // Round a up
-       const Beats au = a.round_up_to_beat();
-       CPPUNIT_ASSERT_EQUAL(au.get_beats(), 2);
-       CPPUNIT_ASSERT_EQUAL(au.get_ticks(), 0);
-
-       // Round a down
-       const Beats ad = a.round_down_to_beat();
-       CPPUNIT_ASSERT_EQUAL(ad.get_beats(), 1);
-       CPPUNIT_ASSERT_EQUAL(ad.get_ticks(), 0);
-
-       // Round result down again
-       const Beats add = ad.round_down_to_beat();
-       CPPUNIT_ASSERT_EQUAL(ad, add);
-
-       // Round result up
-       const Beats adu = ad.round_up_to_beat();
-       CPPUNIT_ASSERT_EQUAL(ad, adu);
-
-       // Snap to 1.5
-       const Beats snapped = a.snap_to(Beats(1.5));
-       CPPUNIT_ASSERT_EQUAL(snapped.get_beats(), 1);
-       CPPUNIT_ASSERT_EQUAL(snapped.get_ticks(), Beats::PPQN / 2);
-}
-
-void
-BeatsTest::convertTest()
-{
-       const Beats a = Beats::ticks_at_rate(72000, 48000);
-       CPPUNIT_ASSERT_DOUBLES_EQUAL(1, a.get_beats(), delta);
-       CPPUNIT_ASSERT_DOUBLES_EQUAL(Beats::PPQN / 2, a.get_ticks(), delta);
-       CPPUNIT_ASSERT_DOUBLES_EQUAL(1.5, a.to_double(), delta);
-
-       const Beats b = Beats::ticks_at_rate(8, 48000);
-       CPPUNIT_ASSERT_DOUBLES_EQUAL(0, b.get_beats(), delta);
-       CPPUNIT_ASSERT_DOUBLES_EQUAL(Beats::PPQN * 8 / 48000, b.get_ticks(), delta);
-       CPPUNIT_ASSERT_DOUBLES_EQUAL((8 / 48000.0), b.to_double(), delta);
-
-       CPPUNIT_ASSERT_EQUAL(int64_t(1.5 * Beats::PPQN), a.to_ticks());
-       CPPUNIT_ASSERT_EQUAL(int64_t(1.5 * 192), a.to_ticks(192));
-}
diff --git a/libs/evoral/test/BeatsTest.h b/libs/evoral/test/BeatsTest.h
new file mode 100644 (file)
index 0000000..0db3831
--- /dev/null
@@ -0,0 +1,22 @@
+#include <cppunit/TestFixture.h>
+#include <cppunit/extensions/HelperMacros.h>
+
+class BeatsTest : public CppUnit::TestFixture
+{
+       CPPUNIT_TEST_SUITE(BeatsTest);
+       CPPUNIT_TEST(createTest);
+       CPPUNIT_TEST(addTest);
+       CPPUNIT_TEST(subtractTest);
+       CPPUNIT_TEST(multiplyTest);
+       CPPUNIT_TEST(convertTest);
+       CPPUNIT_TEST(roundTest);
+       CPPUNIT_TEST_SUITE_END();
+
+public:
+       void createTest();
+       void addTest();
+       void subtractTest();
+       void multiplyTest();
+       void convertTest();
+       void roundTest();
+};
diff --git a/libs/evoral/test/BeatsTest.hpp b/libs/evoral/test/BeatsTest.hpp
deleted file mode 100644 (file)
index 0db3831..0000000
+++ /dev/null
@@ -1,22 +0,0 @@
-#include <cppunit/TestFixture.h>
-#include <cppunit/extensions/HelperMacros.h>
-
-class BeatsTest : public CppUnit::TestFixture
-{
-       CPPUNIT_TEST_SUITE(BeatsTest);
-       CPPUNIT_TEST(createTest);
-       CPPUNIT_TEST(addTest);
-       CPPUNIT_TEST(subtractTest);
-       CPPUNIT_TEST(multiplyTest);
-       CPPUNIT_TEST(convertTest);
-       CPPUNIT_TEST(roundTest);
-       CPPUNIT_TEST_SUITE_END();
-
-public:
-       void createTest();
-       void addTest();
-       void subtractTest();
-       void multiplyTest();
-       void convertTest();
-       void roundTest();
-};
diff --git a/libs/evoral/test/CurveTest.cc b/libs/evoral/test/CurveTest.cc
new file mode 100644 (file)
index 0000000..195be51
--- /dev/null
@@ -0,0 +1,476 @@
+#include "CurveTest.hpp"
+#include "evoral/ControlList.h"
+#include "evoral/Curve.h"
+#include <stdlib.h>
+
+CPPUNIT_TEST_SUITE_REGISTRATION (CurveTest);
+
+#if defined(PLATFORM_WINDOWS) && defined(COMPILER_MINGW)
+/* cppunit-1.13.2  uses assertion_traits<double>
+ *    sprintf( , "%.*g", precision, x)
+ * to format a double. The actual comparison is performed on a string.
+ * This is problematic with mingw/windows|wine, "%.*g" formatting fails.
+ *
+ * This quick hack compares float, however float compatisons are at most Y.MMMM+eXX,
+ * the max precision needs to be limited. to the last mantissa digit.
+ *
+ * Anyway, actual maths is verified with Linux and OSX unit-tests,
+ * and this needs to go to https://sourceforge.net/p/cppunit/bugs/
+ */
+#define MAXPREC(P) ((P) < .0005 ? .0005 : (P))
+#define CPPUNIT_ASSERT_DOUBLES_EQUAL_MESSAGE(M,A,B,P) CPPUNIT_ASSERT_EQUAL_MESSAGE(M, (float)rint ((A) / MAXPREC(P)),(float)rint ((B) / MAXPREC(P)))
+#define CPPUNIT_ASSERT_DOUBLES_EQUAL(A,B,P) CPPUNIT_ASSERT_EQUAL((float)rint ((A) / MAXPREC(P)),(float)rint ((B) / MAXPREC(P)))
+#endif
+
+using namespace Evoral;
+
+// linear y = Y0 + YS * x ;  with x = i * (X1 - X0) + X0; and i = [0..1023]
+#define VEC1024LINCMP(X0, X1, Y0, YS)                                        \
+    cl->curve ().get_vector ((X0), (X1), vec, 1024);                         \
+    for (int i = 0; i < 1024; ++i) {                                         \
+        char msg[64];                                                        \
+        snprintf (msg, 64, "at i=%d (x0=%.1f, x1=%.1f, y0=%.1f, ys=%.3f)",   \
+            i, X0, X1, Y0, YS);                                              \
+        CPPUNIT_ASSERT_DOUBLES_EQUAL_MESSAGE (                               \
+            msg,                                                             \
+            (Y0) + i * (YS), vec[i],                                         \
+            1e-24                                                            \
+            );                                                               \
+    }
+
+void
+CurveTest::trivial ()
+{
+       float vec[1024];
+
+       boost::shared_ptr<Evoral::ControlList> cl = TestCtrlList();
+
+       cl->create_curve ();
+
+       // Empty curve
+       cl->curve().get_vector (1024.0, 2047.0, vec, 1024);
+       for (int i = 0; i < 1024; ++i) {
+               CPPUNIT_ASSERT_EQUAL (0.0f, vec[i]);
+       }
+
+       // Single point curve
+       cl->fast_simple_add(0.0, 42.0);
+       cl->curve().get_vector (1024.0, 2047.0, vec, 1024);
+       for (int i = 0; i < 1024; ++i) {
+               CPPUNIT_ASSERT_EQUAL (42.0f, vec[i]);
+       }
+}
+
+void
+CurveTest::rtGet ()
+{
+       float vec[1024];
+
+       // Create simple control list
+       boost::shared_ptr<Evoral::ControlList> cl = TestCtrlList();
+       cl->create_curve ();
+       cl->fast_simple_add(0.0, 42.0);
+
+       {
+               // Write-lock list
+               Glib::Threads::RWLock::WriterLock lm(cl->lock());
+
+               // Attempt to get vector in RT (expect failure)
+               CPPUNIT_ASSERT (!cl->curve().rt_safe_get_vector (1024.0, 2047.0, vec, 1024));
+       }
+
+       // Attempt to get vector in RT (expect success)
+       CPPUNIT_ASSERT (cl->curve().rt_safe_get_vector (1024.0, 2047.0, vec, 1024));
+       for (int i = 0; i < 1024; ++i) {
+               CPPUNIT_ASSERT_EQUAL (42.0f, vec[i]);
+       }
+}
+
+void
+CurveTest::twoPointLinear ()
+{
+       float vec[1024];
+
+       boost::shared_ptr<Evoral::ControlList> cl = TestCtrlList();
+
+       cl->create_curve ();
+       cl->set_interpolation (ControlList::Linear);
+
+       // add two points to curve
+       cl->fast_simple_add (   0.0 , 2048.0);
+       cl->fast_simple_add (8192.0 , 4096.0);
+
+       cl->curve ().get_vector (1024.0, 2047.0, vec, 1024);
+
+       VEC1024LINCMP (1024.0, 2047.0, 2304.f,  .25f);
+       VEC1024LINCMP (2048.0, 2559.5, 2560.f,  .125f);
+       VEC1024LINCMP (   0.0, 4092.0, 2048.f, 1.f);
+
+       // greetings to tartina
+       cl->curve ().get_vector (2048.0, 2048.0, vec, 1);
+       CPPUNIT_ASSERT_EQUAL_MESSAGE ("veclen=1 @ 2048..2048", 2560.f, vec[0]);
+
+       /* XXX WHAT DO WE EXPECT WITH veclen=1 AND  x1 > x0 ? */
+#if 0
+       /* .. interpolated value at (x1+x0)/2 */
+       cl->curve ().get_vector (2048.0, 2049.0, vec, 1);
+       CPPUNIT_ASSERT_EQUAL_MESSAGE ("veclen=1 @ 2048-2049", 2560.125f, vec[0]);
+
+       cl->curve ().get_vector (2048.0, 2056.0, vec, 1);
+       CPPUNIT_ASSERT_EQUAL_MESSAGE ("veclen=1 @ 2048-2049", 2561.f, vec[0]);
+#else
+       /* .. value at x0 */
+       cl->curve ().get_vector (2048.0, 2049.0, vec, 1);
+       CPPUNIT_ASSERT_EQUAL_MESSAGE ("veclen=1 , 2048..2049", 2560.f, vec[0]);
+
+       cl->curve ().get_vector (2048.0, 2056.0, vec, 1);
+       CPPUNIT_ASSERT_EQUAL_MESSAGE ("veclen=1 , 2048..2049", 2560.f, vec[0]);
+#endif
+
+       cl->curve ().get_vector (2048.0, 2048.0, vec, 2);
+       CPPUNIT_ASSERT_EQUAL_MESSAGE ("veclen=2 , 2048..2048 @ 0", 2560.f, vec[0]);
+       CPPUNIT_ASSERT_EQUAL_MESSAGE ("veclen=2 , 2048..2048 @ 1", 2560.f, vec[1]);
+
+       cl->curve ().get_vector (2048.0, 2056.0, vec, 2);
+       CPPUNIT_ASSERT_EQUAL_MESSAGE ("veclen=2 , 2048..2056 @ 0", 2560.f, vec[0]);
+       CPPUNIT_ASSERT_EQUAL_MESSAGE ("veclen=2 , 2048..2056 @ 0", 2562.f, vec[1]);
+
+       cl->curve ().get_vector (2048.0, 2056.0, vec, 3);
+       CPPUNIT_ASSERT_EQUAL_MESSAGE ("veclen=3 , 2048..2056 @ 0", 2560.f, vec[0]);
+       CPPUNIT_ASSERT_EQUAL_MESSAGE ("veclen=3 , 2048..2056 @ 1", 2561.f, vec[1]);
+       CPPUNIT_ASSERT_EQUAL_MESSAGE ("veclen=3 , 2048..2056 @ 2", 2562.f, vec[2]);
+
+       /* check out-of range..
+        * we expect the first and last value - no interpolation
+        */
+       cl->curve ().get_vector (-1, -1, vec, 1);
+       CPPUNIT_ASSERT_EQUAL_MESSAGE ("veclen=1 @ -1", 2048.f, vec[0]);
+
+       cl->curve ().get_vector (9999.0, 9999.0, vec, 1);
+       CPPUNIT_ASSERT_EQUAL_MESSAGE ("veclen=1 @ 9999", 4096.f, vec[0]);
+
+       cl->curve ().get_vector (-999.0, 0, vec, 13);
+       for (int i = 0; i < 13; ++i) {
+               CPPUNIT_ASSERT_EQUAL_MESSAGE ("veclen=13 @ -999..0", 2048.f, vec[i]);
+       }
+
+       cl->curve ().get_vector (9998.0, 9999.0, vec, 8);
+       for (int i = 0; i < 8; ++i) {
+               CPPUNIT_ASSERT_EQUAL_MESSAGE ("veclen=8 @ 9998..9999", 4096.f, vec[i]);
+       }
+}
+
+void
+CurveTest::threePointLinear ()
+{
+       float vec[4];
+
+       boost::shared_ptr<Evoral::ControlList> cl = TestCtrlList();
+
+       cl->create_curve ();
+       cl->set_interpolation (ControlList::Linear);
+
+       // add 3 points to curve
+       cl->fast_simple_add (   0.0 , 2.0);
+       cl->fast_simple_add ( 100.0 , 4.0);
+       cl->fast_simple_add ( 200.0 , 0.0);
+
+       cl->curve ().get_vector (50.0, 60.0, vec, 1);
+       CPPUNIT_ASSERT_EQUAL_MESSAGE ("veclen=1 @ 50", 3.f, vec[0]);
+
+       cl->curve ().get_vector (100.0, 100.0, vec, 1);
+       CPPUNIT_ASSERT_EQUAL_MESSAGE ("veclen=1 @ 100", 4.f, vec[0]);
+
+       cl->curve ().get_vector (150.0, 150.0, vec, 1);
+       CPPUNIT_ASSERT_EQUAL_MESSAGE ("veclen=1 @ 150", 2.f, vec[0]);
+
+       cl->curve ().get_vector (130.0, 150.0, vec, 3);
+       CPPUNIT_ASSERT_EQUAL_MESSAGE ("veclen=3 130..150 @ 0", 2.8f, vec[0]);
+       CPPUNIT_ASSERT_EQUAL_MESSAGE ("veclen=3 130..150 @ 2", 2.4f, vec[1]);
+       CPPUNIT_ASSERT_EQUAL_MESSAGE ("veclen=3 130..150 @ 3", 2.0f, vec[2]);
+
+       cl->curve ().get_vector (80.0, 160.0, vec, 3);
+       CPPUNIT_ASSERT_EQUAL_MESSAGE ("veclen=3 80..160 @ 0", 3.6f, vec[0]);
+       CPPUNIT_ASSERT_EQUAL_MESSAGE ("veclen=3 80..160 @ 2", 3.2f, vec[1]);
+       CPPUNIT_ASSERT_EQUAL_MESSAGE ("veclen=3 80..160 @ 3", 1.6f, vec[2]);
+}
+
+void
+CurveTest::threePointDiscete ()
+{
+       boost::shared_ptr<Evoral::ControlList> cl = TestCtrlList();
+       cl->set_interpolation (ControlList::Discrete);
+
+       // add 3 points to curve
+       cl->fast_simple_add (   0.0 , 2.0);
+       cl->fast_simple_add ( 100.0 , 4.0);
+       cl->fast_simple_add ( 200.0 , 0.0);
+
+       CPPUNIT_ASSERT_EQUAL(2.0, cl->unlocked_eval(80.));
+       CPPUNIT_ASSERT_EQUAL(4.0, cl->unlocked_eval(120.));
+       CPPUNIT_ASSERT_EQUAL(4.0, cl->unlocked_eval(160.));
+
+       cl->set_interpolation (ControlList::Linear);
+
+       CPPUNIT_ASSERT_EQUAL(3.6, cl->unlocked_eval(80.));
+       CPPUNIT_ASSERT_EQUAL(3.2, cl->unlocked_eval(120.));
+       CPPUNIT_ASSERT_EQUAL(1.6, cl->unlocked_eval(160.));
+}
+
+void
+CurveTest::ctrlListEval ()
+{
+       boost::shared_ptr<Evoral::ControlList> cl = TestCtrlList();
+
+       cl->fast_simple_add (   0.0 , 2.0);
+
+       cl->set_interpolation (ControlList::Discrete);
+       CPPUNIT_ASSERT_EQUAL(2.0, cl->unlocked_eval(80.));
+       CPPUNIT_ASSERT_EQUAL(2.0, cl->unlocked_eval(120.));
+       CPPUNIT_ASSERT_EQUAL(2.0, cl->unlocked_eval(160.));
+
+       cl->set_interpolation (ControlList::Linear);
+       CPPUNIT_ASSERT_EQUAL(2.0, cl->unlocked_eval(80.));
+       CPPUNIT_ASSERT_EQUAL(2.0, cl->unlocked_eval(120.));
+       CPPUNIT_ASSERT_EQUAL(2.0, cl->unlocked_eval(160.));
+
+       cl->fast_simple_add ( 100.0 , 4.0);
+
+       cl->set_interpolation (ControlList::Discrete);
+       CPPUNIT_ASSERT_EQUAL(2.0, cl->unlocked_eval(80.));
+       CPPUNIT_ASSERT_EQUAL(4.0, cl->unlocked_eval(120.));
+       CPPUNIT_ASSERT_EQUAL(4.0, cl->unlocked_eval(160.));
+
+       cl->set_interpolation (ControlList::Linear);
+       CPPUNIT_ASSERT_EQUAL(3.6, cl->unlocked_eval(80.));
+       CPPUNIT_ASSERT_EQUAL(4.0, cl->unlocked_eval(120.));
+       CPPUNIT_ASSERT_EQUAL(4.0, cl->unlocked_eval(160.));
+
+       cl->fast_simple_add ( 200.0 , 0.0);
+
+       cl->set_interpolation (ControlList::Discrete);
+       CPPUNIT_ASSERT_EQUAL(2.0, cl->unlocked_eval(80.));
+       CPPUNIT_ASSERT_EQUAL(4.0, cl->unlocked_eval(120.));
+       CPPUNIT_ASSERT_EQUAL(4.0, cl->unlocked_eval(160.));
+
+       cl->set_interpolation (ControlList::Linear);
+       CPPUNIT_ASSERT_EQUAL(3.6, cl->unlocked_eval(80.));
+       CPPUNIT_ASSERT_EQUAL(3.2, cl->unlocked_eval(120.));
+       CPPUNIT_ASSERT_EQUAL(1.6, cl->unlocked_eval(160.));
+
+       cl->fast_simple_add ( 300.0 , 8.0);
+
+       cl->set_interpolation (ControlList::Discrete);
+       CPPUNIT_ASSERT_EQUAL(2.0, cl->unlocked_eval(80.));
+       CPPUNIT_ASSERT_EQUAL(4.0, cl->unlocked_eval(120.));
+       CPPUNIT_ASSERT_EQUAL(4.0, cl->unlocked_eval(160.));
+       CPPUNIT_ASSERT_EQUAL(0.0, cl->unlocked_eval(250.));
+       CPPUNIT_ASSERT_EQUAL(8.0, cl->unlocked_eval(999.));
+
+       cl->set_interpolation (ControlList::Linear);
+       CPPUNIT_ASSERT_EQUAL(3.6, cl->unlocked_eval(80.));
+       CPPUNIT_ASSERT_EQUAL(3.2, cl->unlocked_eval(120.));
+       CPPUNIT_ASSERT_EQUAL(1.6, cl->unlocked_eval(160.));
+       CPPUNIT_ASSERT_EQUAL(4.0, cl->unlocked_eval(250.));
+       CPPUNIT_ASSERT_EQUAL(8.0, cl->unlocked_eval(999.));
+
+       cl->fast_simple_add ( 400.0 , 9.0);
+
+       cl->set_interpolation (ControlList::Discrete);
+       CPPUNIT_ASSERT_EQUAL(2.0, cl->unlocked_eval(80.));
+       CPPUNIT_ASSERT_EQUAL(4.0, cl->unlocked_eval(120.));
+       CPPUNIT_ASSERT_EQUAL(4.0, cl->unlocked_eval(160.));
+       CPPUNIT_ASSERT_EQUAL(0.0, cl->unlocked_eval(250.));
+       CPPUNIT_ASSERT_EQUAL(8.0, cl->unlocked_eval(350.));
+       CPPUNIT_ASSERT_EQUAL(9.0, cl->unlocked_eval(999.));
+
+       cl->set_interpolation (ControlList::Linear);
+       CPPUNIT_ASSERT_EQUAL(3.6, cl->unlocked_eval(80.));
+       CPPUNIT_ASSERT_EQUAL(3.2, cl->unlocked_eval(120.));
+       CPPUNIT_ASSERT_EQUAL(1.6, cl->unlocked_eval(160.));
+       CPPUNIT_ASSERT_EQUAL(4.0, cl->unlocked_eval(250.));
+       CPPUNIT_ASSERT_EQUAL(8.5, cl->unlocked_eval(350.));
+       CPPUNIT_ASSERT_EQUAL(9.0, cl->unlocked_eval(999.));
+}
+
+void
+CurveTest::constrainedCubic ()
+{
+
+       struct point {
+               int x, y;
+       };
+
+       static const struct point data[] = {
+               /* values from worked example in www.korf.co.uk/spline.pdf */
+               {   0,  30 },
+               {  10, 130 },
+               {  30, 150 },
+               {  50, 150 },
+               {  70, 170 },
+               {  90, 220 },
+               { 100, 320 },
+       };
+
+       int32_t type = 0;
+       Evoral::Parameter p(type);
+       Evoral::ParameterDescriptor pd;
+       pd.lower = 5;
+       pd.upper = 325;
+       Evoral::ControlList l(p,pd);
+
+       size_t i;
+       l.set_interpolation(Evoral::ControlList::Curved);
+
+       for (i=0; i<sizeof(data)/sizeof(data[0]); i++) {
+               l.add (data[i].x, data[i].y);
+       }
+
+       Evoral::Curve curve(l);
+
+       float f[121];
+       curve.get_vector(-10, 110, f, 121);
+
+       const float *g = &f[10]; /* so g starts at x==0 */
+
+       /* given points - should be exactly equal */
+       CPPUNIT_ASSERT_EQUAL( 30.0f, g[-10]);
+       CPPUNIT_ASSERT_EQUAL( 30.0f, g[  0]);
+       CPPUNIT_ASSERT_EQUAL(130.0f, g[ 10]);
+       CPPUNIT_ASSERT_EQUAL(150.0f, g[ 30]);
+       CPPUNIT_ASSERT_EQUAL(150.0f, g[ 40]);
+       CPPUNIT_ASSERT_EQUAL(150.0f, g[ 50]);
+       CPPUNIT_ASSERT_EQUAL(320.0f, g[100]);
+       CPPUNIT_ASSERT_EQUAL(320.0f, g[110]);
+
+       /*
+          First segment, i=1, for 0 <= x <= 10
+          f'1(x1) = 2/((x2 â€“ x1)/(y2 â€“ y1) + (x1 â€“ x0)/(y1 â€“ y0))
+                  = 2/((30 â€“ 10)/(150 â€“ 130) + (10 â€“ 0)/(130 â€“ 30))
+                  = 1.8181
+          f'1(x0) = 3/2*(y1 â€“ y0)/(x1 â€“ x0) - f'1(x1)/2
+                  = 3/2*(130 â€“ 30)/(10 â€“ 0) â€“ 1.818/2
+                  = 14.0909
+          f"1(x0) = -2*(f'1(x1) + 2* f'1(x0))/(x1 â€“ x0) + 6*(y1 â€“ y0)/ (x1 â€“ x0)^2
+                  = -2*(1.8181 + 2*14.0909)/(10 â€“ 0) + 6*(130 â€“ 30)/(10 â€“ 0)^2
+                  = 0
+          f"1(x1) = 2*(2*f'1(x1) + f'1(x0))/(x1 â€“ x0) - 6*(y1 â€“ y0)/ (x1 â€“ x0)^2
+                  = 2*(2*1.818 + 14.0909)/(10 â€“ 0) â€“ 6*(130 â€“ 30)/(10 â€“ 0)^2
+                  = -2.4545
+          d1 = 1/6 * (f"1(x1) - f"1(x0))/(x1 â€“ x0)
+             = 1/6 * (-2.4545 â€“ 0)/(10 â€“ 0)
+             = -0.0409
+          c1 = 1/2 * (x1*f"1(x0) â€“ x0*f"1(x1))/(x1 â€“ x0)
+             = 1/2 * (10*0 â€“ 0*1.8181)/(10 â€“ 0)
+             = 0
+          b1 = ((y1 â€“ y0) â€“ c1*(x21 â€“ x20) â€“ d1*( x31 â€“ x30))/(x1 â€“ x0)
+             = ((130 â€“ 30) â€“ 0*(102 â€“ 02) + 0.0409*(103 â€“ 03))/(10 â€“ 0)
+             = 14.09
+          a1 = y0 â€“ b1*x0 â€“ c1*x20 â€“ d1*x30
+             = 30
+          y1 = 30 + 14.09x - 0.0409x3 for 0 <= x <= 10
+        */
+       /*
+          Second segment, i=2, for 10 <= x <= 30
+          f'2(x2) = 2/((x3 â€“ x2)/(y3 â€“ y2) + (x2 â€“ x1)/(y2 â€“ y1))
+                  = 2/((50 â€“ 30)/(150 â€“ 150) + (30 â€“ 10)/(150 â€“ 130))
+                  = 0
+          f'2(x1) = 2/((x2 â€“ x1)/(y2 â€“ y1) + (x1 â€“ x0)/(y1 â€“ y0))
+                  = 1.8181
+
+          f"2(x1) = -2*(f'2(x2) + 2* f'2(x1))/(x2 â€“ x1) + 6*(y2 â€“ y1)/ (x2 â€“ x1)^2
+                  = -2*(0 + 2*1.8181)/(30 â€“ 10) + 6*(150 â€“ 130)/(30 â€“ 10)2
+                  = -0.063636
+          f"2(x2) = 2*(2*f'2(x2) + f'2(x1))/(x2 â€“ x1) - 6*(y2 â€“ y1)/ (x2 â€“ x1)^2
+                  = 2*(2*0 + 1.8181)/(30 â€“ 10) â€“ 6*(150 â€“ 130)/(30 â€“ 10)^2
+                  = -0.11818
+
+          d2 = 1/6 * (f"2(x2) - f"2(x1))/(x2 â€“ x1)
+             = 1/6 * (-0.11818 + 0.063636)/(30 â€“ 10)
+             = -0.0004545
+          c2 = 1/2 * (x2*f"2(x1) â€“ x1*f"2(x2))/(x2 â€“ x1)
+             = 1/2 * (-30*0.063636 + 10*0.11818)/(30 â€“ 10)
+             = -0.01818
+          b2 = ((y2 â€“ y1) â€“ c2*(x2^2 â€“ x1^2) â€“ d2*( x2^3 â€“ x1^3))/(x2 â€“ x1)
+             = ((150 â€“ 130) + 0.01818*(302 â€“ 102) + 0.0004545*(303 â€“ 103))/(30 â€“ 10)
+             = 2.31818
+          a2 = y1 â€“ b2*x1 â€“ c2*x1^2 â€“ d2*x1^3
+             = 130 â€“ 2.31818*10 + 0.01818*102 + 0.0004545*103
+             = 109.09
+          y2 = 109.09 + 2.31818x - 0.01818x^2 - 0.0004545x^3 for 10 <= x <= 30
+        */
+
+
+       int x;
+       long double a1, b1, c1, d1, a2, b2, c2, d2, fdx0, fddx0, fdx1, fdx2, fddx1, fddx2;
+       double x0 = data[0].x;
+       double y0 = data[0].y;
+       double x1 = data[1].x;
+       double y1 = data[1].y;
+       double x2 = data[2].x;
+       double y2 = data[2].y;
+       double x3 = data[3].x;
+       double y3 = data[3].y;
+
+       double dx0 = x1 - x0;
+       double dy0 = y1 - y0;
+       double dx1 = x2 - x1;
+       double dy1 = y2 - y1;
+       double dx2 = x3 - x2;
+       double dy2 = y3 - y2;
+
+       // First (leftmost) segment
+       fdx1 = 2.0 / ( dx1 / dy1 + dx0 / dy0 );
+       fdx0 = 3.0 / 2.0 * dy0 / dx0 - fdx1 / 2.0;
+
+       fddx0 = -2.0 * (fdx1 + 2.0 * fdx0) / dx0 + 6.0 * dy0 / (dx0*dx0);
+       fddx1 =  2.0 * (2.0 * fdx1 + fdx0) / dx0 - 6.0 * dy0 / (dx0*dx0);
+       d1 = 1.0 / 6.0 * (fddx1 - fddx0) / dx0;
+       c1 = 1.0 / 2.0 * (x1 * fddx0 - x0 * fddx1) / dx0;
+       b1 = (dy0 - c1 * (x1* x1 - x0*x0) - d1 * (x1*x1*x1 - x0*x0*x0)) / dx0;
+       a1 = y0 - b1*x0 - c1*x0*x0 - d1*x0*x0*x0;
+
+       // printf("dx0=%f, dy0=%f, dx1=%f, dy1=%f\n", dx0, dy0, dx1, dy1);
+       // printf("fdx0=%Lf, fdx1=%Lf, fddx0=%Lf, fddx1=%Lf\n", fdx0, fdx1, fddx0, fddx1);
+       // printf("a1=%Lf, b1=%Lf, c1=%Lf, d1=%Lf\n", a1, b1, c1, d1);
+
+       // values from worked example: deltas rather arbitrary, I'm afraid
+       CPPUNIT_ASSERT_DOUBLES_EQUAL(30.0, a1, 0.1);
+       CPPUNIT_ASSERT_DOUBLES_EQUAL(14.09, b1, 0.01);
+       CPPUNIT_ASSERT_DOUBLES_EQUAL(0.0, c1, 0.1);
+       CPPUNIT_ASSERT_DOUBLES_EQUAL(-0.0409, d1, 0.0001);
+
+       for (x = 0; x <= 10; x++) {
+               double v = a1 + b1*x + c1*x*x  + d1*x*x*x;
+               char msg[64];
+               snprintf(msg, 64, "interpolating %d: v=%f, x=%f...\n", x, v, g[x]);
+               CPPUNIT_ASSERT_DOUBLES_EQUAL(v, g[x], 0.000004);
+       }
+
+       // Second segment
+       fdx2 = 2.0 / ( dx2 / dy2 + dx1 / dy1 );
+
+       fddx1 = -2.0 * (fdx2 + 2.0 * fdx1) / dx1 + 6.0 * dy1 / (dx1*dx1);
+       fddx2 =  2.0 * (2.0 * fdx2 + fdx1) / dx1 - 6.0 * dy1 / (dx1*dx1);
+       d2 = 1.0 / 6.0 * (fddx2 - fddx1) / dx1;
+       c2 = 1.0 / 2.0 * (x2 * fddx1 - x1 * fddx2) / dx1;
+       b2 = (dy1 - c2 * (x2*x2 - x1*x1) - d2 * (x2*x2*x2 - x1*x1*x1)) / dx1;
+       a2 = y1 - b2*x1 - c2*x1*x1 - d2*x1*x1*x1;
+
+       // printf("dx0=%f, dy0=%f, dx1=%f, dy1=%f dx2=%f, dy2=%f\n", dx0, dy0, dx1, dy1, dx2, dy2);
+       // printf("fdx1=%Lf, fdx2=%Lf, fddx1=%Lf, fddx2=%Lf\n", fdx1, fdx2, fddx1, fddx2);
+       // printf("a2=%Lf, b2=%Lf, c2=%Lf, d2=%Lf\n", a2, b2, c2, d2);
+
+       // values from worked example: deltas rather arbitrary, I'm afraid
+       CPPUNIT_ASSERT_DOUBLES_EQUAL(109.09, a2, 0.01);
+       CPPUNIT_ASSERT_DOUBLES_EQUAL(2.31818, b2, 0.00001);
+       CPPUNIT_ASSERT_DOUBLES_EQUAL(-0.01818, c2, 0.00001);
+       CPPUNIT_ASSERT_DOUBLES_EQUAL(-0.0004545, d2, 0.0000001);
+
+       for (x = 10; x <= 30; x++) {
+               double v = a2 + b2*x + c2*x*x  + d2*x*x*x;
+               char msg[64];
+               snprintf(msg, 64, "interpolating %d: v=%f, x=%f...\n", x, v, g[x]);
+               CPPUNIT_ASSERT_DOUBLES_EQUAL(v, g[x], 0.000008);
+       }
+}
diff --git a/libs/evoral/test/CurveTest.cpp b/libs/evoral/test/CurveTest.cpp
deleted file mode 100644 (file)
index 820c53f..0000000
+++ /dev/null
@@ -1,476 +0,0 @@
-#include "CurveTest.hpp"
-#include "evoral/ControlList.hpp"
-#include "evoral/Curve.hpp"
-#include <stdlib.h>
-
-CPPUNIT_TEST_SUITE_REGISTRATION (CurveTest);
-
-#if defined(PLATFORM_WINDOWS) && defined(COMPILER_MINGW)
-/* cppunit-1.13.2  uses assertion_traits<double>
- *    sprintf( , "%.*g", precision, x)
- * to format a double. The actual comparison is performed on a string.
- * This is problematic with mingw/windows|wine, "%.*g" formatting fails.
- *
- * This quick hack compares float, however float compatisons are at most Y.MMMM+eXX,
- * the max precision needs to be limited. to the last mantissa digit.
- *
- * Anyway, actual maths is verified with Linux and OSX unit-tests,
- * and this needs to go to https://sourceforge.net/p/cppunit/bugs/
- */
-#define MAXPREC(P) ((P) < .0005 ? .0005 : (P))
-#define CPPUNIT_ASSERT_DOUBLES_EQUAL_MESSAGE(M,A,B,P) CPPUNIT_ASSERT_EQUAL_MESSAGE(M, (float)rint ((A) / MAXPREC(P)),(float)rint ((B) / MAXPREC(P)))
-#define CPPUNIT_ASSERT_DOUBLES_EQUAL(A,B,P) CPPUNIT_ASSERT_EQUAL((float)rint ((A) / MAXPREC(P)),(float)rint ((B) / MAXPREC(P)))
-#endif
-
-using namespace Evoral;
-
-// linear y = Y0 + YS * x ;  with x = i * (X1 - X0) + X0; and i = [0..1023]
-#define VEC1024LINCMP(X0, X1, Y0, YS)                                        \
-    cl->curve ().get_vector ((X0), (X1), vec, 1024);                         \
-    for (int i = 0; i < 1024; ++i) {                                         \
-        char msg[64];                                                        \
-        snprintf (msg, 64, "at i=%d (x0=%.1f, x1=%.1f, y0=%.1f, ys=%.3f)",   \
-            i, X0, X1, Y0, YS);                                              \
-        CPPUNIT_ASSERT_DOUBLES_EQUAL_MESSAGE (                               \
-            msg,                                                             \
-            (Y0) + i * (YS), vec[i],                                         \
-            1e-24                                                            \
-            );                                                               \
-    }
-
-void
-CurveTest::trivial ()
-{
-       float vec[1024];
-
-       boost::shared_ptr<Evoral::ControlList> cl = TestCtrlList();
-
-       cl->create_curve ();
-
-       // Empty curve
-       cl->curve().get_vector (1024.0, 2047.0, vec, 1024);
-       for (int i = 0; i < 1024; ++i) {
-               CPPUNIT_ASSERT_EQUAL (0.0f, vec[i]);
-       }
-
-       // Single point curve
-       cl->fast_simple_add(0.0, 42.0);
-       cl->curve().get_vector (1024.0, 2047.0, vec, 1024);
-       for (int i = 0; i < 1024; ++i) {
-               CPPUNIT_ASSERT_EQUAL (42.0f, vec[i]);
-       }
-}
-
-void
-CurveTest::rtGet ()
-{
-       float vec[1024];
-
-       // Create simple control list
-       boost::shared_ptr<Evoral::ControlList> cl = TestCtrlList();
-       cl->create_curve ();
-       cl->fast_simple_add(0.0, 42.0);
-
-       {
-               // Write-lock list
-               Glib::Threads::RWLock::WriterLock lm(cl->lock());
-
-               // Attempt to get vector in RT (expect failure)
-               CPPUNIT_ASSERT (!cl->curve().rt_safe_get_vector (1024.0, 2047.0, vec, 1024));
-       }
-
-       // Attempt to get vector in RT (expect success)
-       CPPUNIT_ASSERT (cl->curve().rt_safe_get_vector (1024.0, 2047.0, vec, 1024));
-       for (int i = 0; i < 1024; ++i) {
-               CPPUNIT_ASSERT_EQUAL (42.0f, vec[i]);
-       }
-}
-
-void
-CurveTest::twoPointLinear ()
-{
-       float vec[1024];
-
-       boost::shared_ptr<Evoral::ControlList> cl = TestCtrlList();
-
-       cl->create_curve ();
-       cl->set_interpolation (ControlList::Linear);
-
-       // add two points to curve
-       cl->fast_simple_add (   0.0 , 2048.0);
-       cl->fast_simple_add (8192.0 , 4096.0);
-
-       cl->curve ().get_vector (1024.0, 2047.0, vec, 1024);
-
-       VEC1024LINCMP (1024.0, 2047.0, 2304.f,  .25f);
-       VEC1024LINCMP (2048.0, 2559.5, 2560.f,  .125f);
-       VEC1024LINCMP (   0.0, 4092.0, 2048.f, 1.f);
-
-       // greetings to tartina
-       cl->curve ().get_vector (2048.0, 2048.0, vec, 1);
-       CPPUNIT_ASSERT_EQUAL_MESSAGE ("veclen=1 @ 2048..2048", 2560.f, vec[0]);
-
-       /* XXX WHAT DO WE EXPECT WITH veclen=1 AND  x1 > x0 ? */
-#if 0
-       /* .. interpolated value at (x1+x0)/2 */
-       cl->curve ().get_vector (2048.0, 2049.0, vec, 1);
-       CPPUNIT_ASSERT_EQUAL_MESSAGE ("veclen=1 @ 2048-2049", 2560.125f, vec[0]);
-
-       cl->curve ().get_vector (2048.0, 2056.0, vec, 1);
-       CPPUNIT_ASSERT_EQUAL_MESSAGE ("veclen=1 @ 2048-2049", 2561.f, vec[0]);
-#else
-       /* .. value at x0 */
-       cl->curve ().get_vector (2048.0, 2049.0, vec, 1);
-       CPPUNIT_ASSERT_EQUAL_MESSAGE ("veclen=1 , 2048..2049", 2560.f, vec[0]);
-
-       cl->curve ().get_vector (2048.0, 2056.0, vec, 1);
-       CPPUNIT_ASSERT_EQUAL_MESSAGE ("veclen=1 , 2048..2049", 2560.f, vec[0]);
-#endif
-
-       cl->curve ().get_vector (2048.0, 2048.0, vec, 2);
-       CPPUNIT_ASSERT_EQUAL_MESSAGE ("veclen=2 , 2048..2048 @ 0", 2560.f, vec[0]);
-       CPPUNIT_ASSERT_EQUAL_MESSAGE ("veclen=2 , 2048..2048 @ 1", 2560.f, vec[1]);
-
-       cl->curve ().get_vector (2048.0, 2056.0, vec, 2);
-       CPPUNIT_ASSERT_EQUAL_MESSAGE ("veclen=2 , 2048..2056 @ 0", 2560.f, vec[0]);
-       CPPUNIT_ASSERT_EQUAL_MESSAGE ("veclen=2 , 2048..2056 @ 0", 2562.f, vec[1]);
-
-       cl->curve ().get_vector (2048.0, 2056.0, vec, 3);
-       CPPUNIT_ASSERT_EQUAL_MESSAGE ("veclen=3 , 2048..2056 @ 0", 2560.f, vec[0]);
-       CPPUNIT_ASSERT_EQUAL_MESSAGE ("veclen=3 , 2048..2056 @ 1", 2561.f, vec[1]);
-       CPPUNIT_ASSERT_EQUAL_MESSAGE ("veclen=3 , 2048..2056 @ 2", 2562.f, vec[2]);
-
-       /* check out-of range..
-        * we expect the first and last value - no interpolation
-        */
-       cl->curve ().get_vector (-1, -1, vec, 1);
-       CPPUNIT_ASSERT_EQUAL_MESSAGE ("veclen=1 @ -1", 2048.f, vec[0]);
-
-       cl->curve ().get_vector (9999.0, 9999.0, vec, 1);
-       CPPUNIT_ASSERT_EQUAL_MESSAGE ("veclen=1 @ 9999", 4096.f, vec[0]);
-
-       cl->curve ().get_vector (-999.0, 0, vec, 13);
-       for (int i = 0; i < 13; ++i) {
-               CPPUNIT_ASSERT_EQUAL_MESSAGE ("veclen=13 @ -999..0", 2048.f, vec[i]);
-       }
-
-       cl->curve ().get_vector (9998.0, 9999.0, vec, 8);
-       for (int i = 0; i < 8; ++i) {
-               CPPUNIT_ASSERT_EQUAL_MESSAGE ("veclen=8 @ 9998..9999", 4096.f, vec[i]);
-       }
-}
-
-void
-CurveTest::threePointLinear ()
-{
-       float vec[4];
-
-       boost::shared_ptr<Evoral::ControlList> cl = TestCtrlList();
-
-       cl->create_curve ();
-       cl->set_interpolation (ControlList::Linear);
-
-       // add 3 points to curve
-       cl->fast_simple_add (   0.0 , 2.0);
-       cl->fast_simple_add ( 100.0 , 4.0);
-       cl->fast_simple_add ( 200.0 , 0.0);
-
-       cl->curve ().get_vector (50.0, 60.0, vec, 1);
-       CPPUNIT_ASSERT_EQUAL_MESSAGE ("veclen=1 @ 50", 3.f, vec[0]);
-
-       cl->curve ().get_vector (100.0, 100.0, vec, 1);
-       CPPUNIT_ASSERT_EQUAL_MESSAGE ("veclen=1 @ 100", 4.f, vec[0]);
-
-       cl->curve ().get_vector (150.0, 150.0, vec, 1);
-       CPPUNIT_ASSERT_EQUAL_MESSAGE ("veclen=1 @ 150", 2.f, vec[0]);
-
-       cl->curve ().get_vector (130.0, 150.0, vec, 3);
-       CPPUNIT_ASSERT_EQUAL_MESSAGE ("veclen=3 130..150 @ 0", 2.8f, vec[0]);
-       CPPUNIT_ASSERT_EQUAL_MESSAGE ("veclen=3 130..150 @ 2", 2.4f, vec[1]);
-       CPPUNIT_ASSERT_EQUAL_MESSAGE ("veclen=3 130..150 @ 3", 2.0f, vec[2]);
-
-       cl->curve ().get_vector (80.0, 160.0, vec, 3);
-       CPPUNIT_ASSERT_EQUAL_MESSAGE ("veclen=3 80..160 @ 0", 3.6f, vec[0]);
-       CPPUNIT_ASSERT_EQUAL_MESSAGE ("veclen=3 80..160 @ 2", 3.2f, vec[1]);
-       CPPUNIT_ASSERT_EQUAL_MESSAGE ("veclen=3 80..160 @ 3", 1.6f, vec[2]);
-}
-
-void
-CurveTest::threePointDiscete ()
-{
-       boost::shared_ptr<Evoral::ControlList> cl = TestCtrlList();
-       cl->set_interpolation (ControlList::Discrete);
-
-       // add 3 points to curve
-       cl->fast_simple_add (   0.0 , 2.0);
-       cl->fast_simple_add ( 100.0 , 4.0);
-       cl->fast_simple_add ( 200.0 , 0.0);
-
-       CPPUNIT_ASSERT_EQUAL(2.0, cl->unlocked_eval(80.));
-       CPPUNIT_ASSERT_EQUAL(4.0, cl->unlocked_eval(120.));
-       CPPUNIT_ASSERT_EQUAL(4.0, cl->unlocked_eval(160.));
-
-       cl->set_interpolation (ControlList::Linear);
-
-       CPPUNIT_ASSERT_EQUAL(3.6, cl->unlocked_eval(80.));
-       CPPUNIT_ASSERT_EQUAL(3.2, cl->unlocked_eval(120.));
-       CPPUNIT_ASSERT_EQUAL(1.6, cl->unlocked_eval(160.));
-}
-
-void
-CurveTest::ctrlListEval ()
-{
-       boost::shared_ptr<Evoral::ControlList> cl = TestCtrlList();
-
-       cl->fast_simple_add (   0.0 , 2.0);
-
-       cl->set_interpolation (ControlList::Discrete);
-       CPPUNIT_ASSERT_EQUAL(2.0, cl->unlocked_eval(80.));
-       CPPUNIT_ASSERT_EQUAL(2.0, cl->unlocked_eval(120.));
-       CPPUNIT_ASSERT_EQUAL(2.0, cl->unlocked_eval(160.));
-
-       cl->set_interpolation (ControlList::Linear);
-       CPPUNIT_ASSERT_EQUAL(2.0, cl->unlocked_eval(80.));
-       CPPUNIT_ASSERT_EQUAL(2.0, cl->unlocked_eval(120.));
-       CPPUNIT_ASSERT_EQUAL(2.0, cl->unlocked_eval(160.));
-
-       cl->fast_simple_add ( 100.0 , 4.0);
-
-       cl->set_interpolation (ControlList::Discrete);
-       CPPUNIT_ASSERT_EQUAL(2.0, cl->unlocked_eval(80.));
-       CPPUNIT_ASSERT_EQUAL(4.0, cl->unlocked_eval(120.));
-       CPPUNIT_ASSERT_EQUAL(4.0, cl->unlocked_eval(160.));
-
-       cl->set_interpolation (ControlList::Linear);
-       CPPUNIT_ASSERT_EQUAL(3.6, cl->unlocked_eval(80.));
-       CPPUNIT_ASSERT_EQUAL(4.0, cl->unlocked_eval(120.));
-       CPPUNIT_ASSERT_EQUAL(4.0, cl->unlocked_eval(160.));
-
-       cl->fast_simple_add ( 200.0 , 0.0);
-
-       cl->set_interpolation (ControlList::Discrete);
-       CPPUNIT_ASSERT_EQUAL(2.0, cl->unlocked_eval(80.));
-       CPPUNIT_ASSERT_EQUAL(4.0, cl->unlocked_eval(120.));
-       CPPUNIT_ASSERT_EQUAL(4.0, cl->unlocked_eval(160.));
-
-       cl->set_interpolation (ControlList::Linear);
-       CPPUNIT_ASSERT_EQUAL(3.6, cl->unlocked_eval(80.));
-       CPPUNIT_ASSERT_EQUAL(3.2, cl->unlocked_eval(120.));
-       CPPUNIT_ASSERT_EQUAL(1.6, cl->unlocked_eval(160.));
-
-       cl->fast_simple_add ( 300.0 , 8.0);
-
-       cl->set_interpolation (ControlList::Discrete);
-       CPPUNIT_ASSERT_EQUAL(2.0, cl->unlocked_eval(80.));
-       CPPUNIT_ASSERT_EQUAL(4.0, cl->unlocked_eval(120.));
-       CPPUNIT_ASSERT_EQUAL(4.0, cl->unlocked_eval(160.));
-       CPPUNIT_ASSERT_EQUAL(0.0, cl->unlocked_eval(250.));
-       CPPUNIT_ASSERT_EQUAL(8.0, cl->unlocked_eval(999.));
-
-       cl->set_interpolation (ControlList::Linear);
-       CPPUNIT_ASSERT_EQUAL(3.6, cl->unlocked_eval(80.));
-       CPPUNIT_ASSERT_EQUAL(3.2, cl->unlocked_eval(120.));
-       CPPUNIT_ASSERT_EQUAL(1.6, cl->unlocked_eval(160.));
-       CPPUNIT_ASSERT_EQUAL(4.0, cl->unlocked_eval(250.));
-       CPPUNIT_ASSERT_EQUAL(8.0, cl->unlocked_eval(999.));
-
-       cl->fast_simple_add ( 400.0 , 9.0);
-
-       cl->set_interpolation (ControlList::Discrete);
-       CPPUNIT_ASSERT_EQUAL(2.0, cl->unlocked_eval(80.));
-       CPPUNIT_ASSERT_EQUAL(4.0, cl->unlocked_eval(120.));
-       CPPUNIT_ASSERT_EQUAL(4.0, cl->unlocked_eval(160.));
-       CPPUNIT_ASSERT_EQUAL(0.0, cl->unlocked_eval(250.));
-       CPPUNIT_ASSERT_EQUAL(8.0, cl->unlocked_eval(350.));
-       CPPUNIT_ASSERT_EQUAL(9.0, cl->unlocked_eval(999.));
-
-       cl->set_interpolation (ControlList::Linear);
-       CPPUNIT_ASSERT_EQUAL(3.6, cl->unlocked_eval(80.));
-       CPPUNIT_ASSERT_EQUAL(3.2, cl->unlocked_eval(120.));
-       CPPUNIT_ASSERT_EQUAL(1.6, cl->unlocked_eval(160.));
-       CPPUNIT_ASSERT_EQUAL(4.0, cl->unlocked_eval(250.));
-       CPPUNIT_ASSERT_EQUAL(8.5, cl->unlocked_eval(350.));
-       CPPUNIT_ASSERT_EQUAL(9.0, cl->unlocked_eval(999.));
-}
-
-void
-CurveTest::constrainedCubic ()
-{
-
-       struct point {
-               int x, y;
-       };
-
-       static const struct point data[] = {
-               /* values from worked example in www.korf.co.uk/spline.pdf */
-               {   0,  30 },
-               {  10, 130 },
-               {  30, 150 },
-               {  50, 150 },
-               {  70, 170 },
-               {  90, 220 },
-               { 100, 320 },
-       };
-
-       int32_t type = 0;
-       Evoral::Parameter p(type);
-       Evoral::ParameterDescriptor pd;
-       pd.lower = 5;
-       pd.upper = 325;
-       Evoral::ControlList l(p,pd);
-
-       size_t i;
-       l.set_interpolation(Evoral::ControlList::Curved);
-
-       for (i=0; i<sizeof(data)/sizeof(data[0]); i++) {
-               l.add (data[i].x, data[i].y);
-       }
-
-       Evoral::Curve curve(l);
-
-       float f[121];
-       curve.get_vector(-10, 110, f, 121);
-
-       const float *g = &f[10]; /* so g starts at x==0 */
-
-       /* given points - should be exactly equal */
-       CPPUNIT_ASSERT_EQUAL( 30.0f, g[-10]);
-       CPPUNIT_ASSERT_EQUAL( 30.0f, g[  0]);
-       CPPUNIT_ASSERT_EQUAL(130.0f, g[ 10]);
-       CPPUNIT_ASSERT_EQUAL(150.0f, g[ 30]);
-       CPPUNIT_ASSERT_EQUAL(150.0f, g[ 40]);
-       CPPUNIT_ASSERT_EQUAL(150.0f, g[ 50]);
-       CPPUNIT_ASSERT_EQUAL(320.0f, g[100]);
-       CPPUNIT_ASSERT_EQUAL(320.0f, g[110]);
-
-       /*
-          First segment, i=1, for 0 <= x <= 10
-          f'1(x1) = 2/((x2 â€“ x1)/(y2 â€“ y1) + (x1 â€“ x0)/(y1 â€“ y0))
-                  = 2/((30 â€“ 10)/(150 â€“ 130) + (10 â€“ 0)/(130 â€“ 30))
-                  = 1.8181
-          f'1(x0) = 3/2*(y1 â€“ y0)/(x1 â€“ x0) - f'1(x1)/2
-                  = 3/2*(130 â€“ 30)/(10 â€“ 0) â€“ 1.818/2
-                  = 14.0909
-          f"1(x0) = -2*(f'1(x1) + 2* f'1(x0))/(x1 â€“ x0) + 6*(y1 â€“ y0)/ (x1 â€“ x0)^2
-                  = -2*(1.8181 + 2*14.0909)/(10 â€“ 0) + 6*(130 â€“ 30)/(10 â€“ 0)^2
-                  = 0
-          f"1(x1) = 2*(2*f'1(x1) + f'1(x0))/(x1 â€“ x0) - 6*(y1 â€“ y0)/ (x1 â€“ x0)^2
-                  = 2*(2*1.818 + 14.0909)/(10 â€“ 0) â€“ 6*(130 â€“ 30)/(10 â€“ 0)^2
-                  = -2.4545
-          d1 = 1/6 * (f"1(x1) - f"1(x0))/(x1 â€“ x0)
-             = 1/6 * (-2.4545 â€“ 0)/(10 â€“ 0)
-             = -0.0409
-          c1 = 1/2 * (x1*f"1(x0) â€“ x0*f"1(x1))/(x1 â€“ x0)
-             = 1/2 * (10*0 â€“ 0*1.8181)/(10 â€“ 0)
-             = 0
-          b1 = ((y1 â€“ y0) â€“ c1*(x21 â€“ x20) â€“ d1*( x31 â€“ x30))/(x1 â€“ x0)
-             = ((130 â€“ 30) â€“ 0*(102 â€“ 02) + 0.0409*(103 â€“ 03))/(10 â€“ 0)
-             = 14.09
-          a1 = y0 â€“ b1*x0 â€“ c1*x20 â€“ d1*x30
-             = 30
-          y1 = 30 + 14.09x - 0.0409x3 for 0 <= x <= 10
-        */
-       /*
-          Second segment, i=2, for 10 <= x <= 30
-          f'2(x2) = 2/((x3 â€“ x2)/(y3 â€“ y2) + (x2 â€“ x1)/(y2 â€“ y1))
-                  = 2/((50 â€“ 30)/(150 â€“ 150) + (30 â€“ 10)/(150 â€“ 130))
-                  = 0
-          f'2(x1) = 2/((x2 â€“ x1)/(y2 â€“ y1) + (x1 â€“ x0)/(y1 â€“ y0))
-                  = 1.8181
-
-          f"2(x1) = -2*(f'2(x2) + 2* f'2(x1))/(x2 â€“ x1) + 6*(y2 â€“ y1)/ (x2 â€“ x1)^2
-                  = -2*(0 + 2*1.8181)/(30 â€“ 10) + 6*(150 â€“ 130)/(30 â€“ 10)2
-                  = -0.063636
-          f"2(x2) = 2*(2*f'2(x2) + f'2(x1))/(x2 â€“ x1) - 6*(y2 â€“ y1)/ (x2 â€“ x1)^2
-                  = 2*(2*0 + 1.8181)/(30 â€“ 10) â€“ 6*(150 â€“ 130)/(30 â€“ 10)^2
-                  = -0.11818
-
-          d2 = 1/6 * (f"2(x2) - f"2(x1))/(x2 â€“ x1)
-             = 1/6 * (-0.11818 + 0.063636)/(30 â€“ 10)
-             = -0.0004545
-          c2 = 1/2 * (x2*f"2(x1) â€“ x1*f"2(x2))/(x2 â€“ x1)
-             = 1/2 * (-30*0.063636 + 10*0.11818)/(30 â€“ 10)
-             = -0.01818
-          b2 = ((y2 â€“ y1) â€“ c2*(x2^2 â€“ x1^2) â€“ d2*( x2^3 â€“ x1^3))/(x2 â€“ x1)
-             = ((150 â€“ 130) + 0.01818*(302 â€“ 102) + 0.0004545*(303 â€“ 103))/(30 â€“ 10)
-             = 2.31818
-          a2 = y1 â€“ b2*x1 â€“ c2*x1^2 â€“ d2*x1^3
-             = 130 â€“ 2.31818*10 + 0.01818*102 + 0.0004545*103
-             = 109.09
-          y2 = 109.09 + 2.31818x - 0.01818x^2 - 0.0004545x^3 for 10 <= x <= 30
-        */
-
-
-       int x;
-       long double a1, b1, c1, d1, a2, b2, c2, d2, fdx0, fddx0, fdx1, fdx2, fddx1, fddx2;
-       double x0 = data[0].x;
-       double y0 = data[0].y;
-       double x1 = data[1].x;
-       double y1 = data[1].y;
-       double x2 = data[2].x;
-       double y2 = data[2].y;
-       double x3 = data[3].x;
-       double y3 = data[3].y;
-
-       double dx0 = x1 - x0;
-       double dy0 = y1 - y0;
-       double dx1 = x2 - x1;
-       double dy1 = y2 - y1;
-       double dx2 = x3 - x2;
-       double dy2 = y3 - y2;
-
-       // First (leftmost) segment
-       fdx1 = 2.0 / ( dx1 / dy1 + dx0 / dy0 );
-       fdx0 = 3.0 / 2.0 * dy0 / dx0 - fdx1 / 2.0;
-
-       fddx0 = -2.0 * (fdx1 + 2.0 * fdx0) / dx0 + 6.0 * dy0 / (dx0*dx0);
-       fddx1 =  2.0 * (2.0 * fdx1 + fdx0) / dx0 - 6.0 * dy0 / (dx0*dx0);
-       d1 = 1.0 / 6.0 * (fddx1 - fddx0) / dx0;
-       c1 = 1.0 / 2.0 * (x1 * fddx0 - x0 * fddx1) / dx0;
-       b1 = (dy0 - c1 * (x1* x1 - x0*x0) - d1 * (x1*x1*x1 - x0*x0*x0)) / dx0;
-       a1 = y0 - b1*x0 - c1*x0*x0 - d1*x0*x0*x0;
-
-       // printf("dx0=%f, dy0=%f, dx1=%f, dy1=%f\n", dx0, dy0, dx1, dy1);
-       // printf("fdx0=%Lf, fdx1=%Lf, fddx0=%Lf, fddx1=%Lf\n", fdx0, fdx1, fddx0, fddx1);
-       // printf("a1=%Lf, b1=%Lf, c1=%Lf, d1=%Lf\n", a1, b1, c1, d1);
-
-       // values from worked example: deltas rather arbitrary, I'm afraid
-       CPPUNIT_ASSERT_DOUBLES_EQUAL(30.0, a1, 0.1);
-       CPPUNIT_ASSERT_DOUBLES_EQUAL(14.09, b1, 0.01);
-       CPPUNIT_ASSERT_DOUBLES_EQUAL(0.0, c1, 0.1);
-       CPPUNIT_ASSERT_DOUBLES_EQUAL(-0.0409, d1, 0.0001);
-
-       for (x = 0; x <= 10; x++) {
-               double v = a1 + b1*x + c1*x*x  + d1*x*x*x;
-               char msg[64];
-               snprintf(msg, 64, "interpolating %d: v=%f, x=%f...\n", x, v, g[x]);
-               CPPUNIT_ASSERT_DOUBLES_EQUAL(v, g[x], 0.000004);
-       }
-
-       // Second segment
-       fdx2 = 2.0 / ( dx2 / dy2 + dx1 / dy1 );
-
-       fddx1 = -2.0 * (fdx2 + 2.0 * fdx1) / dx1 + 6.0 * dy1 / (dx1*dx1);
-       fddx2 =  2.0 * (2.0 * fdx2 + fdx1) / dx1 - 6.0 * dy1 / (dx1*dx1);
-       d2 = 1.0 / 6.0 * (fddx2 - fddx1) / dx1;
-       c2 = 1.0 / 2.0 * (x2 * fddx1 - x1 * fddx2) / dx1;
-       b2 = (dy1 - c2 * (x2*x2 - x1*x1) - d2 * (x2*x2*x2 - x1*x1*x1)) / dx1;
-       a2 = y1 - b2*x1 - c2*x1*x1 - d2*x1*x1*x1;
-
-       // printf("dx0=%f, dy0=%f, dx1=%f, dy1=%f dx2=%f, dy2=%f\n", dx0, dy0, dx1, dy1, dx2, dy2);
-       // printf("fdx1=%Lf, fdx2=%Lf, fddx1=%Lf, fddx2=%Lf\n", fdx1, fdx2, fddx1, fddx2);
-       // printf("a2=%Lf, b2=%Lf, c2=%Lf, d2=%Lf\n", a2, b2, c2, d2);
-
-       // values from worked example: deltas rather arbitrary, I'm afraid
-       CPPUNIT_ASSERT_DOUBLES_EQUAL(109.09, a2, 0.01);
-       CPPUNIT_ASSERT_DOUBLES_EQUAL(2.31818, b2, 0.00001);
-       CPPUNIT_ASSERT_DOUBLES_EQUAL(-0.01818, c2, 0.00001);
-       CPPUNIT_ASSERT_DOUBLES_EQUAL(-0.0004545, d2, 0.0000001);
-
-       for (x = 10; x <= 30; x++) {
-               double v = a2 + b2*x + c2*x*x  + d2*x*x*x;
-               char msg[64];
-               snprintf(msg, 64, "interpolating %d: v=%f, x=%f...\n", x, v, g[x]);
-               CPPUNIT_ASSERT_DOUBLES_EQUAL(v, g[x], 0.000008);
-       }
-}
diff --git a/libs/evoral/test/CurveTest.h b/libs/evoral/test/CurveTest.h
new file mode 100644 (file)
index 0000000..3260679
--- /dev/null
@@ -0,0 +1,33 @@
+#include <cppunit/TestFixture.h>
+#include <cppunit/extensions/HelperMacros.h>
+#include <boost/shared_ptr.hpp>
+#include "evoral/ControlList.h"
+
+class CurveTest : public CppUnit::TestFixture
+{
+       CPPUNIT_TEST_SUITE (CurveTest);
+       CPPUNIT_TEST (trivial);
+       CPPUNIT_TEST (rtGet);
+       CPPUNIT_TEST (twoPointLinear);
+       CPPUNIT_TEST (threePointLinear);
+       CPPUNIT_TEST (threePointDiscete);
+       CPPUNIT_TEST (constrainedCubic);
+       CPPUNIT_TEST (ctrlListEval);
+       CPPUNIT_TEST_SUITE_END ();
+
+public:
+       void trivial ();
+       void rtGet ();
+       void twoPointLinear ();
+       void threePointLinear ();
+       void threePointDiscete ();
+       void constrainedCubic ();
+       void ctrlListEval ();
+
+private:
+       boost::shared_ptr<Evoral::ControlList> TestCtrlList() {
+               Evoral::Parameter param (Evoral::Parameter(0));
+               const Evoral::ParameterDescriptor desc;
+               return boost::shared_ptr<Evoral::ControlList> (new Evoral::ControlList(param, desc));
+       }
+};
diff --git a/libs/evoral/test/CurveTest.hpp b/libs/evoral/test/CurveTest.hpp
deleted file mode 100644 (file)
index 60c0cb7..0000000
+++ /dev/null
@@ -1,33 +0,0 @@
-#include <cppunit/TestFixture.h>
-#include <cppunit/extensions/HelperMacros.h>
-#include <boost/shared_ptr.hpp>
-#include "evoral/ControlList.hpp"
-
-class CurveTest : public CppUnit::TestFixture
-{
-       CPPUNIT_TEST_SUITE (CurveTest);
-       CPPUNIT_TEST (trivial);
-       CPPUNIT_TEST (rtGet);
-       CPPUNIT_TEST (twoPointLinear);
-       CPPUNIT_TEST (threePointLinear);
-       CPPUNIT_TEST (threePointDiscete);
-       CPPUNIT_TEST (constrainedCubic);
-       CPPUNIT_TEST (ctrlListEval);
-       CPPUNIT_TEST_SUITE_END ();
-
-public:
-       void trivial ();
-       void rtGet ();
-       void twoPointLinear ();
-       void threePointLinear ();
-       void threePointDiscete ();
-       void constrainedCubic ();
-       void ctrlListEval ();
-
-private:
-       boost::shared_ptr<Evoral::ControlList> TestCtrlList() {
-               Evoral::Parameter param (Evoral::Parameter(0));
-               const Evoral::ParameterDescriptor desc;
-               return boost::shared_ptr<Evoral::ControlList> (new Evoral::ControlList(param, desc));
-       }
-};
diff --git a/libs/evoral/test/NoteTest.cc b/libs/evoral/test/NoteTest.cc
new file mode 100644 (file)
index 0000000..43e599d
--- /dev/null
@@ -0,0 +1,33 @@
+#include "NoteTest.hpp"
+#include "temporal/beats.h"
+#include "evoral/Note.h"
+#include <stdlib.h>
+
+CPPUNIT_TEST_SUITE_REGISTRATION (NoteTest);
+
+using namespace Evoral;
+
+typedef Temporal::Beats Time;
+
+void
+NoteTest::copyTest ()
+{
+       Note<Time> a(0, Time(1.0), Time(2.0), 60, 0x40);
+       Note<Time> b(a);
+       CPPUNIT_ASSERT (a == b);
+
+       // Broken due to event double free!
+       // Note<Time> c(1, Beats(3.0), Beats(4.0), 61, 0x41);
+       // c = a;
+       // CPPUNIT_ASSERT (a == c);
+}
+
+void
+NoteTest::idTest ()
+{
+       Note<Time> a(0, Time(1.0), Time(2.0), 60, 0x40);
+       CPPUNIT_ASSERT_EQUAL (-1, a.id());
+
+       a.set_id(1234);
+       CPPUNIT_ASSERT_EQUAL (1234, a.id());
+}
diff --git a/libs/evoral/test/NoteTest.cpp b/libs/evoral/test/NoteTest.cpp
deleted file mode 100644 (file)
index ead96f1..0000000
+++ /dev/null
@@ -1,33 +0,0 @@
-#include "NoteTest.hpp"
-#include "temporal/beats.h"
-#include "evoral/Note.hpp"
-#include <stdlib.h>
-
-CPPUNIT_TEST_SUITE_REGISTRATION (NoteTest);
-
-using namespace Evoral;
-
-typedef Temporal::Beats Time;
-
-void
-NoteTest::copyTest ()
-{
-       Note<Time> a(0, Time(1.0), Time(2.0), 60, 0x40);
-       Note<Time> b(a);
-       CPPUNIT_ASSERT (a == b);
-
-       // Broken due to event double free!
-       // Note<Time> c(1, Beats(3.0), Beats(4.0), 61, 0x41);
-       // c = a;
-       // CPPUNIT_ASSERT (a == c);
-}
-
-void
-NoteTest::idTest ()
-{
-       Note<Time> a(0, Time(1.0), Time(2.0), 60, 0x40);
-       CPPUNIT_ASSERT_EQUAL (-1, a.id());
-
-       a.set_id(1234);
-       CPPUNIT_ASSERT_EQUAL (1234, a.id());
-}
diff --git a/libs/evoral/test/NoteTest.h b/libs/evoral/test/NoteTest.h
new file mode 100644 (file)
index 0000000..9d0af69
--- /dev/null
@@ -0,0 +1,16 @@
+#include <cppunit/TestFixture.h>
+#include <cppunit/extensions/HelperMacros.h>
+
+class NoteTest : public CppUnit::TestFixture
+{
+       CPPUNIT_TEST_SUITE (NoteTest);
+       CPPUNIT_TEST (copyTest);
+       CPPUNIT_TEST (idTest);
+       CPPUNIT_TEST_SUITE_END ();
+
+public:
+       void copyTest ();
+       void idTest ();
+};
+
+
diff --git a/libs/evoral/test/NoteTest.hpp b/libs/evoral/test/NoteTest.hpp
deleted file mode 100644 (file)
index 9d0af69..0000000
+++ /dev/null
@@ -1,16 +0,0 @@
-#include <cppunit/TestFixture.h>
-#include <cppunit/extensions/HelperMacros.h>
-
-class NoteTest : public CppUnit::TestFixture
-{
-       CPPUNIT_TEST_SUITE (NoteTest);
-       CPPUNIT_TEST (copyTest);
-       CPPUNIT_TEST (idTest);
-       CPPUNIT_TEST_SUITE_END ();
-
-public:
-       void copyTest ();
-       void idTest ();
-};
-
-
diff --git a/libs/evoral/test/RangeTest.cc b/libs/evoral/test/RangeTest.cc
new file mode 100644 (file)
index 0000000..dcbe1c1
--- /dev/null
@@ -0,0 +1,231 @@
+#include "RangeTest.hpp"
+#include "evoral/Range.h"
+#include <stdlib.h>
+
+CPPUNIT_TEST_SUITE_REGISTRATION (RangeTest);
+
+using namespace Evoral;
+
+void
+RangeTest::coalesceTest ()
+{
+       RangeList<int> fred;
+       fred.add (Range<int> (2, 4));
+       fred.add (Range<int> (5, 6));
+       fred.add (Range<int> (6, 8));
+
+       RangeList<int>::List jim = fred.get ();
+
+       RangeList<int>::List::iterator i = jim.begin ();
+       CPPUNIT_ASSERT_EQUAL (2, i->from);
+       CPPUNIT_ASSERT_EQUAL (4, i->to);
+
+       ++i;
+       CPPUNIT_ASSERT_EQUAL (5, i->from);
+       CPPUNIT_ASSERT_EQUAL (8, i->to);
+}
+
+/* Basic subtraction of a few smaller ranges from a larger one */
+void
+RangeTest::subtractTest1 ()
+{
+
+/*         01234567890
+ * fred:   |---------|
+ * jim:      |-|  ||
+ * sheila: ||   ||  ||
+ */
+
+       Range<int> fred (0, 10);
+
+       RangeList<int> jim;
+       jim.add (Range<int> (2, 4));
+       jim.add (Range<int> (7, 8));
+
+       RangeList<int> sheila = subtract (fred, jim);
+
+       RangeList<int>::List s = sheila.get ();
+       CPPUNIT_ASSERT_EQUAL (size_t (3), s.size ());
+
+       RangeList<int>::List::iterator i = s.begin ();
+       CPPUNIT_ASSERT_EQUAL (0, i->from);
+       CPPUNIT_ASSERT_EQUAL (1, i->to);
+
+       ++i;
+       CPPUNIT_ASSERT_EQUAL (5, i->from);
+       CPPUNIT_ASSERT_EQUAL (6, i->to);
+
+       ++i;
+       CPPUNIT_ASSERT_EQUAL (9, i->from);
+       CPPUNIT_ASSERT_EQUAL (10, i->to);
+}
+
+/* Test subtraction of a range B from a range A, where A and B do not overlap */
+void
+RangeTest::subtractTest2 ()
+{
+       Range<int> fred (0, 10);
+
+       RangeList<int> jim;
+       jim.add (Range<int> (12, 19));
+
+       RangeList<int> sheila = subtract (fred, jim);
+
+       RangeList<int>::List s = sheila.get ();
+       CPPUNIT_ASSERT_EQUAL (size_t (1), s.size ());
+
+       RangeList<int>::List::iterator i = s.begin ();
+       CPPUNIT_ASSERT_EQUAL (0, i->from);
+       CPPUNIT_ASSERT_EQUAL (10, i->to);
+}
+
+/* Test subtraction of B from A, where B entirely overlaps A */
+void
+RangeTest::subtractTest3 ()
+{
+       Range<int> fred (0, 10);
+
+       RangeList<int> jim;
+       jim.add (Range<int> (0, 12));
+
+       RangeList<int> sheila = subtract (fred, jim);
+
+       RangeList<int>::List s = sheila.get ();
+       CPPUNIT_ASSERT_EQUAL (size_t (0), s.size ());
+}
+
+/* A bit like subtractTest1, except some of the ranges
+   we are subtracting overlap.
+*/
+void
+RangeTest::subtractTest4 ()
+{
+/*         01234567890
+ * fred:   |---------|
+ * jim:      |-|  ||
+ *                 ||
+ * sheila: ||   ||   |
+ */
+
+       Range<int> fred (0, 10);
+
+       RangeList<int> jim;
+       jim.add (Range<int> (2, 4));
+       jim.add (Range<int> (7, 8));
+       jim.add (Range<int> (8, 9));
+
+       RangeList<int> sheila = subtract (fred, jim);
+
+       RangeList<int>::List s = sheila.get ();
+       CPPUNIT_ASSERT_EQUAL (size_t (3), s.size ());
+
+       RangeList<int>::List::iterator i = s.begin ();
+       CPPUNIT_ASSERT_EQUAL (0, i->from);
+       CPPUNIT_ASSERT_EQUAL (1, i->to);
+
+       ++i;
+       CPPUNIT_ASSERT_EQUAL (5, i->from);
+       CPPUNIT_ASSERT_EQUAL (6, i->to);
+
+       ++i;
+       CPPUNIT_ASSERT_EQUAL (10, i->from);
+       CPPUNIT_ASSERT_EQUAL (10, i->to);
+}
+
+/* A bit like subtractTest1, except some of the ranges
+   we are subtracting overlap the start / end of the
+   initial range.
+*/
+void
+RangeTest::subtractTest5 ()
+{
+/*         01234567890123
+ * fred:    |----------|
+ * jim:    |---| || |------...
+ * sheila:i     |  |
+ */
+
+       Range<int> fred (1, 12);
+
+       RangeList<int> jim;
+       jim.add (Range<int> (0, 4));
+       jim.add (Range<int> (6, 7));
+       jim.add (Range<int> (9, 42));
+
+       RangeList<int> sheila = subtract (fred, jim);
+
+       RangeList<int>::List s = sheila.get ();
+       CPPUNIT_ASSERT_EQUAL (size_t (2), s.size ());
+
+       RangeList<int>::List::iterator i = s.begin ();
+       CPPUNIT_ASSERT_EQUAL (5, i->from);
+       CPPUNIT_ASSERT_EQUAL (5, i->to);
+
+       ++i;
+       CPPUNIT_ASSERT_EQUAL (8, i->from);
+       CPPUNIT_ASSERT_EQUAL (8, i->to);
+}
+
+/* Test coverage() with all possible types of overlap.
+ */
+
+void
+RangeTest::coverageTest ()
+{
+
+       // b starts before a
+       CPPUNIT_ASSERT_EQUAL (coverage(3, 7, 1, 1), Evoral::OverlapNone);
+       CPPUNIT_ASSERT_EQUAL (coverage(3, 7, 1, 2), Evoral::OverlapNone);
+       CPPUNIT_ASSERT_EQUAL (coverage(3, 7, 1, 3), Evoral::OverlapStart);
+       CPPUNIT_ASSERT_EQUAL (coverage(3, 7, 1, 5), Evoral::OverlapStart);
+       CPPUNIT_ASSERT_EQUAL (coverage(3, 7, 1, 7), Evoral::OverlapExternal);
+       CPPUNIT_ASSERT_EQUAL (coverage(3, 7, 1, 9), Evoral::OverlapExternal);
+
+       // b starts at a
+       CPPUNIT_ASSERT_EQUAL (coverage(3, 7, 3, 3), Evoral::OverlapStart);
+       CPPUNIT_ASSERT_EQUAL (coverage(3, 7, 3, 5), Evoral::OverlapStart);
+       CPPUNIT_ASSERT_EQUAL (coverage(3, 7, 3, 7), Evoral::OverlapExternal);
+       CPPUNIT_ASSERT_EQUAL (coverage(3, 7, 3, 9), Evoral::OverlapExternal);
+
+       // b starts inside a
+       CPPUNIT_ASSERT_EQUAL (coverage(3, 7, 4, 4), Evoral::OverlapInternal);
+       CPPUNIT_ASSERT_EQUAL (coverage(3, 7, 4, 6), Evoral::OverlapInternal);
+       CPPUNIT_ASSERT_EQUAL (coverage(3, 7, 4, 7), Evoral::OverlapEnd);
+       CPPUNIT_ASSERT_EQUAL (coverage(3, 7, 4, 8), Evoral::OverlapEnd);
+
+       // b starts at end of a
+       CPPUNIT_ASSERT_EQUAL (coverage(3, 7, 7, 7), Evoral::OverlapEnd);
+       CPPUNIT_ASSERT_EQUAL (coverage(3, 7, 7, 9), Evoral::OverlapEnd);
+
+       // b starts after end of a
+       CPPUNIT_ASSERT_EQUAL (coverage(3, 7, 8, 8), Evoral::OverlapNone);
+       CPPUNIT_ASSERT_EQUAL (coverage(3, 7, 8, 9), Evoral::OverlapNone);
+
+       // zero-length range a
+       CPPUNIT_ASSERT_EQUAL (coverage(3, 3, 2, 4), Evoral::OverlapExternal);
+       CPPUNIT_ASSERT_EQUAL (coverage(3, 3, 1, 2), Evoral::OverlapNone);
+       CPPUNIT_ASSERT_EQUAL (coverage(3, 3, 3, 3), Evoral::OverlapExternal);
+       CPPUNIT_ASSERT_EQUAL (coverage(3, 3, 8, 9), Evoral::OverlapNone);
+
+       // negative length range a
+       // XXX these are debatable - should we just consider start & end to be
+       // swapped if end < start?
+       CPPUNIT_ASSERT_EQUAL (coverage(4, 3, 1, 2), Evoral::OverlapNone);
+       CPPUNIT_ASSERT_EQUAL (coverage(4, 3, 2, 3), Evoral::OverlapNone);
+       CPPUNIT_ASSERT_EQUAL (coverage(4, 3, 2, 4), Evoral::OverlapNone);
+       CPPUNIT_ASSERT_EQUAL (coverage(4, 3, 3, 3), Evoral::OverlapNone);
+       CPPUNIT_ASSERT_EQUAL (coverage(4, 3, 8, 9), Evoral::OverlapNone);
+
+       // negative length range b
+       // b starts before a
+       CPPUNIT_ASSERT_EQUAL (coverage(3, 7, 1, 0), Evoral::OverlapNone);
+       // b starts at a
+       CPPUNIT_ASSERT_EQUAL (coverage(3, 7, 3, 2), Evoral::OverlapNone);
+       // b starts inside a
+       CPPUNIT_ASSERT_EQUAL (coverage(3, 7, 4, 3), Evoral::OverlapNone);
+       // b starts at end of a
+       CPPUNIT_ASSERT_EQUAL (coverage(3, 7, 7, 5), Evoral::OverlapNone);
+       // b starts after end of a
+       CPPUNIT_ASSERT_EQUAL (coverage(3, 7, 8, 7), Evoral::OverlapNone);
+
+}
diff --git a/libs/evoral/test/RangeTest.cpp b/libs/evoral/test/RangeTest.cpp
deleted file mode 100644 (file)
index bbb2bde..0000000
+++ /dev/null
@@ -1,231 +0,0 @@
-#include "RangeTest.hpp"
-#include "evoral/Range.hpp"
-#include <stdlib.h>
-
-CPPUNIT_TEST_SUITE_REGISTRATION (RangeTest);
-
-using namespace Evoral;
-
-void
-RangeTest::coalesceTest ()
-{
-       RangeList<int> fred;
-       fred.add (Range<int> (2, 4));
-       fred.add (Range<int> (5, 6));
-       fred.add (Range<int> (6, 8));
-
-       RangeList<int>::List jim = fred.get ();
-
-       RangeList<int>::List::iterator i = jim.begin ();
-       CPPUNIT_ASSERT_EQUAL (2, i->from);
-       CPPUNIT_ASSERT_EQUAL (4, i->to);
-
-       ++i;
-       CPPUNIT_ASSERT_EQUAL (5, i->from);
-       CPPUNIT_ASSERT_EQUAL (8, i->to);
-}
-
-/* Basic subtraction of a few smaller ranges from a larger one */
-void
-RangeTest::subtractTest1 ()
-{
-
-/*         01234567890
- * fred:   |---------|
- * jim:      |-|  ||
- * sheila: ||   ||  ||
- */
-
-       Range<int> fred (0, 10);
-
-       RangeList<int> jim;
-       jim.add (Range<int> (2, 4));
-       jim.add (Range<int> (7, 8));
-
-       RangeList<int> sheila = subtract (fred, jim);
-
-       RangeList<int>::List s = sheila.get ();
-       CPPUNIT_ASSERT_EQUAL (size_t (3), s.size ());
-
-       RangeList<int>::List::iterator i = s.begin ();
-       CPPUNIT_ASSERT_EQUAL (0, i->from);
-       CPPUNIT_ASSERT_EQUAL (1, i->to);
-
-       ++i;
-       CPPUNIT_ASSERT_EQUAL (5, i->from);
-       CPPUNIT_ASSERT_EQUAL (6, i->to);
-
-       ++i;
-       CPPUNIT_ASSERT_EQUAL (9, i->from);
-       CPPUNIT_ASSERT_EQUAL (10, i->to);
-}
-
-/* Test subtraction of a range B from a range A, where A and B do not overlap */
-void
-RangeTest::subtractTest2 ()
-{
-       Range<int> fred (0, 10);
-
-       RangeList<int> jim;
-       jim.add (Range<int> (12, 19));
-
-       RangeList<int> sheila = subtract (fred, jim);
-
-       RangeList<int>::List s = sheila.get ();
-       CPPUNIT_ASSERT_EQUAL (size_t (1), s.size ());
-
-       RangeList<int>::List::iterator i = s.begin ();
-       CPPUNIT_ASSERT_EQUAL (0, i->from);
-       CPPUNIT_ASSERT_EQUAL (10, i->to);
-}
-
-/* Test subtraction of B from A, where B entirely overlaps A */
-void
-RangeTest::subtractTest3 ()
-{
-       Range<int> fred (0, 10);
-
-       RangeList<int> jim;
-       jim.add (Range<int> (0, 12));
-
-       RangeList<int> sheila = subtract (fred, jim);
-
-       RangeList<int>::List s = sheila.get ();
-       CPPUNIT_ASSERT_EQUAL (size_t (0), s.size ());
-}
-
-/* A bit like subtractTest1, except some of the ranges
-   we are subtracting overlap.
-*/
-void
-RangeTest::subtractTest4 ()
-{
-/*         01234567890
- * fred:   |---------|
- * jim:      |-|  ||
- *                 ||
- * sheila: ||   ||   |
- */
-
-       Range<int> fred (0, 10);
-
-       RangeList<int> jim;
-       jim.add (Range<int> (2, 4));
-       jim.add (Range<int> (7, 8));
-       jim.add (Range<int> (8, 9));
-
-       RangeList<int> sheila = subtract (fred, jim);
-
-       RangeList<int>::List s = sheila.get ();
-       CPPUNIT_ASSERT_EQUAL (size_t (3), s.size ());
-
-       RangeList<int>::List::iterator i = s.begin ();
-       CPPUNIT_ASSERT_EQUAL (0, i->from);
-       CPPUNIT_ASSERT_EQUAL (1, i->to);
-
-       ++i;
-       CPPUNIT_ASSERT_EQUAL (5, i->from);
-       CPPUNIT_ASSERT_EQUAL (6, i->to);
-
-       ++i;
-       CPPUNIT_ASSERT_EQUAL (10, i->from);
-       CPPUNIT_ASSERT_EQUAL (10, i->to);
-}
-
-/* A bit like subtractTest1, except some of the ranges
-   we are subtracting overlap the start / end of the
-   initial range.
-*/
-void
-RangeTest::subtractTest5 ()
-{
-/*         01234567890123
- * fred:    |----------|
- * jim:    |---| || |------...
- * sheila:i     |  |
- */
-
-       Range<int> fred (1, 12);
-
-       RangeList<int> jim;
-       jim.add (Range<int> (0, 4));
-       jim.add (Range<int> (6, 7));
-       jim.add (Range<int> (9, 42));
-
-       RangeList<int> sheila = subtract (fred, jim);
-
-       RangeList<int>::List s = sheila.get ();
-       CPPUNIT_ASSERT_EQUAL (size_t (2), s.size ());
-
-       RangeList<int>::List::iterator i = s.begin ();
-       CPPUNIT_ASSERT_EQUAL (5, i->from);
-       CPPUNIT_ASSERT_EQUAL (5, i->to);
-
-       ++i;
-       CPPUNIT_ASSERT_EQUAL (8, i->from);
-       CPPUNIT_ASSERT_EQUAL (8, i->to);
-}
-
-/* Test coverage() with all possible types of overlap.
- */
-
-void
-RangeTest::coverageTest ()
-{
-
-       // b starts before a
-       CPPUNIT_ASSERT_EQUAL (coverage(3, 7, 1, 1), Evoral::OverlapNone);
-       CPPUNIT_ASSERT_EQUAL (coverage(3, 7, 1, 2), Evoral::OverlapNone);
-       CPPUNIT_ASSERT_EQUAL (coverage(3, 7, 1, 3), Evoral::OverlapStart);
-       CPPUNIT_ASSERT_EQUAL (coverage(3, 7, 1, 5), Evoral::OverlapStart);
-       CPPUNIT_ASSERT_EQUAL (coverage(3, 7, 1, 7), Evoral::OverlapExternal);
-       CPPUNIT_ASSERT_EQUAL (coverage(3, 7, 1, 9), Evoral::OverlapExternal);
-
-       // b starts at a
-       CPPUNIT_ASSERT_EQUAL (coverage(3, 7, 3, 3), Evoral::OverlapStart);
-       CPPUNIT_ASSERT_EQUAL (coverage(3, 7, 3, 5), Evoral::OverlapStart);
-       CPPUNIT_ASSERT_EQUAL (coverage(3, 7, 3, 7), Evoral::OverlapExternal);
-       CPPUNIT_ASSERT_EQUAL (coverage(3, 7, 3, 9), Evoral::OverlapExternal);
-
-       // b starts inside a
-       CPPUNIT_ASSERT_EQUAL (coverage(3, 7, 4, 4), Evoral::OverlapInternal);
-       CPPUNIT_ASSERT_EQUAL (coverage(3, 7, 4, 6), Evoral::OverlapInternal);
-       CPPUNIT_ASSERT_EQUAL (coverage(3, 7, 4, 7), Evoral::OverlapEnd);
-       CPPUNIT_ASSERT_EQUAL (coverage(3, 7, 4, 8), Evoral::OverlapEnd);
-
-       // b starts at end of a
-       CPPUNIT_ASSERT_EQUAL (coverage(3, 7, 7, 7), Evoral::OverlapEnd);
-       CPPUNIT_ASSERT_EQUAL (coverage(3, 7, 7, 9), Evoral::OverlapEnd);
-
-       // b starts after end of a
-       CPPUNIT_ASSERT_EQUAL (coverage(3, 7, 8, 8), Evoral::OverlapNone);
-       CPPUNIT_ASSERT_EQUAL (coverage(3, 7, 8, 9), Evoral::OverlapNone);
-
-       // zero-length range a
-       CPPUNIT_ASSERT_EQUAL (coverage(3, 3, 2, 4), Evoral::OverlapExternal);
-       CPPUNIT_ASSERT_EQUAL (coverage(3, 3, 1, 2), Evoral::OverlapNone);
-       CPPUNIT_ASSERT_EQUAL (coverage(3, 3, 3, 3), Evoral::OverlapExternal);
-       CPPUNIT_ASSERT_EQUAL (coverage(3, 3, 8, 9), Evoral::OverlapNone);
-
-       // negative length range a
-       // XXX these are debatable - should we just consider start & end to be
-       // swapped if end < start?
-       CPPUNIT_ASSERT_EQUAL (coverage(4, 3, 1, 2), Evoral::OverlapNone);
-       CPPUNIT_ASSERT_EQUAL (coverage(4, 3, 2, 3), Evoral::OverlapNone);
-       CPPUNIT_ASSERT_EQUAL (coverage(4, 3, 2, 4), Evoral::OverlapNone);
-       CPPUNIT_ASSERT_EQUAL (coverage(4, 3, 3, 3), Evoral::OverlapNone);
-       CPPUNIT_ASSERT_EQUAL (coverage(4, 3, 8, 9), Evoral::OverlapNone);
-
-       // negative length range b
-       // b starts before a
-       CPPUNIT_ASSERT_EQUAL (coverage(3, 7, 1, 0), Evoral::OverlapNone);
-       // b starts at a
-       CPPUNIT_ASSERT_EQUAL (coverage(3, 7, 3, 2), Evoral::OverlapNone);
-       // b starts inside a
-       CPPUNIT_ASSERT_EQUAL (coverage(3, 7, 4, 3), Evoral::OverlapNone);
-       // b starts at end of a
-       CPPUNIT_ASSERT_EQUAL (coverage(3, 7, 7, 5), Evoral::OverlapNone);
-       // b starts after end of a
-       CPPUNIT_ASSERT_EQUAL (coverage(3, 7, 8, 7), Evoral::OverlapNone);
-
-}
diff --git a/libs/evoral/test/RangeTest.h b/libs/evoral/test/RangeTest.h
new file mode 100644 (file)
index 0000000..b06a477
--- /dev/null
@@ -0,0 +1,26 @@
+#include <cppunit/TestFixture.h>
+#include <cppunit/extensions/HelperMacros.h>
+
+class RangeTest : public CppUnit::TestFixture
+{
+       CPPUNIT_TEST_SUITE (RangeTest);
+       CPPUNIT_TEST (coalesceTest);
+       CPPUNIT_TEST (subtractTest1);
+       CPPUNIT_TEST (subtractTest2);
+       CPPUNIT_TEST (subtractTest3);
+       CPPUNIT_TEST (subtractTest4);
+       CPPUNIT_TEST (subtractTest5);
+       CPPUNIT_TEST (coverageTest);
+       CPPUNIT_TEST_SUITE_END ();
+
+public:
+       void coalesceTest ();
+       void subtractTest1 ();
+       void subtractTest2 ();
+       void subtractTest3 ();
+       void subtractTest4 ();
+       void subtractTest5 ();
+       void coverageTest ();
+};
+
+
diff --git a/libs/evoral/test/RangeTest.hpp b/libs/evoral/test/RangeTest.hpp
deleted file mode 100644 (file)
index b06a477..0000000
+++ /dev/null
@@ -1,26 +0,0 @@
-#include <cppunit/TestFixture.h>
-#include <cppunit/extensions/HelperMacros.h>
-
-class RangeTest : public CppUnit::TestFixture
-{
-       CPPUNIT_TEST_SUITE (RangeTest);
-       CPPUNIT_TEST (coalesceTest);
-       CPPUNIT_TEST (subtractTest1);
-       CPPUNIT_TEST (subtractTest2);
-       CPPUNIT_TEST (subtractTest3);
-       CPPUNIT_TEST (subtractTest4);
-       CPPUNIT_TEST (subtractTest5);
-       CPPUNIT_TEST (coverageTest);
-       CPPUNIT_TEST_SUITE_END ();
-
-public:
-       void coalesceTest ();
-       void subtractTest1 ();
-       void subtractTest2 ();
-       void subtractTest3 ();
-       void subtractTest4 ();
-       void subtractTest5 ();
-       void coverageTest ();
-};
-
-
diff --git a/libs/evoral/test/SMFTest.cc b/libs/evoral/test/SMFTest.cc
new file mode 100644 (file)
index 0000000..7a7f1cd
--- /dev/null
@@ -0,0 +1,119 @@
+#include "SMFTest.hpp"
+
+#include <glibmm/fileutils.h>
+#include <glibmm/miscutils.h>
+
+#include "pbd/file_utils.h"
+
+using namespace std;
+
+CPPUNIT_TEST_SUITE_REGISTRATION( SMFTest );
+
+void
+SMFTest::createNewFileTest ()
+{
+       TestSMF smf;
+
+       string output_dir_path = PBD::tmp_writable_directory (PACKAGE, "createNewFileTest");
+       string new_file_path = Glib::build_filename (output_dir_path, "NewFile.mid");
+       smf.create(new_file_path);
+       smf.close();
+       CPPUNIT_ASSERT(Glib::file_test (new_file_path, Glib::FILE_TEST_IS_REGULAR));
+}
+
+PBD::Searchpath
+test_search_path ()
+{
+#ifdef PLATFORM_WINDOWS
+       if (!getenv("EVORAL_TEST_PATH")) {
+               string wsp(g_win32_get_package_installation_directory_of_module(NULL));
+               return Glib::build_filename (wsp,  "evoral_testdata");
+       }
+#endif
+       return Glib::getenv("EVORAL_TEST_PATH");
+}
+
+void
+SMFTest::takeFiveTest ()
+{
+       TestSMF smf;
+       string testdata_path;
+       CPPUNIT_ASSERT (find_file (test_search_path (), "TakeFive.mid", testdata_path));
+       CPPUNIT_ASSERT (SMF::test(testdata_path));
+
+       smf.open(testdata_path);
+       CPPUNIT_ASSERT(!smf.is_empty());
+
+       CPPUNIT_ASSERT_EQUAL((uint16_t)1, smf.num_tracks());
+       CPPUNIT_ASSERT_EQUAL(0, smf.seek_to_track(1));
+
+       seq->start_write();
+       smf.seek_to_start();
+
+       uint64_t time = 0; /* in SMF ticks */
+       Evoral::Event<Time> ev;
+
+       uint32_t delta_t = 0;
+       uint32_t size    = 0;
+       uint8_t* buf     = NULL;
+       int ret;
+       while ((ret = smf.read_event(&delta_t, &size, &buf)) >= 0) {
+               ev.set(buf, size, Time());
+               time += delta_t;
+
+               if (ret > 0) { // didn't skip (meta) event
+                       //cerr << "read smf event type " << hex << int(buf[0]) << endl;
+                       ev.set_time(Temporal::Beats::ticks_at_rate(time, smf.ppqn()));
+                       ev.set_event_type(Evoral::MIDI_EVENT);
+                       seq->append(ev, next_event_id ());
+               }
+       }
+
+       seq->end_write (Sequence<Time>::Relax,
+                       Temporal::Beats::ticks_at_rate(time, smf.ppqn()));
+       CPPUNIT_ASSERT(!seq->empty());
+
+       // Iterate over all notes
+       bool   on          = true;
+       size_t num_notes   = 0;
+       size_t num_sysexes = 0;
+       for (Sequence<Time>::const_iterator i = seq->begin(Time()); i != seq->end(); ++i) {
+               if (i->is_note_on()) {
+                       ++num_notes;
+               } else if (i->is_sysex()) {
+                       ++num_sysexes;
+               }
+       }
+       CPPUNIT_ASSERT_EQUAL(size_t(3833), seq->notes().size());
+       CPPUNIT_ASSERT_EQUAL(size_t(3833), num_notes);
+       CPPUNIT_ASSERT_EQUAL(size_t(232), seq->sysexes().size());
+       CPPUNIT_ASSERT_EQUAL(size_t(232), num_sysexes);
+}
+
+void
+SMFTest::writeTest ()
+{
+       TestSMF smf;
+       string  testdata_path;
+       CPPUNIT_ASSERT (find_file (test_search_path (), "TakeFive.mid", testdata_path));
+
+       smf.open(testdata_path);
+       CPPUNIT_ASSERT(!smf.is_empty());
+
+       TestSMF out;
+       const string output_dir_path = PBD::tmp_writable_directory (PACKAGE, "writeTest");
+       const string new_file_path   = Glib::build_filename (output_dir_path, "TakeFiveCopy.mid");
+       CPPUNIT_ASSERT_EQUAL (0, out.create(new_file_path, 1, 1920));
+       out.begin_write();
+
+       uint32_t delta_t = 0;
+       uint32_t size    = 0;
+       uint8_t* buf     = NULL;
+       while (smf.read_event(&delta_t, &size, &buf) >= 0) {
+               out.append_event_delta(delta_t, size, buf, 0);
+       }
+
+       out.end_write(new_file_path);
+
+       // TODO: Check files are actually equivalent
+}
diff --git a/libs/evoral/test/SMFTest.cpp b/libs/evoral/test/SMFTest.cpp
deleted file mode 100644 (file)
index 7a7f1cd..0000000
+++ /dev/null
@@ -1,119 +0,0 @@
-#include "SMFTest.hpp"
-
-#include <glibmm/fileutils.h>
-#include <glibmm/miscutils.h>
-
-#include "pbd/file_utils.h"
-
-using namespace std;
-
-CPPUNIT_TEST_SUITE_REGISTRATION( SMFTest );
-
-void
-SMFTest::createNewFileTest ()
-{
-       TestSMF smf;
-
-       string output_dir_path = PBD::tmp_writable_directory (PACKAGE, "createNewFileTest");
-       string new_file_path = Glib::build_filename (output_dir_path, "NewFile.mid");
-       smf.create(new_file_path);
-       smf.close();
-       CPPUNIT_ASSERT(Glib::file_test (new_file_path, Glib::FILE_TEST_IS_REGULAR));
-}
-
-PBD::Searchpath
-test_search_path ()
-{
-#ifdef PLATFORM_WINDOWS
-       if (!getenv("EVORAL_TEST_PATH")) {
-               string wsp(g_win32_get_package_installation_directory_of_module(NULL));
-               return Glib::build_filename (wsp,  "evoral_testdata");
-       }
-#endif
-       return Glib::getenv("EVORAL_TEST_PATH");
-}
-
-void
-SMFTest::takeFiveTest ()
-{
-       TestSMF smf;
-       string testdata_path;
-       CPPUNIT_ASSERT (find_file (test_search_path (), "TakeFive.mid", testdata_path));
-       CPPUNIT_ASSERT (SMF::test(testdata_path));
-
-       smf.open(testdata_path);
-       CPPUNIT_ASSERT(!smf.is_empty());
-
-       CPPUNIT_ASSERT_EQUAL((uint16_t)1, smf.num_tracks());
-       CPPUNIT_ASSERT_EQUAL(0, smf.seek_to_track(1));
-
-       seq->start_write();
-       smf.seek_to_start();
-
-       uint64_t time = 0; /* in SMF ticks */
-       Evoral::Event<Time> ev;
-
-       uint32_t delta_t = 0;
-       uint32_t size    = 0;
-       uint8_t* buf     = NULL;
-       int ret;
-       while ((ret = smf.read_event(&delta_t, &size, &buf)) >= 0) {
-               ev.set(buf, size, Time());
-               time += delta_t;
-
-               if (ret > 0) { // didn't skip (meta) event
-                       //cerr << "read smf event type " << hex << int(buf[0]) << endl;
-                       ev.set_time(Temporal::Beats::ticks_at_rate(time, smf.ppqn()));
-                       ev.set_event_type(Evoral::MIDI_EVENT);
-                       seq->append(ev, next_event_id ());
-               }
-       }
-
-       seq->end_write (Sequence<Time>::Relax,
-                       Temporal::Beats::ticks_at_rate(time, smf.ppqn()));
-       CPPUNIT_ASSERT(!seq->empty());
-
-       // Iterate over all notes
-       bool   on          = true;
-       size_t num_notes   = 0;
-       size_t num_sysexes = 0;
-       for (Sequence<Time>::const_iterator i = seq->begin(Time()); i != seq->end(); ++i) {
-               if (i->is_note_on()) {
-                       ++num_notes;
-               } else if (i->is_sysex()) {
-                       ++num_sysexes;
-               }
-       }
-       CPPUNIT_ASSERT_EQUAL(size_t(3833), seq->notes().size());
-       CPPUNIT_ASSERT_EQUAL(size_t(3833), num_notes);
-       CPPUNIT_ASSERT_EQUAL(size_t(232), seq->sysexes().size());
-       CPPUNIT_ASSERT_EQUAL(size_t(232), num_sysexes);
-}
-
-void
-SMFTest::writeTest ()
-{
-       TestSMF smf;
-       string  testdata_path;
-       CPPUNIT_ASSERT (find_file (test_search_path (), "TakeFive.mid", testdata_path));
-
-       smf.open(testdata_path);
-       CPPUNIT_ASSERT(!smf.is_empty());
-
-       TestSMF out;
-       const string output_dir_path = PBD::tmp_writable_directory (PACKAGE, "writeTest");
-       const string new_file_path   = Glib::build_filename (output_dir_path, "TakeFiveCopy.mid");
-       CPPUNIT_ASSERT_EQUAL (0, out.create(new_file_path, 1, 1920));
-       out.begin_write();
-
-       uint32_t delta_t = 0;
-       uint32_t size    = 0;
-       uint8_t* buf     = NULL;
-       while (smf.read_event(&delta_t, &size, &buf) >= 0) {
-               out.append_event_delta(delta_t, size, buf, 0);
-       }
-
-       out.end_write(new_file_path);
-
-       // TODO: Check files are actually equivalent
-}
diff --git a/libs/evoral/test/SMFTest.h b/libs/evoral/test/SMFTest.h
new file mode 100644 (file)
index 0000000..79bf3d0
--- /dev/null
@@ -0,0 +1,85 @@
+/*
+ * Copyright (C) 2009-2010 Carl Hetherington <carl@carlh.net>
+ * Copyright (C) 2009-2016 David Robillard <d@drobilla.net>
+ * Copyright (C) 2009 Hans Baier <hansfbaier@googlemail.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include <cassert>
+#include <stdint.h>
+#include <sigc++/sigc++.h>
+#include <cppunit/TestFixture.h>
+#include <cppunit/extensions/HelperMacros.h>
+#include "temporal/beats.h"
+#include "evoral/SMF.h"
+#include "SequenceTest.hpp"
+
+using namespace Evoral;
+
+class TestSMF : public SMF {
+public:
+       std::string path() const { return _path; }
+
+       int open(const std::string& path) {
+               _path = path;
+               return SMF::open(path);
+       }
+
+       void close() {
+               return SMF::close();
+       }
+
+       int read_event(uint32_t* delta_t, uint32_t* size, uint8_t** buf) const {
+               event_id_t id;
+               return SMF::read_event(delta_t, size, buf, &id);
+       }
+
+private:
+       std::string  _path;
+};
+
+class SMFTest : public CppUnit::TestFixture
+{
+       CPPUNIT_TEST_SUITE(SMFTest);
+       CPPUNIT_TEST(createNewFileTest);
+       CPPUNIT_TEST(takeFiveTest);
+       CPPUNIT_TEST(writeTest);
+       CPPUNIT_TEST_SUITE_END();
+
+public:
+       typedef Temporal::Beats Time;
+
+       void setUp() {
+               type_map = new DummyTypeMap();
+               assert(type_map);
+               seq = new MySequence<Time>(*type_map);
+               assert(seq);
+       }
+
+       void tearDown() {
+               delete seq;
+               delete type_map;
+       }
+
+       void createNewFileTest();
+       void takeFiveTest();
+       void writeTest();
+
+private:
+       DummyTypeMap*     type_map;
+       MySequence<Time>* seq;
+};
+
diff --git a/libs/evoral/test/SMFTest.hpp b/libs/evoral/test/SMFTest.hpp
deleted file mode 100644 (file)
index d3f01c3..0000000
+++ /dev/null
@@ -1,85 +0,0 @@
-/*
- * Copyright (C) 2009-2010 Carl Hetherington <carl@carlh.net>
- * Copyright (C) 2009-2016 David Robillard <d@drobilla.net>
- * Copyright (C) 2009 Hans Baier <hansfbaier@googlemail.com>
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-
-#include <cassert>
-#include <stdint.h>
-#include <sigc++/sigc++.h>
-#include <cppunit/TestFixture.h>
-#include <cppunit/extensions/HelperMacros.h>
-#include "temporal/beats.h"
-#include "evoral/SMF.hpp"
-#include "SequenceTest.hpp"
-
-using namespace Evoral;
-
-class TestSMF : public SMF {
-public:
-       std::string path() const { return _path; }
-
-       int open(const std::string& path) {
-               _path = path;
-               return SMF::open(path);
-       }
-
-       void close() {
-               return SMF::close();
-       }
-
-       int read_event(uint32_t* delta_t, uint32_t* size, uint8_t** buf) const {
-               event_id_t id;
-               return SMF::read_event(delta_t, size, buf, &id);
-       }
-
-private:
-       std::string  _path;
-};
-
-class SMFTest : public CppUnit::TestFixture
-{
-       CPPUNIT_TEST_SUITE(SMFTest);
-       CPPUNIT_TEST(createNewFileTest);
-       CPPUNIT_TEST(takeFiveTest);
-       CPPUNIT_TEST(writeTest);
-       CPPUNIT_TEST_SUITE_END();
-
-public:
-       typedef Temporal::Beats Time;
-
-       void setUp() {
-               type_map = new DummyTypeMap();
-               assert(type_map);
-               seq = new MySequence<Time>(*type_map);
-               assert(seq);
-       }
-
-       void tearDown() {
-               delete seq;
-               delete type_map;
-       }
-
-       void createNewFileTest();
-       void takeFiveTest();
-       void writeTest();
-
-private:
-       DummyTypeMap*     type_map;
-       MySequence<Time>* seq;
-};
-
diff --git a/libs/evoral/test/SequenceTest.cc b/libs/evoral/test/SequenceTest.cc
new file mode 100644 (file)
index 0000000..6a5fbb6
--- /dev/null
@@ -0,0 +1,196 @@
+#include "SequenceTest.hpp"
+#include <cassert>
+
+CPPUNIT_TEST_SUITE_REGISTRATION(SequenceTest);
+
+using namespace std;
+using namespace Evoral;
+
+void
+SequenceTest::createTest ()
+{
+       CPPUNIT_ASSERT_EQUAL(size_t(0), seq->sysexes().size());
+       CPPUNIT_ASSERT_EQUAL(size_t(0), seq->notes().size());
+       CPPUNIT_ASSERT(seq->notes().begin() == seq->notes().end());
+}
+
+void
+SequenceTest::copyTest ()
+{
+       DummyTypeMap map;
+       MySequence<Time> a(map);
+       for (Notes::const_iterator i = test_notes.begin(); i != test_notes.end(); ++i) {
+               a.notes().insert(*i);
+       }
+
+       MySequence<Time> b(a);
+       CPPUNIT_ASSERT_EQUAL(b.notes().size(), a.notes().size());
+}
+
+void
+SequenceTest::preserveEventOrderingTest ()
+{
+       vector< boost::shared_ptr< Event<Time> > > inserted_events;
+
+       seq->start_write();
+
+       for (Notes::const_iterator i = test_notes.begin(); i != test_notes.end(); ++i) {
+               uint8_t buffer[3];
+               Event<Time>* event = new Event<Time>(
+                       (Evoral::EventType)DummyTypeMap::CONTROL, (*i)->on_event().time(), 3, buffer, true
+               );
+
+               event->buffer()[0] = MIDI_CMD_CONTROL;
+               event->buffer()[1] = event->time().to_double() / 1000;
+               event->buffer()[2] = event->time().to_double() / 1000;
+
+               boost::shared_ptr<Event<Time> > event_ptr(event);
+
+               seq->append((*i)->on_event(), next_event_id ());
+               inserted_events.push_back(
+                               boost::shared_ptr<Event<Time> >(
+                                               new Event<Time>((*i)->on_event(), true)
+               ));
+
+               seq->append(*event_ptr, next_event_id ());
+               inserted_events.push_back(event_ptr);
+
+               seq->append((*i)->off_event(), next_event_id ());
+               inserted_events.push_back(
+                               boost::shared_ptr<Event<Time> >(
+                                               new Event<Time>((*i)->off_event(), true)
+               ));
+       }
+
+       seq->end_write (Sequence<Time>::Relax);
+
+       TestSink<Time> sink;
+       sink.writing.connect(sigc::mem_fun(&sink, &TestSink<Time>::assertLastEventTimeEarlier));
+
+
+       for (MySequence<Time>::const_iterator i = seq->begin(); i != seq->end(); ++i) {
+               sink.write(i->time(), i->event_type(), i->size(), i->buffer());
+       }
+
+       CPPUNIT_ASSERT_EQUAL(size_t(12), test_notes.size());
+}
+
+void
+SequenceTest::iteratorSeekTest ()
+{
+       size_t num_notes = 0;
+
+       seq->clear();
+
+       for (Notes::const_iterator i = test_notes.begin(); i != test_notes.end(); ++i) {
+               seq->notes().insert(*i);
+       }
+
+       // Iterate over all notes
+       bool on = true;
+       for (Sequence<Time>::const_iterator i = seq->begin(Time(600)); i != seq->end(); ++i) {
+               if (on) {
+                       CPPUNIT_ASSERT(i->is_note_on());
+                       CPPUNIT_ASSERT_EQUAL(i->time(), Time((num_notes + 6) * 100));
+                       ++num_notes;
+                       on = false;
+               } else {
+                       CPPUNIT_ASSERT(i->is_note_off());
+                       on = true;
+               }
+       }
+
+       CPPUNIT_ASSERT_EQUAL(size_t(6), num_notes);
+
+       // Test invalidation
+       Sequence<Time>::const_iterator i = seq->begin(Time(600));
+       std::set< boost::weak_ptr< Note<Time> > > active_notes;
+       i.invalidate(&active_notes);
+       CPPUNIT_ASSERT_EQUAL((size_t)1, active_notes.size());
+
+       // Test resuming after invalidation
+       i = seq->begin(Time(601), false, std::set<Evoral::Parameter>(), &active_notes);
+       CPPUNIT_ASSERT(i->is_note_off());
+       on = false;
+       num_notes = 1;
+       for (; i != seq->end(); ++i) {
+               if (on) {
+                       CPPUNIT_ASSERT(i->is_note_on());
+                       CPPUNIT_ASSERT_EQUAL(Time((num_notes + 6) * 100), i->time());
+                       ++num_notes;
+                       on = false;
+               } else {
+                       CPPUNIT_ASSERT(i->is_note_off());
+                       on = true;
+               }
+       }
+
+       CPPUNIT_ASSERT_EQUAL(size_t(6), num_notes);
+
+       // Test equality of copied iterators
+       i = seq->begin();
+       ++i;
+       Sequence<Time>::const_iterator j = i;
+       CPPUNIT_ASSERT(i == j);
+}
+
+void
+SequenceTest::controlInterpolationTest ()
+{
+       seq->clear();
+
+       static const uint64_t delay   = 1000;
+       static const uint32_t cc_type = 1;
+
+       boost::shared_ptr<Control> c = seq->control(Parameter(cc_type, 1, 1), true);
+       CPPUNIT_ASSERT(c);
+
+       double min = 0.0;
+       double max = 127.0;
+
+       // Make a ramp like /\ from min to max and back to min
+       c->set_double(min, 0, true);
+       c->set_double(max, delay, true);
+       c->set_double(min, 2*delay, true);
+
+       CCTestSink<Time> sink(cc_type);
+
+       // Test discrete (lack of) interpolation
+       c->list()->set_interpolation(ControlList::Discrete);
+       for (MySequence<Time>::const_iterator i = seq->begin(); i != seq->end(); ++i) {
+               sink.write(i->time(), i->event_type(), i->size(), i->buffer());
+       }
+       CPPUNIT_ASSERT_EQUAL((size_t)3, sink.events.size());
+       CPPUNIT_ASSERT_EQUAL(Time(0), sink.events[0].first);
+       CPPUNIT_ASSERT_EQUAL((uint8_t)0, sink.events[0].second);
+       CPPUNIT_ASSERT_EQUAL(Time(1000), sink.events[1].first);
+       CPPUNIT_ASSERT_EQUAL((uint8_t)127, sink.events[1].second);
+       CPPUNIT_ASSERT_EQUAL(Time(2000), sink.events[2].first);
+       CPPUNIT_ASSERT_EQUAL((uint8_t)0, sink.events[2].second);
+       sink.events.clear();
+       CPPUNIT_ASSERT_EQUAL((size_t)0, sink.events.size());
+
+       // Test linear interpolation
+       c->list()->set_interpolation(ControlList::Linear);
+       for (MySequence<Time>::const_iterator i = seq->begin(); i != seq->end(); ++i) {
+               sink.write(i->time(), i->event_type(), i->size(), i->buffer());
+       }
+       CPPUNIT_ASSERT_EQUAL((size_t)(128 * 2 - 1), sink.events.size());
+       Time    last_time(0);
+       int16_t last_value = -1;
+       bool    ascending  = true;
+       for (CCTestSink<Time>::Events::const_iterator i = sink.events.begin();
+                       i != sink.events.end(); ++i) {
+               CPPUNIT_ASSERT(last_time == 0 || i->first > last_time);
+               if (last_value == 127) {
+                       ascending = false;
+               }
+               if (ascending) {
+                       CPPUNIT_ASSERT_EQUAL((int)i->second, last_value + 1);
+               } else {
+                       CPPUNIT_ASSERT_EQUAL((int)i->second, last_value - 1);
+               }
+               last_time = i->first;
+               last_value = i->second;
+       }
+}
diff --git a/libs/evoral/test/SequenceTest.cpp b/libs/evoral/test/SequenceTest.cpp
deleted file mode 100644 (file)
index 6a5fbb6..0000000
+++ /dev/null
@@ -1,196 +0,0 @@
-#include "SequenceTest.hpp"
-#include <cassert>
-
-CPPUNIT_TEST_SUITE_REGISTRATION(SequenceTest);
-
-using namespace std;
-using namespace Evoral;
-
-void
-SequenceTest::createTest ()
-{
-       CPPUNIT_ASSERT_EQUAL(size_t(0), seq->sysexes().size());
-       CPPUNIT_ASSERT_EQUAL(size_t(0), seq->notes().size());
-       CPPUNIT_ASSERT(seq->notes().begin() == seq->notes().end());
-}
-
-void
-SequenceTest::copyTest ()
-{
-       DummyTypeMap map;
-       MySequence<Time> a(map);
-       for (Notes::const_iterator i = test_notes.begin(); i != test_notes.end(); ++i) {
-               a.notes().insert(*i);
-       }
-
-       MySequence<Time> b(a);
-       CPPUNIT_ASSERT_EQUAL(b.notes().size(), a.notes().size());
-}
-
-void
-SequenceTest::preserveEventOrderingTest ()
-{
-       vector< boost::shared_ptr< Event<Time> > > inserted_events;
-
-       seq->start_write();
-
-       for (Notes::const_iterator i = test_notes.begin(); i != test_notes.end(); ++i) {
-               uint8_t buffer[3];
-               Event<Time>* event = new Event<Time>(
-                       (Evoral::EventType)DummyTypeMap::CONTROL, (*i)->on_event().time(), 3, buffer, true
-               );
-
-               event->buffer()[0] = MIDI_CMD_CONTROL;
-               event->buffer()[1] = event->time().to_double() / 1000;
-               event->buffer()[2] = event->time().to_double() / 1000;
-
-               boost::shared_ptr<Event<Time> > event_ptr(event);
-
-               seq->append((*i)->on_event(), next_event_id ());
-               inserted_events.push_back(
-                               boost::shared_ptr<Event<Time> >(
-                                               new Event<Time>((*i)->on_event(), true)
-               ));
-
-               seq->append(*event_ptr, next_event_id ());
-               inserted_events.push_back(event_ptr);
-
-               seq->append((*i)->off_event(), next_event_id ());
-               inserted_events.push_back(
-                               boost::shared_ptr<Event<Time> >(
-                                               new Event<Time>((*i)->off_event(), true)
-               ));
-       }
-
-       seq->end_write (Sequence<Time>::Relax);
-
-       TestSink<Time> sink;
-       sink.writing.connect(sigc::mem_fun(&sink, &TestSink<Time>::assertLastEventTimeEarlier));
-
-
-       for (MySequence<Time>::const_iterator i = seq->begin(); i != seq->end(); ++i) {
-               sink.write(i->time(), i->event_type(), i->size(), i->buffer());
-       }
-
-       CPPUNIT_ASSERT_EQUAL(size_t(12), test_notes.size());
-}
-
-void
-SequenceTest::iteratorSeekTest ()
-{
-       size_t num_notes = 0;
-
-       seq->clear();
-
-       for (Notes::const_iterator i = test_notes.begin(); i != test_notes.end(); ++i) {
-               seq->notes().insert(*i);
-       }
-
-       // Iterate over all notes
-       bool on = true;
-       for (Sequence<Time>::const_iterator i = seq->begin(Time(600)); i != seq->end(); ++i) {
-               if (on) {
-                       CPPUNIT_ASSERT(i->is_note_on());
-                       CPPUNIT_ASSERT_EQUAL(i->time(), Time((num_notes + 6) * 100));
-                       ++num_notes;
-                       on = false;
-               } else {
-                       CPPUNIT_ASSERT(i->is_note_off());
-                       on = true;
-               }
-       }
-
-       CPPUNIT_ASSERT_EQUAL(size_t(6), num_notes);
-
-       // Test invalidation
-       Sequence<Time>::const_iterator i = seq->begin(Time(600));
-       std::set< boost::weak_ptr< Note<Time> > > active_notes;
-       i.invalidate(&active_notes);
-       CPPUNIT_ASSERT_EQUAL((size_t)1, active_notes.size());
-
-       // Test resuming after invalidation
-       i = seq->begin(Time(601), false, std::set<Evoral::Parameter>(), &active_notes);
-       CPPUNIT_ASSERT(i->is_note_off());
-       on = false;
-       num_notes = 1;
-       for (; i != seq->end(); ++i) {
-               if (on) {
-                       CPPUNIT_ASSERT(i->is_note_on());
-                       CPPUNIT_ASSERT_EQUAL(Time((num_notes + 6) * 100), i->time());
-                       ++num_notes;
-                       on = false;
-               } else {
-                       CPPUNIT_ASSERT(i->is_note_off());
-                       on = true;
-               }
-       }
-
-       CPPUNIT_ASSERT_EQUAL(size_t(6), num_notes);
-
-       // Test equality of copied iterators
-       i = seq->begin();
-       ++i;
-       Sequence<Time>::const_iterator j = i;
-       CPPUNIT_ASSERT(i == j);
-}
-
-void
-SequenceTest::controlInterpolationTest ()
-{
-       seq->clear();
-
-       static const uint64_t delay   = 1000;
-       static const uint32_t cc_type = 1;
-
-       boost::shared_ptr<Control> c = seq->control(Parameter(cc_type, 1, 1), true);
-       CPPUNIT_ASSERT(c);
-
-       double min = 0.0;
-       double max = 127.0;
-
-       // Make a ramp like /\ from min to max and back to min
-       c->set_double(min, 0, true);
-       c->set_double(max, delay, true);
-       c->set_double(min, 2*delay, true);
-
-       CCTestSink<Time> sink(cc_type);
-
-       // Test discrete (lack of) interpolation
-       c->list()->set_interpolation(ControlList::Discrete);
-       for (MySequence<Time>::const_iterator i = seq->begin(); i != seq->end(); ++i) {
-               sink.write(i->time(), i->event_type(), i->size(), i->buffer());
-       }
-       CPPUNIT_ASSERT_EQUAL((size_t)3, sink.events.size());
-       CPPUNIT_ASSERT_EQUAL(Time(0), sink.events[0].first);
-       CPPUNIT_ASSERT_EQUAL((uint8_t)0, sink.events[0].second);
-       CPPUNIT_ASSERT_EQUAL(Time(1000), sink.events[1].first);
-       CPPUNIT_ASSERT_EQUAL((uint8_t)127, sink.events[1].second);
-       CPPUNIT_ASSERT_EQUAL(Time(2000), sink.events[2].first);
-       CPPUNIT_ASSERT_EQUAL((uint8_t)0, sink.events[2].second);
-       sink.events.clear();
-       CPPUNIT_ASSERT_EQUAL((size_t)0, sink.events.size());
-
-       // Test linear interpolation
-       c->list()->set_interpolation(ControlList::Linear);
-       for (MySequence<Time>::const_iterator i = seq->begin(); i != seq->end(); ++i) {
-               sink.write(i->time(), i->event_type(), i->size(), i->buffer());
-       }
-       CPPUNIT_ASSERT_EQUAL((size_t)(128 * 2 - 1), sink.events.size());
-       Time    last_time(0);
-       int16_t last_value = -1;
-       bool    ascending  = true;
-       for (CCTestSink<Time>::Events::const_iterator i = sink.events.begin();
-                       i != sink.events.end(); ++i) {
-               CPPUNIT_ASSERT(last_time == 0 || i->first > last_time);
-               if (last_value == 127) {
-                       ascending = false;
-               }
-               if (ascending) {
-                       CPPUNIT_ASSERT_EQUAL((int)i->second, last_value + 1);
-               } else {
-                       CPPUNIT_ASSERT_EQUAL((int)i->second, last_value - 1);
-               }
-               last_time = i->first;
-               last_value = i->second;
-       }
-}
diff --git a/libs/evoral/test/SequenceTest.h b/libs/evoral/test/SequenceTest.h
new file mode 100644 (file)
index 0000000..b40cf15
--- /dev/null
@@ -0,0 +1,160 @@
+#include <cassert>
+#include <sigc++/sigc++.h>
+#include <cppunit/TestFixture.h>
+#include <cppunit/extensions/HelperMacros.h>
+#include "temporal/beats.h"
+#include "evoral/Sequence.h"
+#include "evoral/TypeMap.h"
+#include "evoral/EventSink.h"
+#include "evoral/midi_events.h"
+#include "evoral/Control.h"
+
+using namespace Evoral;
+
+class DummyTypeMap : public TypeMap {
+public:
+       enum DummyEventType {
+               NOTE,
+               CONTROL,
+               SYSEX
+       };
+
+       ~DummyTypeMap() {}
+
+       bool type_is_midi (uint32_t /*type*/) const { return true; }
+
+       uint8_t parameter_midi_type(const Parameter& param) const {
+               switch (param.type()) {
+               case CONTROL:       return MIDI_CMD_CONTROL;
+               case SYSEX:         return MIDI_CMD_COMMON_SYSEX;
+               default:            return 0;
+               };
+       }
+
+       virtual ParameterType midi_parameter_type(const uint8_t* buf, uint32_t len) const {
+               switch (buf[0] & 0xF0) {
+               case MIDI_CMD_CONTROL:      return CONTROL;
+               case MIDI_CMD_COMMON_SYSEX: return SYSEX;
+               case MIDI_CMD_NOTE_ON:      return NOTE;
+               case MIDI_CMD_NOTE_OFF:     return NOTE;
+               default: return 0;
+               }
+       }
+
+       ParameterDescriptor descriptor(const Parameter& param) const {
+               Evoral::ParameterDescriptor desc;
+               desc.upper = 127;
+               desc.rangesteps = 128;
+               return desc;
+       }
+
+       std::string to_symbol(const Parameter& /*param*/) const { return "control"; }
+};
+
+template<typename Time>
+class MySequence : public Sequence<Time> {
+public:
+       MySequence(DummyTypeMap&map) : Sequence<Time>(map) {}
+       MySequence(const MySequence& copy) : ControlSet(copy), Sequence<Time>(copy) {}
+
+       virtual bool find_next_event(double start, double end, ControlEvent& ev, bool only_active) const { return false; }
+
+       boost::shared_ptr<Control> control_factory(const Parameter& param) {
+               Evoral::ParameterDescriptor desc;
+               desc.upper = 127;
+               desc.rangesteps = 128;
+               boost::shared_ptr<ControlList> list(new ControlList(param, desc));
+               return boost::shared_ptr<Control>(new Control(param, desc, list));
+       }
+};
+
+template<typename Time>
+class TestSink : public EventSink<Time> {
+public:
+       TestSink() : _last_event_time(-1) {}
+
+       /// return value, time, type, size, buffer
+       sigc::signal<uint32_t, Time, EventType, uint32_t, const uint8_t*> writing;
+
+       virtual uint32_t write(Time time, EventType type, uint32_t size, const uint8_t* buf) {
+               //std::cerr << "last event time: " << _last_event_time << " time: " << time << std::endl;
+               uint32_t result = writing(time, type, size, buf);
+               _last_event_time = time;
+               return result;
+       }
+
+       uint32_t assertLastEventTimeEarlier(
+                       Time time, EventType /*type*/, uint32_t /*size*/, const uint8_t* /*buf*/) {
+               CPPUNIT_ASSERT(_last_event_time <= time);
+               return 0;
+       }
+
+       Time last_event_time() const { return _last_event_time; }
+
+private:
+       Time _last_event_time;
+};
+
+template<typename Time>
+class CCTestSink : public EventSink<Time> {
+public:
+       CCTestSink(uint32_t t) : cc_type(t) {}
+
+       virtual uint32_t write(Time time, EventType type, uint32_t size, const uint8_t* buf) {
+               if (type == cc_type) {
+                       CPPUNIT_ASSERT_EQUAL((uint32_t)3, size);
+                       events.push_back(std::make_pair(time, buf[2]));
+               }
+               return size;
+       }
+
+       typedef std::vector< std::pair<Time, uint8_t> > Events;
+       Events events;
+       uint32_t cc_type;
+};
+
+class SequenceTest : public CppUnit::TestFixture
+{
+       CPPUNIT_TEST_SUITE (SequenceTest);
+       CPPUNIT_TEST (createTest);
+       CPPUNIT_TEST (copyTest);
+       CPPUNIT_TEST (preserveEventOrderingTest);
+       CPPUNIT_TEST (iteratorSeekTest);
+       CPPUNIT_TEST (controlInterpolationTest);
+       CPPUNIT_TEST_SUITE_END ();
+
+public:
+       typedef Temporal::Beats Time;
+       typedef std::vector< boost::shared_ptr< Note<Time> > > Notes;
+
+       void setUp () {
+               type_map = new DummyTypeMap();
+               assert(type_map);
+               seq = new MySequence<Time>(*type_map);
+               assert(seq);
+
+               for (int i = 0; i < 12; i++) {
+                       test_notes.push_back(
+                               boost::shared_ptr<Note<Time> >(
+                                       new Note<Time>(0, Time(i * 100), Time(100), 64 + i, 64)));
+               }
+       }
+
+       void tearDown () {
+               test_notes.clear();
+               delete seq;
+               delete type_map;
+       }
+
+       void createTest ();
+       void copyTest ();
+       void preserveEventOrderingTest ();
+       void iteratorSeekTest ();
+       void controlInterpolationTest ();
+
+private:
+       DummyTypeMap*       type_map;
+       MySequence<Time>*   seq;
+
+       Notes test_notes;
+};
diff --git a/libs/evoral/test/SequenceTest.hpp b/libs/evoral/test/SequenceTest.hpp
deleted file mode 100644 (file)
index a337b47..0000000
+++ /dev/null
@@ -1,160 +0,0 @@
-#include <cassert>
-#include <sigc++/sigc++.h>
-#include <cppunit/TestFixture.h>
-#include <cppunit/extensions/HelperMacros.h>
-#include "temporal/beats.h"
-#include "evoral/Sequence.hpp"
-#include "evoral/TypeMap.hpp"
-#include "evoral/EventSink.hpp"
-#include "evoral/midi_events.h"
-#include "evoral/Control.hpp"
-
-using namespace Evoral;
-
-class DummyTypeMap : public TypeMap {
-public:
-       enum DummyEventType {
-               NOTE,
-               CONTROL,
-               SYSEX
-       };
-
-       ~DummyTypeMap() {}
-
-       bool type_is_midi (uint32_t /*type*/) const { return true; }
-
-       uint8_t parameter_midi_type(const Parameter& param) const {
-               switch (param.type()) {
-               case CONTROL:       return MIDI_CMD_CONTROL;
-               case SYSEX:         return MIDI_CMD_COMMON_SYSEX;
-               default:            return 0;
-               };
-       }
-
-       virtual ParameterType midi_parameter_type(const uint8_t* buf, uint32_t len) const {
-               switch (buf[0] & 0xF0) {
-               case MIDI_CMD_CONTROL:      return CONTROL;
-               case MIDI_CMD_COMMON_SYSEX: return SYSEX;
-               case MIDI_CMD_NOTE_ON:      return NOTE;
-               case MIDI_CMD_NOTE_OFF:     return NOTE;
-               default: return 0;
-               }
-       }
-
-       ParameterDescriptor descriptor(const Parameter& param) const {
-               Evoral::ParameterDescriptor desc;
-               desc.upper = 127;
-               desc.rangesteps = 128;
-               return desc;
-       }
-
-       std::string to_symbol(const Parameter& /*param*/) const { return "control"; }
-};
-
-template<typename Time>
-class MySequence : public Sequence<Time> {
-public:
-       MySequence(DummyTypeMap&map) : Sequence<Time>(map) {}
-       MySequence(const MySequence& copy) : ControlSet(copy), Sequence<Time>(copy) {}
-
-       virtual bool find_next_event(double start, double end, ControlEvent& ev, bool only_active) const { return false; }
-
-       boost::shared_ptr<Control> control_factory(const Parameter& param) {
-               Evoral::ParameterDescriptor desc;
-               desc.upper = 127;
-               desc.rangesteps = 128;
-               boost::shared_ptr<ControlList> list(new ControlList(param, desc));
-               return boost::shared_ptr<Control>(new Control(param, desc, list));
-       }
-};
-
-template<typename Time>
-class TestSink : public EventSink<Time> {
-public:
-       TestSink() : _last_event_time(-1) {}
-
-       /// return value, time, type, size, buffer
-       sigc::signal<uint32_t, Time, EventType, uint32_t, const uint8_t*> writing;
-
-       virtual uint32_t write(Time time, EventType type, uint32_t size, const uint8_t* buf) {
-               //std::cerr << "last event time: " << _last_event_time << " time: " << time << std::endl;
-               uint32_t result = writing(time, type, size, buf);
-               _last_event_time = time;
-               return result;
-       }
-
-       uint32_t assertLastEventTimeEarlier(
-                       Time time, EventType /*type*/, uint32_t /*size*/, const uint8_t* /*buf*/) {
-               CPPUNIT_ASSERT(_last_event_time <= time);
-               return 0;
-       }
-
-       Time last_event_time() const { return _last_event_time; }
-
-private:
-       Time _last_event_time;
-};
-
-template<typename Time>
-class CCTestSink : public EventSink<Time> {
-public:
-       CCTestSink(uint32_t t) : cc_type(t) {}
-
-       virtual uint32_t write(Time time, EventType type, uint32_t size, const uint8_t* buf) {
-               if (type == cc_type) {
-                       CPPUNIT_ASSERT_EQUAL((uint32_t)3, size);
-                       events.push_back(std::make_pair(time, buf[2]));
-               }
-               return size;
-       }
-
-       typedef std::vector< std::pair<Time, uint8_t> > Events;
-       Events events;
-       uint32_t cc_type;
-};
-
-class SequenceTest : public CppUnit::TestFixture
-{
-       CPPUNIT_TEST_SUITE (SequenceTest);
-       CPPUNIT_TEST (createTest);
-       CPPUNIT_TEST (copyTest);
-       CPPUNIT_TEST (preserveEventOrderingTest);
-       CPPUNIT_TEST (iteratorSeekTest);
-       CPPUNIT_TEST (controlInterpolationTest);
-       CPPUNIT_TEST_SUITE_END ();
-
-public:
-       typedef Temporal::Beats Time;
-       typedef std::vector< boost::shared_ptr< Note<Time> > > Notes;
-
-       void setUp () {
-               type_map = new DummyTypeMap();
-               assert(type_map);
-               seq = new MySequence<Time>(*type_map);
-               assert(seq);
-
-               for (int i = 0; i < 12; i++) {
-                       test_notes.push_back(
-                               boost::shared_ptr<Note<Time> >(
-                                       new Note<Time>(0, Time(i * 100), Time(100), 64 + i, 64)));
-               }
-       }
-
-       void tearDown () {
-               test_notes.clear();
-               delete seq;
-               delete type_map;
-       }
-
-       void createTest ();
-       void copyTest ();
-       void preserveEventOrderingTest ();
-       void iteratorSeekTest ();
-       void controlInterpolationTest ();
-
-private:
-       DummyTypeMap*       type_map;
-       MySequence<Time>*   seq;
-
-       Notes test_notes;
-};
diff --git a/libs/evoral/test/testrunner.cc b/libs/evoral/test/testrunner.cc
new file mode 100644 (file)
index 0000000..12f2c9a
--- /dev/null
@@ -0,0 +1,31 @@
+#include <cppunit/CompilerOutputter.h>
+#include <cppunit/extensions/TestFactoryRegistry.h>
+#include <cppunit/TestResult.h>
+#include <cppunit/TestResultCollector.h>
+#include <cppunit/TestRunner.h>
+#include <cppunit/BriefTestProgressListener.h>
+
+#include "pbd/pbd.h"
+
+int
+main()
+{
+       if (!PBD::init ()) return 1;
+
+       CppUnit::TestResult testresult;
+
+       CppUnit::TestResultCollector collectedresults;
+       testresult.addListener (&collectedresults);
+
+       CppUnit::BriefTestProgressListener progress;
+       testresult.addListener (&progress);
+
+       CppUnit::TestRunner testrunner;
+       testrunner.addTest (CppUnit::TestFactoryRegistry::getRegistry ().makeTest ());
+       testrunner.run (testresult);
+
+       CppUnit::CompilerOutputter compileroutputter (&collectedresults, std::cerr);
+       compileroutputter.write ();
+
+       return collectedresults.wasSuccessful () ? 0 : 1;
+}
diff --git a/libs/evoral/test/testrunner.cpp b/libs/evoral/test/testrunner.cpp
deleted file mode 100644 (file)
index 12f2c9a..0000000
+++ /dev/null
@@ -1,31 +0,0 @@
-#include <cppunit/CompilerOutputter.h>
-#include <cppunit/extensions/TestFactoryRegistry.h>
-#include <cppunit/TestResult.h>
-#include <cppunit/TestResultCollector.h>
-#include <cppunit/TestRunner.h>
-#include <cppunit/BriefTestProgressListener.h>
-
-#include "pbd/pbd.h"
-
-int
-main()
-{
-       if (!PBD::init ()) return 1;
-
-       CppUnit::TestResult testresult;
-
-       CppUnit::TestResultCollector collectedresults;
-       testresult.addListener (&collectedresults);
-
-       CppUnit::BriefTestProgressListener progress;
-       testresult.addListener (&progress);
-
-       CppUnit::TestRunner testrunner;
-       testrunner.addTest (CppUnit::TestFactoryRegistry::getRegistry ().makeTest ());
-       testrunner.run (testresult);
-
-       CppUnit::CompilerOutputter compileroutputter (&collectedresults, std::cerr);
-       compileroutputter.write ();
-
-       return collectedresults.wasSuccessful () ? 0 : 1;
-}
index 5cc7957e316042ccb73910821c216e17b83bbf5c..fba2667d619e22f3cb07910bf3ca8a7ad8b2b8c0 100644 (file)
@@ -61,7 +61,7 @@ def configure(conf):
 def build(bld):
     # Headers
     #bld.install_files('${INCLUDEDIR}/evoral', 'evoral/*.h')
-    #bld.install_files('${INCLUDEDIR}/evoral', 'evoral/*.hpp')
+    #bld.install_files('${INCLUDEDIR}/evoral', 'evoral/*.h')
 
     # Pkgconfig file
     #autowaf.build_pc(bld, 'EVORAL', EVORAL_VERSION, 'GLIBMM GTHREAD')
@@ -87,16 +87,16 @@ def build(bld):
         libsmf.cflags       = [ bld.env['compiler_flags_dict']['pic'] ]
 
     lib_source = '''
-            src/Control.cpp
-            src/ControlList.cpp
-            src/ControlSet.cpp
-            src/Curve.cpp
-            src/Event.cpp
-            src/Note.cpp
-            src/SMF.cpp
-            src/Sequence.cpp
-            src/TimeConverter.cpp
-            src/debug.cpp
+            src/Control.cc
+            src/ControlList.cc
+            src/ControlSet.cc
+            src/Curve.cc
+            src/Event.cc
+            src/Note.cc
+            src/SMF.cc
+            src/Sequence.cc
+            src/TimeConverter.cc
+            src/debug.cc
     '''
 
     # Library
@@ -141,12 +141,12 @@ def build(bld):
         # Unit tests
         obj              = bld(features = 'cxx cxxprogram')
         obj.source       = '''
-                test/SequenceTest.cpp
-                test/SMFTest.cpp
-                test/RangeTest.cpp
-                test/NoteTest.cpp
-                test/CurveTest.cpp
-                test/testrunner.cpp
+                test/SequenceTest.cc
+                test/SMFTest.cc
+                test/RangeTest.cc
+                test/NoteTest.cc
+                test/CurveTest.cc
+                test/testrunner.cc
         '''
         obj.includes     = ['.', './src']
         obj.use          = 'libevoral_static'
index 5ff3ad59a9658b40d79e66e8f3b6b4458241d787..bd1af668c36a4be8ea97b53941e72c498a794f1b 100644 (file)
@@ -38,6 +38,6 @@
  */
 #define EVORAL_EVENT_ALLOC 1
 
-#include "evoral/Event.hpp"
+#include "evoral/Event.h"
 
 #endif /* __libmidipp_midi_event_h__ */
index de7621ddb37a5a5b1757a07813afa4f3bc6372b2..0c5e1b99d1526d35f427d0a7b3fd47d347c64718 100644 (file)
@@ -39,7 +39,7 @@
 #include "pbd/xml++.h"
 #include "pbd/enumwriter.h"
 
-#include "evoral/Curve.hpp"
+#include "evoral/Curve.h"
 
 #include "ardour/session.h"
 #include "ardour/panner.h"
index bf0c1966a3d62fe084e8077b0e1bea1a0cea8304..de1833bd77b41c5aa21ed2040f05e75690970429 100644 (file)
@@ -38,7 +38,7 @@
 #include "pbd/xml++.h"
 #include "pbd/enumwriter.h"
 
-#include "evoral/Curve.hpp"
+#include "evoral/Curve.h"
 
 #include "ardour/audio_buffer.h"
 #include "ardour/audio_buffer.h"
index 1ffe58ef66899bdc6b60ba1a0e9e97e7ddde0816..2687646bfc17b8ab50e0287fa050e5722da90d6e 100644 (file)
@@ -37,7 +37,7 @@
 #include "pbd/xml++.h"
 #include "pbd/enumwriter.h"
 
-#include "evoral/Curve.hpp"
+#include "evoral/Curve.h"
 
 #include "ardour/audio_buffer.h"
 #include "ardour/audio_buffer.h"
index a752db107cc74cef7b7f05c1b3714e231bdfa172..e1dc344b6c77cde60ba737176c89fa5d143574c4 100644 (file)
@@ -23,7 +23,7 @@
 #include <string>
 #include <iostream>
 
-#include "evoral/Parameter.hpp"
+#include "evoral/Parameter.h"
 
 #include "pbd/property_basics.h"
 #include "pbd/ringbuffer.h"
index 4ee3f55ac0af91802dee6ba71cac87b4964559ca..7c4b0d98fc7840cfc8e65eb63e0da52a7ae02e79 100644 (file)
@@ -22,7 +22,7 @@
 #include <string>
 #include <iostream>
 
-#include "evoral/Parameter.hpp"
+#include "evoral/Parameter.h"
 
 #include "pbd/property_basics.h"
 #include "pbd/ringbuffer.h"
index 71f84774f99ed5d622a187ecba5303d9c66ad3b2..c96905a47232b3b291aba3809189919ccafaa41f 100644 (file)
@@ -39,8 +39,8 @@
 #include "ardour/source_factory.h"
 #include "ardour/tempo.h"
 
-#include "evoral/Note.hpp"
-#include "evoral/Sequence.hpp"
+#include "evoral/Note.h"
+#include "evoral/Sequence.h"
 
 #include "common.h"