diff options
author | David Robillard <d@drobilla.net> | 2008-09-19 00:47:49 +0000 |
---|---|---|
committer | David Robillard <d@drobilla.net> | 2008-09-19 00:47:49 +0000 |
commit | d357eca668044badcb4bab318e2e74cfffa9a0b0 (patch) | |
tree | eab9bf33b194f9e37c20f84375e5caa748ee994a /libs/evoral | |
parent | 3d976c5b727e4d55ce439b1d7c055a814477fa1a (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/SConscript | 42 | ||||
-rw-r--r-- | libs/evoral/evoral/Control.hpp | 58 | ||||
-rw-r--r-- | libs/evoral/evoral/ControlList.hpp | 274 | ||||
-rw-r--r-- | libs/evoral/evoral/ControlSet.hpp | 69 | ||||
-rw-r--r-- | libs/evoral/evoral/Curve.hpp | 58 | ||||
-rw-r--r-- | libs/evoral/evoral/Event.hpp | 219 | ||||
-rw-r--r-- | libs/evoral/evoral/EventSink.hpp | 40 | ||||
-rw-r--r-- | libs/evoral/evoral/MIDIParameters.hpp | 52 | ||||
-rw-r--r-- | libs/evoral/evoral/Note.hpp | 79 | ||||
-rw-r--r-- | libs/evoral/evoral/Parameter.hpp | 135 | ||||
-rw-r--r-- | libs/evoral/evoral/Sequence.hpp | 215 | ||||
-rw-r--r-- | libs/evoral/evoral/midi_events.h | 133 | ||||
-rw-r--r-- | libs/evoral/evoral/types.hpp | 31 | ||||
-rw-r--r-- | libs/evoral/src/Control.cpp | 82 | ||||
-rw-r--r-- | libs/evoral/src/ControlList.cpp | 1310 | ||||
-rw-r--r-- | libs/evoral/src/ControlSet.cpp | 139 | ||||
-rw-r--r-- | libs/evoral/src/Curve.cpp | 401 | ||||
-rw-r--r-- | libs/evoral/src/Event.cpp | 107 | ||||
-rw-r--r-- | libs/evoral/src/Note.cpp | 103 | ||||
-rw-r--r-- | libs/evoral/src/Sequence.cpp | 643 | ||||
-rw-r--r-- | libs/evoral/test/sequence.cpp | 12 |
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; +} |