summaryrefslogtreecommitdiff
path: root/libs/evoral
diff options
context:
space:
mode:
authorDavid Robillard <d@drobilla.net>2008-09-19 00:47:49 +0000
committerDavid Robillard <d@drobilla.net>2008-09-19 00:47:49 +0000
commitd357eca668044badcb4bab318e2e74cfffa9a0b0 (patch)
treeeab9bf33b194f9e37c20f84375e5caa748ee994a /libs/evoral
parent3d976c5b727e4d55ce439b1d7c055a814477fa1a (diff)
Factor out sequencing related things into an independant new library: "evoral".
Anything related to the storage of events/values over a range of time lives in evoral. This includes MidiModel (Evoral::Sequence) and automation data (AutomationList (Evoral::ControlList), Automatable (Evoral::ControlSet), etc). libs/evoral synced with http://svn.drobilla.net/lad/trunk/evoral r1511. git-svn-id: svn://localhost/ardour2/branches/3.0@3754 d708f5d6-7413-0410-9779-e7cbd77b26cf
Diffstat (limited to 'libs/evoral')
-rw-r--r--libs/evoral/SConscript42
-rw-r--r--libs/evoral/evoral/Control.hpp58
-rw-r--r--libs/evoral/evoral/ControlList.hpp274
-rw-r--r--libs/evoral/evoral/ControlSet.hpp69
-rw-r--r--libs/evoral/evoral/Curve.hpp58
-rw-r--r--libs/evoral/evoral/Event.hpp219
-rw-r--r--libs/evoral/evoral/EventSink.hpp40
-rw-r--r--libs/evoral/evoral/MIDIParameters.hpp52
-rw-r--r--libs/evoral/evoral/Note.hpp79
-rw-r--r--libs/evoral/evoral/Parameter.hpp135
-rw-r--r--libs/evoral/evoral/Sequence.hpp215
-rw-r--r--libs/evoral/evoral/midi_events.h133
-rw-r--r--libs/evoral/evoral/types.hpp31
-rw-r--r--libs/evoral/src/Control.cpp82
-rw-r--r--libs/evoral/src/ControlList.cpp1310
-rw-r--r--libs/evoral/src/ControlSet.cpp139
-rw-r--r--libs/evoral/src/Curve.cpp401
-rw-r--r--libs/evoral/src/Event.cpp107
-rw-r--r--libs/evoral/src/Note.cpp103
-rw-r--r--libs/evoral/src/Sequence.cpp643
-rw-r--r--libs/evoral/test/sequence.cpp12
21 files changed, 4202 insertions, 0 deletions
diff --git a/libs/evoral/SConscript b/libs/evoral/SConscript
new file mode 100644
index 0000000000..fcc29c9b56
--- /dev/null
+++ b/libs/evoral/SConscript
@@ -0,0 +1,42 @@
+# -*- python -*-
+
+import os
+import os.path
+import glob
+
+Import('env libraries install_prefix')
+
+evoral = env.Clone()
+evoral.Merge([
+ libraries['glibmm2'],
+ libraries['xml'],
+ libraries['pbd'],
+ ])
+
+if evoral['IS_OSX']:
+ evoral.Append (LINKFLAGS="-Xlinker -headerpad -Xlinker 2048")
+
+domain = 'evoral'
+
+evoral.Append(DOMAIN=domain, MAJOR=1, MINOR=0, MICRO=0)
+evoral.Append(CXXFLAGS="-DEVENT_WITH_XML")
+
+sources = Split("""
+src/Control.cpp
+src/ControlList.cpp
+src/ControlSet.cpp
+src/Event.cpp
+src/Note.cpp
+src/Sequence.cpp
+src/Curve.cpp
+""")
+
+libevoral = evoral.SharedLibrary('evoral', [ sources ])
+
+Default(libevoral)
+
+env.Alias('install', env.Install(os.path.join(install_prefix, env['LIBDIR'], 'ardour3'), libevoral))
+
+env.Alias('tarball', env.Distribute (env['DISTTREE'],
+ [ 'SConscript' ] + sources +
+ glob.glob('midi++/*.h')))
diff --git a/libs/evoral/evoral/Control.hpp b/libs/evoral/evoral/Control.hpp
new file mode 100644
index 0000000000..ed957522a1
--- /dev/null
+++ b/libs/evoral/evoral/Control.hpp
@@ -0,0 +1,58 @@
+/* This file is part of Evoral.
+ * Copyright (C) 2008 Dave Robillard <http://drobilla.net>
+ * Copyright (C) 2000-2008 Paul Davis
+ *
+ * Evoral 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.
+ *
+ * Evoral 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 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 St, 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 <glibmm/thread.h>
+#include <evoral/types.hpp>
+#include <evoral/Parameter.hpp>
+
+namespace Evoral {
+
+class ControlList;
+class Transport;
+
+class Control
+{
+public:
+ Control(boost::shared_ptr<ControlList>);
+ virtual ~Control() {}
+
+ void set_value(float val, bool to_list=false, nframes_t frame=0);
+ float get_value(bool from_list=false, nframes_t frame=0) const;
+ float user_value() const;
+
+ void set_list(boost::shared_ptr<ControlList>);
+
+ boost::shared_ptr<ControlList> list() { return _list; }
+ boost::shared_ptr<const ControlList> list() const { return _list; }
+
+ Parameter parameter() const;
+
+protected:
+ boost::shared_ptr<ControlList> _list;
+ float _user_value;
+};
+
+} // namespace Evoral
+
+#endif // EVORAL_CONTROL_HPP
diff --git a/libs/evoral/evoral/ControlList.hpp b/libs/evoral/evoral/ControlList.hpp
new file mode 100644
index 0000000000..c035b6ddcf
--- /dev/null
+++ b/libs/evoral/evoral/ControlList.hpp
@@ -0,0 +1,274 @@
+/* This file is part of Evoral.
+ * Copyright (C) 2008 Dave Robillard <http://drobilla.net>
+ * Copyright (C) 2000-2008 Paul Davis
+ *
+ * Evoral 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.
+ *
+ * Evoral 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 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 St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#ifndef EVORAL_CONTROL_LIST_HPP
+#define EVORAL_CONTROL_LIST_HPP
+
+#include <list>
+#include <boost/pool/pool.hpp>
+#include <boost/pool/pool_alloc.hpp>
+#include <glibmm/thread.h>
+#include <evoral/types.hpp>
+#include <evoral/Parameter.hpp>
+#include <evoral/Curve.hpp>
+
+namespace Evoral {
+
+
+/** A single event (time-stamped value) for a control
+ */
+struct ControlEvent {
+ 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
+};
+
+
+/** Pool allocator for control lists that does not use a lock
+ * and allocates 8k blocks of new pointers at a time
+ */
+typedef boost::fast_pool_allocator<
+ ControlEvent*,
+ boost::default_user_allocator_new_delete,
+ boost::details::pool::null_mutex,
+ 8192>
+ ControlEventAllocator;
+
+
+/** A list (sequence) of time-stamped values for a control
+ */
+class ControlList
+{
+public:
+ typedef std::list<ControlEvent*,ControlEventAllocator> EventList;
+ typedef EventList::iterator iterator;
+ typedef EventList::reverse_iterator reverse_iterator;
+ typedef EventList::const_iterator const_iterator;
+
+ ControlList (Parameter id);
+ //ControlList (const XMLNode&, Parameter id);
+ ~ControlList();
+
+ virtual boost::shared_ptr<ControlList> create(Parameter id);
+
+ ControlList (const ControlList&);
+ ControlList (const ControlList&, double start, double end);
+ ControlList& operator= (const ControlList&);
+ bool operator== (const ControlList&);
+
+ void freeze();
+ void thaw ();
+
+ const Parameter& parameter() const { return _parameter; }
+ void set_parameter(Parameter p) { _parameter = p; }
+
+ EventList::size_type size() const { return _events.size(); }
+ bool empty() const { return _events.empty(); }
+
+ void reset_default (double val) {
+ _default_value = val;
+ }
+
+ void clear ();
+ void x_scale (double factor);
+ bool extend_to (double);
+ void slide (iterator before, double distance);
+
+ void reposition_for_rt_add (double when);
+ void rt_add (double when, double value);
+ void add (double when, double value);
+ void fast_simple_add (double when, double value);
+
+ void reset_range (double start, double end);
+ void erase_range (double start, double end);
+ void erase (iterator);
+ void erase (iterator, iterator);
+ void move_range (iterator start, iterator end, double, double);
+ void modify (iterator, double, double);
+
+ boost::shared_ptr<ControlList> cut (double, double);
+ boost::shared_ptr<ControlList> copy (double, double);
+ void clear (double, double);
+
+ boost::shared_ptr<ControlList> cut (iterator, iterator);
+ boost::shared_ptr<ControlList> copy (iterator, iterator);
+ void clear (iterator, iterator);
+
+ bool paste (ControlList&, double position, float times);
+
+ void set_yrange (double min, double max) {
+ _min_yval = min;
+ _max_yval = max;
+ }
+
+ double get_max_y() const { return _max_yval; }
+ double get_min_y() const { return _min_yval; }
+
+ void truncate_end (double length);
+ void truncate_start (double 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(); }
+ 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::Mutex::Lock lm (_lock);
+ (obj.*method)(*this);
+ }
+
+ void set_max_xval (double);
+ double get_max_xval() const { return _max_xval; }
+
+ double eval (double where) {
+ Glib::Mutex::Lock lm (_lock);
+ return unlocked_eval (where);
+ }
+
+ double rt_safe_eval (double where, bool& ok) {
+
+ Glib::Mutex::Lock lm (_lock, Glib::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 between left and right */
+ struct SearchCache {
+ SearchCache() : left(-1), right(-1) {}
+ double left; /* leftmost x coordinate used when finding "range" */
+ double right; /* rightmost x coordinate used when finding "range" */
+ std::pair<ControlList::const_iterator,ControlList::const_iterator> range;
+ };
+
+ const EventList& events() const { return _events; }
+ double default_value() const { return _parameter.normal(); }
+
+ // FIXME: const violations for Curve
+ Glib::Mutex& 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 end, double& x, double& y, bool start_inclusive=false) const;
+ bool rt_safe_earliest_event_unlocked (double start, double end, double& x, double& y, bool start_inclusive=false) const;
+
+ Curve& curve() { return *_curve; }
+ const Curve& curve() const { return *_curve; }
+
+ virtual void mark_dirty () const;
+
+ enum InterpolationStyle {
+ Discrete,
+ Linear,
+ Curved
+ };
+
+ InterpolationStyle interpolation() const { return _interpolation; }
+ void set_interpolation(InterpolationStyle style) { _interpolation = style; }
+
+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, double end) const;
+
+ bool rt_safe_earliest_event_discrete_unlocked (double start, double end, double& x, double& y, bool inclusive) const;
+ bool rt_safe_earliest_event_linear_unlocked (double start, double end, double& x, double& y, bool inclusive) const;
+
+ boost::shared_ptr<ControlList> cut_copy_clear (double, double, int op);
+
+ virtual void maybe_signal_changed ();
+
+ void _x_scale (double factor);
+
+ mutable LookupCache _lookup_cache;
+ mutable SearchCache _search_cache;
+
+ Parameter _parameter;
+ InterpolationStyle _interpolation;
+ EventList _events;
+ mutable Glib::Mutex _lock;
+ int8_t _frozen;
+ bool _changed_when_thawed;
+ bool _new_value;
+ double _max_xval;
+ double _min_yval;
+ double _max_yval;
+ double _default_value;
+ bool _sort_pending;
+ iterator _rt_insertion_point;
+ double _rt_pos;
+
+ Curve* _curve;
+};
+
+} // namespace Evoral
+
+#endif // EVORAL_CONTROL_LIST_HPP
+
diff --git a/libs/evoral/evoral/ControlSet.hpp b/libs/evoral/evoral/ControlSet.hpp
new file mode 100644
index 0000000000..ba6e5e5623
--- /dev/null
+++ b/libs/evoral/evoral/ControlSet.hpp
@@ -0,0 +1,69 @@
+/* This file is part of Evoral.
+ * Copyright (C) 2008 Dave Robillard <http://drobilla.net>
+ * Copyright (C) 2000-2008 Paul Davis
+ *
+ * Evoral 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.
+ *
+ * Evoral 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 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 St, 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 <glibmm/thread.h>
+#include <evoral/types.hpp>
+#include <evoral/Parameter.hpp>
+
+namespace Evoral {
+
+class Control;
+class ControlList;
+class ControlEvent;
+
+class ControlSet {
+public:
+ ControlSet();
+ virtual ~ControlSet() {}
+
+ virtual boost::shared_ptr<Control> control(Evoral::Parameter id, bool create_if_missing=false);
+ virtual boost::shared_ptr<const Control> control(Evoral::Parameter id) const;
+
+ virtual boost::shared_ptr<Control> control_factory(boost::shared_ptr<ControlList> list) const;
+ virtual boost::shared_ptr<ControlList> control_list_factory(const Parameter& param) const;
+
+ typedef std::map< Parameter, boost::shared_ptr<Control> > Controls;
+ Controls& controls() { return _controls; }
+ const Controls& controls() const { return _controls; }
+
+ virtual void add_control(boost::shared_ptr<Control>);
+
+ virtual bool find_next_event(nframes_t start, nframes_t end, ControlEvent& ev) const;
+
+ virtual float default_parameter_value(Parameter param) { return 1.0f; }
+
+ virtual void clear();
+
+ void what_has_data(std::set<Parameter>&) const;
+
+ Glib::Mutex& control_lock() const { return _control_lock; }
+
+protected:
+ mutable Glib::Mutex _control_lock;
+ Controls _controls;
+};
+
+} // namespace Evoral
+
+#endif // EVORAL_CONTROLLABLE_HPP
diff --git a/libs/evoral/evoral/Curve.hpp b/libs/evoral/evoral/Curve.hpp
new file mode 100644
index 0000000000..7cfc6bcb69
--- /dev/null
+++ b/libs/evoral/evoral/Curve.hpp
@@ -0,0 +1,58 @@
+/* This file is part of Evoral.
+ * Copyright (C) 2008 Dave Robillard <http://drobilla.net>
+ * Copyright (C) 2000-2008 Paul Davis
+ *
+ * Evoral 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.
+ *
+ * Evoral 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 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 St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#ifndef EVORAL_CURVE_HPP
+#define EVORAL_CURVE_HPP
+
+#include <inttypes.h>
+#include <boost/utility.hpp>
+
+namespace Evoral {
+
+class ControlList;
+
+class Curve : public boost::noncopyable
+{
+public:
+ Curve (const ControlList& cl);
+
+ bool rt_safe_get_vector (double x0, double x1, float *arg, int32_t veclen);
+ void get_vector (double x0, double x1, float *arg, int32_t veclen);
+
+ void solve ();
+
+ void mark_dirty() const { _dirty = true; }
+
+private:
+ double unlocked_eval (double where);
+ double multipoint_eval (double x);
+
+ void _get_vector (double x0, double x1, float *arg, int32_t veclen);
+
+ mutable bool _dirty;
+ const ControlList& _list;
+};
+
+} // namespace Evoral
+
+extern "C" {
+ void curve_get_vector_from_c (void *arg, double, double, float*, int32_t);
+}
+
+#endif // EVORAL_CURVE_HPP
+
diff --git a/libs/evoral/evoral/Event.hpp b/libs/evoral/evoral/Event.hpp
new file mode 100644
index 0000000000..beffb01eb5
--- /dev/null
+++ b/libs/evoral/evoral/Event.hpp
@@ -0,0 +1,219 @@
+/* This file is part of Evoral.
+ * Copyright (C) 2008 Dave Robillard <http://drobilla.net>
+ * Copyright (C) 2000-2008 Paul Davis
+ *
+ * Evoral 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.
+ *
+ * Evoral 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 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 St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#ifndef EVORAL_EVENT_HPP
+#define EVORAL_EVENT_HPP
+
+#include <stdint.h>
+#include <cstdlib>
+#include <cstring>
+#include <sstream>
+#include <assert.h>
+#include <evoral/midi_events.h>
+#ifdef EVENT_WITH_XML
+ #include <pbd/xml++.h>
+#endif
+
+/** 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 EVENT_ALLOW_ALLOC 1
+
+//#define EVENT_WITH_XML
+
+namespace Evoral {
+
+
+/** Identical to jack_midi_event_t, but with double timestamp
+ *
+ * time is either a frame time (from/to Jack) or a beat time (internal
+ * tempo time, used in MidiModel) depending on context.
+ */
+struct Event {
+#ifdef EVENT_ALLOW_ALLOC
+ Event(double t=0, uint32_t s=0, uint8_t* b=NULL, bool owns_buffer=false);
+
+ /** Copy \a copy.
+ *
+ * If \a owns_buffer 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 owns_buffer);
+
+#ifdef EVENT_WITH_XML
+ /** Event from XML ala http://www.midi.org/dtds/MIDIEvents10.dtd
+ */
+ Event(const XMLNode& event);
+
+ /** Event to XML ala http://www.midi.org/dtds/MIDIEvents10.dtd
+ */
+ boost::shared_ptr<XMLNode> to_xml() const;
+#endif
+
+ ~Event();
+
+ inline const Event& operator=(const Event& copy) {
+ _time = copy._time;
+ if (_owns_buffer) {
+ if (copy._buffer) {
+ if (copy._size > _size) {
+ _buffer = (uint8_t*)::realloc(_buffer, copy._size);
+ }
+ memcpy(_buffer, copy._buffer, copy._size);
+ } else {
+ free(_buffer);
+ _buffer = NULL;
+ }
+ } else {
+ _buffer = copy._buffer;
+ }
+
+ _size = copy._size;
+ return *this;
+ }
+
+ inline void shallow_copy(const Event& copy) {
+ if (_owns_buffer) {
+ free(_buffer);
+ _buffer = false;
+ _owns_buffer = false;
+ }
+
+ _time = copy._time;
+ _size = copy._size;
+ _buffer = copy._buffer;
+ }
+
+ inline void set(uint8_t* buf, size_t size, double t) {
+ if (_owns_buffer) {
+ if (_size < size) {
+ _buffer = (uint8_t*) ::realloc(_buffer, size);
+ }
+ memcpy (_buffer, buf, size);
+ } else {
+ _buffer = buf;
+ }
+
+ _size = size;
+ _time = t;
+ }
+
+ inline bool operator==(const Event& other) const {
+ if (_time != other._time)
+ return false;
+
+ if (_size != other._size)
+ return false;
+
+ if (_buffer == other._buffer)
+ return true;
+
+ for (size_t i=0; i < _size; ++i)
+ if (_buffer[i] != other._buffer[i])
+ return false;
+
+ return true;
+ }
+
+ inline bool operator!=(const Event& other) const { return ! operator==(other); }
+
+ inline bool owns_buffer() const { return _owns_buffer; }
+
+ inline void set_buffer(size_t size, uint8_t* buf, bool own) {
+ if (_owns_buffer) {
+ free(_buffer);
+ _buffer = NULL;
+ }
+ _size = size;
+ _buffer = buf;
+ _owns_buffer = own;
+ }
+
+ inline void realloc(size_t size) {
+ if (_owns_buffer) {
+ if (size > _size)
+ _buffer = (uint8_t*) ::realloc(_buffer, size);
+ } else {
+ _buffer = (uint8_t*) ::malloc(size);
+ _owns_buffer = true;
+ }
+
+ _size = size;
+ }
+
+
+#else
+
+ inline void set_buffer(uint8_t* buf) { _buffer = buf; }
+
+#endif // EVENT_ALLOW_ALLOC
+
+ inline double time() const { return _time; }
+ inline double& time() { return _time; }
+ inline uint32_t size() const { return _size; }
+ inline uint32_t& size() { return _size; }
+ inline uint8_t type() const { return (_buffer[0] & 0xF0); }
+ inline void set_type(uint8_t type) { _buffer[0] = (0x0F & _buffer[0])
+ | (0xF0 & type); }
+ inline uint8_t channel() const { return (_buffer[0] & 0x0F); }
+ inline void set_channel(uint8_t channel) { _buffer[0] = (0xF0 & _buffer[0])
+ | (0x0F & channel); }
+ inline bool is_note_on() const { return (type() == MIDI_CMD_NOTE_ON); }
+ inline bool is_note_off() const { return (type() == MIDI_CMD_NOTE_OFF); }
+ inline bool is_cc() const { return (type() == MIDI_CMD_CONTROL); }
+ inline bool is_pitch_bender() const { return (type() == MIDI_CMD_BENDER); }
+ inline bool is_pgm_change() const { return (type() == MIDI_CMD_PGM_CHANGE); }
+ inline bool is_note() const { return (is_note_on() || is_note_off()); }
+ inline bool is_aftertouch() const { return (type() == MIDI_CMD_NOTE_PRESSURE); }
+ inline bool is_channel_aftertouch() const { return (type() == MIDI_CMD_CHANNEL_PRESSURE); }
+ inline uint8_t note() const { return (_buffer[1]); }
+ inline uint8_t velocity() const { return (_buffer[2]); }
+ inline uint8_t cc_number() const { return (_buffer[1]); }
+ inline uint8_t cc_value() const { return (_buffer[2]); }
+ inline uint8_t pitch_bender_lsb() const { return (_buffer[1]); }
+ inline uint8_t pitch_bender_msb() const { return (_buffer[2]); }
+ inline uint16_t pitch_bender_value() const { return ( ((0x7F & _buffer[2]) << 7)
+ | (0x7F & _buffer[1]) ); }
+ inline uint8_t pgm_number() const { return (_buffer[1]); }
+ inline void set_pgm_number(uint8_t number){ _buffer[1] = number; }
+ inline uint8_t aftertouch() const { return (_buffer[1]); }
+ inline uint8_t channel_aftertouch() const { return (_buffer[1]); }
+ inline bool is_channel_event() const { return (0x80 <= type()) && (type() <= 0xE0); }
+ inline bool is_smf_meta_event() const { return _buffer[0] == 0xFF; }
+ inline bool is_sysex() const { return _buffer[0] == 0xF0
+ || _buffer[0] == 0xF7; }
+ inline const uint8_t* buffer() const { return _buffer; }
+ inline uint8_t*& buffer() { return _buffer; }
+
+private:
+ double _time; /**< Sample index (or beat time) at which event is valid */
+ uint32_t _size; /**< Number of uint8_ts of data in \a buffer */
+ uint8_t* _buffer; /**< Raw MIDI data */
+
+#ifdef EVENT_ALLOW_ALLOC
+ bool _owns_buffer; /**< Whether buffer is locally allocated */
+#endif
+};
+
+
+} // namespace Evoral
+
+#endif // EVORAL_EVENT_HPP
+
diff --git a/libs/evoral/evoral/EventSink.hpp b/libs/evoral/evoral/EventSink.hpp
new file mode 100644
index 0000000000..fde6399f2e
--- /dev/null
+++ b/libs/evoral/evoral/EventSink.hpp
@@ -0,0 +1,40 @@
+/* This file is part of Evoral.
+ * Copyright (C) 2008 Dave Robillard <http://drobilla.net>
+ * Copyright (C) 2000-2008 Paul Davis
+ *
+ * Evoral 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.
+ *
+ * Evoral 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 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 St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#ifndef EVORAL_EVENT_SINK_HPP
+#define EVORAL_EVENT_SINK_HPP
+
+#include <evoral/types.hpp>
+
+namespace Evoral {
+
+
+/** Pure virtual base for anything you can write events to.
+ */
+class EventSink {
+public:
+ virtual size_t write(timestamp_t time,
+ uint32_t size,
+ const uint8_t* buf) = 0;
+};
+
+
+} // namespace Evoral
+
+#endif // EVORAL_EVENT_SINK_HPP
+
diff --git a/libs/evoral/evoral/MIDIParameters.hpp b/libs/evoral/evoral/MIDIParameters.hpp
new file mode 100644
index 0000000000..c21a86264d
--- /dev/null
+++ b/libs/evoral/evoral/MIDIParameters.hpp
@@ -0,0 +1,52 @@
+/* This file is part of Evoral.
+ * Copyright (C) 2008 Dave Robillard <http://drobilla.net>
+ * Copyright (C) 2000-2008 Paul Davis
+ *
+ * Evoral 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.
+ *
+ * Evoral 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 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 St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#ifndef EVORAL_MIDI_PARAMETERS_HPP
+#define EVORAL_MIDI_PARAMETERS_HPP
+
+namespace Evoral {
+namespace MIDI {
+
+struct ContinuousController : public Parameter {
+ ContinuousController(uint32_t cc_type, uint32_t channel, uint32_t controller)
+ : Parameter(cc_type, controller, channel) { set_range(*this); }
+ static void set_range(Parameter& p) { p.set_range(0.0, 127.0, 0.0); }
+};
+
+struct ProgramChange : public Parameter {
+ ProgramChange(uint32_t pc_type, uint32_t channel)
+ : Parameter(pc_type, 0, channel) { set_range(*this); }
+ static void set_range(Parameter& p) { p.set_range(0.0, 127.0, 0.0); }
+};
+
+struct ChannelAftertouch : public Parameter {
+ ChannelAftertouch(uint32_t ca_type, uint32_t channel)
+ : Parameter(ca_type, 0, channel) { set_range(*this); }
+ static void set_range(Parameter& p) { p.set_range(0.0, 127.0, 0.0); }
+};
+
+struct PitchBender : public Parameter {
+ PitchBender(uint32_t pb_type, uint32_t channel)
+ : Parameter(pb_type, 0, channel) { set_range(*this); }
+ static void set_range(Parameter& p) { p.set_range(0.0, 16383.0, 8192.0); }
+};
+
+} // namespace MIDI
+} // namespace Evoral
+
+#endif // EVORAL_MIDI_PARAMETERS_HPP
diff --git a/libs/evoral/evoral/Note.hpp b/libs/evoral/evoral/Note.hpp
new file mode 100644
index 0000000000..76095e733c
--- /dev/null
+++ b/libs/evoral/evoral/Note.hpp
@@ -0,0 +1,79 @@
+/* This file is part of Evoral.
+ * Copyright (C) 2008 Dave Robillard <http://drobilla.net>
+ * Copyright (C) 2000-2008 Paul Davis
+ *
+ * Evoral 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.
+ *
+ * Evoral 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 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 St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#ifndef EVORAL_NOTE_HPP
+#define EVORAL_NOTE_HPP
+
+#include <stdint.h>
+#include <evoral/Event.hpp>
+
+namespace Evoral {
+
+
+/** An abstract (protocol agnostic) note.
+ *
+ * Currently a note is defined as (on event, duration, off event).
+ */
+class Note {
+public:
+ Note(uint8_t chan=0, double time=0, double dur=0, uint8_t note=0, uint8_t vel=0x40);
+ Note(const Note& copy);
+ ~Note();
+
+ const Note& operator=(const Note& copy);
+
+ inline bool operator==(const Note& other) {
+ return time() == other.time() &&
+ note() == other.note() &&
+ duration() == other.duration() &&
+ velocity() == other.velocity() &&
+ channel() == other.channel();
+ }
+
+ inline double time() const { return _on_event.time(); }
+ inline double 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 double duration() const { return _off_event.time() - _on_event.time(); }
+ inline uint8_t channel() const {
+ assert(_on_event.channel() == _off_event.channel());
+ return _on_event.channel();
+ }
+
+ inline void set_time(double t) { _off_event.time() = t + duration(); _on_event.time() = t; }
+ inline void set_note(uint8_t n) { _on_event.buffer()[1] = n; _off_event.buffer()[1] = n; }
+ inline void set_velocity(uint8_t n) { _on_event.buffer()[2] = n; }
+ inline void set_duration(double d) { _off_event.time() = _on_event.time() + d; }
+ inline void set_channel(uint8_t c) { _on_event.set_channel(c); _off_event.set_channel(c); }
+
+ inline Event& on_event() { return _on_event; }
+ inline const Event& on_event() const { return _on_event; }
+ inline Event& off_event() { return _off_event; }
+ inline const Event& off_event() const { return _off_event; }
+
+private:
+ // Event buffers are self-contained
+ Event _on_event;
+ Event _off_event;
+};
+
+
+} // namespace Evoral
+
+#endif // EVORAL_NOTE_HPP
+
diff --git a/libs/evoral/evoral/Parameter.hpp b/libs/evoral/evoral/Parameter.hpp
new file mode 100644
index 0000000000..ae405ec039
--- /dev/null
+++ b/libs/evoral/evoral/Parameter.hpp
@@ -0,0 +1,135 @@
+/* This file is part of Evoral.
+ * Copyright (C) 2008 Dave Robillard <http://drobilla.net>
+ * Copyright (C) 2000-2008 Paul Davis
+ *
+ * Evoral 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.
+ *
+ * Evoral 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 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 St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#ifndef EVORAL_PARAMETER_HPP
+#define EVORAL_PARAMETER_HPP
+
+#include <string>
+#include <boost/format.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 Parameter
+{
+public:
+ Parameter(uint32_t type, uint32_t id, int8_t channel=0,
+ double min=0.0f, double max=0.0f, double def=0.0f)
+ : _type(type), _id(id), _channel(channel), _min(min), _max(max), _normal(def)
+ {}
+
+ //Parameter(const std::string& str);
+
+ inline uint32_t type() const { return _type; }
+ inline uint32_t id() const { return _id; }
+ inline uint8_t channel() const { return _channel; }
+
+ /**
+ * 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 && _id == id._id && _channel == id._channel);
+ }
+
+ /** Strict weak ordering
+ * See: http://www.sgi.com/tech/stl/StrictWeakOrdering.html
+ * Sort Parameters first according to type then to id and lastly to channel.
+ *
+ * Proof:
+ * <ol>
+ * <li>Irreflexivity: f(x, x) is false because of the irreflexivity of \c < in each branch.</li>
+ * <li>Antisymmetry: given x != y, f(x, y) implies !f(y, x) because of the same
+ * property of \c < in each branch and the symmetry of operator==. </li>
+ * <li>Transitivity: let f(x, y) and f(y, z) be true.
+ * We prove by contradiction, assuming the contrary (f(x, z) is false).
+ * That would imply exactly one of the following:
+ * <ol>
+ * <li> x == z which contradicts the assumption f(x, y) and f(y, x)
+ * because of antisymmetry.
+ * </li>
+ * <li> f(z, x) is true. That would imply that one of the ivars (we call it i)
+ * of x is greater than the same ivar in z while all "previous" ivars
+ * are equal. That would imply that also in y all those "previous"
+ * ivars are equal and because if x.i > z.i it is impossible
+ * that there is an y that satisfies x.i < y.i < z.i at the same
+ * time which contradicts the assumption.
+ * </li>
+ * Therefore f(x, z) is true (transitivity)
+ * </ol>
+ * </li>
+ * </ol>
+ */
+ inline bool operator<(const Parameter& id) const {
+ if (_type < id._type) {
+ return true;
+ } else if (_type == id._type && _id < id._id) {
+ return true;
+ } else if (_id == id._id && _channel < id._channel) {
+ return true;
+ }
+
+ return false;
+ }
+
+ inline operator bool() const { return (_type != 0); }
+
+ virtual std::string symbol() const {
+ return (boost::format("%1%_c%2%_n%3%\n") % _type % _channel % _id).str();
+ }
+
+ inline void set_range(double min, double max, double normal) {
+ _min = min;
+ _max = max;
+ _normal = normal;
+ }
+
+ inline const double min() const { return _min; }
+ inline const double max() const { return _max; }
+ inline const double normal() const { return _normal; }
+
+protected:
+ // Default copy constructor is ok
+
+ // ID (used in comparison)
+ uint32_t _type;
+ uint32_t _id;
+ uint8_t _channel;
+
+ // Metadata (not used in comparison)
+ double _min;
+ double _max;
+ double _normal;
+};
+
+
+} // namespace Evoral
+
+#endif // EVORAL_PARAMETER_HPP
+
diff --git a/libs/evoral/evoral/Sequence.hpp b/libs/evoral/evoral/Sequence.hpp
new file mode 100644
index 0000000000..ef3ad39fc1
--- /dev/null
+++ b/libs/evoral/evoral/Sequence.hpp
@@ -0,0 +1,215 @@
+/* This file is part of Evoral.
+ * Copyright (C) 2008 Dave Robillard <http://drobilla.net>
+ * Copyright (C) 2000-2008 Paul Davis
+ *
+ * Evoral 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.
+ *
+ * Evoral 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 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 St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#ifndef EVORAL_SEQUENCE_HPP
+#define EVORAL_SEQUENCE_HPP
+
+#include <vector>
+#include <queue>
+#include <deque>
+#include <map>
+#include <utility>
+#include <boost/shared_ptr.hpp>
+#include <boost/utility.hpp>
+#include <glibmm/thread.h>
+#include <evoral/types.hpp>
+#include <evoral/Note.hpp>
+#include <evoral/Parameter.hpp>
+#include <evoral/ControlSet.hpp>
+
+namespace Evoral {
+
+class EventSink;
+class Note;
+class Event;
+class ControlList;
+
+/** This class keeps track of the current x and y for a control
+ */
+class ControlIterator {
+public:
+ boost::shared_ptr<const ControlList> list;
+ double x;
+ double y;
+
+ ControlIterator(boost::shared_ptr<const ControlList> a_list,
+ double a_x,
+ double a_y)
+ : list(a_list)
+ , x(a_x)
+ , y(a_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.
+ */
+class Sequence : public boost::noncopyable, virtual public ControlSet {
+public:
+ Sequence(size_t size);
+
+ void write_lock();
+ void write_unlock();
+
+ void read_lock() const;
+ void read_unlock() const;
+
+ void clear();
+
+ bool percussive() const { return _percussive; }
+ void set_percussive(bool p) { _percussive = p; }
+
+ void start_write();
+ bool writing() const { return _writing; }
+ void end_write(bool delete_stuck=false);
+
+ size_t read(EventSink& dst,
+ timestamp_t start,
+ timedur_t length,
+ timestamp_t stamp_offset) const;
+
+ /** Resizes vector if necessary (NOT realtime safe) */
+ void append(const Event& ev);
+
+ inline const boost::shared_ptr<const Note> note_at(unsigned i) const { return _notes[i]; }
+ inline const boost::shared_ptr<Note> note_at(unsigned i) { return _notes[i]; }
+
+ inline size_t n_notes() const { return _notes.size(); }
+ inline bool empty() const { return _notes.size() == 0 && _controls.size() == 0; }
+
+ inline static bool note_time_comparator(const boost::shared_ptr<const Note> a,
+ const boost::shared_ptr<const Note> b) {
+ return a->time() < b->time();
+ }
+
+ struct LaterNoteEndComparator {
+ typedef const Note* value_type;
+ inline bool operator()(const boost::shared_ptr<const Note> a,
+ const boost::shared_ptr<const Note> b) const {
+ return a->end_time() > b->end_time();
+ }
+ };
+
+ typedef std::vector< boost::shared_ptr<Note> > Notes;
+ inline Notes& notes() { return _notes; }
+ inline const Notes& notes() const { return _notes; }
+
+ /** Read iterator */
+ class const_iterator {
+ public:
+ const_iterator(const Sequence& seq, double t);
+ ~const_iterator();
+
+ inline bool locked() const { return _locked; }
+
+ const Event& operator*() const { return *_event; }
+ const boost::shared_ptr<Event> operator->() const { return _event; }
+ const boost::shared_ptr<Event> get_event_pointer() { 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;
+
+ const Sequence* _seq;
+ boost::shared_ptr<Event> _event;
+
+ typedef std::priority_queue< boost::shared_ptr<Note>,
+ std::deque< boost::shared_ptr<Note> >,
+ LaterNoteEndComparator >
+ ActiveNotes;
+
+ mutable ActiveNotes _active_notes;
+
+ bool _is_end;
+ bool _locked;
+ Notes::const_iterator _note_iter;
+ std::vector<ControlIterator> _control_iters;
+ std::vector<ControlIterator>::iterator _control_iter;
+ };
+
+ const_iterator begin() const { return const_iterator(*this, 0); }
+ const const_iterator& end() const { return _end_iter; }
+
+ bool control_to_midi_event(boost::shared_ptr<Event>& ev,
+ const ControlIterator& iter) const;
+
+ typedef std::map< Parameter, boost::shared_ptr<Control> > Controls;
+ Controls& controls() { return _controls; }
+ const Controls& controls() const { return _controls; }
+
+ bool edited() const { return _edited; }
+ void set_edited(bool yn) { _edited = yn; }
+
+protected:
+ void add_note_unlocked(const boost::shared_ptr<Note> note);
+ void remove_note_unlocked(const boost::shared_ptr<const Note> note);
+
+ mutable const_iterator _read_iter;
+ bool _edited;
+#ifndef NDEBUG
+ bool is_sorted() const;
+#endif
+
+private:
+ friend class const_iterator;
+
+ void append_note_on_unlocked(uint8_t chan, double time, uint8_t note, uint8_t velocity);
+ void append_note_off_unlocked(uint8_t chan, double time, uint8_t note);
+ void append_control_unlocked(Parameter param, double time, double value);
+
+ mutable Glib::RWLock _lock;
+
+ Notes _notes;
+ Controls _controls;
+
+ typedef std::vector<size_t> WriteNotes;
+ WriteNotes _write_notes[16];
+ bool _writing;
+
+ typedef std::vector< boost::shared_ptr<const ControlList> > ControlLists;
+ ControlLists _dirty_controls;
+
+ const const_iterator _end_iter;
+ mutable nframes_t _next_read;
+ bool _percussive;
+
+ /** FIXME: Make fully dynamic, map to URIs */
+ enum EventTypes {
+ midi_cc_type,
+ midi_pc_type,
+ midi_pb_type,
+ midi_ca_type
+ };
+
+ typedef std::priority_queue<
+ boost::shared_ptr<Note>, std::deque< boost::shared_ptr<Note> >,
+ LaterNoteEndComparator>
+ ActiveNotes;
+};
+
+} // namespace Evoral
+
+#endif // EVORAL_SEQUENCE_HPP
+
diff --git a/libs/evoral/evoral/midi_events.h b/libs/evoral/evoral/midi_events.h
new file mode 100644
index 0000000000..1c786aa6f7
--- /dev/null
+++ b/libs/evoral/evoral/midi_events.h
@@ -0,0 +1,133 @@
+/* Definitions to ease working with raw MIDI.
+ *
+ * Adapted from ALSA's asounddef.h
+ *
+ * This library is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation; either version 2.1 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 Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ *
+ */
+
+#ifndef RAUL_MIDI_EVENTS_H
+#define RAUL_MIDI_EVENTS_H
+
+
+/**
+ * \defgroup midi MIDI Definitions
+ * MIDI command and controller number definitions.
+ * \{
+ */
+
+
+// Controllers
+#define MIDI_CTL_MSB_BANK 0x00 /**< Bank Selection */
+#define MIDI_CTL_MSB_MODWHEEL 0x01 /**< Modulation */
+#define MIDI_CTL_MSB_BREATH 0x02 /**< Breath */
+#define MIDI_CTL_MSB_FOOT 0x04 /**< Foot */
+#define MIDI_CTL_MSB_PORTAMENTO_TIME 0x05 /**< Portamento Time */
+#define MIDI_CTL_MSB_DATA_ENTRY 0x06 /**< Data Entry */
+#define MIDI_CTL_MSB_MAIN_VOLUME 0x07 /**< Main Volume */
+#define MIDI_CTL_MSB_BALANCE 0x08 /**< Balance */
+#define MIDI_CTL_MSB_PAN 0x0A /**< Panpot */
+#define MIDI_CTL_MSB_EXPRESSION 0x0B /**< Expression */
+#define MIDI_CTL_MSB_EFFECT1 0x0C /**< Effect1 */
+#define MIDI_CTL_MSB_EFFECT2 0x0D /**< Effect2 */
+#define MIDI_CTL_MSB_GENERAL_PURPOSE1 0x10 /**< General Purpose 1 */
+#define MIDI_CTL_MSB_GENERAL_PURPOSE2 0x11 /**< General Purpose 2 */
+#define MIDI_CTL_MSB_GENERAL_PURPOSE3 0x12 /**< General Purpose 3 */
+#define MIDI_CTL_MSB_GENERAL_PURPOSE4 0x13 /**< General Purpose 4 */
+#define MIDI_CTL_LSB_BANK 0x20 /**< Bank Selection */
+#define MIDI_CTL_LSB_MODWHEEL 0x21 /**< Modulation */
+#define MIDI_CTL_LSB_BREATH 0x22 /**< Breath */
+#define MIDI_CTL_LSB_FOOT 0x24 /**< Foot */
+#define MIDI_CTL_LSB_PORTAMENTO_TIME 0x25 /**< Portamento Time */
+#define MIDI_CTL_LSB_DATA_ENTRY 0x26 /**< Data Entry */
+#define MIDI_CTL_LSB_MAIN_VOLUME 0x27 /**< Main Volume */
+#define MIDI_CTL_LSB_BALANCE 0x28 /**< Balance */
+#define MIDI_CTL_LSB_PAN 0x2A /**< Panpot */
+#define MIDI_CTL_LSB_EXPRESSION 0x2B /**< Expression */
+#define MIDI_CTL_LSB_EFFECT1 0x2C /**< Effect1 */
+#define MIDI_CTL_LSB_EFFECT2 0x2D /**< Effect2 */
+#define MIDI_CTL_LSB_GENERAL_PURPOSE1 0x30 /**< General Purpose 1 */
+#define MIDI_CTL_LSB_GENERAL_PURPOSE2 0x31 /**< General Purpose 2 */
+#define MIDI_CTL_LSB_GENERAL_PURPOSE3 0x32 /**< General Purpose 3 */
+#define MIDI_CTL_LSB_GENERAL_PURPOSE4 0x33 /**< General Purpose 4 */
+#define MIDI_CTL_SUSTAIN 0x40 /**< Sustain Pedal */
+#define MIDI_CTL_PORTAMENTO 0x41 /**< Portamento */
+#define MIDI_CTL_SOSTENUTO 0x42 /**< Sostenuto */
+#define MIDI_CTL_SOFT_PEDAL 0x43 /**< Soft Pedal */
+#define MIDI_CTL_LEGATO_FOOTSWITCH 0x44 /**< Legato Foot Switch */
+#define MIDI_CTL_HOLD2 0x45 /**< Hold2 */
+#define MIDI_CTL_SC1_SOUND_VARIATION 0x46 /**< SC1 Sound Variation */
+#define MIDI_CTL_SC2_TIMBRE 0x47 /**< SC2 Timbre */
+#define MIDI_CTL_SC3_RELEASE_TIME 0x48 /**< SC3 Release Time */
+#define MIDI_CTL_SC4_ATTACK_TIME 0x49 /**< SC4 Attack Time */
+#define MIDI_CTL_SC5_BRIGHTNESS 0x4A /**< SC5 Brightness */
+#define MIDI_CTL_SC6 0x4B /**< SC6 */
+#define MIDI_CTL_SC7 0x4C /**< SC7 */
+#define MIDI_CTL_SC8 0x4D /**< SC8 */
+#define MIDI_CTL_SC9 0x4E /**< SC9 */
+#define MIDI_CTL_SC10 0x4F /**< SC10 */
+#define MIDI_CTL_GENERAL_PURPOSE5 0x50 /**< General Purpose 5 */
+#define MIDI_CTL_GENERAL_PURPOSE6 0x51 /**< General Purpose 6 */
+#define MIDI_CTL_GENERAL_PURPOSE7 0x52 /**< General Purpose 7 */
+#define MIDI_CTL_GENERAL_PURPOSE8 0x53 /**< General Purpose 8 */
+#define MIDI_CTL_PORTAMENTO_CONTROL 0x54 /**< Portamento Control */
+#define MIDI_CTL_E1_REVERB_DEPTH 0x5B /**< E1 Reverb Depth */
+#define MIDI_CTL_E2_TREMOLO_DEPTH 0x5C /**< E2 Tremolo Depth */
+#define MIDI_CTL_E3_CHORUS_DEPTH 0x5D /**< E3 Chorus Depth */
+#define MIDI_CTL_E4_DETUNE_DEPTH 0x5E /**< E4 Detune Depth */
+#define MIDI_CTL_E5_PHASER_DEPTH 0x5F /**< E5 Phaser Depth */
+#define MIDI_CTL_DATA_INCREMENT 0x60 /**< Data Increment */
+#define MIDI_CTL_DATA_DECREMENT 0x61 /**< Data Decrement */
+#define MIDI_CTL_NONREG_PARM_NUM_LSB 0x62 /**< Non-registered Parameter Number */
+#define MIDI_CTL_NONREG_PARM_NUM_MSB 0x63 /**< Non-registered Parameter Number */
+#define MIDI_CTL_REGIST_PARM_NUM_LSB 0x64 /**< Registered Parameter Number */
+#define MIDI_CTL_REGIST_PARM_NUM_MSB 0x65 /**< Registered Parameter Number */
+#define MIDI_CTL_ALL_SOUNDS_OFF 0x78 /**< All Sounds Off */
+#define MIDI_CTL_RESET_CONTROLLERS 0x79 /**< Reset Controllers */
+#define MIDI_CTL_LOCAL_CONTROL_SWITCH 0x7A /**< Local Control Switch */
+#define MIDI_CTL_ALL_NOTES_OFF 0x7B /**< All Notes Off */
+#define MIDI_CTL_OMNI_OFF 0x7C /**< Omni Off */
+#define MIDI_CTL_OMNI_ON 0x7D /**< Omni On */
+#define MIDI_CTL_MONO1 0x7E /**< Mono1 */
+#define MIDI_CTL_MONO2 0x7F /**< Mono2 */
+
+// Commands
+#define MIDI_CMD_NOTE_OFF 0x80 /**< Note Off */
+#define MIDI_CMD_NOTE_ON 0x90 /**< Note On */
+#define MIDI_CMD_NOTE_PRESSURE 0xA0 /**< Key Pressure */
+#define MIDI_CMD_CONTROL 0xB0 /**< Control Change */
+#define MIDI_CMD_PGM_CHANGE 0xC0 /**< Program Change */
+#define MIDI_CMD_CHANNEL_PRESSURE 0xD0 /**< Channel Pressure */
+#define MIDI_CMD_BENDER 0xE0 /**< Pitch Bender */
+#define MIDI_CMD_COMMON_SYSEX 0xF0 /**< Sysex (System Exclusive) Begin */
+#define MIDI_CMD_COMMON_MTC_QUARTER 0xF1 /**< MTC Quarter Frame */
+#define MIDI_CMD_COMMON_SONG_POS 0xF2 /**< Song Position */
+#define MIDI_CMD_COMMON_SONG_SELECT 0xF3 /**< Song Select */
+#define MIDI_CMD_COMMON_TUNE_REQUEST 0xF6 /**< Tune Request */
+#define MIDI_CMD_COMMON_SYSEX_END 0xF7 /**< End of Sysex */
+#define MIDI_CMD_COMMON_CLOCK 0xF8 /**< Clock */
+#define MIDI_CMD_COMMON_TICK 0xF9 /**< Tick */
+#define MIDI_CMD_COMMON_START 0xFA /**< Start */
+#define MIDI_CMD_COMMON_CONTINUE 0xFB /**< Continue */
+#define MIDI_CMD_COMMON_STOP 0xFC /**< Stop */
+#define MIDI_CMD_COMMON_SENSING 0xFE /**< Active Sensing */
+#define MIDI_CMD_COMMON_RESET 0xFF /**< Reset */
+
+//@}
+
+
+/** \} */
+
+#endif /* RAUL_MIDI_EVENTS_H */
diff --git a/libs/evoral/evoral/types.hpp b/libs/evoral/evoral/types.hpp
new file mode 100644
index 0000000000..e078a69a03
--- /dev/null
+++ b/libs/evoral/evoral/types.hpp
@@ -0,0 +1,31 @@
+/* This file is part of Evoral.
+ * Copyright (C) 2008 Dave Robillard <http://drobilla.net>
+ * Copyright (C) 2000-2008 Paul Davis
+ *
+ * Evoral 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.
+ *
+ * Evoral 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 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 St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#ifndef EVORAL_TYPES_HPP
+#define EVORAL_TYPES_HPP
+
+/** Frame count (i.e. length of time in audio frames) */
+typedef uint32_t nframes_t;
+
+/** Time-stamp of an event */
+typedef double timestamp_t;
+
+/** Duration of time in timestamp_t units */
+typedef timestamp_t timedur_t;
+
+#endif // EVORAL_TYPES_HPP
diff --git a/libs/evoral/src/Control.cpp b/libs/evoral/src/Control.cpp
new file mode 100644
index 0000000000..8efc2a6659
--- /dev/null
+++ b/libs/evoral/src/Control.cpp
@@ -0,0 +1,82 @@
+/* This file is part of Evoral.
+ * Copyright (C) 2008 Dave Robillard <http://drobilla.net>
+ * Copyright (C) 2000-2008 Paul Davis
+ *
+ * Evoral 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.
+ *
+ * Evoral 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 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 St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include <iostream>
+#include <evoral/Control.hpp>
+#include <evoral/ControlList.hpp>
+
+namespace Evoral {
+
+Control::Control(boost::shared_ptr<ControlList> list)
+ : _list(list)
+ , _user_value(list->default_value())
+{
+}
+
+
+/** Get the currently effective value (ie the one that corresponds to current output)
+ */
+float
+Control::get_value(bool from_list, nframes_t frame) const
+{
+ if (from_list)
+ return _list->eval(frame);
+ else
+ return _user_value;
+}
+
+
+void
+Control::set_value(float value, bool to_list, nframes_t frame)
+{
+ _user_value = value;
+
+ if (to_list)
+ _list->add(frame, value);
+}
+
+
+/** Get the latest user-set value, which may not equal get_value() when automation
+ * is playing back, etc.
+ *
+ * Automation write/touch works by periodically sampling this value and adding it
+ * to the AutomationList.
+ */
+float
+Control::user_value() const
+{
+ return _user_value;
+}
+
+
+void
+Control::set_list(boost::shared_ptr<ControlList> list)
+{
+ _list = list;
+ _user_value = list->default_value();
+}
+
+
+Parameter
+Control::parameter() const
+{
+ return _list->parameter();
+}
+
+} // namespace Evoral
+
diff --git a/libs/evoral/src/ControlList.cpp b/libs/evoral/src/ControlList.cpp
new file mode 100644
index 0000000000..8f6ea1872f
--- /dev/null
+++ b/libs/evoral/src/ControlList.cpp
@@ -0,0 +1,1310 @@
+/* This file is part of Evoral.
+ * Copyright (C) 2008 Dave Robillard <http://drobilla.net>
+ * Copyright (C) 2000-2008 Paul Davis
+ *
+ * Evoral 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.
+ *
+ * Evoral 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 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 St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include <cmath>
+#include <cassert>
+#include <utility>
+#include <iostream>
+#include <evoral/ControlList.hpp>
+
+using namespace std;
+
+namespace Evoral {
+
+
+inline bool event_time_less_than (ControlEvent* a, ControlEvent* b)
+{
+ return a->when < b->when;
+}
+
+
+ControlList::ControlList (Parameter id)
+ : _parameter(id)
+ , _interpolation(Linear)
+ , _curve(new Curve(*this))
+{
+ _frozen = 0;
+ _changed_when_thawed = false;
+ _min_yval = id.min();
+ _max_yval = id.max();
+ _max_xval = 0; // means "no limit"
+ _rt_insertion_point = _events.end();
+ _lookup_cache.left = -1;
+ _lookup_cache.range.first = _events.end();
+ _search_cache.left = -1;
+ _search_cache.range.first = _events.end();
+ _sort_pending = false;
+}
+
+ControlList::ControlList (const ControlList& other)
+ : _parameter(other._parameter)
+ , _interpolation(Linear)
+ , _curve(new Curve(*this))
+{
+ _frozen = 0;
+ _changed_when_thawed = false;
+ _min_yval = other._min_yval;
+ _max_yval = other._max_yval;
+ _max_xval = other._max_xval;
+ _default_value = other._default_value;
+ _rt_insertion_point = _events.end();
+ _lookup_cache.range.first = _events.end();
+ _search_cache.range.first = _events.end();
+ _sort_pending = false;
+
+ for (const_iterator i = other._events.begin(); i != other._events.end(); ++i) {
+ _events.push_back (new ControlEvent (**i));
+ }
+
+ mark_dirty ();
+}
+
+ControlList::ControlList (const ControlList& other, double start, double end)
+ : _parameter(other._parameter)
+ , _interpolation(Linear)
+ , _curve(new Curve(*this))
+{
+ _frozen = 0;
+ _changed_when_thawed = false;
+ _min_yval = other._min_yval;
+ _max_yval = other._max_yval;
+ _max_xval = other._max_xval;
+ _default_value = other._default_value;
+ _rt_insertion_point = _events.end();
+ _lookup_cache.range.first = _events.end();
+ _search_cache.range.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()) {
+ for (iterator i = section->begin(); i != section->end(); ++i) {
+ _events.push_back (new ControlEvent ((*i)->when, (*i)->value));
+ }
+ }
+
+ mark_dirty ();
+}
+
+ControlList::~ControlList()
+{
+ for (EventList::iterator x = _events.begin(); x != _events.end(); ++x) {
+ delete (*x);
+ }
+}
+
+
+boost::shared_ptr<ControlList>
+ControlList::create(Parameter id)
+{
+ return boost::shared_ptr<ControlList>(new ControlList(id));
+}
+
+
+bool
+ControlList::operator== (const ControlList& other)
+{
+ return _events == other._events;
+}
+
+ControlList&
+ControlList::operator= (const ControlList& other)
+{
+ if (this != &other) {
+
+ _events.clear ();
+
+ for (const_iterator i = other._events.begin(); i != other._events.end(); ++i) {
+ _events.push_back (new ControlEvent (**i));
+ }
+
+ _min_yval = other._min_yval;
+ _max_yval = other._max_yval;
+ _max_xval = other._max_xval;
+ _default_value = other._default_value;
+
+ mark_dirty ();
+ maybe_signal_changed ();
+ }
+
+ return *this;
+}
+
+void
+ControlList::maybe_signal_changed ()
+{
+ mark_dirty ();
+
+ if (_frozen)
+ _changed_when_thawed = true;
+}
+
+void
+ControlList::clear ()
+{
+ {
+ Glib::Mutex::Lock lm (_lock);
+ _events.clear ();
+ mark_dirty ();
+ }
+
+ maybe_signal_changed ();
+}
+
+void
+ControlList::x_scale (double factor)
+{
+ Glib::Mutex::Lock lm (_lock);
+ _x_scale (factor);
+}
+
+bool
+ControlList::extend_to (double when)
+{
+ Glib::Mutex::Lock lm (_lock);
+ if (_events.empty() || _events.back()->when == when) {
+ return false;
+ }
+ double factor = when / _events.back()->when;
+ _x_scale (factor);
+ return true;
+}
+
+void ControlList::_x_scale (double factor)
+{
+ for (iterator i = _events.begin(); i != _events.end(); ++i) {
+ (*i)->when = floor ((*i)->when * factor);
+ }
+
+ mark_dirty ();
+}
+
+void
+ControlList::reposition_for_rt_add (double when)
+{
+ _rt_insertion_point = _events.end();
+}
+
+void
+ControlList::rt_add (double when, double value)
+{
+ // cerr << "RT: alist @ " << this << " add " << value << " @ " << when << endl;
+
+ {
+ Glib::Mutex::Lock lm (_lock);
+
+ iterator where;
+ ControlEvent cp (when, 0.0);
+ bool done = false;
+
+ if ((_rt_insertion_point != _events.end()) && ((*_rt_insertion_point)->when < when) ) {
+
+ /* we have a previous insertion point, so we should delete
+ everything between it and the position where we are going
+ to insert this point.
+ */
+
+ iterator after = _rt_insertion_point;
+
+ if (++after != _events.end()) {
+ iterator far = after;
+
+ while (far != _events.end()) {
+ if ((*far)->when > when) {
+ break;
+ }
+ ++far;
+ }
+
+ if (_new_value) {
+ where = far;
+ _rt_insertion_point = where;
+
+ if ((*where)->when == when) {
+ (*where)->value = value;
+ done = true;
+ }
+ } else {
+ where = _events.erase (after, far);
+ }
+
+ } else {
+
+ where = after;
+
+ }
+
+ iterator previous = _rt_insertion_point;
+ --previous;
+
+ if (_rt_insertion_point != _events.begin() && (*_rt_insertion_point)->value == value && (*previous)->value == value) {
+ (*_rt_insertion_point)->when = when;
+ done = true;
+
+ }
+
+ } else {
+
+ where = lower_bound (_events.begin(), _events.end(), &cp, time_comparator);
+
+ if (where != _events.end()) {
+ if ((*where)->when == when) {
+ (*where)->value = value;
+ done = true;
+ }
+ }
+ }
+
+ if (!done) {
+ _rt_insertion_point = _events.insert (where, new ControlEvent (when, value));
+ }
+
+ _new_value = false;
+ mark_dirty ();
+ }
+
+ maybe_signal_changed ();
+}
+
+void
+ControlList::fast_simple_add (double when, double value)
+{
+ /* to be used only for loading pre-sorted data from saved state */
+ _events.insert (_events.end(), new ControlEvent (when, value));
+ assert(_events.back());
+}
+
+void
+ControlList::add (double when, double value)
+{
+ /* this is for graphical editing */
+
+ {
+ Glib::Mutex::Lock lm (_lock);
+ ControlEvent cp (when, 0.0f);
+ bool insert = true;
+ iterator insertion_point;
+
+ for (insertion_point = lower_bound (_events.begin(), _events.end(), &cp, time_comparator); insertion_point != _events.end(); ++insertion_point) {
+
+ /* only one point allowed per time point */
+
+ if ((*insertion_point)->when == when) {
+ (*insertion_point)->value = value;
+ insert = false;
+ break;
+ }
+
+ if ((*insertion_point)->when >= when) {
+ break;
+ }
+ }
+
+ if (insert) {
+
+ _events.insert (insertion_point, new ControlEvent (when, value));
+ reposition_for_rt_add (0);
+
+ }
+
+ mark_dirty ();
+ }
+
+ maybe_signal_changed ();
+}
+
+void
+ControlList::erase (iterator i)
+{
+ {
+ Glib::Mutex::Lock lm (_lock);
+ _events.erase (i);
+ reposition_for_rt_add (0);
+ mark_dirty ();
+ }
+ maybe_signal_changed ();
+}
+
+void
+ControlList::erase (iterator start, iterator end)
+{
+ {
+ Glib::Mutex::Lock lm (_lock);
+ _events.erase (start, end);
+ reposition_for_rt_add (0);
+ mark_dirty ();
+ }
+ maybe_signal_changed ();
+}
+
+void
+ControlList::reset_range (double start, double endt)
+{
+ bool reset = false;
+
+ {
+ Glib::Mutex::Lock lm (_lock);
+ 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);
+
+ for (iterator i = s; i != e; ++i) {
+ (*i)->value = _default_value;
+ }
+
+ reset = true;
+
+ mark_dirty ();
+ }
+ }
+
+ if (reset) {
+ maybe_signal_changed ();
+ }
+}
+
+void
+ControlList::erase_range (double start, double endt)
+{
+ bool erased = false;
+
+ {
+ Glib::Mutex::Lock lm (_lock);
+ 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);
+ reposition_for_rt_add (0);
+ erased = true;
+ mark_dirty ();
+ }
+
+ }
+
+ if (erased) {
+ maybe_signal_changed ();
+ }
+}
+
+void
+ControlList::move_range (iterator start, iterator end, double xdelta, double ydelta)
+{
+ /* 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 end are later than (end)->when.
+ */
+
+ {
+ Glib::Mutex::Lock lm (_lock);
+
+ while (start != end) {
+ (*start)->when += xdelta;
+ (*start)->value += ydelta;
+ if (isnan ((*start)->value)) {
+ abort ();
+ }
+ ++start;
+ }
+
+ if (!_frozen) {
+ _events.sort (event_time_less_than);
+ } else {
+ _sort_pending = true;
+ }
+
+ mark_dirty ();
+ }
+
+ maybe_signal_changed ();
+}
+
+void
+ControlList::slide (iterator before, double distance)
+{
+ {
+ Glib::Mutex::Lock lm (_lock);
+
+ if (before == _events.end()) {
+ return;
+ }
+
+ while (before != _events.end()) {
+ (*before)->when += distance;
+ ++before;
+ }
+ }
+
+ 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.
+ */
+
+ {
+ Glib::Mutex::Lock lm (_lock);
+
+ (*iter)->when = when;
+ (*iter)->value = val;
+
+ if (isnan (val)) {
+ abort ();
+ }
+
+ if (!_frozen) {
+ _events.sort (event_time_less_than);
+ } else {
+ _sort_pending = true;
+ }
+
+ mark_dirty ();
+ }
+
+ maybe_signal_changed ();
+}
+
+std::pair<ControlList::iterator,ControlList::iterator>
+ControlList::control_points_adjacent (double xval)
+{
+ Glib::Mutex::Lock 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::set_max_xval (double x)
+{
+ _max_xval = x;
+}
+
+void
+ControlList::freeze ()
+{
+ _frozen++;
+}
+
+void
+ControlList::thaw ()
+{
+ assert(_frozen > 0);
+
+ if (--_frozen > 0) {
+ return;
+ }
+
+ {
+ Glib::Mutex::Lock lm (_lock);
+
+ if (_sort_pending) {
+ _events.sort (event_time_less_than);
+ _sort_pending = false;
+ }
+ }
+}
+
+void
+ControlList::mark_dirty () const
+{
+ _lookup_cache.left = -1;
+ _search_cache.left = -1;
+ if (_curve)
+ _curve->mark_dirty();
+}
+
+void
+ControlList::truncate_end (double last_coordinate)
+{
+ {
+ Glib::Mutex::Lock 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) _min_yval, last_val);
+ last_val = min ((double) _max_yval, 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.
+ */
+
+ 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;
+ }
+
+ reposition_for_rt_add (0);
+ mark_dirty();
+ }
+
+ maybe_signal_changed ();
+}
+
+void
+ControlList::truncate_start (double overall_length)
+{
+ {
+ Glib::Mutex::Lock lm (_lock);
+ iterator i;
+ double first_legal_value;
+ double first_legal_coordinate;
+
+ assert(!_events.empty());
+
+ 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 (_min_yval, first_legal_value);
+ first_legal_value = min (_max_yval, 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));
+ }
+
+ reposition_for_rt_add (0);
+
+ 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;
+
+ npoints = _events.size();
+
+ switch (npoints) {
+ case 0:
+ return _default_value;
+
+ case 1:
+ if (x >= _events.front()->when) {
+ return _events.front()->value;
+ } else {
+ // return _default_value;
+ 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;
+ } else if (x < _events.front()->when) {
+ // return _default_value;
+ return _events.front()->value;
+ }
+
+ lpos = _events.front()->when;
+ lval = _events.front()->value;
+ upos = _events.back()->when;
+ uval = _events.back()->value;
+
+ if (_interpolation == Discrete)
+ return lval;
+
+ /* linear interpolation betweeen the two points
+ */
+
+ fraction = (double) (x - lpos) / (double) (upos - lpos);
+ return lval + (fraction * (uval - lval));
+
+ default:
+
+ if (x >= _events.back()->when) {
+ return _events.back()->value;
+ } else if (x == _events.front()->when) {
+ return _events.front()->value;
+ } else if (x < _events.front()->when) {
+ // return _default_value;
+ return _events.front()->value;
+ }
+
+ return multipoint_eval (x);
+ break;
+ }
+
+ /*NOTREACHED*/ /* stupid gcc */
+ return 0.0;
+}
+
+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;
+
+ /* linear interpolation betweeen the two points
+ on either side of x
+ */
+
+ fraction = (double) (x - lpos) / (double) (upos - lpos);
+ return lval + (fraction * (uval - lval));
+
+ }
+
+ /* 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, double end) const
+{
+ /* Only do the range lookup if x is in a different range than last time
+ * this was called (or if the search cache has been marked "dirty" (left<0) */
+ if (!_events.empty() && ((_search_cache.left < 0) ||
+ ((_search_cache.left > start) ||
+ (_search_cache.right < end)))) {
+
+ const ControlEvent start_point (start, 0);
+ const ControlEvent end_point (end, 0);
+
+ //cerr << "REBUILD: (" << _search_cache.left << ".." << _search_cache.right << ") := ("
+ // << start << ".." << end << ")" << endl;
+
+ _search_cache.range.first = lower_bound (_events.begin(), _events.end(), &start_point, time_comparator);
+ _search_cache.range.second = upper_bound (_events.begin(), _events.end(), &end_point, time_comparator);
+
+ _search_cache.left = start;
+ _search_cache.right = end;
+ }
+}
+
+/** Get the earliest event between \a start and \a end, 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 end, double& x, double& y, bool inclusive) const
+{
+ // FIXME: It would be nice if this was unnecessary..
+ Glib::Mutex::Lock lm(_lock, Glib::TRY_LOCK);
+ if (!lm.locked()) {
+ return false;
+ }
+
+ return rt_safe_earliest_event_unlocked(start, end, x, y, inclusive);
+}
+
+
+/** Get the earliest event between \a start and \a end, 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 end, double& x, double& y, bool inclusive) const
+{
+ if (_interpolation == Discrete)
+ return rt_safe_earliest_event_discrete_unlocked(start, end, x, y, inclusive);
+ else
+ return rt_safe_earliest_event_linear_unlocked(start, end, x, y, inclusive);
+}
+
+
+/** Get the earliest event between \a start and \a end (Discrete (lack of) 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 end, double& x, double& y, bool inclusive) const
+{
+ build_search_cache_if_necessary(start, end);
+
+ const pair<const_iterator,const_iterator>& range = _search_cache.range;
+
+ if (range.first != _events.end()) {
+ const ControlEvent* const first = *range.first;
+
+ const bool past_start = (inclusive ? first->when >= start : first->when > start);
+
+ /* Earliest points is in range, return it */
+ if (past_start >= start && first->when < end) {
+
+ 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.range.first;
+
+ assert(x >= start);
+ assert(x < end);
+ 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 end, double& x, double& y, bool inclusive) const
+{
+ //cerr << "earliest_event(" << start << ", " << end << ", " << x << ", " << y << ", " << inclusive << endl;
+
+ if (_events.size() == 0)
+ return false;
+ else if (_events.size() == 1)
+ return rt_safe_earliest_event_discrete_unlocked(start, end, x, y, inclusive);
+
+ // Hack to avoid infinitely repeating the same event
+ build_search_cache_if_necessary(start, end);
+
+ pair<const_iterator,const_iterator> range = _search_cache.range;
+
+ if (range.first != _events.end()) {
+
+ const ControlEvent* first = NULL;
+ const ControlEvent* next = NULL;
+
+ /* Step is after first */
+ if (range.first == _events.begin() || (*range.first)->when == start) {
+ first = *range.first;
+ next = *(++range.first);
+ ++_search_cache.range.first;
+
+ /* Step is before first */
+ } else {
+ const_iterator prev = range.first;
+ --prev;
+ first = *prev;
+ next = *range.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;
+ //++_search_cache.range.first;
+ return true;
+ }
+
+ if (abs(first->value - next->value) <= 1) {
+ if (next->when <= end && (!inclusive || 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;
+ //++_search_cache.range.first;
+ 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 && x < end) {
+ /* Move left of cache to this point
+ * (Optimize for immediate call this cycle within range) */
+ _search_cache.left = x;
+
+ return true;
+
+ } else {
+ return false;
+ }
+
+ /* No points in the future, so no steps (towards them) in the future */
+ } else {
+ return false;
+ }
+}
+
+boost::shared_ptr<ControlList>
+ControlList::cut (iterator start, iterator end)
+{
+ boost::shared_ptr<ControlList> nal = create (_parameter);
+
+ {
+ Glib::Mutex::Lock lm (_lock);
+
+ for (iterator x = start; x != end; ) {
+ iterator tmp;
+
+ tmp = x;
+ ++tmp;
+
+ nal->_events.push_back (new ControlEvent (**x));
+ _events.erase (x);
+
+ reposition_for_rt_add (0);
+
+ x = tmp;
+ }
+
+ mark_dirty ();
+ }
+
+ maybe_signal_changed ();
+
+ return nal;
+}
+
+boost::shared_ptr<ControlList>
+ControlList::cut_copy_clear (double start, double end, int op)
+{
+ boost::shared_ptr<ControlList> nal = create (_parameter);
+ iterator s, e;
+ ControlEvent cp (start, 0.0);
+ bool changed = false;
+
+ {
+ Glib::Mutex::Lock lm (_lock);
+
+ if ((s = lower_bound (_events.begin(), _events.end(), &cp, time_comparator)) == _events.end()) {
+ return nal;
+ }
+
+ cp.when = end;
+ e = upper_bound (_events.begin(), _events.end(), &cp, time_comparator);
+
+ if (op != 2 && (*s)->when != start) {
+ nal->_events.push_back (new ControlEvent (0, unlocked_eval (start)));
+ }
+
+ for (iterator x = s; x != e; ) {
+ iterator tmp;
+
+ tmp = x;
+ ++tmp;
+
+ changed = true;
+
+ /* 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) {
+ _events.erase (x);
+ }
+
+ x = tmp;
+ }
+
+ if (op != 2 && nal->_events.back()->when != end - start) {
+ nal->_events.push_back (new ControlEvent (end - start, unlocked_eval (end)));
+ }
+
+ if (changed) {
+ reposition_for_rt_add (0);
+ }
+
+ mark_dirty ();
+ }
+
+ maybe_signal_changed ();
+
+ return nal;
+
+}
+
+boost::shared_ptr<ControlList>
+ControlList::copy (iterator start, iterator end)
+{
+ boost::shared_ptr<ControlList> nal = create (_parameter);
+
+ {
+ Glib::Mutex::Lock lm (_lock);
+
+ for (iterator x = start; x != end; ) {
+ iterator tmp;
+
+ tmp = x;
+ ++tmp;
+
+ nal->_events.push_back (new ControlEvent (**x));
+
+ x = tmp;
+ }
+ }
+
+ 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)
+{
+ (void) cut_copy_clear (start, end, 2);
+}
+
+bool
+ControlList::paste (ControlList& alist, double pos, float times)
+{
+ if (alist._events.empty()) {
+ return false;
+ }
+
+ {
+ Glib::Mutex::Lock 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 (iterator i = alist.begin();i != alist.end(); ++i) {
+ _events.insert (where, new ControlEvent( (*i)->when+pos,( *i)->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;
+ }
+ }
+
+ reposition_for_rt_add (0);
+ mark_dirty ();
+ }
+
+ maybe_signal_changed ();
+ return true;
+}
+
+} // namespace Evoral
+
diff --git a/libs/evoral/src/ControlSet.cpp b/libs/evoral/src/ControlSet.cpp
new file mode 100644
index 0000000000..93a0b0ef25
--- /dev/null
+++ b/libs/evoral/src/ControlSet.cpp
@@ -0,0 +1,139 @@
+/* This file is part of Evoral.
+ * Copyright (C) 2008 Dave Robillard <http://drobilla.net>
+ * Copyright (C) 2000-2008 Paul Davis
+ *
+ * Evoral 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.
+ *
+ * Evoral 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 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 St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#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()
+{
+}
+
+void
+ControlSet::add_control(boost::shared_ptr<Control> ac)
+{
+ _controls[ac->parameter()] = ac;
+}
+
+void
+ControlSet::what_has_data (set<Parameter>& s) const
+{
+ Glib::Mutex::Lock lm (_control_lock);
+ Controls::const_iterator li;
+
+ // FIXME: correct semantics?
+ for (li = _controls.begin(); li != _controls.end(); ++li) {
+ s.insert ((*li).first);
+ }
+}
+
+/** If \a create_if_missing is true, a control list will be created and returned
+ * if one does not already exists. Otherwise NULL will be returned if a control list
+ * for \a parameter does not exist.
+ */
+boost::shared_ptr<Control>
+ControlSet::control (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<ControlList> al (control_list_factory(parameter));
+ boost::shared_ptr<Control> ac(control_factory(al));
+ add_control(ac);
+ return ac;
+
+ } else {
+ //warning << "ControlList " << parameter.to_string() << " not found for " << _name << endmsg;
+ return boost::shared_ptr<Control>();
+ }
+}
+
+boost::shared_ptr<const Control>
+ControlSet::control (Parameter parameter) const
+{
+ Controls::const_iterator i = _controls.find(parameter);
+
+ if (i != _controls.end()) {
+ return i->second;
+ } else {
+ //warning << "ControlList " << parameter.to_string() << " not found for " << _name << endmsg;
+ return boost::shared_ptr<Control>();
+ }
+}
+
+bool
+ControlSet::find_next_event (nframes_t now, nframes_t end, ControlEvent& next_event) const
+{
+ Controls::const_iterator li;
+
+ next_event.when = std::numeric_limits<nframes_t>::max();
+
+ for (li = _controls.begin(); li != _controls.end(); ++li) {
+ ControlList::const_iterator i;
+ boost::shared_ptr<const ControlList> alist (li->second->list());
+ ControlEvent cp (now, 0.0f);
+
+ for (i = lower_bound (alist->begin(), alist->end(), &cp, ControlList::time_comparator);
+ i != alist->end() && (*i)->when < end; ++i) {
+ if ((*i)->when > now) {
+ break;
+ }
+ }
+
+ if (i != alist->end() && (*i)->when < end) {
+ if ((*i)->when < next_event.when) {
+ next_event.when = (*i)->when;
+ }
+ }
+ }
+
+ return next_event.when != std::numeric_limits<nframes_t>::max();
+}
+
+void
+ControlSet::clear ()
+{
+ Glib::Mutex::Lock lm (_control_lock);
+
+ for (Controls::iterator li = _controls.begin(); li != _controls.end(); ++li)
+ li->second->list()->clear();
+}
+
+boost::shared_ptr<Control>
+ControlSet::control_factory(boost::shared_ptr<ControlList> list) const
+{
+ return boost::shared_ptr<Control>(new Control(list));
+}
+
+boost::shared_ptr<ControlList>
+ControlSet::control_list_factory(const Parameter& param) const
+{
+ return boost::shared_ptr<ControlList>(new ControlList(param));
+}
+
+
+} // namespace Evoral
diff --git a/libs/evoral/src/Curve.cpp b/libs/evoral/src/Curve.cpp
new file mode 100644
index 0000000000..c463022525
--- /dev/null
+++ b/libs/evoral/src/Curve.cpp
@@ -0,0 +1,401 @@
+/* This file is part of Evoral.
+ * Copyright (C) 2008 Dave Robillard <http://drobilla.net>
+ * Copyright (C) 2000-2008 Paul Davis
+ *
+ * Evoral 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.
+ *
+ * Evoral 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 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 St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include <iostream>
+#include <float.h>
+#include <cmath>
+#include <climits>
+#include <cfloat>
+#include <cmath>
+
+#include <glibmm/thread.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 ()
+{
+ 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.
+ */
+
+ double x[npoints];
+ 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)
+{
+ Glib::Mutex::Lock lm(_list.lock(), Glib::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)
+{
+ Glib::Mutex::Lock lm(_list.lock());
+ _get_vector (x0, x1, vec, veclen);
+}
+
+void
+Curve::_get_vector (double x0, double x1, float *vec, int32_t veclen)
+{
+ double rx, dx, lx, hx, max_x, min_x;
+ int32_t i;
+ int32_t original_veclen;
+ int32_t npoints;
+
+ if ((npoints = _list.events().size()) == 0) {
+ for (i = 0; i < veclen; ++i) {
+ vec[i] = _list.default_value();
+ }
+ return;
+ }
+
+ /* events is now known not to be empty */
+
+ max_x = _list.events().back()->when;
+ min_x = _list.events().front()->when;
+
+ lx = max (min_x, x0);
+
+ if (x1 < 0) {
+ x1 = _list.events().back()->when;
+ }
+
+ hx = min (max_x, x1);
+
+ 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);
+ int32_t subveclen = (int32_t) floor (veclen * frac);
+
+ subveclen = min (subveclen, veclen);
+
+ for (i = 0; i < subveclen; ++i) {
+ vec[i] = _list.events().front()->value;
+ }
+
+ veclen -= subveclen;
+ vec += subveclen;
+ }
+
+ 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);
+
+ int32_t subveclen = (int32_t) floor (original_veclen * frac);
+
+ float val;
+
+ subveclen = min (subveclen, veclen);
+
+ val = _list.events().back()->value;
+
+ i = veclen - subveclen;
+
+ for (i = veclen - subveclen; i < veclen; ++i) {
+ vec[i] = val;
+ }
+
+ veclen -= subveclen;
+ }
+
+ if (veclen == 0) {
+ return;
+ }
+
+ if (npoints == 1 ) {
+
+ for (i = 0; i < veclen; ++i) {
+ vec[i] = _list.events().front()->value;
+ }
+ return;
+ }
+
+
+ if (npoints == 2) {
+
+ /* linear interpolation between 2 points */
+
+ /* XXX I'm not sure that this is the right thing to
+ do here. but its not a common case for the envisaged
+ uses.
+ */
+
+ if (veclen > 1) {
+ dx = (hx - lx) / (veclen - 1) ;
+ } else {
+ dx = 0; // not used
+ }
+
+ double slope = (_list.events().back()->value - _list.events().front()->value)/
+ (_list.events().back()->when - _list.events().front()->when);
+ double yfrac = dx*slope;
+
+ vec[0] = _list.events().front()->value + slope * (lx - _list.events().front()->when);
+
+ for (i = 1; i < veclen; ++i) {
+ vec[i] = vec[i-1] + yfrac;
+ }
+
+ return;
+ }
+
+ if (_dirty) {
+ solve ();
+ }
+
+ rx = lx;
+
+ if (veclen > 1) {
+
+ dx = (hx - lx) / veclen;
+
+ for (i = 0; i < veclen; ++i, rx += dx) {
+ vec[i] = multipoint_eval (rx);
+ }
+ }
+}
+
+double
+Curve::unlocked_eval (double x)
+{
+ // I don't see the point of this...
+
+ if (_dirty) {
+ solve ();
+ }
+
+ return _list.unlocked_eval (x);
+}
+
+double
+Curve::multipoint_eval (double x)
+{
+ 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;
+ _list.events().front()->value;
+ }
+
+ if (range.second == _list.events().end()) {
+ /* we're after the last point */
+ return _list.events().back()->value;
+ }
+
+ double x2 = x * x;
+ ControlEvent* ev = *range.second;
+
+ return ev->coeff[0] + (ev->coeff[1] * x) + (ev->coeff[2] * x2) + (ev->coeff[3] * x2 * x);
+ }
+
+ /* 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
+
+extern "C" {
+
+void
+curve_get_vector_from_c (void *arg, double x0, double x1, float* vec, int32_t vecsize)
+{
+ static_cast<Evoral::Curve*>(arg)->get_vector (x0, x1, vec, vecsize);
+}
+
+}
diff --git a/libs/evoral/src/Event.cpp b/libs/evoral/src/Event.cpp
new file mode 100644
index 0000000000..71d1808628
--- /dev/null
+++ b/libs/evoral/src/Event.cpp
@@ -0,0 +1,107 @@
+/* This file is part of Evoral.
+ * Copyright (C) 2008 Dave Robillard <http://drobilla.net>
+ * Copyright (C) 2000-2008 Paul Davis
+ *
+ * Evoral 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.
+ *
+ * Evoral 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 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 St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include <evoral/Event.hpp>
+
+namespace Evoral {
+
+#ifdef EVENT_ALLOW_ALLOC
+Event::Event(double t, uint32_t s, uint8_t* b, bool owns_buffer)
+ : _time(t)
+ , _size(s)
+ , _buffer(b)
+ , _owns_buffer(owns_buffer)
+{
+ if (owns_buffer) {
+ _buffer = (uint8_t*)malloc(_size);
+ if (b) {
+ memcpy(_buffer, b, _size);
+ } else {
+ memset(_buffer, 0, _size);
+ }
+ }
+}
+
+Event::Event(const Event& copy, bool owns_buffer)
+ : _time(copy._time)
+ , _size(copy._size)
+ , _buffer(copy._buffer)
+ , _owns_buffer(owns_buffer)
+{
+ if (owns_buffer) {
+ _buffer = (uint8_t*)malloc(_size);
+ if (copy._buffer) {
+ memcpy(_buffer, copy._buffer, _size);
+ } else {
+ memset(_buffer, 0, _size);
+ }
+ }
+}
+
+Event::~Event() {
+ if (_owns_buffer) {
+ free(_buffer);
+ }
+}
+
+#endif // EVENT_ALLOW_ALLOC
+
+#ifdef EVENT_WITH_XML
+
+Event::Event(const XMLNode& event)
+{
+ string name = event.name();
+
+ if (name == "ControlChange") {
+
+ } else if (name == "ProgramChange") {
+
+ }
+}
+
+
+boost::shared_ptr<XMLNode>
+Event::to_xml() const
+{
+ XMLNode *result = 0;
+
+ switch (type()) {
+ case MIDI_CMD_CONTROL:
+ result = new XMLNode("ControlChange");
+ result->add_property("Channel", channel());
+ result->add_property("Control", cc_number());
+ result->add_property("Value", cc_value());
+ break;
+
+ case MIDI_CMD_PGM_CHANGE:
+ result = new XMLNode("ProgramChange");
+ result->add_property("Channel", channel());
+ result->add_property("Number", pgm_number());
+ break;
+
+ default:
+ // The implementation is continued as needed
+ break;
+ }
+
+ return boost::shared_ptr<XMLNode>(result);
+}
+#endif // EVENT_WITH_XML
+
+} // namespace MIDI
+
diff --git a/libs/evoral/src/Note.cpp b/libs/evoral/src/Note.cpp
new file mode 100644
index 0000000000..88be34fb36
--- /dev/null
+++ b/libs/evoral/src/Note.cpp
@@ -0,0 +1,103 @@
+/* This file is part of Evoral.
+ * Copyright (C) 2008 Dave Robillard <http://drobilla.net>
+ * Copyright (C) 2000-2008 Paul Davis
+ *
+ * Evoral 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.
+ *
+ * Evoral 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 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 St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include <iostream>
+#include <evoral/Note.hpp>
+
+namespace Evoral {
+
+Note::Note(uint8_t chan, double t, double d, uint8_t n, uint8_t v)
+ : _on_event(t, 3, NULL, true)
+ , _off_event(t + d, 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(duration() == d);
+ assert(note() == n);
+ assert(velocity() == v);
+ assert(_on_event.channel() == _off_event.channel());
+ assert(channel() == chan);
+}
+
+
+Note::Note(const Note& 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(note() == copy.note());
+ assert(velocity() == copy.velocity());
+ assert(duration() == copy.duration());
+ assert(_on_event.channel() == _off_event.channel());
+ assert(channel() == copy.channel());
+}
+
+
+Note::~Note()
+{
+}
+
+
+const Note&
+Note::operator=(const Note& copy)
+{
+ _on_event = copy._on_event;
+ _off_event = copy._off_event;
+ /*_on_event.time = copy._on_event.time;
+ assert(copy._on_event.size == 3);
+ memcpy(_on_event_buffer, copy._on_event_buffer, 3);
+
+ _off_event.time = copy._off_event.time;
+ assert(copy._off_event.size == 3);
+ memcpy(_off_event_buffer, copy._off_event_buffer, 3);
+ */
+
+ assert(time() == copy.time());
+ assert(end_time() == copy.end_time());
+ assert(note() == copy.note());
+ assert(velocity() == copy.velocity());
+ assert(duration() == copy.duration());
+ assert(_on_event.channel() == _off_event.channel());
+ assert(channel() == copy.channel());
+
+ return *this;
+}
+
+} // namespace Evoral
diff --git a/libs/evoral/src/Sequence.cpp b/libs/evoral/src/Sequence.cpp
new file mode 100644
index 0000000000..81502fdb5f
--- /dev/null
+++ b/libs/evoral/src/Sequence.cpp
@@ -0,0 +1,643 @@
+/* This file is part of Evoral.
+ * Copyright (C) 2008 Dave Robillard <http://drobilla.net>
+ * Copyright (C) 2000-2008 Paul Davis
+ *
+ * Evoral 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.
+ *
+ * Evoral 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 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 St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#define __STDC_LIMIT_MACROS 1
+
+#include <iostream>
+#include <algorithm>
+#include <stdexcept>
+#include <stdint.h>
+#include <evoral/Sequence.hpp>
+#include <evoral/ControlList.hpp>
+#include <evoral/Control.hpp>
+#include <evoral/ControlSet.hpp>
+#include <evoral/EventSink.hpp>
+#include <evoral/MIDIParameters.hpp>
+
+using namespace std;
+
+namespace Evoral {
+
+void Sequence::write_lock() {
+ _lock.writer_lock();
+ _control_lock.lock();
+}
+
+void Sequence::write_unlock() {
+ _lock.writer_unlock();
+ _control_lock.unlock();
+}
+
+void Sequence::read_lock() const {
+ _lock.reader_lock();
+}
+
+void Sequence::read_unlock() const {
+ _lock.reader_unlock();
+}
+
+// Read iterator (const_iterator)
+
+Sequence::const_iterator::const_iterator(const Sequence& seq, double t)
+ : _seq(&seq)
+ , _is_end( (t == DBL_MAX) || seq.empty() )
+ , _locked( !_is_end )
+{
+ //cerr << "Created MIDI iterator @ " << t << " (is end: " << _is_end << ")" << endl;
+
+ if (_is_end) {
+ return;
+ }
+
+ seq.read_lock();
+
+ _note_iter = seq.notes().end();
+ // find first note which begins after t
+ for (Sequence::Notes::const_iterator i = seq.notes().begin(); i != seq.notes().end(); ++i) {
+ if ((*i)->time() >= t) {
+ _note_iter = i;
+ break;
+ }
+ }
+
+ ControlIterator earliest_control(boost::shared_ptr<ControlList>(), DBL_MAX, 0.0);
+
+ _control_iters.reserve(seq.controls().size());
+
+ // find the earliest control event available
+ for (Controls::const_iterator i = seq.controls().begin(); i != seq.controls().end(); ++i) {
+ double x, y;
+ bool ret = i->second->list()->rt_safe_earliest_event_unlocked(t, DBL_MAX, x, y);
+ if (!ret) {
+ //cerr << "MIDI Iterator: CC " << i->first.id() << " (size " << i->second->list()->size()
+ // << ") has no events past " << t << endl;
+ continue;
+ }
+
+ assert(x >= 0);
+
+ if (y < i->first.min() || y > i->first.max()) {
+ cerr << "ERROR: Controller (" << i->first.type() << ") value '" << y
+ << "' out of range [" << i->first.min() << "," << i->first.max()
+ << "], event ignored" << endl;
+ continue;
+ }
+
+ const ControlIterator new_iter(i->second->list(), x, y);
+
+ //cerr << "MIDI Iterator: CC " << i->first.id() << " added (" << x << ", " << y << ")" << endl;
+ _control_iters.push_back(new_iter);
+
+ // if the x of the current control is less than earliest_control
+ // we have a new earliest_control
+ if (x < earliest_control.x) {
+ earliest_control = new_iter;
+ _control_iter = _control_iters.end();
+ --_control_iter;
+ // now _control_iter points to the last Element in _control_iters
+ }
+ }
+
+ if (_note_iter != seq.notes().end()) {
+ _event = boost::shared_ptr<Event>(new Event((*_note_iter)->on_event(), true));
+ }
+
+ double time = DBL_MAX;
+ // in case we have no notes in the region, we still want to get controller messages
+ if (_event.get()) {
+ time = _event->time();
+ // if the note is going to make it this turn, advance _note_iter
+ if (earliest_control.x > time) {
+ _active_notes.push(*_note_iter);
+ ++_note_iter;
+ }
+ }
+
+ // <=, because we probably would want to send control events first
+ if (earliest_control.list.get() && earliest_control.x <= time) {
+ seq.control_to_midi_event(_event, earliest_control);
+ } else {
+ _control_iter = _control_iters.end();
+ }
+
+ if ( (! _event.get()) || _event->size() == 0) {
+ //cerr << "Created MIDI iterator @ " << t << " is at end." << endl;
+ _is_end = true;
+
+ // eliminate possible race condition here (ugly)
+ static Glib::Mutex mutex;
+ Glib::Mutex::Lock lock(mutex);
+ if (_locked) {
+ _seq->read_unlock();
+ _locked = false;
+ }
+ } else {
+ //printf("New MIDI Iterator = %X @ %lf\n", _event->type(), _event->time());
+ }
+
+ assert(_is_end || (_event->buffer() && _event->buffer()[0] != '\0'));
+}
+
+Sequence::const_iterator::~const_iterator()
+{
+ if (_locked) {
+ _seq->read_unlock();
+ }
+}
+
+const Sequence::const_iterator& Sequence::const_iterator::operator++()
+{
+ if (_is_end) {
+ throw std::logic_error("Attempt to iterate past end of Sequence");
+ }
+
+ assert(_event->buffer() && _event->buffer()[0] != '\0');
+
+ /*cerr << "const_iterator::operator++: " << _event->to_string() << endl;*/
+
+ if (! (_event->is_note() || _event->is_cc() || _event->is_pgm_change() || _event->is_pitch_bender() || _event->is_channel_aftertouch()) ) {
+ cerr << "FAILED event buffer: " << hex << int(_event->buffer()[0]) << int(_event->buffer()[1]) << int(_event->buffer()[2]) << endl;
+ }
+ assert((_event->is_note() || _event->is_cc() || _event->is_pgm_change() || _event->is_pitch_bender() || _event->is_channel_aftertouch()));
+
+ // Increment past current control event
+ if (!_event->is_note() && _control_iter != _control_iters.end() && _control_iter->list.get()) {
+ double x = 0.0, y = 0.0;
+ const bool ret = _control_iter->list->rt_safe_earliest_event_unlocked(
+ _control_iter->x, DBL_MAX, x, y, false);
+
+ if (ret) {
+ _control_iter->x = x;
+ _control_iter->y = y;
+ } else {
+ _control_iter->list.reset();
+ _control_iter->x = DBL_MAX;
+ }
+ }
+
+ const std::vector<ControlIterator>::iterator old_control_iter = _control_iter;
+ _control_iter = _control_iters.begin();
+
+ // find the _control_iter with the earliest event time
+ for (std::vector<ControlIterator>::iterator i = _control_iters.begin();
+ i != _control_iters.end(); ++i) {
+ if (i->x < _control_iter->x) {
+ _control_iter = i;
+ }
+ }
+
+ enum Type {NIL, NOTE_ON, NOTE_OFF, AUTOMATION};
+
+ Type type = NIL;
+ double t = 0;
+
+ // Next earliest note on
+ if (_note_iter != _seq->notes().end()) {
+ type = NOTE_ON;
+ t = (*_note_iter)->time();
+ }
+
+ // Use the next earliest note off iff it's earlier than the note on
+ if (!_seq->percussive() && (! _active_notes.empty())) {
+ if (type == NIL || _active_notes.top()->end_time() <= (*_note_iter)->time()) {
+ type = NOTE_OFF;
+ t = _active_notes.top()->end_time();
+ }
+ }
+
+ // Use the next earliest controller iff it's earlier than the note event
+ if (_control_iter != _control_iters.end() && _control_iter->x != DBL_MAX /*&& _control_iter != old_control_iter */) {
+ if (type == NIL || _control_iter->x < t) {
+ type = AUTOMATION;
+ }
+ }
+
+ if (type == NOTE_ON) {
+ //cerr << "********** MIDI Iterator = note on" << endl;
+ *_event = (*_note_iter)->on_event();
+ _active_notes.push(*_note_iter);
+ ++_note_iter;
+ } else if (type == NOTE_OFF) {
+ //cerr << "********** MIDI Iterator = note off" << endl;
+ *_event = _active_notes.top()->off_event();
+ _active_notes.pop();
+ } else if (type == AUTOMATION) {
+ //cerr << "********** MIDI Iterator = Automation" << endl;
+ _seq->control_to_midi_event(_event, *_control_iter);
+ } else {
+ //cerr << "********** MIDI Iterator = End" << endl;
+ _is_end = true;
+ }
+
+ assert(_is_end || _event->size() > 0);
+
+ return *this;
+}
+
+bool Sequence::const_iterator::operator==(const const_iterator& other) const
+{
+ if (_is_end || other._is_end) {
+ return (_is_end == other._is_end);
+ } else {
+ return (_event == other._event);
+ }
+}
+
+Sequence::const_iterator& Sequence::const_iterator::operator=(const const_iterator& other)
+{
+ if (_locked && _seq != other._seq) {
+ _seq->read_unlock();
+ }
+
+ _seq = other._seq;
+ _active_notes = other._active_notes;
+ _is_end = other._is_end;
+ _locked = other._locked;
+ _note_iter = other._note_iter;
+ _control_iters = other._control_iters;
+ size_t index = other._control_iter - other._control_iters.begin();
+ _control_iter = _control_iters.begin() + index;
+
+ if (!_is_end) {
+ _event = boost::shared_ptr<Event>(new Event(*other._event, true));
+ }
+
+ return *this;
+}
+
+// Sequence
+
+Sequence::Sequence(size_t size)
+ : _read_iter(*this, DBL_MAX)
+ , _edited(false)
+ , _notes(size)
+ , _writing(false)
+ , _end_iter(*this, DBL_MAX)
+ , _next_read(UINT32_MAX)
+ , _percussive(false)
+{
+ assert(_end_iter._is_end);
+ assert( ! _end_iter._locked);
+}
+
+/** Read events in frame range \a start .. \a start+cnt into \a dst,
+ * adding \a offset to each event's timestamp.
+ * \return number of events written to \a dst
+ */
+size_t Sequence::read(EventSink& dst, timestamp_t start, timestamp_t nframes, timedur_t offset) const
+{
+ //cerr << this << " MM::read @ " << start << " frames: " << nframes << " -> " << stamp_offset << endl;
+ //cerr << this << " MM # notes: " << n_notes() << endl;
+
+ size_t read_events = 0;
+
+ if (start != _next_read) {
+ _read_iter = const_iterator(*this, (double)start);
+ //cerr << "Repositioning iterator from " << _next_read << " to " << start << endl;
+ } else {
+ //cerr << "Using cached iterator at " << _next_read << endl;
+ }
+
+ _next_read = start + nframes;
+
+ while (_read_iter != end() && _read_iter->time() < start + nframes) {
+ assert(_read_iter->size() > 0);
+ assert(_read_iter->buffer());
+ dst.write(_read_iter->time() + offset,
+ _read_iter->size(),
+ _read_iter->buffer());
+
+ /*cerr << this << " Sequence::read event @ " << _read_iter->time()
+ << " type: " << hex << int(_read_iter->type()) << dec
+ << " note: " << int(_read_iter->note())
+ << " velocity: " << int(_read_iter->velocity())
+ << endl;*/
+
+ ++_read_iter;
+ ++read_events;
+ }
+
+ return read_events;
+}
+
+/** Write the controller event pointed to by \a iter to \a ev.
+ * The buffer of \a ev will be allocated or resized as necessary.
+ * \return true on success
+ */
+bool
+Sequence::control_to_midi_event(boost::shared_ptr<Event>& ev, const ControlIterator& iter) const
+{
+ assert(iter.list.get());
+ if (!ev) {
+ ev = boost::shared_ptr<Event>(new Event(0, 3, NULL, true));
+ }
+
+ switch (iter.list->parameter().type()) {
+ case midi_cc_type:
+ assert(iter.list.get());
+ assert(iter.list->parameter().channel() < 16);
+ assert(iter.list->parameter().id() <= INT8_MAX);
+ assert(iter.y <= INT8_MAX);
+
+ ev->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_pc_type:
+ assert(iter.list.get());
+ assert(iter.list->parameter().channel() < 16);
+ assert(iter.list->parameter().id() == 0);
+ assert(iter.y <= INT8_MAX);
+
+ ev->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_pb_type:
+ assert(iter.list.get());
+ assert(iter.list->parameter().channel() < 16);
+ assert(iter.list->parameter().id() == 0);
+ assert(iter.y < (1<<14));
+
+ ev->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_ca_type:
+ assert(iter.list.get());
+ assert(iter.list->parameter().channel() < 16);
+ assert(iter.list->parameter().id() == 0);
+ assert(iter.y <= INT8_MAX);
+
+ ev->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.
+ */
+void Sequence::clear()
+{
+ _lock.writer_lock();
+ _notes.clear();
+ for (Controls::iterator li = _controls.begin(); li != _controls.end(); ++li)
+ li->second->list()->clear();
+ _next_read = 0;
+ _read_iter = end();
+ _lock.writer_unlock();
+}
+
+
+/** Begin a write of events to the model.
+ *
+ * If \a mode is Sustained, complete notes with duration 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 duration 0.
+ */
+void Sequence::start_write()
+{
+ //cerr << "MM " << this << " START WRITE, PERCUSSIVE = " << _percussive << endl;
+ write_lock();
+ _writing = true;
+ for (int i = 0; i < 16; ++i)
+ _write_notes[i].clear();
+
+ _dirty_controls.clear();
+ write_unlock();
+}
+
+/** 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 duration 0.
+ */
+void Sequence::end_write(bool delete_stuck)
+{
+ write_lock();
+ assert(_writing);
+
+ //cerr << "MM " << this << " END WRITE: " << _notes.size() << " NOTES\n";
+
+ if (!_percussive && delete_stuck) {
+ for (Notes::iterator n = _notes.begin(); n != _notes.end() ;) {
+ if ((*n)->duration() == 0) {
+ cerr << "WARNING: Stuck note lost: " << (*n)->note() << endl;
+ n = _notes.erase(n);
+ // we have to break here because erase invalidates the iterator
+ break;
+ } else {
+ ++n;
+ }
+ }
+ }
+
+ for (int i = 0; i < 16; ++i) {
+ if (!_write_notes[i].empty()) {
+ cerr << "WARNING: Sequence::end_write: Channel " << i << " has "
+ << _write_notes[i].size() << " stuck notes" << endl;
+ }
+ _write_notes[i].clear();
+ }
+
+ for (ControlLists::const_iterator i = _dirty_controls.begin(); i != _dirty_controls.end(); ++i) {
+ (*i)->mark_dirty();
+ }
+
+ _writing = false;
+ write_unlock();
+}
+
+/** Append \a ev to model. NOT realtime safe.
+ *
+ * Timestamps of events in \a buf are 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.
+ */
+void Sequence::append(const Event& ev)
+{
+ write_lock();
+ _edited = true;
+
+ assert(_notes.empty() || ev.time() >= _notes.back()->time());
+ assert(_writing);
+
+ if (ev.is_note_on()) {
+ append_note_on_unlocked(ev.channel(), ev.time(), ev.note(),
+ ev.velocity());
+ } else if (ev.is_note_off()) {
+ append_note_off_unlocked(ev.channel(), ev.time(), ev.note());
+ } else if (ev.is_cc()) {
+ append_control_unlocked(
+ Evoral::MIDI::ContinuousController(midi_cc_type, ev.cc_number(), ev.channel()),
+ ev.time(), ev.cc_value());
+ } else if (ev.is_pgm_change()) {
+ append_control_unlocked(
+ Evoral::MIDI::ProgramChange(midi_pc_type, ev.channel()),
+ ev.time(), ev.pgm_number());
+ } else if (ev.is_pitch_bender()) {
+ append_control_unlocked(
+ Evoral::MIDI::PitchBender(midi_pb_type, ev.channel()),
+ ev.time(), double( (0x7F & ev.pitch_bender_msb()) << 7
+ | (0x7F & ev.pitch_bender_lsb()) ));
+ } else if (ev.is_channel_aftertouch()) {
+ append_control_unlocked(
+ Evoral::MIDI::ChannelAftertouch(midi_ca_type, ev.channel()),
+ ev.time(), ev.channel_aftertouch());
+ } else {
+ printf("WARNING: Sequence: Unknown event type %X\n", ev.type());
+ }
+
+ write_unlock();
+}
+
+void Sequence::append_note_on_unlocked(uint8_t chan, double time,
+ uint8_t note_num, uint8_t velocity)
+{
+ /*cerr << "Sequence " << this << " chan " << (int)chan <<
+ " note " << (int)note_num << " on @ " << time << endl;*/
+
+ assert(note_num <= 127);
+ assert(chan < 16);
+ assert(_writing);
+ _edited = true;
+
+ boost::shared_ptr<Note> new_note(new Note(chan, time, 0, note_num, velocity));
+ _notes.push_back(new_note);
+ if (!_percussive) {
+ //cerr << "MM Sustained: Appending active note on " << (unsigned)(uint8_t)note_num << endl;
+ _write_notes[chan].push_back(_notes.size() - 1);
+ }/* else {
+ cerr << "MM Percussive: NOT appending active note on" << endl;
+ }*/
+}
+
+void Sequence::append_note_off_unlocked(uint8_t chan, double time,
+ uint8_t note_num)
+{
+ /*cerr << "Sequence " << this << " chan " << (int)chan <<
+ " note " << (int)note_num << " off @ " << time << endl;*/
+
+ assert(note_num <= 127);
+ assert(chan < 16);
+ assert(_writing);
+ _edited = true;
+
+ if (_percussive) {
+ cerr << "Sequence Ignoring note off (percussive mode)" << endl;
+ return;
+ }
+
+ /* FIXME: make _write_notes fixed size (127 noted) for speed */
+
+ /* FIXME: note off velocity for that one guy out there who actually has
+ * keys that send it */
+
+ bool resolved = false;
+
+ for (WriteNotes::iterator n = _write_notes[chan].begin(); n
+ != _write_notes[chan].end(); ++n) {
+ Note& note = *_notes[*n].get();
+ if (note.note() == note_num) {
+ assert(time >= note.time());
+ note.set_duration(time - note.time());
+ _write_notes[chan].erase(n);
+ //cerr << "MM resolved note, duration: " << note.duration() << endl;
+ resolved = true;
+ break;
+ }
+ }
+
+ if (!resolved) {
+ cerr << "Sequence " << this << " spurious note off chan " << (int)chan
+ << ", note " << (int)note_num << " @ " << time << endl;
+ }
+}
+
+void Sequence::append_control_unlocked(Parameter param, double time, double value)
+{
+ control(param, true)->list()->rt_add(time, value);
+}
+
+
+void Sequence::add_note_unlocked(const boost::shared_ptr<Note> note)
+{
+ //cerr << "Sequence " << this << " add note " << (int)note.note() << " @ " << note.time() << endl;
+ _edited = true;
+ Notes::iterator i = upper_bound(_notes.begin(), _notes.end(), note,
+ note_time_comparator);
+ _notes.insert(i, note);
+}
+
+void Sequence::remove_note_unlocked(const boost::shared_ptr<const Note> note)
+{
+ _edited = true;
+ //cerr << "Sequence " << this << " remove note " << (int)note.note() << " @ " << note.time() << endl;
+ for (Notes::iterator n = _notes.begin(); n != _notes.end(); ++n) {
+ Note& _n = *(*n);
+ const Note& _note = *note;
+ // TODO: There is still the issue, that after restarting ardour
+ // persisted undo does not work, because of rounding errors in the
+ // event times after saving/restoring to/from MIDI files
+ /*cerr << "======================================= " << endl;
+ cerr << int(_n.note()) << "@" << int(_n.time()) << "[" << int(_n.channel()) << "] --" << int(_n.duration()) << "-- #" << int(_n.velocity()) << endl;
+ cerr << int(_note.note()) << "@" << int(_note.time()) << "[" << int(_note.channel()) << "] --" << int(_note.duration()) << "-- #" << int(_note.velocity()) << endl;
+ cerr << "Equal: " << bool(_n == _note) << endl;
+ cerr << endl << endl;*/
+ if (_n == _note) {
+ _notes.erase(n);
+ // we have to break here, because erase invalidates all iterators, ie. n itself
+ break;
+ }
+ }
+}
+
+/** Slow! for debugging only. */
+#ifndef NDEBUG
+bool Sequence::is_sorted() const {
+ bool t = 0;
+ for (Notes::const_iterator n = _notes.begin(); n != _notes.end(); ++n)
+ if ((*n)->time() < t)
+ return false;
+ else
+ t = (*n)->time();
+
+ return true;
+}
+#endif
+
+} // namespace Evoral
+
diff --git a/libs/evoral/test/sequence.cpp b/libs/evoral/test/sequence.cpp
new file mode 100644
index 0000000000..ffe4c639f8
--- /dev/null
+++ b/libs/evoral/test/sequence.cpp
@@ -0,0 +1,12 @@
+#include <evoral/Sequence.hpp>
+
+using namespace Evoral;
+
+int
+main()
+{
+ Glib::thread_init();
+
+ Sequence s(100);
+ return 0;
+}