diff options
Diffstat (limited to 'libs')
70 files changed, 4505 insertions, 3106 deletions
diff --git a/libs/ardour/SConscript b/libs/ardour/SConscript index 4bdbf7d88a..b51e81c2ca 100644 --- a/libs/ardour/SConscript +++ b/libs/ardour/SConscript @@ -109,7 +109,6 @@ mix.cc mtc_slave.cc midi_clock_slave.cc named_selection.cc -note.cc onset_detector.cc panner.cc parameter.cc @@ -330,6 +329,7 @@ ardour.Merge ([ libraries['glibmm2'], libraries['lrdf'], libraries['midi++2'], + libraries['evoral'], libraries['pbd'], libraries['raptor'], libraries['samplerate'], diff --git a/libs/ardour/ardour/automatable.h b/libs/ardour/ardour/automatable.h index a2c1d98ae7..ce31721802 100644 --- a/libs/ardour/ardour/automatable.h +++ b/libs/ardour/ardour/automatable.h @@ -27,49 +27,33 @@ #include <ardour/automation_event.h> #include <ardour/automation_control.h> #include <ardour/parameter.h> +#include <evoral/ControlSet.hpp> namespace ARDOUR { class Session; class AutomationControl; -class Automatable : public SessionObject +class Automatable : public SessionObject, virtual public Evoral::ControlSet { public: Automatable(Session&, const std::string& name); virtual ~Automatable() {} - // shorthand for gain, pan, etc - inline boost::shared_ptr<AutomationControl> - control(AutomationType type, bool create_if_missing=false) { - return control(Parameter(type), create_if_missing); - } - - virtual boost::shared_ptr<AutomationControl> control(Parameter id, bool create_if_missing=false); - virtual boost::shared_ptr<const AutomationControl> control(Parameter id) const; + boost::shared_ptr<Evoral::Control> control_factory(boost::shared_ptr<Evoral::ControlList> list) const; + boost::shared_ptr<Evoral::ControlList> control_list_factory(const Evoral::Parameter& param) const; - boost::shared_ptr<AutomationControl> control_factory(boost::shared_ptr<AutomationList> list); + virtual void add_control(boost::shared_ptr<Evoral::Control>); - typedef std::map<Parameter,boost::shared_ptr<AutomationControl> > Controls; - Controls& controls() { return _controls; } - const Controls& controls() const { return _controls; } - - virtual void add_control(boost::shared_ptr<AutomationControl>); - virtual void automation_snapshot(nframes_t now, bool force); bool should_snapshot (nframes_t now) { return (_last_automation_snapshot > now || (now - _last_automation_snapshot) > _automation_interval); } virtual void transport_stopped(nframes_t now); - virtual bool find_next_event(nframes_t start, nframes_t end, ControlEvent& ev) const; - virtual string describe_parameter(Parameter param); - virtual float default_parameter_value(Parameter param) { return 1.0f; } - virtual void clear_automation(); - AutoState get_parameter_automation_state (Parameter param, bool lock = true); virtual void set_parameter_automation_state (Parameter param, AutoState); @@ -78,14 +62,11 @@ public: void protect_automation (); - void what_has_automation(std::set<Parameter>&) const; - void what_has_visible_automation(std::set<Parameter>&) const; + void what_has_visible_data(std::set<Parameter>&) const; const std::set<Parameter>& what_can_be_automated() const { return _can_automate_list; } void mark_automation_visible(Parameter, bool); - Glib::Mutex& automation_lock() const { return _automation_lock; } - static void set_automation_interval (jack_nframes_t frames) { _automation_interval = frames; } @@ -106,9 +87,6 @@ protected: int load_automation (const std::string& path); int old_set_automation_state(const XMLNode&); - mutable Glib::Mutex _automation_lock; - - Controls _controls; std::set<Parameter> _visible_controls; std::set<Parameter> _can_automate_list; diff --git a/libs/ardour/ardour/automation_control.h b/libs/ardour/ardour/automation_control.h index 68ac5797dc..c414f7bc40 100644 --- a/libs/ardour/ardour/automation_control.h +++ b/libs/ardour/ardour/automation_control.h @@ -24,6 +24,8 @@ #include <boost/shared_ptr.hpp> #include <pbd/controllable.h> #include <ardour/parameter.h> +#include <evoral/Control.hpp> +#include <ardour/automation_event.h> namespace ARDOUR { @@ -34,28 +36,42 @@ class Automatable; /** A PBD:Controllable with associated automation data (AutomationList) */ -class AutomationControl : public PBD::Controllable +class AutomationControl : public PBD::Controllable, public Evoral::Control { public: AutomationControl(ARDOUR::Session&, boost::shared_ptr<ARDOUR::AutomationList>, std::string name="unnamed controllable"); + + boost::shared_ptr<AutomationList> alist() { return boost::dynamic_pointer_cast<AutomationList>(_list); } + + void set_list(boost::shared_ptr<Evoral::ControlList>); + + inline bool automation_playback() const { + return ((ARDOUR::AutomationList*)_list.get())->automation_playback(); + } + + inline bool automation_write() const { + return ((ARDOUR::AutomationList*)_list.get())->automation_write(); + } + + inline AutoState automation_state() { + return ((ARDOUR::AutomationList*)_list.get())->automation_state(); + } + + inline void start_touch() { + return ((ARDOUR::AutomationList*)_list.get())->start_touch(); + } + + inline void stop_touch() { + return ((ARDOUR::AutomationList*)_list.get())->stop_touch(); + } void set_value(float val); float get_value() const; - float user_value() const; - - void set_list(boost::shared_ptr<ARDOUR::AutomationList>); - - boost::shared_ptr<ARDOUR::AutomationList> list() { return _list; } - boost::shared_ptr<const ARDOUR::AutomationList> list() const { return _list; } - - Parameter parameter() const; protected: - ARDOUR::Session& _session; - boost::shared_ptr<ARDOUR::AutomationList> _list; - float _user_value; + ARDOUR::Session& _session; }; diff --git a/libs/ardour/ardour/automation_event.h b/libs/ardour/ardour/automation_event.h index 4362b9c867..ed3379bc15 100644 --- a/libs/ardour/ardour/automation_event.h +++ b/libs/ardour/ardour/automation_event.h @@ -37,105 +37,29 @@ #include <ardour/ardour.h> #include <ardour/parameter.h> -namespace ARDOUR { - -class Curve; - -struct ControlEvent { - - ControlEvent (double w, double v) - : when (w), value (v), coeff (0) { - } +#include <evoral/ControlList.hpp> - 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; } +using Evoral::ControlEvent; - 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 -}; - -/* automation lists use a pool allocator that does not use a lock and - allocates 8k 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; +namespace ARDOUR { -class AutomationList : public PBD::StatefulDestructible +class AutomationList : public PBD::StatefulDestructible, public Evoral::ControlList { public: - typedef std::list<ControlEvent*,ControlEventAllocator> EventList; - typedef EventList::iterator iterator; - typedef EventList::reverse_iterator reverse_iterator; - typedef EventList::const_iterator const_iterator; - - AutomationList (Parameter id, double min_val, double max_val, double default_val); + AutomationList (Parameter id); AutomationList (const XMLNode&, Parameter id); ~AutomationList(); + virtual boost::shared_ptr<Evoral::ControlList> create(Evoral::Parameter id); + AutomationList (const AutomationList&); AutomationList (const AutomationList&, double start, double end); AutomationList& operator= (const AutomationList&); bool operator== (const AutomationList&); - - const Parameter& parameter() const { return _parameter; } - void set_parameter(Parameter p) { _parameter = p; } - + void freeze(); void thaw (); - - 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); - /* this should be private but old-school automation loading needs it in IO/IOProcessor */ - 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); - - AutomationList* cut (double, double); - AutomationList* copy (double, double); - void clear (double, double); - - AutomationList* cut (iterator, iterator); - AutomationList* copy (iterator, iterator); - void clear (iterator, iterator); - - bool paste (AutomationList&, double position, float times); + void mark_dirty () const; void set_automation_state (AutoState); AutoState automation_state() const { return _state; } @@ -151,157 +75,29 @@ class AutomationList : public PBD::StatefulDestructible bool automation_write () const { return (_state & Write) || ((_state & Touch) && _touching); } + + sigc::signal<void> StateChanged; + + static sigc::signal<void, AutomationList*> AutomationListCreated; + mutable sigc::signal<void> Dirty; void start_touch (); void stop_touch (); bool touching() const { return _touching; } - 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(); } - iterator end() { return _events.end(); } - - ControlEvent* back() { return _events.back(); } - ControlEvent* front() { return _events.front(); } - - const_iterator const_begin() const { return _events.begin(); } - const_iterator const_end() const { return _events.end(); } - - std::pair<AutomationList::iterator,AutomationList::iterator> control_points_adjacent (double when); - - template<class T> void apply_to_points (T& obj, void (T::*method)(const AutomationList&)) { - Glib::Mutex::Lock lm (_lock); - (obj.*method)(*this); - } - - sigc::signal<void> StateChanged; - XMLNode& get_state(void); int set_state (const XMLNode &s); XMLNode& state (bool full); XMLNode& serialize_events (); - 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<AutomationList::const_iterator,AutomationList::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<AutomationList::const_iterator,AutomationList::const_iterator> range; - }; - - static sigc::signal<void, AutomationList*> AutomationListCreated; - - const EventList& events() const { return _events; } - double default_value() const { return _default_value; } - - // teeny const violations for Curve - mutable sigc::signal<void> Dirty; - 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; } - - enum InterpolationStyle { - Discrete, - Linear, - Curved - }; - - InterpolationStyle interpolation() const { return _interpolation; } - void set_interpolation(InterpolationStyle style) { _interpolation = style; } - private: - - /** 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; - - AutomationList* cut_copy_clear (double, double, int op); - int deserialize_events (const XMLNode&); void maybe_signal_changed (); - void mark_dirty (); - 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; - AutoState _state; - AutoStyle _style; - bool _touching; - bool _new_touch; - double _max_xval; - double _min_yval; - double _max_yval; - double _default_value; - bool _sort_pending; - iterator _rt_insertion_point; - double _rt_pos; - - Curve* _curve; + AutoState _state; + AutoStyle _style; + bool _touching; }; } // namespace diff --git a/libs/ardour/ardour/midi_buffer.h b/libs/ardour/ardour/midi_buffer.h index 699f461b17..0c13199825 100644 --- a/libs/ardour/ardour/midi_buffer.h +++ b/libs/ardour/ardour/midi_buffer.h @@ -39,7 +39,7 @@ public: void copy(const MidiBuffer& copy); - bool push_back(const MIDI::Event& event); + bool push_back(const Evoral::Event& event); bool push_back(const jack_midi_event_t& event); uint8_t* reserve(double time, size_t size); @@ -50,7 +50,7 @@ public: struct iterator { iterator(MidiBuffer& b, size_t i) : buffer(b), index(i) {} - inline MIDI::Event& operator*() const { return buffer[index]; } + inline Evoral::Event& operator*() const { return buffer[index]; } inline iterator& operator++() { ++index; return *this; } // prefix inline bool operator!=(const iterator& other) const { return index != other.index; } @@ -61,7 +61,7 @@ public: struct const_iterator { const_iterator(const MidiBuffer& b, size_t i) : buffer(b), index(i) {} - inline const MIDI::Event& operator*() const { return buffer[index]; } + inline const Evoral::Event& operator*() const { return buffer[index]; } inline const_iterator& operator++() { ++index; return *this; } // prefix inline bool operator!=(const const_iterator& other) const { return index != other.index; } @@ -80,8 +80,8 @@ private: friend class iterator; friend class const_iterator; - const MIDI::Event& operator[](size_t i) const { assert(i < _size); return _events[i]; } - MIDI::Event& operator[](size_t i) { assert(i < _size); return _events[i]; } + const Evoral::Event& operator[](size_t i) const { assert(i < _size); return _events[i]; } + Evoral::Event& operator[](size_t i) { assert(i < _size); return _events[i]; } // FIXME: Eliminate this static const size_t MAX_EVENT_SIZE = 4; // bytes @@ -92,8 +92,8 @@ private: /* FIXME: this is utter crap. rewrite as a flat/packed buffer like MidiRingBuffer */ - MIDI::Event* _events; ///< Event structs that point to offsets in _data - uint8_t* _data; ///< MIDI, straight up. No time stamps. + Evoral::Event* _events; ///< Event structs that point to offsets in _data + uint8_t* _data; ///< MIDI, straight up. No time stamps. }; diff --git a/libs/ardour/ardour/midi_model.h b/libs/ardour/ardour/midi_model.h index 2481aa8d34..51bddfde44 100644 --- a/libs/ardour/ardour/midi_model.h +++ b/libs/ardour/ardour/midi_model.h @@ -31,84 +31,28 @@ #include <ardour/midi_buffer.h> #include <ardour/midi_ring_buffer.h> #include <ardour/automatable.h> -#include <ardour/note.h> #include <ardour/types.h> +#include <evoral/Note.hpp> +#include <evoral/Sequence.hpp> namespace ARDOUR { class Session; class MidiSource; -/** - * This class keeps track of the current x and y for a control - */ -class MidiControlIterator { -public: - boost::shared_ptr<const AutomationList> automation_list; - double x; - double y; - - MidiControlIterator(boost::shared_ptr<const AutomationList> a_list, - double a_x, - double a_y) - : automation_list(a_list) - , x(a_x) - , y(a_y) - {} -}; - - /** This is a higher level (than MidiBuffer) model of MIDI data, with separate * representations for notes (instead of just unassociated note on/off events) * and controller data. Controller data is represented as part of the * Automatable base (i.e. in a map of AutomationList, keyed by Parameter). + * Because of this MIDI controllers and automatable controllers/widgets/etc + * are easily interchangeable. */ -class MidiModel : public boost::noncopyable, public Automatable { +class MidiModel : public Automatable, public Evoral::Sequence { public: MidiModel(MidiSource* s, size_t size=0); - void write_lock(); - void write_unlock(); - - void read_lock() const; - void read_unlock() const; - - void clear(); - - NoteMode note_mode() const { return _note_mode; } - void set_note_mode(NoteMode mode) { _note_mode = mode; } - - void start_write(); - bool writing() const { return _writing; } - void end_write(bool delete_stuck=false); - - size_t read (MidiRingBuffer& dst, nframes_t start, nframes_t nframes, nframes_t stamp_offset, nframes_t negative_stamp_offset) const; - - /** Resizes vector if necessary (NOT realtime safe) */ - void append(const MIDI::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; } + NoteMode note_mode() const { return (percussive() ? Percussive : Sustained); } + void set_note_mode(NoteMode mode) { set_percussive(mode == Percussive); }; /** Add/Remove notes. * Technically all operations can be implemented as one of these. @@ -127,17 +71,17 @@ public: int set_state (const XMLNode&); XMLNode& get_state (); - void add(const boost::shared_ptr<Note> note); - void remove(const boost::shared_ptr<Note> note); + void add(const boost::shared_ptr<Evoral::Note> note); + void remove(const boost::shared_ptr<Evoral::Note> note); private: - XMLNode &marshal_note(const boost::shared_ptr<Note> note); - boost::shared_ptr<Note> unmarshal_note(XMLNode *xml_note); + XMLNode &marshal_note(const boost::shared_ptr<Evoral::Note> note); + boost::shared_ptr<Evoral::Note> unmarshal_note(XMLNode *xml_note); boost::shared_ptr<MidiModel> _model; const std::string _name; - typedef std::list< boost::shared_ptr<Note> > NoteList; + typedef std::list< boost::shared_ptr<Evoral::Note> > NoteList; NoteList _added_notes; NoteList _removed_notes; @@ -146,8 +90,6 @@ public: MidiModel::DeltaCommand* new_delta_command(const std::string name="midi edit"); void apply_command(Command* cmd); - bool edited() const { return _edited; } - void set_edited(bool yn) { _edited = yn; } bool write_to(boost::shared_ptr<MidiSource> source); // MidiModel doesn't use the normal AutomationList serialisation code @@ -157,90 +99,11 @@ public: sigc::signal<void> ContentsChanged; - /** Read iterator */ - class const_iterator { - public: - const_iterator(const MidiModel& model, double t); - ~const_iterator(); - - inline bool locked() const { return _locked; } - - const MIDI::Event& operator*() const { return *_event; } - const boost::shared_ptr<MIDI::Event> operator->() const { return _event; } - const boost::shared_ptr<MIDI::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 MidiModel; - - const MidiModel* _model; - boost::shared_ptr<MIDI::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<MidiControlIterator> _control_iters; - std::vector<MidiControlIterator>::iterator _control_iter; - }; - - const_iterator begin() const { return const_iterator(*this, 0); } - const const_iterator& end() const { return _end_iter; } - const MidiSource* midi_source() const { return _midi_source; } void set_midi_source(MidiSource* source) { _midi_source = source; } - bool control_to_midi_event(boost::shared_ptr<MIDI::Event>& ev, const MidiControlIterator& iter) const; private: friend class DeltaCommand; - void add_note_unlocked(const boost::shared_ptr<Note> note); - void remove_note_unlocked(const boost::shared_ptr<const Note> note); - - friend class const_iterator; - -#ifndef NDEBUG - bool is_sorted() const; -#endif - - 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_automation_event_unlocked(AutomationType type, uint8_t chan, double time, uint8_t first_byte, uint8_t second_byte); - void append_pgm_change_unlocked(uint8_t chan, double time, uint8_t number); - - mutable Glib::RWLock _lock; - - Notes _notes; - - NoteMode _note_mode; - - typedef std::vector<size_t> WriteNotes; - WriteNotes _write_notes[16]; - bool _writing; - bool _edited; - - typedef std::vector< boost::shared_ptr<const ARDOUR::AutomationList> > AutomationLists; - AutomationLists _dirty_automations; - - const const_iterator _end_iter; - - mutable nframes_t _next_read; - mutable const_iterator _read_iter; - - typedef std::priority_queue< - boost::shared_ptr<Note>, std::deque< boost::shared_ptr<Note> >, - LaterNoteEndComparator> - ActiveNotes; // We cannot use a boost::shared_ptr here to avoid a retain cycle MidiSource* _midi_source; diff --git a/libs/ardour/ardour/midi_region.h b/libs/ardour/ardour/midi_region.h index 44f775dc1c..66257372a9 100644 --- a/libs/ardour/ardour/midi_region.h +++ b/libs/ardour/ardour/midi_region.h @@ -80,10 +80,10 @@ class MidiRegion : public Region Controls& controls() { return midi_source()->model()->controls(); } const Controls& controls() const { return midi_source()->model()->controls(); } - boost::shared_ptr<AutomationControl> control(Parameter id, bool create_if_missing=false) + boost::shared_ptr<Evoral::Control> control(Evoral::Parameter id, bool create_if_missing=false) { return midi_source()->model()->control(id, create_if_missing); } - boost::shared_ptr<const AutomationControl> control(Parameter id) const + boost::shared_ptr<const Evoral::Control> control(Evoral::Parameter id) const { return midi_source()->model()->control(id); } int exportme (ARDOUR::Session&, ARDOUR::ExportSpecification&); diff --git a/libs/ardour/ardour/midi_ring_buffer.h b/libs/ardour/ardour/midi_ring_buffer.h index ff0be5c997..db0c3029a8 100644 --- a/libs/ardour/ardour/midi_ring_buffer.h +++ b/libs/ardour/ardour/midi_ring_buffer.h @@ -23,6 +23,7 @@ #include <algorithm> #include <ardour/types.h> #include <ardour/buffer.h> +#include <evoral/EventSink.hpp> namespace ARDOUR { @@ -243,7 +244,7 @@ MidiRingBufferBase<T>::write(size_t size, const T* src) * * [timestamp][size][size bytes of raw MIDI][timestamp][size][etc..] */ -class MidiRingBuffer : public MidiRingBufferBase<uint8_t> { +class MidiRingBuffer : public MidiRingBufferBase<uint8_t>, public Evoral::EventSink { public: /** @param size Size in bytes. */ @@ -251,8 +252,8 @@ public: : MidiRingBufferBase<uint8_t>(size), _channel_mask(0x0000FFFF) {} - size_t write(double time, size_t size, const uint8_t* buf); - bool read(double* time, size_t* size, uint8_t* buf); + size_t write(double time, uint32_t size, const uint8_t* buf); + bool read(double* time, uint32_t* size, uint8_t* buf); bool read_prefix(double* time, size_t* size); bool read_contents(size_t size, uint8_t* buf); @@ -292,7 +293,7 @@ private: inline bool -MidiRingBuffer::read(double* time, size_t* size, uint8_t* buf) +MidiRingBuffer::read(double* time, uint32_t* size, uint8_t* buf) { bool success = MidiRingBufferBase<uint8_t>::full_read(sizeof(double), (uint8_t*)time); @@ -333,7 +334,7 @@ MidiRingBuffer::read_contents(size_t size, uint8_t* buf) inline size_t -MidiRingBuffer::write(double time, size_t size, const uint8_t* buf) +MidiRingBuffer::write(double time, uint32_t size, const uint8_t* buf) { /*fprintf(stderr, "MRB %p write (t = %f) ", this, time); for (size_t i = 0; i < size; ++i) diff --git a/libs/ardour/ardour/midi_source.h b/libs/ardour/ardour/midi_source.h index 997f3f9d69..9cb222d207 100644 --- a/libs/ardour/ardour/midi_source.h +++ b/libs/ardour/ardour/midi_source.h @@ -58,7 +58,7 @@ class MidiSource : public Source virtual nframes_t midi_read (MidiRingBuffer& dst, nframes_t start, nframes_t cnt, nframes_t stamp_offset, nframes_t negative_stamp_offset) const; virtual nframes_t midi_write (MidiRingBuffer& src, nframes_t cnt); - virtual void append_event_unlocked(EventTimeUnit unit, const MIDI::Event& ev) = 0; + virtual void append_event_unlocked(EventTimeUnit unit, const Evoral::Event& ev) = 0; virtual void mark_for_remove() = 0; virtual void mark_streaming_midi_write_started (NoteMode mode, nframes_t start_time); diff --git a/libs/ardour/ardour/midi_track.h b/libs/ardour/ardour/midi_track.h index 6e4677df22..7eda6f904b 100644 --- a/libs/ardour/ardour/midi_track.h +++ b/libs/ardour/ardour/midi_track.h @@ -75,7 +75,7 @@ public: struct MidiControl : public AutomationControl { MidiControl(MidiTrack* route, boost::shared_ptr<AutomationList> al) - : AutomationControl (route->session(), al, al->parameter().to_string()) + : AutomationControl (route->session(), al, al->parameter().symbol()) , _route (route) {} diff --git a/libs/ardour/ardour/note.h b/libs/ardour/ardour/note.h deleted file mode 100644 index 0f649b3370..0000000000 --- a/libs/ardour/ardour/note.h +++ /dev/null @@ -1,82 +0,0 @@ -/* - Copyright (C) 2007 Paul Davis - Author: Dave Robillard - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 2 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. - -*/ - -#ifndef __ardour_note_h__ -#define __ardour_note_h__ - -#include <stdint.h> -#include <midi++/event.h> - -namespace ARDOUR { - - -/** A MIDI Note. - * - * A note is (unfortunately) special and not just another MIDI::Event as it - * has a duration and two separate MIDI events (on and off). - */ -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 MIDI::Event& on_event() { return _on_event; } - inline MIDI::Event& off_event() { return _off_event; } - - inline const MIDI::Event& on_event() const { return _on_event; } - inline const MIDI::Event& off_event() const { return _off_event; } - -private: - // Event buffers are self-contained - MIDI::Event _on_event; - MIDI::Event _off_event; -}; - - -} // namespace ARDOUR - -#endif /* __ardour_note_h__ */ diff --git a/libs/ardour/ardour/panner.h b/libs/ardour/ardour/panner.h index 2559eed003..1b85495d7a 100644 --- a/libs/ardour/ardour/panner.h +++ b/libs/ardour/ardour/panner.h @@ -101,14 +101,16 @@ class StreamPanner : public sigc::trackable, public PBD::Stateful float effective_y; float effective_z; - bool _muted; + bool _muted; struct PanControllable : public AutomationControl { PanControllable (Session& s, std::string name, StreamPanner& p, Parameter param) - : AutomationControl (s, boost::shared_ptr<AutomationList>(new AutomationList( - param, 0.0, 1.0, 0.5)), name) - , panner (p) { assert(param.type() != NullAutomation); } + : AutomationControl (s, + boost::shared_ptr<AutomationList>(new AutomationList(param)), name) + , panner (p) + { assert(param.type() != NullAutomation); } + AutomationList* alist() { return (AutomationList*)_list.get(); } StreamPanner& panner; void set_value (float); diff --git a/libs/ardour/ardour/parameter.h b/libs/ardour/ardour/parameter.h index a5dd9cdca9..dbcccd811f 100644 --- a/libs/ardour/ardour/parameter.h +++ b/libs/ardour/ardour/parameter.h @@ -24,140 +24,86 @@ #include <pbd/compose.h> #include <pbd/error.h> #include <ardour/types.h> +#include <evoral/Parameter.hpp> +#include <evoral/MIDIParameters.hpp> namespace ARDOUR { - /** ID of an automatable parameter. * * A given automatable object has a number of automatable parameters. This is * the unique ID for those parameters. Anything automatable (AutomationList, - * Curve) must have an ID unique with respect to it's Automatable parent. - * - * A parameter ID has two parts, a type and an int (only used by some types). + * Curve) must have unique Parameter ID with respect to it's Automatable parent. * - * This is a bit more ugly than it could be, due to using the existing/legacy - * ARDOUR::AutomationType: GainAutomation, PanAutomation, SoloAutomation, - * and MuteAutomation use only the type(), but PluginAutomation and - * MidiCCAutomation use the id() as port number and CC number, respectively. + * These are fast to compare, but passing a (const) reference around is + * probably more efficient than copying because the Parameter contains + * metadata not used for comparison. * - * Future types may use a string or URI or whatever, as long as these are - * comparable anything may be added. ints are best as these should be fast to - * copy and compare with one another. + * See evoral/Parameter.hpp for precise definition. */ -class Parameter +class Parameter : public Evoral::Parameter { public: Parameter(AutomationType type = NullAutomation, uint32_t id=0, uint8_t channel=0) - : _type(type), _id(id), _channel(channel) - {} - - Parameter(const std::string& str); - - inline AutomationType 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); + : Evoral::Parameter((uint32_t)type, id, channel) + { + init(type); } - /** Strict weak ordering - * (see: http://www.sgi.com/tech/stl/StrictWeakOrdering.html) - * This is necessary so that std::set works): - * 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 assuming the contrary, - * that f(x, z) does not hold. - * 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> - * </ol> - * </li> - * </ol> - */ - inline bool operator<(const Parameter& id) const { -#ifndef NDEBUG - if (_type == NullAutomation) - PBD::warning << "Uninitialized Parameter compared." << endmsg; -#endif - 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; + Parameter(AutomationType type, double min, double max, double normal) + : Evoral::Parameter((uint32_t)type, 0, 0, min, max, normal) + {} + + Parameter(const Evoral::Parameter& copy) + : Evoral::Parameter(copy) + { + init((AutomationType)_type); } - inline operator bool() const { return (_type != 0); } - - std::string to_string() const; - - /* The below properties are only used for CC right now, but unchanging properties - * of parameters (rather than changing parameters of automation lists themselves) - * should be moved here */ - - inline double min() const { - switch(_type) { + void init(AutomationType type) { + _normal = 0.0f; + switch(type) { + case NullAutomation: + case GainAutomation: + _min = 0.0f; + _max = 2.0f; + _normal = 1.0f; + break; + case PanAutomation: + _min = 0.0f; + _max = 1.0f; + _normal = 0.5f; + case PluginAutomation: + case SoloAutomation: + case MuteAutomation: + case FadeInAutomation: + case FadeOutAutomation: + case EnvelopeAutomation: + _min = 0.0f; + _max = 2.0f; + _normal = 1.0f; case MidiCCAutomation: + Evoral::MIDI::ContinuousController::set_range(*this); break; case MidiPgmChangeAutomation: + Evoral::MIDI::ProgramChange::set_range(*this); break; case MidiPitchBenderAutomation: + Evoral::MIDI::PitchBender::set_range(*this); break; case MidiChannelAftertouchAutomation: - return 0.0; - - default: - return DBL_MIN; + Evoral::MIDI::ChannelAftertouch::set_range(*this); break; } } - inline double max() const { - switch(_type) { - case MidiCCAutomation: - case MidiPgmChangeAutomation: - case MidiChannelAftertouchAutomation: - return 127.0; - case MidiPitchBenderAutomation: - return 16383.0; - - default: - return DBL_MAX; - } - } + Parameter(const std::string& str); + + inline AutomationType type() const { return (AutomationType)_type; } + + std::string symbol() const; inline bool is_integer() const { return (_type >= MidiCCAutomation && _type <= MidiChannelAftertouchAutomation); } -private: - // default copy constructor is ok - AutomationType _type; - uint32_t _id; - uint8_t _channel; + inline operator Parameter() { return (Parameter)*this; } }; diff --git a/libs/ardour/ardour/plugin_insert.h b/libs/ardour/ardour/plugin_insert.h index c19d113256..8db9fb14fe 100644 --- a/libs/ardour/ardour/plugin_insert.h +++ b/libs/ardour/ardour/plugin_insert.h @@ -77,7 +77,7 @@ class PluginInsert : public Processor void set_parameter (Parameter param, float val); float get_parameter (Parameter param); - float default_parameter_value (Parameter param); + float default_parameter_value (Evoral::Parameter param); struct PluginControl : public AutomationControl { diff --git a/libs/ardour/ardour/smf_source.h b/libs/ardour/ardour/smf_source.h index 88bf1e5d13..3217c8e3e8 100644 --- a/libs/ardour/ardour/smf_source.h +++ b/libs/ardour/ardour/smf_source.h @@ -26,6 +26,8 @@ #include <ardour/midi_source.h> +namespace Evoral { class Event; } + namespace ARDOUR { class MidiRingBuffer; @@ -71,7 +73,7 @@ class SMFSource : public MidiSource { void set_allow_remove_if_empty (bool yn); void mark_for_remove(); - void append_event_unlocked(EventTimeUnit unit, const MIDI::Event& ev); + void append_event_unlocked(EventTimeUnit unit, const Evoral::Event& ev); int flush_header (); int flush_footer (); diff --git a/libs/ardour/audio_track.cc b/libs/ardour/audio_track.cc index cd05e5fc86..532abeb123 100644 --- a/libs/ardour/audio_track.cc +++ b/libs/ardour/audio_track.cc @@ -604,9 +604,9 @@ AudioTrack::roll (nframes_t nframes, nframes_t start_frame, nframes_t end_frame, /* don't waste time with automation if we're recording or we've just stopped (yes it can happen) */ if (!diskstream->record_enabled() && _session.transport_rolling()) { - Glib::Mutex::Lock am (_automation_lock, Glib::TRY_LOCK); + Glib::Mutex::Lock am (_control_lock, Glib::TRY_LOCK); - if (am.locked() && gain_control()->list()->automation_playback()) { + if (am.locked() && gain_control()->automation_playback()) { apply_gain_automation = gain_control()->list()->curve().rt_safe_get_vector (start_frame, end_frame, _session.gain_automation_buffer(), nframes); } } @@ -702,7 +702,7 @@ AudioTrack::export_stuff (BufferSet& buffers, nframes_t start, nframes_t nframes } } - if (gain_control()->list()->automation_state() == Play) { + if (gain_control()->automation_state() == Play) { gain_control()->list()->curve().get_vector (start, start + nframes, gain_automation, nframes); diff --git a/libs/ardour/audioregion.cc b/libs/ardour/audioregion.cc index 271544297d..707f10e91a 100644 --- a/libs/ardour/audioregion.cc +++ b/libs/ardour/audioregion.cc @@ -77,9 +77,9 @@ AudioRegion::init () /* constructor for use by derived types only */ AudioRegion::AudioRegion (Session& s, nframes_t start, nframes_t length, string name) : Region (s, start, length, name, DataType::AUDIO) - , _fade_in (new AutomationList(Parameter(FadeInAutomation), 0.0, 2.0, 1.0)) - , _fade_out (new AutomationList(Parameter(FadeOutAutomation), 0.0, 2.0, 1.0)) - , _envelope (new AutomationList(Parameter(EnvelopeAutomation), 0.0, 2.0, 1.0)) + , _fade_in (new AutomationList(Parameter(FadeInAutomation))) + , _fade_out (new AutomationList(Parameter(FadeOutAutomation))) + , _envelope (new AutomationList(Parameter(EnvelopeAutomation))) { init (); } @@ -87,9 +87,9 @@ AudioRegion::AudioRegion (Session& s, nframes_t start, nframes_t length, string /** Basic AudioRegion constructor (one channel) */ AudioRegion::AudioRegion (boost::shared_ptr<AudioSource> src, nframes_t start, nframes_t length) : Region (src, start, length, PBD::basename_nosuffix(src->name()), DataType::AUDIO, 0, Region::Flag(Region::DefaultFlags|Region::External)) - , _fade_in (new AutomationList(Parameter(FadeInAutomation), 0.0, 2.0, 1.0)) - , _fade_out (new AutomationList(Parameter(FadeOutAutomation), 0.0, 2.0, 1.0)) - , _envelope (new AutomationList(Parameter(EnvelopeAutomation), 0.0, 2.0, 1.0)) + , _fade_in (new AutomationList(Parameter(FadeInAutomation))) + , _fade_out (new AutomationList(Parameter(FadeOutAutomation))) + , _envelope (new AutomationList(Parameter(EnvelopeAutomation))) { boost::shared_ptr<AudioFileSource> afs = boost::dynamic_pointer_cast<AudioFileSource> (src); if (afs) { @@ -102,9 +102,9 @@ AudioRegion::AudioRegion (boost::shared_ptr<AudioSource> src, nframes_t start, n /* Basic AudioRegion constructor (one channel) */ AudioRegion::AudioRegion (boost::shared_ptr<AudioSource> src, nframes_t start, nframes_t length, const string& name, layer_t layer, Flag flags) : Region (src, start, length, name, DataType::AUDIO, layer, flags) - , _fade_in (new AutomationList(Parameter(FadeInAutomation), 0.0, 2.0, 1.0)) - , _fade_out (new AutomationList(Parameter(FadeOutAutomation), 0.0, 2.0, 1.0)) - , _envelope (new AutomationList(Parameter(EnvelopeAutomation), 0.0, 2.0, 1.0)) + , _fade_in (new AutomationList(Parameter(FadeInAutomation))) + , _fade_out (new AutomationList(Parameter(FadeOutAutomation))) + , _envelope (new AutomationList(Parameter(EnvelopeAutomation))) { boost::shared_ptr<AudioFileSource> afs = boost::dynamic_pointer_cast<AudioFileSource> (src); if (afs) { @@ -117,9 +117,9 @@ AudioRegion::AudioRegion (boost::shared_ptr<AudioSource> src, nframes_t start, n /* Basic AudioRegion constructor (many channels) */ AudioRegion::AudioRegion (const SourceList& srcs, nframes_t start, nframes_t length, const string& name, layer_t layer, Flag flags) : Region (srcs, start, length, name, DataType::AUDIO, layer, flags) - , _fade_in (new AutomationList(Parameter(FadeInAutomation), 0.0, 2.0, 1.0)) - , _fade_out (new AutomationList(Parameter(FadeOutAutomation), 0.0, 2.0, 1.0)) - , _envelope (new AutomationList(Parameter(EnvelopeAutomation), 0.0, 2.0, 1.0)) + , _fade_in (new AutomationList(Parameter(FadeInAutomation))) + , _fade_out (new AutomationList(Parameter(FadeOutAutomation))) + , _envelope (new AutomationList(Parameter(EnvelopeAutomation))) { init (); listen_to_my_sources (); @@ -128,9 +128,9 @@ AudioRegion::AudioRegion (const SourceList& srcs, nframes_t start, nframes_t len /** Create a new AudioRegion, that is part of an existing one */ AudioRegion::AudioRegion (boost::shared_ptr<const AudioRegion> other, nframes_t offset, nframes_t length, const string& name, layer_t layer, Flag flags) : Region (other, offset, length, name, layer, flags) - , _fade_in (new AutomationList(Parameter(FadeInAutomation), 0.0, 2.0, 1.0)) - , _fade_out (new AutomationList(Parameter(FadeOutAutomation), 0.0, 2.0, 1.0)) - , _envelope (new AutomationList(Parameter(EnvelopeAutomation), 0.0, 2.0, 1.0)) + , _fade_in (new AutomationList(Parameter(FadeInAutomation))) + , _fade_out (new AutomationList(Parameter(FadeOutAutomation))) + , _envelope (new AutomationList(Parameter(EnvelopeAutomation))) { set<boost::shared_ptr<Source> > unique_srcs; @@ -180,9 +180,9 @@ AudioRegion::AudioRegion (boost::shared_ptr<const AudioRegion> other, nframes_t AudioRegion::AudioRegion (boost::shared_ptr<const AudioRegion> other) : Region (other) - , _fade_in (new AutomationList(Parameter(FadeInAutomation), 0.0, 2.0, 1.0)) - , _fade_out (new AutomationList(Parameter(FadeOutAutomation), 0.0, 2.0, 1.0)) - , _envelope (new AutomationList(Parameter(EnvelopeAutomation), 0.0, 2.0, 1.0)) + , _fade_in (new AutomationList(Parameter(FadeInAutomation))) + , _fade_out (new AutomationList(Parameter(FadeOutAutomation))) + , _envelope (new AutomationList(Parameter(EnvelopeAutomation))) { assert(_type == DataType::AUDIO); _scale_amplitude = other->_scale_amplitude; @@ -196,9 +196,9 @@ AudioRegion::AudioRegion (boost::shared_ptr<const AudioRegion> other) AudioRegion::AudioRegion (boost::shared_ptr<AudioSource> src, const XMLNode& node) : Region (src, node) - , _fade_in (new AutomationList(Parameter(FadeInAutomation), 0.0, 2.0, 1.0)) - , _fade_out (new AutomationList(Parameter(FadeOutAutomation), 0.0, 2.0, 1.0)) - , _envelope (new AutomationList(Parameter(EnvelopeAutomation), 0.0, 2.0, 1.0)) + , _fade_in (new AutomationList(Parameter(FadeInAutomation))) + , _fade_out (new AutomationList(Parameter(FadeOutAutomation))) + , _envelope (new AutomationList(Parameter(EnvelopeAutomation))) { boost::shared_ptr<AudioFileSource> afs = boost::dynamic_pointer_cast<AudioFileSource> (src); if (afs) { @@ -217,9 +217,9 @@ AudioRegion::AudioRegion (boost::shared_ptr<AudioSource> src, const XMLNode& nod AudioRegion::AudioRegion (SourceList& srcs, const XMLNode& node) : Region (srcs, node) - , _fade_in (new AutomationList(Parameter(FadeInAutomation), 0.0, 2.0, 1.0)) - , _fade_out (new AutomationList(Parameter(FadeOutAutomation), 0.0, 2.0, 1.0)) - , _envelope (new AutomationList(Parameter(EnvelopeAutomation), 0.0, 2.0, 1.0)) + , _fade_in (new AutomationList(Parameter(FadeInAutomation))) + , _fade_out (new AutomationList(Parameter(FadeOutAutomation))) + , _envelope (new AutomationList(Parameter(EnvelopeAutomation))) { init (); diff --git a/libs/ardour/automatable.cc b/libs/ardour/automatable.cc index cacebe59a4..349c09e136 100644 --- a/libs/ardour/automatable.cc +++ b/libs/ardour/automatable.cc @@ -92,7 +92,7 @@ Automatable::load_automation (const string& path) return 1; } - Glib::Mutex::Lock lm (_automation_lock); + Glib::Mutex::Lock lm (_control_lock); set<Parameter> tosave; _controls.clear (); @@ -108,7 +108,7 @@ Automatable::load_automation (const string& path) in >> value; if (!in) goto bad; /* FIXME: this is legacy and only used for plugin inserts? I think? */ - boost::shared_ptr<AutomationControl> c = control (Parameter(PluginAutomation, port), true); + boost::shared_ptr<Evoral::Control> c = control (Parameter(PluginAutomation, port), true); c->list()->add (when, value); tosave.insert (Parameter(PluginAutomation, port)); } @@ -122,12 +122,10 @@ Automatable::load_automation (const string& path) } void -Automatable::add_control(boost::shared_ptr<AutomationControl> ac) +Automatable::add_control(boost::shared_ptr<Evoral::Control> ac) { Parameter param = ac->parameter(); - _controls[param] = ac; - _can_automate_list.insert(param); // Sync everything (derived classes) up to initial values @@ -135,21 +133,9 @@ Automatable::add_control(boost::shared_ptr<AutomationControl> ac) } void -Automatable::what_has_automation (set<Parameter>& s) const -{ - Glib::Mutex::Lock lm (_automation_lock); - Controls::const_iterator li; - - // FIXME: correct semantics? - for (li = _controls.begin(); li != _controls.end(); ++li) { - s.insert ((*li).first); - } -} - -void -Automatable::what_has_visible_automation (set<Parameter>& s) const +Automatable::what_has_visible_data (set<Parameter>& s) const { - Glib::Mutex::Lock lm (_automation_lock); + Glib::Mutex::Lock lm (_control_lock); set<Parameter>::const_iterator li; for (li = _visible_controls.begin(); li != _visible_controls.end(); ++li) { @@ -157,42 +143,6 @@ Automatable::what_has_visible_automation (set<Parameter>& s) const } } -/** Returns NULL if we don't have an AutomationList for \a parameter. - */ -boost::shared_ptr<AutomationControl> -Automatable::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<AutomationList> al (new AutomationList ( - parameter, FLT_MIN, FLT_MAX, default_parameter_value (parameter))); - boost::shared_ptr<AutomationControl> ac(control_factory(al)); - add_control(ac); - return ac; - - } else { - //warning << "AutomationList " << parameter.to_string() << " not found for " << _name << endmsg; - return boost::shared_ptr<AutomationControl>(); - } -} - -boost::shared_ptr<const AutomationControl> -Automatable::control (Parameter parameter) const -{ - Controls::const_iterator i = _controls.find(parameter); - - if (i != _controls.end()) { - return i->second; - } else { - //warning << "AutomationList " << parameter.to_string() << " not found for " << _name << endmsg; - return boost::shared_ptr<AutomationControl>(); - } -} - string Automatable::describe_parameter (Parameter param) { @@ -213,7 +163,7 @@ Automatable::describe_parameter (Parameter param) } else if (param.type() == MidiChannelAftertouchAutomation) { return string_compose("Aftertouch [%1]", int(param.channel()) + 1); } else { - return param.to_string(); + return param.symbol(); } } @@ -237,37 +187,6 @@ Automatable::mark_automation_visible (Parameter what, bool yn) } } -bool -Automatable::find_next_event (nframes_t now, nframes_t end, ControlEvent& next_event) const -{ - Controls::const_iterator li; - - next_event.when = max_frames; - - for (li = _controls.begin(); li != _controls.end(); ++li) { - - AutomationList::const_iterator i; - boost::shared_ptr<const AutomationList> alist (li->second->list()); - ControlEvent cp (now, 0.0f); - - for (i = lower_bound (alist->const_begin(), alist->const_end(), &cp, AutomationList::time_comparator); - i != alist->const_end() && (*i)->when < end; ++i) { - if ((*i)->when > now) { - break; - } - } - - if (i != alist->const_end() && (*i)->when < end) { - - if ((*i)->when < next_event.when) { - next_event.when = (*i)->when; - } - } - } - - return next_event.when != max_frames; -} - /** \a legacy_param is used for loading legacy sessions where an object (IO, Panner) * had a single automation parameter, with it's type implicit. Derived objects should * pass that type and it will be used for the untyped AutomationList found. @@ -275,7 +194,7 @@ Automatable::find_next_event (nframes_t now, nframes_t end, ControlEvent& next_e int Automatable::set_automation_state (const XMLNode& node, Parameter legacy_param) { - Glib::Mutex::Lock lm (_automation_lock); + Glib::Mutex::Lock lm (_control_lock); /* Don't clear controls, since some may be special derived Controllable classes */ @@ -301,11 +220,11 @@ Automatable::set_automation_state (const XMLNode& node, Parameter legacy_param) if (!id_prop) { warning << "AutomationList node without automation-id property, " - << "using default: " << legacy_param.to_string() << endmsg; + << "using default: " << legacy_param.symbol() << endmsg; al->set_parameter(legacy_param); } - boost::shared_ptr<AutomationControl> existing = control(param); + boost::shared_ptr<Evoral::Control> existing = control(param); if (existing) existing->set_list(al); else @@ -324,7 +243,7 @@ Automatable::set_automation_state (const XMLNode& node, Parameter legacy_param) XMLNode& Automatable::get_automation_state () { - Glib::Mutex::Lock lm (_automation_lock); + Glib::Mutex::Lock lm (_control_lock); XMLNode* node = new XMLNode (X_("Automation")); if (_controls.empty()) { @@ -332,30 +251,24 @@ Automatable::get_automation_state () } for (Controls::iterator li = _controls.begin(); li != _controls.end(); ++li) { - node->add_child_nocopy (li->second->list()->get_state ()); + boost::shared_ptr<AutomationList> l + = boost::dynamic_pointer_cast<AutomationList>(li->second->list()); + node->add_child_nocopy (l->get_state ()); } return *node; } void -Automatable::clear_automation () -{ - Glib::Mutex::Lock lm (_automation_lock); - - for (Controls::iterator li = _controls.begin(); li != _controls.end(); ++li) - li->second->list()->clear(); -} - -void Automatable::set_parameter_automation_state (Parameter param, AutoState s) { - Glib::Mutex::Lock lm (_automation_lock); + Glib::Mutex::Lock lm (_control_lock); - boost::shared_ptr<AutomationControl> c = control (param, true); + boost::shared_ptr<Evoral::Control> c = control (param, true); + boost::shared_ptr<AutomationList> l = boost::dynamic_pointer_cast<AutomationList>(c->list()); - if (s != c->list()->automation_state()) { - c->list()->set_automation_state (s); + if (s != l->automation_state()) { + l->set_automation_state (s); _session.set_dirty (); } } @@ -366,15 +279,16 @@ Automatable::get_parameter_automation_state (Parameter param, bool lock) AutoState result = Off; if (lock) - _automation_lock.lock(); + _control_lock.lock(); - boost::shared_ptr<AutomationControl> c = control(param); + boost::shared_ptr<Evoral::Control> c = control(param); + boost::shared_ptr<AutomationList> l = boost::dynamic_pointer_cast<AutomationList>(c->list()); if (c) - result = c->list()->automation_state(); + result = l->automation_state(); if (lock) - _automation_lock.unlock(); + _control_lock.unlock(); return result; } @@ -382,12 +296,13 @@ Automatable::get_parameter_automation_state (Parameter param, bool lock) void Automatable::set_parameter_automation_style (Parameter param, AutoStyle s) { - Glib::Mutex::Lock lm (_automation_lock); + Glib::Mutex::Lock lm (_control_lock); - boost::shared_ptr<AutomationControl> c = control(param, true); + boost::shared_ptr<Evoral::Control> c = control(param, true); + boost::shared_ptr<AutomationList> l = boost::dynamic_pointer_cast<AutomationList>(c->list()); - if (s != c->list()->automation_style()) { - c->list()->set_automation_style (s); + if (s != l->automation_style()) { + l->set_automation_style (s); _session.set_dirty (); } } @@ -395,12 +310,13 @@ Automatable::set_parameter_automation_style (Parameter param, AutoStyle s) AutoStyle Automatable::get_parameter_automation_style (Parameter param) { - Glib::Mutex::Lock lm (_automation_lock); + Glib::Mutex::Lock lm (_control_lock); - boost::shared_ptr<AutomationControl> c = control(param); + boost::shared_ptr<Evoral::Control> c = control(param); + boost::shared_ptr<AutomationList> l = boost::dynamic_pointer_cast<AutomationList>(c->list()); if (c) { - return c->list()->automation_style(); + return l->automation_style(); } else { return Absolute; // whatever } @@ -409,20 +325,22 @@ Automatable::get_parameter_automation_style (Parameter param) void Automatable::protect_automation () { - set<Parameter> automated_params; + typedef set<Evoral::Parameter> ParameterSet; + ParameterSet automated_params; - what_has_automation (automated_params); + what_has_data (automated_params); - for (set<Parameter>::iterator i = automated_params.begin(); i != automated_params.end(); ++i) { + for (ParameterSet::iterator i = automated_params.begin(); i != automated_params.end(); ++i) { - boost::shared_ptr<AutomationControl> c = control(*i); + boost::shared_ptr<Evoral::Control> c = control(*i); + boost::shared_ptr<AutomationList> l = boost::dynamic_pointer_cast<AutomationList>(c->list()); - switch (c->list()->automation_state()) { + switch (l->automation_state()) { case Write: - c->list()->set_automation_state (Off); + l->set_automation_state (Off); break; case Touch: - c->list()->set_automation_state (Play); + l->set_automation_state (Play); break; default: break; @@ -436,8 +354,10 @@ Automatable::automation_snapshot (nframes_t now, bool force) if (force || _last_automation_snapshot > now || (now - _last_automation_snapshot) > _automation_interval) { for (Controls::iterator i = _controls.begin(); i != _controls.end(); ++i) { - if (i->second->list()->automation_write()) { - i->second->list()->rt_add (now, i->second->user_value()); + boost::shared_ptr<AutomationControl> c + = boost::dynamic_pointer_cast<AutomationControl>(i->second); + if (c->automation_write()) { + c->list()->rt_add (now, i->second->user_value()); } } @@ -450,29 +370,34 @@ Automatable::transport_stopped (nframes_t now) { for (Controls::iterator li = _controls.begin(); li != _controls.end(); ++li) { - boost::shared_ptr<AutomationControl> c = li->second; + boost::shared_ptr<AutomationControl> c + = boost::dynamic_pointer_cast<AutomationControl>(li->second); + boost::shared_ptr<AutomationList> l + = boost::dynamic_pointer_cast<AutomationList>(c->list()); c->list()->reposition_for_rt_add (now); - if (c->list()->automation_state() != Off) { + if (c->automation_state() != Off) { c->set_value(c->list()->eval(now)); } } } -/* FIXME: this probably doesn't belong here */ -boost::shared_ptr<AutomationControl> -Automatable::control_factory(boost::shared_ptr<AutomationList> list) +boost::shared_ptr<Evoral::Control> +Automatable::control_factory(boost::shared_ptr<Evoral::ControlList> list) const { - if ( - list->parameter().type() == MidiCCAutomation || - list->parameter().type() == MidiPgmChangeAutomation || - list->parameter().type() == MidiChannelAftertouchAutomation - ) { - // FIXME: this will die horribly if this is not a MidiTrack - return boost::shared_ptr<AutomationControl>(new MidiTrack::MidiControl((MidiTrack*)this, list)); + boost::shared_ptr<AutomationList> l = boost::dynamic_pointer_cast<AutomationList>(list); + assert(l); + if (l->parameter().type() >= MidiCCAutomation + && l->parameter().type() <= MidiChannelAftertouchAutomation) { + return boost::shared_ptr<Evoral::Control>(new MidiTrack::MidiControl((MidiTrack*)this, l)); } else { - return boost::shared_ptr<AutomationControl>(new AutomationControl(_session, list)); + return boost::shared_ptr<Evoral::Control>(new AutomationControl(_session, l)); } } +boost::shared_ptr<Evoral::ControlList> +Automatable::control_list_factory(const Evoral::Parameter& param) const +{ + return boost::shared_ptr<Evoral::ControlList>(new AutomationList(param)); +} diff --git a/libs/ardour/automation_control.cc b/libs/ardour/automation_control.cc index 4885a6fed9..a16306838a 100644 --- a/libs/ardour/automation_control.cc +++ b/libs/ardour/automation_control.cc @@ -30,10 +30,9 @@ using namespace PBD; AutomationControl::AutomationControl(Session& session, boost::shared_ptr<AutomationList> list, string name) - : Controllable((name == "unnamed controllable") ? list->parameter().to_string() : name) + : Controllable((name == "unnamed controllable") ? list->parameter().symbol() : name) + , Evoral::Control(list) , _session(session) - , _list(list) - , _user_value(list->default_value()) { } @@ -43,49 +42,27 @@ AutomationControl::AutomationControl(Session& session, boost::shared_ptr<Automat float AutomationControl::get_value() const { - if (_list->automation_playback()) - return _list->eval(_session.transport_frame()); - else - return _user_value; + bool from_list = ((AutomationList*)_list.get())->automation_playback(); + return Control::get_value(from_list, _session.transport_frame()); } void AutomationControl::set_value(float value) { - _user_value = value; + bool to_list = _session.transport_stopped() + && ((AutomationList*)_list.get())->automation_playback(); - if (_session.transport_stopped() && _list->automation_write()) - _list->add(_session.transport_frame(), value); + Control::set_value(value, to_list, _session.transport_frame()); Changed(); /* EMIT SIGNAL */ } -/** 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 -AutomationControl::user_value() const -{ - return _user_value; -} - - void -AutomationControl::set_list(boost::shared_ptr<ARDOUR::AutomationList> list) +AutomationControl::set_list(boost::shared_ptr<Evoral::ControlList> list) { - _list = list; - _user_value = list->default_value(); + Control::set_list(list); Changed(); /* EMIT SIGNAL */ } - -Parameter -AutomationControl::parameter() const -{ - return _list->parameter(); -} diff --git a/libs/ardour/automation_event.cc b/libs/ardour/automation_event.cc index af390953f4..2d98f1c27c 100644 --- a/libs/ardour/automation_event.cc +++ b/libs/ardour/automation_event.cc @@ -39,11 +39,6 @@ using namespace PBD; sigc::signal<void,AutomationList *> AutomationList::AutomationListCreated; -static bool sort_events_by_time (ControlEvent* a, ControlEvent* b) -{ - return a->when < b->when; -} - #if 0 static void dumpit (const AutomationList& al, string prefix = "") { @@ -56,92 +51,33 @@ static void dumpit (const AutomationList& al, string prefix = "") #endif /* XXX: min_val max_val redundant? (param.min() param.max()) */ -AutomationList::AutomationList (Parameter id, double min_val, double max_val, double default_val) - : _parameter(id) - , _interpolation(Linear) - , _curve(new Curve(*this)) -{ - _parameter = id; - _frozen = 0; - _changed_when_thawed = false; +AutomationList::AutomationList (Parameter id) + : ControlList(id) +{ _state = Off; _style = Absolute; - _min_yval = min_val; - _max_yval = max_val; _touching = false; - _max_xval = 0; // means "no limit" - _default_value = default_val; - _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; assert(_parameter.type() != NullAutomation); AutomationListCreated(this); } AutomationList::AutomationList (const AutomationList& other) - : _parameter(other._parameter) - , _interpolation(Linear) - , _curve(new Curve(*this)) + : ControlList(other) { - _frozen = 0; - _changed_when_thawed = false; - _style = other._style; - _min_yval = other._min_yval; - _max_yval = other._max_yval; - _max_xval = other._max_xval; - _default_value = other._default_value; _state = other._state; _touching = other._touching; - _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 (); + assert(_parameter.type() != NullAutomation); AutomationListCreated(this); } AutomationList::AutomationList (const AutomationList& other, double start, double end) - : _parameter(other._parameter) - , _interpolation(Linear) - , _curve(new Curve(*this)) + : ControlList(other) { - _frozen = 0; - _changed_when_thawed = false; _style = other._style; - _min_yval = other._min_yval; - _max_yval = other._max_yval; - _max_xval = other._max_xval; - _default_value = other._default_value; _state = other._state; _touching = other._touching; - _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 */ - - AutomationList* section = const_cast<AutomationList*>(&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)); - } - } - - delete section; - - mark_dirty (); assert(_parameter.type() != NullAutomation); AutomationListCreated(this); @@ -151,21 +87,11 @@ AutomationList::AutomationList (const AutomationList& other, double start, doubl * in or below the <AutomationList> node. It is used if \a id is non-null. */ AutomationList::AutomationList (const XMLNode& node, Parameter id) - : _interpolation(Linear) - , _curve(new Curve(*this)) + : ControlList(id) { - _frozen = 0; - _changed_when_thawed = false; _touching = false; - _min_yval = FLT_MIN; - _max_yval = FLT_MAX; - _max_xval = 0; // means "no limit" _state = Off; _style = Absolute; - _rt_insertion_point = _events.end(); - _lookup_cache.range.first = _events.end(); - _search_cache.range.first = _events.end(); - _sort_pending = false; set_state (node); @@ -179,10 +105,12 @@ AutomationList::AutomationList (const XMLNode& node, Parameter id) AutomationList::~AutomationList() { GoingAway (); - - for (EventList::iterator x = _events.begin(); x != _events.end(); ++x) { - delete (*x); - } +} + +boost::shared_ptr<Evoral::ControlList> +AutomationList::create(Evoral::Parameter id) +{ + return boost::shared_ptr<Evoral::ControlList>(new AutomationList(id)); } bool @@ -217,12 +145,10 @@ AutomationList::operator= (const AutomationList& other) void AutomationList::maybe_signal_changed () { - mark_dirty (); + ControlList::maybe_signal_changed (); - if (_frozen) { - _changed_when_thawed = true; - } else { - StateChanged (); + if (!_frozen) { + StateChanged (); /* EMIT SIGNAL */ } } @@ -248,390 +174,14 @@ void AutomationList::start_touch () { _touching = true; - _new_touch = true; + _new_value = true; } void AutomationList::stop_touch () { _touching = false; - _new_touch = false; -} - -void -AutomationList::clear () -{ - { - Glib::Mutex::Lock lm (_lock); - _events.clear (); - mark_dirty (); - } - - maybe_signal_changed (); -} - -void -AutomationList::x_scale (double factor) -{ - Glib::Mutex::Lock lm (_lock); - _x_scale (factor); -} - -bool -AutomationList::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 AutomationList::_x_scale (double factor) -{ - for (iterator i = _events.begin(); i != _events.end(); ++i) { - (*i)->when = floor ((*i)->when * factor); - } - - mark_dirty (); -} - -void -AutomationList::reposition_for_rt_add (double when) -{ - _rt_insertion_point = _events.end(); -} - -void -AutomationList::rt_add (double when, double value) -{ - /* this is for automation recording */ - - if ((_state & Touch) && !_touching) { - return; - } - - // 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_touch) { - 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_touch = false; - mark_dirty (); - } - - maybe_signal_changed (); -} - -void -AutomationList::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 -AutomationList::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 -AutomationList::erase (iterator i) -{ - { - Glib::Mutex::Lock lm (_lock); - _events.erase (i); - reposition_for_rt_add (0); - mark_dirty (); - } - maybe_signal_changed (); -} - -void -AutomationList::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 -AutomationList::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 -AutomationList::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 -AutomationList::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 (sort_events_by_time); - } else { - _sort_pending = true; - } - - mark_dirty (); - } - - maybe_signal_changed (); -} - -void -AutomationList::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 -AutomationList::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 (sort_events_by_time); - } else { - _sort_pending = true; - } - - mark_dirty (); - } - - maybe_signal_changed (); -} - -std::pair<AutomationList::iterator,AutomationList::iterator> -AutomationList::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; + _new_value = false; } void @@ -643,790 +193,20 @@ AutomationList::freeze () void AutomationList::thaw () { - if (_frozen == 0) { - PBD::stacktrace (cerr); - fatal << string_compose (_("programming error: %1"), X_("AutomationList::thaw() called while not frozen")) << endmsg; - /*NOTREACHED*/ - } - - if (--_frozen > 0) { - return; - } - - { - Glib::Mutex::Lock lm (_lock); - - if (_sort_pending) { - _events.sort (sort_events_by_time); - _sort_pending = false; - } - } + ControlList::thaw(); if (_changed_when_thawed) { StateChanged(); /* EMIT SIGNAL */ } } -void -AutomationList::set_max_xval (double x) -{ - _max_xval = x; -} - void -AutomationList::mark_dirty () +AutomationList::mark_dirty () const { - _lookup_cache.left = -1; - _search_cache.left = -1; + ControlList::mark_dirty (); Dirty (); /* EMIT SIGNAL */ } -void -AutomationList::truncate_end (double last_coordinate) -{ - { - Glib::Mutex::Lock lm (_lock); - ControlEvent cp (last_coordinate, 0); - AutomationList::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) { - AutomationList::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 -AutomationList::truncate_start (double overall_length) -{ - { - Glib::Mutex::Lock lm (_lock); - iterator i; - double first_legal_value; - double first_legal_coordinate; - - if (_events.empty()) { - fatal << _("programming error:") - << "AutomationList::truncate_start() called on an empty list" - << endmsg; - /*NOTREACHED*/ - return; - } - - 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()) { - AutomationList::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 -AutomationList::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 -AutomationList::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 -AutomationList::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 -AutomationList::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 -AutomationList::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 -AutomationList::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 -AutomationList::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; - } -} - -AutomationList* -AutomationList::cut (iterator start, iterator end) -{ - AutomationList* nal = new AutomationList (_parameter, _min_yval, _max_yval, _default_value); - - { - 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; -} - -AutomationList* -AutomationList::cut_copy_clear (double start, double end, int op) -{ - AutomationList* nal = new AutomationList (_parameter, _min_yval, _max_yval, _default_value); - 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; - -} - -AutomationList* -AutomationList::copy (iterator start, iterator end) -{ - AutomationList* nal = new AutomationList (_parameter, _min_yval, _max_yval, _default_value); - - { - 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; -} - -AutomationList* -AutomationList::cut (double start, double end) -{ - return cut_copy_clear (start, end, 0); -} - -AutomationList* -AutomationList::copy (double start, double end) -{ - return cut_copy_clear (start, end, 1); -} - -void -AutomationList::clear (double start, double end) -{ - (void) cut_copy_clear (start, end, 2); -} - -bool -AutomationList::paste (AutomationList& 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; -} - XMLNode& AutomationList::get_state () { @@ -1440,7 +220,7 @@ AutomationList::state (bool full) char buf[64]; LocaleGuard lg (X_("POSIX")); - root->add_property ("automation-id", _parameter.to_string()); + root->add_property ("automation-id", _parameter.symbol()); root->add_property ("id", _id.to_s()); diff --git a/libs/ardour/crossfade.cc b/libs/ardour/crossfade.cc index 213e7504b0..b6ca322c73 100644 --- a/libs/ardour/crossfade.cc +++ b/libs/ardour/crossfade.cc @@ -78,8 +78,8 @@ Crossfade::Crossfade (boost::shared_ptr<AudioRegion> in, boost::shared_ptr<Audio nframes_t position, AnchorPoint ap) : AudioRegion (in->session(), position, length, "foobar"), - _fade_in (Parameter(FadeInAutomation), 0.0, 2.0, 1.0), // linear (gain coefficient) => -inf..+6dB - _fade_out (Parameter(FadeOutAutomation), 0.0, 2.0, 1.0) // linear (gain coefficient) => -inf..+6dB + _fade_in (Parameter(FadeInAutomation)), // linear (gain coefficient) => -inf..+6dB + _fade_out (Parameter(FadeOutAutomation)) // linear (gain coefficient) => -inf..+6dB { _in = in; @@ -95,8 +95,8 @@ Crossfade::Crossfade (boost::shared_ptr<AudioRegion> in, boost::shared_ptr<Audio Crossfade::Crossfade (boost::shared_ptr<AudioRegion> a, boost::shared_ptr<AudioRegion> b, CrossfadeModel model, bool act) : AudioRegion (a->session(), 0, 0, "foobar"), - _fade_in (Parameter(FadeInAutomation), 0.0, 2.0, 1.0), // linear (gain coefficient) => -inf..+6dB - _fade_out (Parameter(FadeOutAutomation), 0.0, 2.0, 1.0) // linear (gain coefficient) => -inf..+6dB + _fade_in (Parameter(FadeInAutomation)), // linear (gain coefficient) => -inf..+6dB + _fade_out (Parameter(FadeOutAutomation)) // linear (gain coefficient) => -inf..+6dB { _in_update = false; _fixed = false; @@ -114,8 +114,8 @@ Crossfade::Crossfade (boost::shared_ptr<AudioRegion> a, boost::shared_ptr<AudioR Crossfade::Crossfade (const Playlist& playlist, XMLNode& node) : AudioRegion (playlist.session(), 0, 0, "foobar"), - _fade_in (Parameter(FadeInAutomation), 0.0, 2.0, 1.0), // linear (gain coefficient) => -inf..+6dB - _fade_out (Parameter(FadeOutAutomation), 0.0, 2.0, 1.0) // linear (gain coefficient) => -inf..+6dB + _fade_in (Parameter(FadeInAutomation)), // linear (gain coefficient) => -inf..+6dB + _fade_out (Parameter(FadeOutAutomation)) // linear (gain coefficient) => -inf..+6dB { boost::shared_ptr<Region> r; diff --git a/libs/ardour/gain.cc b/libs/ardour/gain.cc index 49596d6614..741ac2b57a 100644 --- a/libs/ardour/gain.cc +++ b/libs/ardour/gain.cc @@ -22,7 +22,7 @@ using namespace ARDOUR; Gain::Gain () - : AutomationList (Parameter(GainAutomation), 0.0, 2.0, 1.0f) /* XXX yuck; clamps gain to -inf .. +6db */ + : AutomationList (Parameter(GainAutomation)) /* XXX yuck; clamps gain to -inf .. +6db */ { } diff --git a/libs/ardour/import.cc b/libs/ardour/import.cc index d4afda8b5a..199a2fd1a4 100644 --- a/libs/ardour/import.cc +++ b/libs/ardour/import.cc @@ -305,7 +305,7 @@ static void write_midi_data_to_new_files (SMFReader* source, Session::import_status& status, vector<boost::shared_ptr<Source> >& newfiles) { - MIDI::Event ev(0.0, 4, NULL, true); + Evoral::Event ev(0.0, 4, NULL, true); status.progress = 0.0f; diff --git a/libs/ardour/io.cc b/libs/ardour/io.cc index 1b3c2e2378..69c74f5f03 100644 --- a/libs/ardour/io.cc +++ b/libs/ardour/io.cc @@ -137,7 +137,7 @@ IO::IO (Session& s, const string& name, deferred_state = 0; boost::shared_ptr<AutomationList> gl( - new AutomationList(Parameter(GainAutomation), 0.0, 2.0, 1.0)); + new AutomationList(Parameter(GainAutomation))); _gain_control = boost::shared_ptr<GainControl>( new GainControl(X_("gaincontrol"), *this, gl)); @@ -178,7 +178,7 @@ IO::IO (Session& s, const XMLNode& node, DataType dt) apply_gain_automation = false; boost::shared_ptr<AutomationList> gl( - new AutomationList(Parameter(GainAutomation), 0.0, 2.0, 1.0)); + new AutomationList(Parameter(GainAutomation))); _gain_control = boost::shared_ptr<GainControl>( new GainControl(X_("gaincontrol"), *this, gl)); @@ -1153,7 +1153,7 @@ IO::ensure_outputs (ChanCount count, bool clear, bool lockit, void* src) gain_t IO::effective_gain () const { - if (_gain_control->list()->automation_playback()) { + if (_gain_control->automation_playback()) { return _gain_control->get_value(); } else { return _desired_gain; @@ -2266,7 +2266,7 @@ IO::meter () void IO::clear_automation () { - Automatable::clear_automation (); // clears gain automation + Automatable::clear (); // clears gain automation _panner->clear_automation (); } @@ -2280,9 +2280,10 @@ IO::set_parameter_automation_state (Parameter param, AutoState state) bool changed = false; { - Glib::Mutex::Lock lm (_automation_lock); + Glib::Mutex::Lock lm (_control_lock); - boost::shared_ptr<AutomationList> gain_auto = _gain_control->list(); + boost::shared_ptr<AutomationList> gain_auto + = boost::dynamic_pointer_cast<AutomationList>(_gain_control->list()); if (state != gain_auto->automation_state()) { changed = true; @@ -2337,7 +2338,7 @@ IO::set_gain (gain_t val, void *src) _gain = val; } - if (_session.transport_stopped() && src != 0 && src != this && _gain_control->list()->automation_write()) { + if (_session.transport_stopped() && src != 0 && src != this && _gain_control->automation_write()) { _gain_control->list()->add (_session.transport_frame(), val); } @@ -2349,7 +2350,7 @@ void IO::start_pan_touch (uint32_t which) { if (which < _panner->size()) { - (*_panner)[which]->pan_control()->list()->start_touch(); + (*_panner)[which]->pan_control()->start_touch(); } } @@ -2357,7 +2358,7 @@ void IO::end_pan_touch (uint32_t which) { if (which < _panner->size()) { - (*_panner)[which]->pan_control()->list()->stop_touch(); + (*_panner)[which]->pan_control()->stop_touch(); } } @@ -2380,7 +2381,7 @@ IO::transport_stopped (nframes_t frame) { _gain_control->list()->reposition_for_rt_add (frame); - if (_gain_control->list()->automation_state() != Off) { + if (_gain_control->automation_state() != Off) { /* the src=0 condition is a special signal to not propagate automation gain changes into the mix group when locating. diff --git a/libs/ardour/jack_midi_port.cc b/libs/ardour/jack_midi_port.cc index 6d8e4c8c5d..d831864ec9 100644 --- a/libs/ardour/jack_midi_port.cc +++ b/libs/ardour/jack_midi_port.cc @@ -82,7 +82,7 @@ JackMidiPort::cycle_end (nframes_t nframes, nframes_t offset) jack_midi_clear_buffer (jack_buffer); for (MidiBuffer::iterator i = _buffer->begin(); i != _buffer->end(); ++i) { - const MIDI::Event& ev = *i; + const Evoral::Event& ev = *i; // event times should be frames, relative to cycle start assert(ev.time() >= 0); assert(ev.time() < nframes); diff --git a/libs/ardour/meter.cc b/libs/ardour/meter.cc index aedfd17be8..363cbe242f 100644 --- a/libs/ardour/meter.cc +++ b/libs/ardour/meter.cc @@ -47,7 +47,7 @@ PeakMeter::run_in_place (BufferSet& bufs, nframes_t start_frame, nframes_t end_f // GUI needs a better MIDI meter, not much information can be // expressed through peaks alone for (MidiBuffer::iterator i = bufs.get_midi(n).begin(); i != bufs.get_midi(n).end(); ++i) { - const MIDI::Event& ev = *i; + const Evoral::Event& ev = *i; if (ev.is_note_on()) { const float this_vel = log(ev.buffer()[2] / 127.0 * (M_E*M_E-M_E) + M_E) - 1.0; //printf("V %d -> %f\n", (int)((Byte)ev.buffer[2]), this_vel); diff --git a/libs/ardour/midi_buffer.cc b/libs/ardour/midi_buffer.cc index 1530babe34..afd8481795 100644 --- a/libs/ardour/midi_buffer.cc +++ b/libs/ardour/midi_buffer.cc @@ -74,10 +74,10 @@ MidiBuffer::resize (size_t size) _capacity = size; #ifdef NO_POSIX_MEMALIGN - _events = (MIDI::Event *) malloc(sizeof(MIDI::Event) * _capacity); + _events = (Evoral::Event *) malloc(sizeof(Evoral::Event) * _capacity); _data = (uint8_t *) malloc(sizeof(uint8_t) * _capacity * MAX_EVENT_SIZE); #else - posix_memalign((void**)&_events, CPU_CACHE_ALIGN, sizeof(MIDI::Event) * _capacity); + posix_memalign((void**)&_events, CPU_CACHE_ALIGN, sizeof(Evoral::Event) * _capacity); posix_memalign((void**)&_data, CPU_CACHE_ALIGN, sizeof(uint8_t) * _capacity * MAX_EVENT_SIZE); #endif assert(_data); @@ -115,7 +115,7 @@ MidiBuffer::read_from(const Buffer& src, nframes_t nframes, nframes_t offset) // FIXME: slow for (size_t i=0; i < msrc.size(); ++i) { - const MIDI::Event& ev = msrc[i]; + const Evoral::Event& ev = msrc[i]; if (ev.time() >= offset && ev.time() < offset+nframes) { //cout << "MidiBuffer::read_from got event, " << int(ev.type()) << " time: " << ev.time() << " buffer size: " << _size << endl; push_back(ev); @@ -136,7 +136,7 @@ MidiBuffer::read_from(const Buffer& src, nframes_t nframes, nframes_t offset) * @return false if operation failed (not enough room) */ bool -MidiBuffer::push_back(const MIDI::Event& ev) +MidiBuffer::push_back(const Evoral::Event& ev) { if (_size == _capacity) return false; @@ -223,7 +223,7 @@ MidiBuffer::silence(nframes_t dur, nframes_t offset) if (offset != 0) cerr << "WARNING: MidiBuffer::silence w/ offset != 0 (not implemented)" << endl; - memset(_events, 0, sizeof(MIDI::Event) * _capacity); + memset(_events, 0, sizeof(Evoral::Event) * _capacity); memset(_data, 0, sizeof(uint8_t) * _capacity * MAX_EVENT_SIZE); _size = 0; _silent = true; @@ -262,8 +262,8 @@ MidiBuffer::merge(const MidiBuffer& a, const MidiBuffer& b) push_back(b[b_index]); ++b_index; } else { - const MIDI::Event& a_ev = a[a_index]; - const MIDI::Event& b_ev = b[b_index]; + const Evoral::Event& a_ev = a[a_index]; + const Evoral::Event& b_ev = b[b_index]; if (a_ev.time() <= b_ev.time()) { push_back(a_ev); diff --git a/libs/ardour/midi_diskstream.cc b/libs/ardour/midi_diskstream.cc index 5b4716d51e..b2991c1a7f 100644 --- a/libs/ardour/midi_diskstream.cc +++ b/libs/ardour/midi_diskstream.cc @@ -520,7 +520,7 @@ MidiDiskstream::process (nframes_t transport_frame, nframes_t nframes, nframes_t MidiBuffer::iterator port_iter = _source_port->get_midi_buffer().begin(); for (size_t i=0; i < to_write; ++i) { - const MIDI::Event& ev = *port_iter; + const Evoral::Event& ev = *port_iter; assert(ev.buffer()); _capture_buf->write(ev.time() + transport_frame, ev.size(), ev.buffer()); ++port_iter; diff --git a/libs/ardour/midi_model.cc b/libs/ardour/midi_model.cc index b21bf38873..da0fa364b9 100644 --- a/libs/ardour/midi_model.cc +++ b/libs/ardour/midi_model.cc @@ -35,655 +35,15 @@ using namespace std; using namespace ARDOUR; -void MidiModel::write_lock() { - _lock.writer_lock(); - _automation_lock.lock(); -} - -void MidiModel::write_unlock() { - _lock.writer_unlock(); - _automation_lock.unlock(); -} - -void MidiModel::read_lock() const { - _lock.reader_lock(); - /*_automation_lock.lock();*/ -} - -void MidiModel::read_unlock() const { - _lock.reader_unlock(); - /*_automation_lock.unlock();*/ -} - -// Read iterator (const_iterator) - -MidiModel::const_iterator::const_iterator(const MidiModel& model, double t) - : _model(&model) - , _is_end( (t == DBL_MAX) || model.empty() ) - , _locked( !_is_end ) -{ - //cerr << "Created MIDI iterator @ " << t << " (is end: " << _is_end << ")" << endl; - - if (_is_end) { - return; - } - - model.read_lock(); - - _note_iter = model.notes().end(); - // find first note which begins after t - for (MidiModel::Notes::const_iterator i = model.notes().begin(); i != model.notes().end(); ++i) { - if ((*i)->time() >= t) { - _note_iter = i; - break; - } - } - - MidiControlIterator earliest_control(boost::shared_ptr<AutomationList>(), DBL_MAX, 0.0); - - _control_iters.reserve(model.controls().size()); - - // find the earliest control event available - for (Automatable::Controls::const_iterator i = model.controls().begin(); - i != model.controls().end(); ++i) { - - assert( - i->first.type() == MidiCCAutomation || - i->first.type() == MidiPgmChangeAutomation || - i->first.type() == MidiPitchBenderAutomation || - i->first.type() == MidiChannelAftertouchAutomation); - - 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.to_string() << ") value '" << y - << "' out of range [" << i->first.min() << "," << i->first.max() - << "], event ignored" << endl; - continue; - } - - const MidiControlIterator 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 != model.notes().end()) { - _event = boost::shared_ptr<MIDI::Event>(new MIDI::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.automation_list.get() && earliest_control.x <= time) { - model.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) { - _model->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')); -} - -MidiModel::const_iterator::~const_iterator() -{ - if (_locked) { - _model->read_unlock(); - } -} - -const MidiModel::const_iterator& MidiModel::const_iterator::operator++() -{ - if (_is_end) { - throw std::logic_error("Attempt to iterate past end of MidiModel"); - } - - 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->automation_list.get()) { - double x = 0.0, y = 0.0; - const bool ret = _control_iter->automation_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->automation_list.reset(); - _control_iter->x = DBL_MAX; - } - } - - const std::vector<MidiControlIterator>::iterator old_control_iter = _control_iter; - _control_iter = _control_iters.begin(); - - // find the _control_iter with the earliest event time - for (std::vector<MidiControlIterator>::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 != _model->notes().end()) { - type = NOTE_ON; - t = (*_note_iter)->time(); - } - - // Use the next earliest note off iff it's earlier than the note on - if (_model->note_mode() == Sustained && (! _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(); - cerr << "Event contents on note on: " << _event->to_string() << endl; - _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; - _model->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 MidiModel::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); - } -} - -MidiModel::const_iterator& MidiModel::const_iterator::operator=(const const_iterator& other) -{ - if (_locked && _model != other._model) { - _model->read_unlock(); - } - - _model = other._model; - _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<MIDI::Event>(new MIDI::Event(*other._event, true)); - } - - return *this; -} - -// MidiModel MidiModel::MidiModel(MidiSource *s, size_t size) - : Automatable(s->session(), "midi model") - , _notes(size) - , _note_mode(Sustained) - , _writing(false) - , _edited(false) - , _end_iter(*this, DBL_MAX) - , _next_read(UINT32_MAX) - , _read_iter(*this, DBL_MAX) + : ControlSet() + , Automatable(s->session(), "midi model") + , Sequence(size) , _midi_source(s) { - assert(_end_iter._is_end); - assert( ! _end_iter._locked); } -/** Read events in frame range \a start .. \a start+cnt into \a dst, - * adding \a stamp_offset to each event's timestamp. - * \return number of events written to \a dst - */ -size_t MidiModel::read(MidiRingBuffer& dst, nframes_t start, nframes_t nframes, - nframes_t stamp_offset, nframes_t negative_stamp_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() + stamp_offset - negative_stamp_offset, - _read_iter->size(), - _read_iter->buffer()); - - /*cerr << this << " MidiModel::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 -MidiModel::control_to_midi_event(boost::shared_ptr<MIDI::Event>& ev, const MidiControlIterator& iter) const -{ - assert(iter.automation_list.get()); - if (!ev) { - ev = boost::shared_ptr<MIDI::Event>(new MIDI::Event(0, 3, NULL, true)); - } - - switch (iter.automation_list->parameter().type()) { - case MidiCCAutomation: - assert(iter.automation_list.get()); - assert(iter.automation_list->parameter().channel() < 16); - assert(iter.automation_list->parameter().id() <= INT8_MAX); - assert(iter.y <= INT8_MAX); - - ev->time() = iter.x; - ev->realloc(3); - ev->buffer()[0] = MIDI_CMD_CONTROL + iter.automation_list->parameter().channel(); - ev->buffer()[1] = (uint8_t)iter.automation_list->parameter().id(); - ev->buffer()[2] = (uint8_t)iter.y; - break; - - case MidiPgmChangeAutomation: - assert(iter.automation_list.get()); - assert(iter.automation_list->parameter().channel() < 16); - assert(iter.automation_list->parameter().id() == 0); - assert(iter.y <= INT8_MAX); - - ev->time() = iter.x; - ev->realloc(2); - ev->buffer()[0] = MIDI_CMD_PGM_CHANGE + iter.automation_list->parameter().channel(); - ev->buffer()[1] = (uint8_t)iter.y; - break; - - case MidiPitchBenderAutomation: - assert(iter.automation_list.get()); - assert(iter.automation_list->parameter().channel() < 16); - assert(iter.automation_list->parameter().id() == 0); - assert(iter.y < (1<<14)); - - ev->time() = iter.x; - ev->realloc(3); - ev->buffer()[0] = MIDI_CMD_BENDER + iter.automation_list->parameter().channel(); - ev->buffer()[1] = uint16_t(iter.y) & 0x7F; // LSB - ev->buffer()[2] = (uint16_t(iter.y) >> 7) & 0x7F; // MSB - //cerr << "Pitch bender event: " << ev->to_string() << " value: " << ev->pitch_bender_value() << " original value: " << iter.y << std::endl; - break; - - case MidiChannelAftertouchAutomation: - assert(iter.automation_list.get()); - assert(iter.automation_list->parameter().channel() < 16); - assert(iter.automation_list->parameter().id() == 0); - assert(iter.y <= INT8_MAX); - - ev->time() = iter.x; - ev->realloc(2); - ev->buffer()[0] - = MIDI_CMD_CHANNEL_PRESSURE + iter.automation_list->parameter().channel(); - ev->buffer()[1] = (uint8_t)iter.y; - break; - - default: - return false; - } - - return true; -} - - -/** Clear all events from the model. - */ -void MidiModel::clear() -{ - _lock.writer_lock(); - _notes.clear(); - clear_automation(); - _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 MidiModel::start_write() -{ - //cerr << "MM " << this << " START WRITE, MODE = " << enum_2_string(_note_mode) << endl; - write_lock(); - _writing = true; - for (int i = 0; i < 16; ++i) - _write_notes[i].clear(); - - _dirty_automations.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 MidiModel::end_write(bool delete_stuck) -{ - write_lock(); - assert(_writing); - - //cerr << "MM " << this << " END WRITE: " << _notes.size() << " NOTES\n"; - - if (_note_mode == Sustained && 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: MidiModel::end_write: Channel " << i << " has " - << _write_notes[i].size() << " stuck notes" << endl; - } - _write_notes[i].clear(); - } - - for (AutomationLists::const_iterator i = _dirty_automations.begin(); i != _dirty_automations.end(); ++i) { - (*i)->Dirty.emit(); - (*i)->lookup_cache().left = -1; - (*i)->search_cache().left = -1; - } - - _writing = false; - write_unlock(); -} - -/** Append \a in_event 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 MidiModel::append(const MIDI::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_automation_event_unlocked(MidiCCAutomation, ev.channel(), - ev.time(), ev.cc_number(), ev.cc_value()); - } else if (ev.is_pgm_change()) { - append_automation_event_unlocked(MidiPgmChangeAutomation, ev.channel(), - ev.time(), ev.pgm_number(), 0); - } else if (ev.is_pitch_bender()) { - append_automation_event_unlocked(MidiPitchBenderAutomation, - ev.channel(), ev.time(), ev.pitch_bender_lsb(), - ev.pitch_bender_msb()); - } else if (ev.is_channel_aftertouch()) { - append_automation_event_unlocked(MidiChannelAftertouchAutomation, - ev.channel(), ev.time(), ev.channel_aftertouch(), 0); - } else { - printf("WARNING: MidiModel: Unknown event type %X\n", ev.type()); - } - - write_unlock(); -} - -void MidiModel::append_note_on_unlocked(uint8_t chan, double time, - uint8_t note_num, uint8_t velocity) -{ - /*cerr << "MidiModel " << 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 (_note_mode == Sustained) { - //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 MidiModel::append_note_off_unlocked(uint8_t chan, double time, - uint8_t note_num) -{ - /*cerr << "MidiModel " << this << " chan " << (int)chan << - " note " << (int)note_num << " off @ " << time << endl;*/ - - assert(note_num <= 127); - assert(chan < 16); - assert(_writing); - _edited = true; - - if (_note_mode == Percussive) { - cerr << "MidiModel 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 << "MidiModel " << this << " spurious note off chan " << (int)chan - << ", note " << (int)note_num << " @ " << time << endl; - } -} - -void MidiModel::append_automation_event_unlocked(AutomationType type, - uint8_t chan, double time, uint8_t first_byte, uint8_t second_byte) -{ - //cerr << "MidiModel " << this << " chan " << (int)chan << - // " CC " << (int)number << " = " << (int)value << " @ " << time << endl; - - assert(chan < 16); - assert(_writing); - _edited = true; - double value; - - uint32_t id = 0; - - switch (type) { - case MidiCCAutomation: - id = first_byte; - value = double(second_byte); - break; - case MidiChannelAftertouchAutomation: - case MidiPgmChangeAutomation: - id = 0; - value = double(first_byte); - break; - case MidiPitchBenderAutomation: - id = 0; - value = double((0x7F & second_byte) << 7 | (0x7F & first_byte)); - break; - default: - assert(false); - } - - Parameter param(type, id, chan); - boost::shared_ptr<AutomationControl> control = Automatable::control(param, true); - control->list()->rt_add(time, value); -} - -void MidiModel::add_note_unlocked(const boost::shared_ptr<Note> note) -{ - //cerr << "MidiModel " << 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 MidiModel::remove_note_unlocked(const boost::shared_ptr<const Note> note) -{ - _edited = true; - //cerr << "MidiModel " << 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 MidiModel::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 - /** Start a new command. * * This has no side-effects on the model or Session, the returned command @@ -701,7 +61,8 @@ MidiModel::DeltaCommand* MidiModel::new_delta_command(const string name) * Ownership of cmd is taken, it must not be deleted by the caller. * The command will constitute one item on the undo stack. */ -void MidiModel::apply_command(Command* cmd) +void +MidiModel::apply_command(Command* cmd) { _session.begin_reversible_command(cmd->name()); (*cmd)(); @@ -710,7 +71,8 @@ void MidiModel::apply_command(Command* cmd) _edited = true; } -// MidiEditCommand + +// DeltaCommand MidiModel::DeltaCommand::DeltaCommand(boost::shared_ptr<MidiModel> m, const std::string& name) @@ -727,21 +89,24 @@ MidiModel::DeltaCommand::DeltaCommand(boost::shared_ptr<MidiModel> m, set_state(node); } -void MidiModel::DeltaCommand::add(const boost::shared_ptr<Note> note) +void +MidiModel::DeltaCommand::add(const boost::shared_ptr<Evoral::Note> note) { //cerr << "MEC: apply" << endl; _removed_notes.remove(note); _added_notes.push_back(note); } -void MidiModel::DeltaCommand::remove(const boost::shared_ptr<Note> note) +void +MidiModel::DeltaCommand::remove(const boost::shared_ptr<Evoral::Note> note) { //cerr << "MEC: remove" << endl; _added_notes.remove(note); _removed_notes.push_back(note); } -void MidiModel::DeltaCommand::operator()() +void +MidiModel::DeltaCommand::operator()() { // This could be made much faster by using a priority_queue for added and // removed notes (or sort here), and doing a single iteration over _model @@ -763,10 +128,10 @@ void MidiModel::DeltaCommand::operator()() _model->write_lock(); - for (std::list< boost::shared_ptr<Note> >::iterator i = _added_notes.begin(); i != _added_notes.end(); ++i) + for (std::list< boost::shared_ptr<Evoral::Note> >::iterator i = _added_notes.begin(); i != _added_notes.end(); ++i) _model->add_note_unlocked(*i); - for (std::list< boost::shared_ptr<Note> >::iterator i = _removed_notes.begin(); i != _removed_notes.end(); ++i) + for (std::list< boost::shared_ptr<Evoral::Note> >::iterator i = _removed_notes.begin(); i != _removed_notes.end(); ++i) _model->remove_note_unlocked(*i); _model->write_unlock(); @@ -778,7 +143,8 @@ void MidiModel::DeltaCommand::operator()() _model->ContentsChanged(); /* EMIT SIGNAL */ } -void MidiModel::DeltaCommand::undo() +void +MidiModel::DeltaCommand::undo() { // This could be made much faster by using a priority_queue for added and // removed notes (or sort here), and doing a single iteration over _model @@ -800,11 +166,11 @@ void MidiModel::DeltaCommand::undo() _model->write_lock(); - for (std::list< boost::shared_ptr<Note> >::iterator i = _added_notes.begin(); i + for (std::list< boost::shared_ptr<Evoral::Note> >::iterator i = _added_notes.begin(); i != _added_notes.end(); ++i) _model->remove_note_unlocked(*i); - for (std::list< boost::shared_ptr<Note> >::iterator i = + for (std::list< boost::shared_ptr<Evoral::Note> >::iterator i = _removed_notes.begin(); i != _removed_notes.end(); ++i) _model->add_note_unlocked(*i); @@ -817,7 +183,8 @@ void MidiModel::DeltaCommand::undo() _model->ContentsChanged(); /* EMIT SIGNAL */ } -XMLNode & MidiModel::DeltaCommand::marshal_note(const boost::shared_ptr<Note> note) +XMLNode& +MidiModel::DeltaCommand::marshal_note(const boost::shared_ptr<Evoral::Note> note) { XMLNode *xml_note = new XMLNode("note"); ostringstream note_str(ios::ate); @@ -843,7 +210,7 @@ XMLNode & MidiModel::DeltaCommand::marshal_note(const boost::shared_ptr<Note> no return *xml_note; } -boost::shared_ptr<Note> MidiModel::DeltaCommand::unmarshal_note(XMLNode *xml_note) +boost::shared_ptr<Evoral::Note> MidiModel::DeltaCommand::unmarshal_note(XMLNode *xml_note) { unsigned int note; istringstream note_str(xml_note->property("note")->value()); @@ -865,7 +232,7 @@ boost::shared_ptr<Note> MidiModel::DeltaCommand::unmarshal_note(XMLNode *xml_not istringstream velocity_str(xml_note->property("velocity")->value()); velocity_str >> velocity; - boost::shared_ptr<Note> note_ptr(new Note(channel, time, duration, note, velocity)); + boost::shared_ptr<Evoral::Note> note_ptr(new Evoral::Note(channel, time, duration, note, velocity)); return note_ptr; } @@ -913,8 +280,8 @@ XMLNode& MidiModel::DeltaCommand::get_state() } struct EventTimeComparator { - typedef const MIDI::Event* value_type; - inline bool operator()(const MIDI::Event& a, const MIDI::Event& b) const { + typedef const Evoral::Event* value_type; + inline bool operator()(const Evoral::Event& a, const Evoral::Event& b) const { return a.time() >= b.time(); } }; @@ -930,14 +297,14 @@ bool MidiModel::write_to(boost::shared_ptr<MidiSource> source) { read_lock(); - const NoteMode old_note_mode = _note_mode; - _note_mode = Sustained; + const bool old_percussive = percussive(); + set_percussive(false); for (const_iterator i = begin(); i != end(); ++i) { source->append_event_unlocked(Frames, *i); } - _note_mode = old_note_mode; + set_percussive(old_percussive); read_unlock(); _edited = false; diff --git a/libs/ardour/midi_source.cc b/libs/ardour/midi_source.cc index 4e83413c13..d6d27a1f89 100644 --- a/libs/ardour/midi_source.cc +++ b/libs/ardour/midi_source.cc @@ -106,7 +106,7 @@ MidiSource::midi_read (MidiRingBuffer& dst, nframes_t start, nframes_t cnt, nfra Glib::Mutex::Lock lm (_lock); if (_model) { //const size_t n_events = - _model->read(dst, start, cnt, stamp_offset, negative_stamp_offset); + _model->read(dst, start, cnt, stamp_offset - negative_stamp_offset); //cout << "Read " << n_events << " events from model." << endl; return cnt; } else { diff --git a/libs/ardour/midi_stretch.cc b/libs/ardour/midi_stretch.cc index f33c58a0fd..c11963c9d5 100644 --- a/libs/ardour/midi_stretch.cc +++ b/libs/ardour/midi_stretch.cc @@ -91,7 +91,7 @@ MidiStretch::run (boost::shared_ptr<Region> r) const double new_time = i->time() * _request.time_fraction; // FIXME: double copy - MIDI::Event ev = MIDI::Event(*i, true); + Evoral::Event ev = Evoral::Event(*i, true); ev.time() = new_time; new_model->append(ev); } diff --git a/libs/ardour/midi_track.cc b/libs/ardour/midi_track.cc index 4588d6f5b9..c1203d7ba9 100644 --- a/libs/ardour/midi_track.cc +++ b/libs/ardour/midi_track.cc @@ -590,7 +590,7 @@ MidiTrack::write_controller_messages(MidiBuffer& output_buf, nframes_t start_fra uint8_t buf[3]; // CC = 3 bytes buf[0] = MIDI_CMD_CONTROL; - MIDI::Event ev(0, 3, buf, false); + Evoral::Event ev(0, 3, buf, false); // Write track controller automation // This now lives in MidiModel. Any need for track automation like this? @@ -733,7 +733,7 @@ MidiTrack::MidiControl::set_value(float val) assert(val <= _list->parameter().max()); size_t size = 3; - if ( ! _list->automation_playback()) { + if ( ! automation_playback()) { uint8_t ev[3] = { _list->parameter().channel(), int(val), 0 }; switch(_list->parameter().type()) { case MidiCCAutomation: diff --git a/libs/ardour/panner.cc b/libs/ardour/panner.cc index 2163ba5cc0..b89c021042 100644 --- a/libs/ardour/panner.cc +++ b/libs/ardour/panner.cc @@ -228,7 +228,7 @@ BaseStereoPanner::load (istream& in, string path, uint32_t& linecnt) /* now that we are done loading */ - _control->list()->StateChanged (); + ((AutomationList*)_control->list().get())->StateChanged (); return 0; } @@ -486,7 +486,7 @@ EqualPowerStereoPanner::state (bool full_state) root->add_property (X_("type"), EqualPowerStereoPanner::name); XMLNode* autonode = new XMLNode (X_("Automation")); - autonode->add_child_nocopy (_control->list()->state (full_state)); + autonode->add_child_nocopy (((AutomationList*)_control->list().get())->state (full_state)); root->add_child_nocopy (*autonode); StreamPanner::add_state (*root); @@ -519,9 +519,9 @@ EqualPowerStereoPanner::set_state (const XMLNode& node) } else if ((*iter)->name() == X_("Automation")) { - _control->list()->set_state (*((*iter)->children().front())); + _control->alist()->set_state (*((*iter)->children().front())); - if (_control->list()->automation_state() != Off) { + if (_control->alist()->automation_state() != Off) { set_position (_control->list()->eval (parent.session().transport_frame())); } } @@ -917,7 +917,7 @@ void Panner::set_automation_style (AutoStyle style) { for (vector<StreamPanner*>::iterator i = begin(); i != end(); ++i) { - (*i)->pan_control()->list()->set_automation_style (style); + ((AutomationList*)(*i)->pan_control()->list().get())->set_automation_style (style); } _session.set_dirty (); } @@ -926,7 +926,7 @@ void Panner::set_automation_state (AutoState state) { for (vector<StreamPanner*>::iterator i = begin(); i != end(); ++i) { - (*i)->pan_control()->list()->set_automation_state (state); + ((AutomationList*)(*i)->pan_control()->list().get())->set_automation_state (state); } _session.set_dirty (); } @@ -935,7 +935,7 @@ AutoState Panner::automation_state () const { if (!empty()) { - return front()->pan_control()->list()->automation_state (); + return ((AutomationList*)front()->pan_control()->list().get())->automation_state (); } else { return Off; } @@ -945,7 +945,7 @@ AutoStyle Panner::automation_style () const { if (!empty()) { - return front()->pan_control()->list()->automation_style (); + return ((AutomationList*)front()->pan_control()->list().get())->automation_style (); } else { return Absolute; } @@ -955,7 +955,7 @@ void Panner::transport_stopped (nframes_t frame) { for (vector<StreamPanner*>::iterator i = begin(); i != end(); ++i) { - (*i)->pan_control()->list()->reposition_for_rt_add (frame); + ((AutomationList*)(*i)->pan_control()->list().get())->reposition_for_rt_add (frame); } } @@ -963,7 +963,7 @@ void Panner::snapshot (nframes_t now) { for (vector<StreamPanner*>::iterator i = begin(); i != end(); ++i) { - boost::shared_ptr<AutomationList> list = (*i)->pan_control()->list(); + AutomationList* list = ((AutomationList*)(*i)->pan_control()->list().get()); if (list->automation_write()) list->rt_add(now, (*i)->pan_control()->get_value()); } @@ -1127,7 +1127,7 @@ bool Panner::touching () const { for (vector<StreamPanner*>::const_iterator i = begin(); i != end(); ++i) { - if ((*i)->pan_control()->list()->touching ()) { + if (((AutomationList*)(*i)->pan_control()->list().get())->touching ()) { return true; } } diff --git a/libs/ardour/parameter.cc b/libs/ardour/parameter.cc index 1b94e5dc4d..ea70ea7927 100644 --- a/libs/ardour/parameter.cc +++ b/libs/ardour/parameter.cc @@ -26,9 +26,7 @@ using namespace ARDOUR; * (AutomationList automation-id property) */ Parameter::Parameter(const std::string& str) - : _type(NullAutomation) - , _id(0) - , _channel(0) + : Evoral::Parameter (NullAutomation, 0) { if (str == "gain") { _type = GainAutomation; @@ -80,6 +78,8 @@ Parameter::Parameter(const std::string& str) } else { PBD::warning << "Unknown Parameter '" << str << "'" << endmsg; } + + init((AutomationType)_type); // set min/max/normal } @@ -87,7 +87,7 @@ Parameter::Parameter(const std::string& str) * e.g. <AutomationList automation-id="whatthisreturns"> */ std::string -Parameter::to_string() const +Parameter::symbol() const { if (_type == GainAutomation) { return "gain"; diff --git a/libs/ardour/plugin_insert.cc b/libs/ardour/plugin_insert.cc index f44a5df003..8158e55cc2 100644 --- a/libs/ardour/plugin_insert.cc +++ b/libs/ardour/plugin_insert.cc @@ -152,9 +152,10 @@ PluginInsert::auto_state_changed (Parameter which) if (which.type() != PluginAutomation) return; - boost::shared_ptr<AutomationControl> c = control (which); + boost::shared_ptr<AutomationControl> c + = boost::dynamic_pointer_cast<AutomationControl>(control (which)); - if (c && c->list()->automation_state() != Off) { + if (c && ((AutomationList*)c->list().get())->automation_state() != Off) { _plugins[0]->set_parameter (which.id(), c->list()->eval (_session.transport_frame())); } } @@ -225,15 +226,10 @@ PluginInsert::set_automatable () if (i->type() == PluginAutomation) { can_automate (*i); _plugins.front()->get_parameter_descriptor(i->id(), desc); - boost::shared_ptr<AutomationList> list(new AutomationList( - *i, - //(desc.min_unbound ? FLT_MIN : desc.lower), - //(desc.max_unbound ? FLT_MAX : desc.upper), - desc.lower, desc.upper, - _plugins.front()->default_value(i->id()))); - - add_control(boost::shared_ptr<AutomationControl>( - new PluginControl(*this, list))); + Parameter param(*i); + param.set_range(desc.lower, desc.upper, _plugins.front()->default_value(i->id())); + boost::shared_ptr<AutomationList> list(new AutomationList(param)); + add_control(boost::shared_ptr<AutomationControl>(new PluginControl(*this, list))); } } } @@ -296,9 +292,10 @@ PluginInsert::connect_and_run (BufferSet& bufs, nframes_t nframes, nframes_t off for (Controls::iterator li = _controls.begin(); li != _controls.end(); ++li, ++n) { - boost::shared_ptr<AutomationControl> c = li->second; + boost::shared_ptr<AutomationControl> c + = boost::dynamic_pointer_cast<AutomationControl>(li->second); - if (c->parameter().type() == PluginAutomation && c->list()->automation_playback()) { + if (c->parameter().type() == PluginAutomation && c->automation_playback()) { bool valid; const float val = c->list()->rt_safe_eval (now, valid); @@ -371,8 +368,7 @@ PluginInsert::set_parameter (Parameter param, float val) _plugins[0]->set_parameter (param.id(), val); - boost::shared_ptr<AutomationControl> c = control (param); - + boost::shared_ptr<Evoral::Control> c = control (param); if (c) c->set_value(val); @@ -396,7 +392,7 @@ PluginInsert::automation_run (BufferSet& bufs, nframes_t nframes, nframes_t offs nframes_t now = _session.transport_frame (); nframes_t end = now + nframes; - Glib::Mutex::Lock lm (_automation_lock, Glib::TRY_LOCK); + Glib::Mutex::Lock lm (_control_lock, Glib::TRY_LOCK); if (!lm.locked()) { connect_and_run (bufs, nframes, offset, false); @@ -434,7 +430,7 @@ PluginInsert::automation_run (BufferSet& bufs, nframes_t nframes, nframes_t offs } float -PluginInsert::default_parameter_value (Parameter param) +PluginInsert::default_parameter_value (Evoral::Parameter param) { if (param.type() != PluginAutomation) return 1.0; @@ -640,7 +636,7 @@ PluginInsert::state (bool full) child->add_child_nocopy (automation_list (*x).state (full)); autonode->add_child_nocopy (*child); */ - autonode->add_child_nocopy (control(*x)->list()->state (full)); + autonode->add_child_nocopy (((AutomationList*)control(*x)->list().get())->state (full)); } node.add_child_nocopy (*autonode); @@ -763,8 +759,11 @@ PluginInsert::set_state(const XMLNode& node) continue; } + boost::shared_ptr<AutomationControl> c = boost::dynamic_pointer_cast<AutomationControl>( + control(Parameter(PluginAutomation, port_id), true)); + if (!child->children().empty()) { - control (Parameter(PluginAutomation, port_id), true)->list()->set_state (*child->children().front()); + c->alist()->set_state (*child->children().front()); } else { if ((cprop = child->property("auto")) != 0) { @@ -772,13 +771,13 @@ PluginInsert::set_state(const XMLNode& node) int x; sscanf (cprop->value().c_str(), "0x%x", &x); - control (Parameter(PluginAutomation, port_id), true)->list()->set_automation_state (AutoState (x)); + c->alist()->set_automation_state (AutoState (x)); } else { /* missing */ - control (Parameter(PluginAutomation, port_id), true)->list()->set_automation_state (Off); + c->alist()->set_automation_state (Off); } } diff --git a/libs/ardour/route.cc b/libs/ardour/route.cc index 638c026056..79d2b4f9b6 100644 --- a/libs/ardour/route.cc +++ b/libs/ardour/route.cc @@ -2528,11 +2528,11 @@ Route::roll (nframes_t nframes, nframes_t start_frame, nframes_t end_frame, nfra apply_gain_automation = false; { - Glib::Mutex::Lock am (_automation_lock, Glib::TRY_LOCK); + Glib::Mutex::Lock am (_control_lock, Glib::TRY_LOCK); if (am.locked() && _session.transport_rolling()) { - if (_gain_control->list()->automation_playback()) { + if (_gain_control->alist()->automation_playback()) { apply_gain_automation = _gain_control->list()->curve().rt_safe_get_vector ( start_frame, end_frame, _session.gain_automation_buffer(), nframes); } diff --git a/libs/ardour/smf_source.cc b/libs/ardour/smf_source.cc index bb43d4791a..2d5c0af0d5 100644 --- a/libs/ardour/smf_source.cc +++ b/libs/ardour/smf_source.cc @@ -450,7 +450,7 @@ SMFSource::write_unlocked (MidiRingBuffer& src, nframes_t cnt) if (_model && ! _model->writing()) _model->start_write(); - MIDI::Event ev(0.0, 4, NULL, true); + Evoral::Event ev(0.0, 4, NULL, true); while (true) { bool ret = src.full_peek(sizeof(double), (uint8_t*)&time); @@ -500,7 +500,7 @@ SMFSource::write_unlocked (MidiRingBuffer& src, nframes_t cnt) void -SMFSource::append_event_unlocked(EventTimeUnit unit, const MIDI::Event& ev) +SMFSource::append_event_unlocked(EventTimeUnit unit, const Evoral::Event& ev) { if (ev.size() == 0) return; @@ -925,7 +925,7 @@ SMFSource::load_model(bool lock, bool force_reload) fseek(_fd, _header_size, SEEK_SET); uint64_t time = 0; /* in SMF ticks */ - MIDI::Event ev; + Evoral::Event ev; size_t scratch_size = 0; // keep track of scratch and minimize reallocs 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/ardour/note.cc b/libs/evoral/src/Note.cpp index ea1e7133af..88be34fb36 100644 --- a/libs/ardour/note.cc +++ b/libs/evoral/src/Note.cpp @@ -1,27 +1,25 @@ -/* - Copyright (C) 2007 Paul Davis - Author: Dave Robillard +/* 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 + */ - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 2 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. - -*/ - -#include <ardour/note.h> #include <iostream> +#include <evoral/Note.hpp> -namespace ARDOUR { +namespace Evoral { Note::Note(uint8_t chan, double t, double d, uint8_t n, uint8_t v) : _on_event(t, 3, NULL, true) @@ -71,14 +69,9 @@ Note::Note(const Note& copy) assert(channel() == copy.channel()); } + Note::~Note() { - std::cerr << "Note::~Note() Note time: " << time() - << " pitch: " << int(note()) - << " duration: " << duration() - << " end-time: " << end_time() - << " velocity: " << int(velocity()) - << std::endl; } @@ -107,4 +100,4 @@ Note::operator=(const Note& copy) return *this; } -} // namespace ARDOUR +} // 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; +} diff --git a/libs/midi++2/SConscript b/libs/midi++2/SConscript index 222e9e249a..95b64a0599 100644 --- a/libs/midi++2/SConscript +++ b/libs/midi++2/SConscript @@ -9,6 +9,7 @@ Import('env libraries install_prefix') midi2 = env.Clone() midi2.Merge([ libraries['sigc2'], libraries['xml'], + libraries['evoral'], libraries['glibmm2'], libraries['glib2'], libraries['pbd'], @@ -25,7 +26,6 @@ midi2.Append(DOMAIN=domain,MAJOR=2,MINOR=1,MICRO=1) sources = Split(""" fd_midiport.cc fifomidi.cc -event.cc midi.cc midichannel.cc midifactory.cc diff --git a/libs/midi++2/event.cc b/libs/midi++2/event.cc deleted file mode 100644 index 09e3fcf732..0000000000 --- a/libs/midi++2/event.cc +++ /dev/null @@ -1,97 +0,0 @@ -#include "midi++/event.h" - -namespace MIDI { - -#ifdef MIDI_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 XMLNode& event) -{ - string name = event.name(); - - if (name == "ControlChange") { - - } else if (name == "ProgramChange") { - - } -} - -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 // MIDI_EVENT_ALLOW_ALLOC - -std::string -Event::to_string() const -{ - std::ostringstream result(std::ios::ate); - result << "MIDI::Event type:" << std::hex << "0x" << int(type()) << " buffer: "; - - for(uint32_t i = 0; i < size(); ++i) { - result << " 0x" << int(_buffer[i]); - } - return result.str(); -} - -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); -} - -} // namespace MIDI diff --git a/libs/midi++2/jack_midiport.cc b/libs/midi++2/jack_midiport.cc index bcaa683bf8..fc1ba234a3 100644 --- a/libs/midi++2/jack_midiport.cc +++ b/libs/midi++2/jack_midiport.cc @@ -93,7 +93,7 @@ JACK_MidiPort::write(byte * msg, size_t msglen, timestamp_t timestamp) if (!is_process_thread()) { Glib::Mutex::Lock lm (non_process_thread_fifo_lock); - RingBuffer<Event>::rw_vector vec; + RingBuffer<Evoral::Event>::rw_vector vec; non_process_thread_fifo.get_write_vector (&vec); @@ -157,7 +157,7 @@ JACK_MidiPort::write(byte * msg, size_t msglen, timestamp_t timestamp) void JACK_MidiPort::flush (void* jack_port_buffer) { - RingBuffer<Event>::rw_vector vec; + RingBuffer<Evoral::Event>::rw_vector vec; size_t written; non_process_thread_fifo.get_read_vector (&vec); @@ -167,7 +167,7 @@ JACK_MidiPort::flush (void* jack_port_buffer) } if (vec.len[0]) { - Event* evp = vec.buf[0]; + Evoral::Event* evp = vec.buf[0]; for (size_t n = 0; n < vec.len[0]; ++n, ++evp) { jack_midi_event_write (jack_port_buffer, @@ -176,7 +176,7 @@ JACK_MidiPort::flush (void* jack_port_buffer) } if (vec.len[1]) { - Event* evp = vec.buf[1]; + Evoral::Event* evp = vec.buf[1]; for (size_t n = 0; n < vec.len[1]; ++n, ++evp) { jack_midi_event_write (jack_port_buffer, diff --git a/libs/midi++2/midi++/event.h b/libs/midi++2/midi++/event.h index 64f99090ad..8973ef48bd 100644 --- a/libs/midi++2/midi++/event.h +++ b/libs/midi++2/midi++/event.h @@ -35,186 +35,11 @@ * but MidiEvent will never deep copy and (depending on the scenario) * may not be usable in STL containers, signals, etc. */ -#define MIDI_EVENT_ALLOW_ALLOC 1 +#define EVENT_ALLOW_ALLOC 1 -namespace MIDI { +/** Support serialisation of MIDI events to/from XML */ +#define EVENT_WITH_XML 1 - -/** 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 MIDI_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); - - /** - * see the MIDI XML specification: http://www.midi.org/dtds/MIDIEvents10.dtd - */ - Event(const XMLNode &event); - - ~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 // MIDI_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]); } - // midi channel events range from 0x80 to 0xE0 - 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; } - - /** - * mainly used for debugging purposes - */ - std::string to_string() const; - - /** - * see the MIDI XML specification: http://www.midi.org/dtds/MIDIEvents10.dtd - */ - boost::shared_ptr<XMLNode> to_xml() const; - -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 MIDI_EVENT_ALLOW_ALLOC - bool _owns_buffer; /**< Whether buffer is locally allocated */ -#endif -}; - - -} +#include <evoral/Event.hpp> #endif /* __libmidipp_midi_event_h__ */ diff --git a/libs/midi++2/midi++/jack.h b/libs/midi++2/midi++/jack.h index 6d3e3341bc..10a121baee 100644 --- a/libs/midi++2/midi++/jack.h +++ b/libs/midi++2/midi++/jack.h @@ -80,7 +80,7 @@ private: static pthread_t _process_thread; static bool is_process_thread(); - RingBuffer<MIDI::Event> non_process_thread_fifo; + RingBuffer<Evoral::Event> non_process_thread_fifo; Glib::Mutex non_process_thread_fifo_lock; }; diff --git a/libs/midi++2/midi++/midnam_patch.h b/libs/midi++2/midi++/midnam_patch.h index 50c51b317f..d4f8dbf466 100644 --- a/libs/midi++2/midi++/midnam_patch.h +++ b/libs/midi++2/midi++/midnam_patch.h @@ -18,7 +18,7 @@ namespace Name class Patch : public PBD::Stateful { public: - typedef std::list<MIDI::Event> PatchMidiCommands; + typedef std::list<Evoral::Event> PatchMidiCommands; Patch() {}; Patch(string a_number, string a_name) : _number(a_number), _name(a_name) {}; diff --git a/libs/midi++2/midnam_patch.cc b/libs/midi++2/midnam_patch.cc index f149f2884b..3434db896a 100644 --- a/libs/midi++2/midnam_patch.cc +++ b/libs/midi++2/midnam_patch.cc @@ -33,7 +33,7 @@ Patch::set_state (const XMLNode& node) assert(commands); const XMLNodeList events = commands->children(); for (XMLNodeList::const_iterator i = events.begin(); i != events.end(); ++i) { - _patch_midi_commands.push_back(*(new Event(*(*i)))); + _patch_midi_commands.push_back(*(new Evoral::Event(*(*i)))); } return 0; @@ -132,13 +132,16 @@ ChannelNameSet::set_state (const XMLNode& node) } int -MIDINameDocument::set_state(const XMLNode & a_node) +MIDINameDocument::set_state(const XMLNode& a_node) { + return 0; } XMLNode& MIDINameDocument::get_state(void) { + static XMLNode nothing("<nothing>"); + return nothing; } diff --git a/libs/surfaces/control_protocol/SConscript b/libs/surfaces/control_protocol/SConscript index f45349b835..a9d60c6e25 100644 --- a/libs/surfaces/control_protocol/SConscript +++ b/libs/surfaces/control_protocol/SConscript @@ -38,6 +38,7 @@ cp.Merge ([ libraries['sigc2'], libraries['pbd'], libraries['midi++2'], + libraries['evoral'], libraries['xml'], libraries['glib2'], libraries['glibmm2'] diff --git a/libs/surfaces/frontier/tranzport/SConscript b/libs/surfaces/frontier/tranzport/SConscript index 0f05e379cc..b3794899b5 100644 --- a/libs/surfaces/frontier/tranzport/SConscript +++ b/libs/surfaces/frontier/tranzport/SConscript @@ -36,6 +36,7 @@ tranzport.Merge ([ libraries['sigc2'], libraries['pbd'], libraries['midi++2'], + libraries['evoral'], libraries['xml'], libraries['usb'], libraries['glib2'], diff --git a/libs/surfaces/generic_midi/SConscript b/libs/surfaces/generic_midi/SConscript index 33cfd70741..e3e62e7402 100644 --- a/libs/surfaces/generic_midi/SConscript +++ b/libs/surfaces/generic_midi/SConscript @@ -37,6 +37,7 @@ genericmidi.Merge ([ libraries['ardour_cp'], libraries['sndfile'], libraries['midi++2'], + libraries['evoral'], libraries['pbd'], libraries['sigc2'], libraries['usb'], diff --git a/libs/surfaces/mackie/SConscript b/libs/surfaces/mackie/SConscript index e1972d3bdb..143ebc9856 100644 --- a/libs/surfaces/mackie/SConscript +++ b/libs/surfaces/mackie/SConscript @@ -52,6 +52,7 @@ mackie.Merge ([ libraries['sigc2'], libraries['pbd'], libraries['midi++2'], + libraries['evoral'], libraries['xml'], libraries['glib2'], libraries['glibmm2'], diff --git a/libs/surfaces/powermate/SConscript b/libs/surfaces/powermate/SConscript index 26ecc511eb..c1a79fe955 100644 --- a/libs/surfaces/powermate/SConscript +++ b/libs/surfaces/powermate/SConscript @@ -36,6 +36,7 @@ powermate.Merge ([ libraries['sigc2'], libraries['pbd'], libraries['midi++2'], + libraries['evoral'], libraries['xml'], libraries['usb'], libraries['glib2'], |