#include "pbd/memento_command.h"
#include "pbd/stacktrace.h"
-#include "evoral/Curve.hpp"
+#include "evoral/Curve.h"
#include "gtkmm2ext/gtk_ui.h"
#include "gtkmm2ext/utils.h"
#include "ardour/parameter_types.h"
#include "ardour/tempo.h"
-#include "evoral/Curve.hpp"
+#include "evoral/Curve.h"
#include "canvas/debug.h"
#include <sigc++/signal.h>
-#include "evoral/TimeConverter.hpp"
+#include "evoral/TimeConverter.h"
#include "pbd/undo.h"
#include "pbd/statefuldestructible.h"
#include <list>
#include "ardour/automation_list.h"
-#include "evoral/Parameter.hpp"
+#include "evoral/Parameter.h"
class AutomationSelection : public std::list<boost::shared_ptr<ARDOUR::AutomationList> > {
public:
#include "canvas/canvas.h"
-#include "evoral/Curve.hpp"
+#include "evoral/Curve.h"
#include "ardour/session_handle.h"
#include "ardour_dialog.h"
#include <unistd.h>
#include "ardour/automation_list.h"
-#include "evoral/Curve.hpp"
+#include "evoral/Curve.h"
using namespace std;
using namespace ARDOUR;
#include "pbd/failed_constructor.h"
#include "evoral/midi_events.h"
-#include "evoral/PatchChange.hpp"
+#include "evoral/PatchChange.h"
#include "midi++/midnam_patch.h"
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
-#include "evoral/Note.hpp"
+#include "evoral/Note.h"
#include "ardour/parameter_descriptor.h"
#include "temporal/beats.h"
-#include "evoral/Note.hpp"
+#include "evoral/Note.h"
#include "canvas/polygon.h"
#include "canvas/debug.h"
#include <utility>
#include "ardour/data_type.h"
-#include "evoral/Parameter.hpp"
+#include "evoral/Parameter.h"
/** A count of various GUI items.
*
#include <gtkmm/cellrenderercombo.h>
#include "evoral/midi_util.h"
-#include "evoral/Note.hpp"
+#include "evoral/Note.h"
#include "ardour/beats_samples_converter.h"
#include "ardour/midi_model.h"
#include "ardour/operations.h"
#include "ardour/session.h"
-#include "evoral/Parameter.hpp"
-#include "evoral/Event.hpp"
-#include "evoral/Control.hpp"
+#include "evoral/Parameter.h"
+#include "evoral/Event.h"
+#include "evoral/Control.h"
#include "evoral/midi_util.h"
#include "canvas/debug.h"
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
-#include "evoral/Note.hpp"
+#include "evoral/Note.h"
#include "canvas/note.h"
#include "canvas/debug.h"
#include "gtkmm2ext/keyboard.h"
-#include "evoral/Note.hpp"
+#include "evoral/Note.h"
#include "canvas/text.h"
#include <boost/shared_ptr.hpp>
#include <sigc++/trackable.h>
-#include "evoral/Note.hpp"
+#include "evoral/Note.h"
namespace ARDOUR {
class MidiTrack;
#include <gtkmm/spinbutton.h>
#include <gtkmm/comboboxtext.h>
-#include "evoral/PatchChange.hpp"
+#include "evoral/PatchChange.h"
#include "ardour_dialog.h"
#include "audio_clock.h"
#include "pbd/unwind.h"
#include "evoral/midi_events.h"
-#include "evoral/PatchChange.hpp"
+#include "evoral/PatchChange.h"
#include "midi++/midnam_patch.h"
#include "temporal/beats.h"
-#include "evoral/Note.hpp"
+#include "evoral/Note.h"
#include "ardour/session_handle.h"
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
-#include "evoral/Curve.hpp"
+#include "evoral/Curve.h"
#include "pbd/memento_command.h"
#include "pbd/stateful_diff_command.h"
#include "pbd/enumwriter.h"
#include "pbd/stateful_diff_command.h"
-#include "evoral/Parameter.hpp"
+#include "evoral/Parameter.h"
#include "ardour/amp.h"
#include "ardour/meter.h"
#include <gtkmm2ext/utils.h>
-#include "evoral/SMF.hpp"
+#include "evoral/SMF.h"
#include "ardour/audio_library.h"
#include "ardour/auditioner.h"
#include "pbd/stateful.h"
#include "pbd/signals.h"
-#include "evoral/Parameter.hpp"
+#include "evoral/Parameter.h"
#include "ardour/types.h"
#include "ardour/presentation_info.h"
#include <cmath>
#include <algorithm>
-#include "evoral/Curve.hpp"
+#include "evoral/Curve.h"
#include "ardour/amp.h"
#include "ardour/audio_buffer.h"
#include "pbd/signals.h"
#include "pbd/ringbuffer.h"
-#include "evoral/Event.hpp"
+#include "evoral/Event.h"
#include "midi++/types.h"
#include "midi++/parser.h"
#include "pbd/rcu.h"
#include "pbd/signals.h"
-#include "evoral/ControlSet.hpp"
+#include "evoral/ControlSet.h"
#include "ardour/libardour_visibility.h"
#include "ardour/slavable.h"
#ifndef __ardour_automatable_sequence_h__
#define __ardour_automatable_sequence_h__
-#include "evoral/Sequence.hpp"
+#include "evoral/Sequence.h"
#include "ardour/automatable.h"
#include "ardour/event_type_map.h"
#include "pbd/controllable.h"
-#include "evoral/types.hpp"
-#include "evoral/Control.hpp"
+#include "evoral/types.h"
+#include "evoral/Control.h"
#include "ardour/automation_list.h"
#include "ardour/control_group_member.h"
#include <glibmm/threads.h>
-#include "evoral/ControlList.hpp"
-#include "evoral/Parameter.hpp"
+#include "evoral/ControlList.h"
+#include "evoral/Parameter.h"
#include "pbd/undo.h"
#include "pbd/xml++.h"
*/
#include "temporal/beats.h"
-#include "evoral/TimeConverter.hpp"
+#include "evoral/TimeConverter.h"
#include "ardour/libardour_visibility.h"
#include "ardour/types.h"
#include "ardour/types.h"
#if defined WINDOWS_VST_SUPPORT || defined LXVST_SUPPORT || defined MACVST_SUPPORT
-#include "evoral/Event.hpp"
+#include "evoral/Event.h"
struct _VstEvents;
typedef struct _VstEvents VstEvents;
struct _VstMidiEvent;
#include "pbd/controllable.h"
-#include "evoral/Parameter.hpp"
+#include "evoral/Parameter.h"
#include "ardour/automation_control.h"
#include "ardour/types.h"
#include "pbd/ringbufferNPT.h"
-#include "evoral/EventSink.hpp"
-#include "evoral/types.hpp"
+#include "evoral/EventSink.h"
+#include "evoral/types.h"
namespace ARDOUR {
#include <map>
#include <string>
-#include "evoral/TypeMap.hpp"
-#include "evoral/ControlList.hpp"
-#include "evoral/ParameterDescriptor.hpp"
+#include "evoral/TypeMap.h"
+#include "evoral/ControlList.h"
+#include "evoral/ParameterDescriptor.h"
#include "ardour/libardour_visibility.h"
#include "pbd/enum_convert.h"
#include "temporal/beats.h"
-#include "evoral/ControlList.hpp"
+#include "evoral/ControlList.h"
namespace PBD {
#include "pbd/controllable.h"
-#include "evoral/Parameter.hpp"
+#include "evoral/Parameter.h"
#include "ardour/slavable_automation_control.h"
#include "ardour/libardour_visibility.h"
#include "pbd/signals.h"
-#include "evoral/Parameter.hpp"
+#include "evoral/Parameter.h"
#include "midi++/libmidi_visibility.h"
#include "ardour/libardour_visibility.h"
#include <boost/shared_ptr.hpp>
#include <vamp-hostsdk/Plugin.h>
-#include "evoral/Note.hpp"
+#include "evoral/Note.h"
#include "ardour/libardour_visibility.h"
#define __ardour_midi_automation_list_binder_h__
#include "pbd/memento_command.h"
-#include "evoral/Parameter.hpp"
+#include "evoral/Parameter.h"
#include "ardour/session.h"
namespace ARDOUR {
#ifndef __ardour_midi_buffer_h__
#define __ardour_midi_buffer_h__
-#include "evoral/EventSink.hpp"
+#include "evoral/EventSink.h"
#include "evoral/midi_util.h"
-#include "evoral/types.hpp"
+#include "evoral/types.h"
#include "midi++/event.h"
#include "pbd/signals.h"
#include "temporal/beats.h"
-#include "evoral/Sequence.hpp"
+#include "evoral/Sequence.h"
#include "ardour/types.h"
#include "ardour/types.h"
#include "ardour/variant.h"
-#include "evoral/Note.hpp"
-#include "evoral/Sequence.hpp"
+#include "evoral/Note.h"
+#include "evoral/Sequence.h"
namespace ARDOUR {
#include <string>
#include "temporal/beats.h"
-#include "evoral/Sequence.hpp"
+#include "evoral/Sequence.h"
class Command;
#include <boost/utility.hpp>
-#include "evoral/Parameter.hpp"
+#include "evoral/Parameter.h"
#include "ardour/ardour.h"
#include "ardour/midi_cursor.h"
#include "ardour/midi_model.h"
#include "ardour/midi_state_tracker.h"
#include "ardour/playlist.h"
-#include "evoral/Note.hpp"
-#include "evoral/Parameter.hpp"
+#include "evoral/Note.h"
+#include "evoral/Parameter.h"
#include "ardour/rt_midibuffer.h"
namespace Evoral {
#include <vector>
#include "temporal/beats.h"
-#include "evoral/Range.hpp"
+#include "evoral/Range.h"
#include "pbd/string_convert.h"
#ifndef __libardour_midi_scene_change_h__
#define __libardour_midi_scene_change_h__
-#include "evoral/PatchChange.hpp"
+#include "evoral/PatchChange.h"
#include "pbd/signals.h"
#include <boost/enable_shared_from_this.hpp>
#include "pbd/stateful.h"
#include "pbd/xml++.h"
-#include "evoral/Sequence.hpp"
-#include "evoral/Range.hpp"
+#include "evoral/Sequence.h"
+#include "evoral/Range.h"
#include "ardour/ardour.h"
#include "ardour/buffer.h"
#include "ardour/midi_cursor.h"
#include "pbd/signals.h"
#include "pbd/stateful.h"
-#include "evoral/Parameter.hpp"
+#include "evoral/Parameter.h"
#include "ardour/session_handle.h"
#include "ardour/types.h"
#include <boost/shared_ptr.hpp>
-#include "evoral/Parameter.hpp"
+#include "evoral/Parameter.h"
#include "ardour/automation_control.h"
#include "ardour/automation_list.h"
#include <boost/shared_ptr.hpp>
#include "pbd/stateful.h"
-#include "evoral/Parameter.hpp"
+#include "evoral/Parameter.h"
#include "ardour/automatable.h"
#include "ardour/session_handle.h"
#include "ardour/types.h"
#include "ardour/variant.h"
-#include "evoral/Parameter.hpp"
-#include "evoral/ParameterDescriptor.hpp"
+#include "evoral/Parameter.h"
+#include "evoral/ParameterDescriptor.h"
namespace ARDOUR {
#include <stdint.h>
#include "ardour/types.h"
-#include "evoral/Parameter.hpp"
+#include "evoral/Parameter.h"
#include "evoral/midi_events.h"
namespace ARDOUR {
#include "pbd/sequence_property.h"
#include "pbd/stacktrace.h"
-#include "evoral/Range.hpp"
+#include "evoral/Range.h"
#include "ardour/ardour.h"
#include "ardour/region.h"
#include <glibmm/threads.h>
-#include "evoral/Event.hpp"
-#include "evoral/EventSink.hpp"
+#include "evoral/Event.h"
+#include "evoral/EventSink.h"
#include "ardour/types.h"
namespace ARDOUR {
#include "lua/luastate.h"
-#include "evoral/Range.hpp"
+#include "evoral/Range.h"
#include "midi++/types.h"
#include "midi++/mmc.h"
#include "pbd/signals.h"
-#include "evoral/Parameter.hpp"
+#include "evoral/Parameter.h"
#include "ardour/types.h"
#include "ardour/libardour_visibility.h"
#include <cstdio>
#include <time.h>
-#include "evoral/SMF.hpp"
+#include "evoral/SMF.h"
#include "ardour/midi_source.h"
#include "ardour/file_source.h"
#include "pbd/id.h"
-#include "evoral/Range.hpp"
+#include "evoral/Range.h"
#include "ardour/chan_count.h"
#include "ardour/plugin_types.h"
InsertMergeExtend ///< extend new (or old) to the range of old+new
};
-/** See evoral/Parameter.hpp
+/** See evoral/Parameter.h
*
* When you add things here, you REALLY SHOULD add a case clause to
* the constructor of ParameterDescriptor, unless the Controllables
#include "pbd/enumwriter.h"
#include "pbd/error.h"
-#include "evoral/Curve.hpp"
+#include "evoral/Curve.h"
#include "ardour/amp.h"
#include "ardour/audio_buffer.h"
#include "pbd/enumwriter.h"
#include "pbd/convert.h"
-#include "evoral/Curve.hpp"
+#include "evoral/Curve.h"
#include "ardour/audioregion.h"
#include "ardour/session.h"
#include "ardour/parameter_types.h"
#include "ardour/evoral_types_convert.h"
#include "ardour/types_convert.h"
-#include "evoral/Curve.hpp"
+#include "evoral/Curve.h"
#include "pbd/memento_command.h"
#include "pbd/stacktrace.h"
#include "pbd/enumwriter.h"
#include "pbd/enumwriter.h"
#include "midi++/types.h"
-#include "evoral/Range.hpp" // shouldn't Evoral have its own enum registration?
+#include "evoral/Range.h" // shouldn't Evoral have its own enum registration?
#include "ardour/delivery.h"
#include "ardour/disk_io.h"
#ifdef LV2_SUPPORT
#include "ardour/uri_map.h"
#endif
-#include "evoral/Parameter.hpp"
-#include "evoral/ParameterDescriptor.hpp"
+#include "evoral/Parameter.h"
+#include "evoral/ParameterDescriptor.h"
#include "evoral/midi_events.h"
#include "pbd/error.h"
#include "pbd/compose.h"
#include "pbd/convert.h"
#include "pbd/strsplit.h"
-#include "evoral/Curve.hpp"
+#include "evoral/Curve.h"
#include "ardour/dB.h"
#include "ardour/gain_control.h"
#include "pbd/basename.h"
#include "pbd/convert.h"
-#include "evoral/SMF.hpp"
+#include "evoral/SMF.h"
#include "ardour/analyser.h"
#include "ardour/ardour.h"
#include "temporal/bbt_time.h"
-#include "evoral/Control.hpp"
-#include "evoral/ControlList.hpp"
-#include "evoral/Range.hpp"
+#include "evoral/Control.h"
+#include "evoral/ControlList.h"
+#include "evoral/Range.h"
#include "ardour/amp.h"
#include "ardour/async_midi_port.h"
#include "pbd/enumwriter.h"
#include "pbd/error.h"
-#include "evoral/Control.hpp"
+#include "evoral/Control.h"
#include "midi++/events.h"
#include <iostream>
#include <utility>
-#include "evoral/EventList.hpp"
-#include "evoral/Control.hpp"
+#include "evoral/EventList.h"
+#include "evoral/Control.h"
#include "ardour/beats_samples_converter.h"
#include "ardour/debug.h"
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
-#include "evoral/Event.hpp"
+#include "evoral/Event.h"
#include "midi++/channel.h"
#include "midi++/parser.h"
#include "midi++/port.h"
#include "pbd/basename.h"
#include "pbd/timing.h"
-#include "evoral/Control.hpp"
-#include "evoral/EventSink.hpp"
+#include "evoral/Control.h"
+#include "evoral/EventSink.h"
#include "ardour/debug.h"
#include "ardour/file_source.h"
#include "pbd/compose.h"
#include "pbd/stacktrace.h"
-#include "evoral/EventSink.hpp"
+#include "evoral/EventSink.h"
#include "ardour/debug.h"
#include "ardour/midi_source.h"
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
-#include "evoral/ControlList.hpp"
+#include "evoral/ControlList.h"
#include "ardour/mute_master.h"
#include "ardour/session.h"
#include "pbd/xml++.h"
#include "pbd/enumwriter.h"
-#include "evoral/Curve.hpp"
+#include "evoral/Curve.h"
#include "ardour/audio_buffer.h"
#include "ardour/audioengine.h"
#include "ardour/session_playlists.h"
#include "ardour/source.h"
#include "ardour/tempo.h"
-#include "evoral/Curve.hpp"
+#include "evoral/Curve.h"
#include "pbd/error.h"
#include "pbd/failed_constructor.h"
#include "pbd/id.h"
#include "midi++/mmc.h"
#include "midi++/port.h"
-#include "evoral/SMF.hpp"
+#include "evoral/SMF.h"
#include "pbd/basename.h"
#include "pbd/debug.h"
#include "pbd/memento_command.h"
#include "pbd/types_convert.h"
-#include "evoral/Curve.hpp"
+#include "evoral/Curve.h"
#include "ardour/audioengine.h"
#include "ardour/runtime_functions.h"
#include <glibmm/miscutils.h>
#include <glibmm/fileutils.h>
-#include "evoral/Control.hpp"
-#include "evoral/SMF.hpp"
+#include "evoral/Control.h"
+#include "evoral/SMF.h"
#include "ardour/debug.h"
#include "ardour/midi_channel_filter.h"
#include "ardour/audioplaylist.h"
#include "ardour/region.h"
#include "ardour/audioregion.h"
-#include "evoral/Curve.hpp"
+#include "evoral/Curve.h"
CPPUNIT_TEST_SUITE_REGISTRATION (CombineRegionsTest);
--- /dev/null
+/*
+ * Copyright (C) 2008-2014 David Robillard <d@drobilla.net>
+ * Copyright (C) 2010-2012 Carl Hetherington <carl@carlh.net>
+ * Copyright (C) 2010-2013 Paul Davis <paul@linuxaudiosystems.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef EVORAL_CONTROL_HPP
+#define EVORAL_CONTROL_HPP
+
+#include <set>
+#include <map>
+#include <boost/shared_ptr.hpp>
+#include "pbd/signals.h"
+
+#include "evoral/visibility.h"
+#include "evoral/Parameter.h"
+#include "evoral/ParameterDescriptor.h"
+
+namespace Evoral {
+
+class ControlList;
+class ParameterDescriptor;
+class Transport;
+class TypeMap;
+
+/** Base class representing some kind of (automatable) control; a fader's gain,
+ * for example, or a compressor plugin's threshold.
+ *
+ * The class knows the Evoral::Parameter that it is controlling, and has
+ * a list of values for automation.
+ */
+class LIBEVORAL_API Control
+{
+public:
+ Control(const Parameter& parameter,
+ const ParameterDescriptor& desc,
+ boost::shared_ptr<ControlList> list);
+
+ virtual ~Control() {}
+
+ virtual void set_double (double val, double frame=0, bool to_list=false);
+ virtual double get_double (bool from_list=false, double frame=0) const;
+
+ /** Get the latest user-set value
+ * (which may not equal get_value() when automation is playing back).
+ *
+ * Automation write/touch works by periodically sampling this value
+ * and adding it to the ControlList.
+ */
+ double user_double() const { return _user_value; }
+
+ void set_list(boost::shared_ptr<ControlList>);
+
+ boost::shared_ptr<ControlList> list() { return _list; }
+ boost::shared_ptr<const ControlList> list() const { return _list; }
+
+ inline const Parameter& parameter() const { return _parameter; }
+
+ /** Emitted when the our ControlList is marked dirty */
+ PBD::Signal0<void> ListMarkedDirty;
+
+protected:
+ Parameter _parameter;
+ boost::shared_ptr<ControlList> _list;
+ double _user_value;
+ PBD::ScopedConnection _list_marked_dirty_connection;
+
+private:
+ void list_marked_dirty ();
+};
+
+} // namespace Evoral
+
+#endif // EVORAL_CONTROL_HPP
+++ /dev/null
-/*
- * Copyright (C) 2008-2014 David Robillard <d@drobilla.net>
- * Copyright (C) 2010-2012 Carl Hetherington <carl@carlh.net>
- * Copyright (C) 2010-2013 Paul Davis <paul@linuxaudiosystems.com>
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-
-#ifndef EVORAL_CONTROL_HPP
-#define EVORAL_CONTROL_HPP
-
-#include <set>
-#include <map>
-#include <boost/shared_ptr.hpp>
-#include "pbd/signals.h"
-
-#include "evoral/visibility.h"
-#include "evoral/Parameter.hpp"
-#include "evoral/ParameterDescriptor.hpp"
-
-namespace Evoral {
-
-class ControlList;
-class ParameterDescriptor;
-class Transport;
-class TypeMap;
-
-/** Base class representing some kind of (automatable) control; a fader's gain,
- * for example, or a compressor plugin's threshold.
- *
- * The class knows the Evoral::Parameter that it is controlling, and has
- * a list of values for automation.
- */
-class LIBEVORAL_API Control
-{
-public:
- Control(const Parameter& parameter,
- const ParameterDescriptor& desc,
- boost::shared_ptr<ControlList> list);
-
- virtual ~Control() {}
-
- virtual void set_double (double val, double frame=0, bool to_list=false);
- virtual double get_double (bool from_list=false, double frame=0) const;
-
- /** Get the latest user-set value
- * (which may not equal get_value() when automation is playing back).
- *
- * Automation write/touch works by periodically sampling this value
- * and adding it to the ControlList.
- */
- double user_double() const { return _user_value; }
-
- void set_list(boost::shared_ptr<ControlList>);
-
- boost::shared_ptr<ControlList> list() { return _list; }
- boost::shared_ptr<const ControlList> list() const { return _list; }
-
- inline const Parameter& parameter() const { return _parameter; }
-
- /** Emitted when the our ControlList is marked dirty */
- PBD::Signal0<void> ListMarkedDirty;
-
-protected:
- Parameter _parameter;
- boost::shared_ptr<ControlList> _list;
- double _user_value;
- PBD::ScopedConnection _list_marked_dirty_connection;
-
-private:
- void list_marked_dirty ();
-};
-
-} // namespace Evoral
-
-#endif // EVORAL_CONTROL_HPP
--- /dev/null
+/*
+ * Copyright (C) 2008-2012 Carl Hetherington <carl@carlh.net>
+ * Copyright (C) 2008-2014 Paul Davis <paul@linuxaudiosystems.com>
+ * Copyright (C) 2008-2015 David Robillard <d@drobilla.net>
+ * Copyright (C) 2012-2017 Robin Gareus <robin@gareus.org>
+ * Copyright (C) 2015 Nick Mainsbridge <mainsbridge@gmail.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef EVORAL_CONTROL_LIST_HPP
+#define EVORAL_CONTROL_LIST_HPP
+
+#include <cassert>
+#include <list>
+#include <stdint.h>
+
+#include <boost/pool/pool.hpp>
+#include <boost/pool/pool_alloc.hpp>
+
+#include <glibmm/threads.h>
+
+#include "pbd/signals.h"
+
+#include "evoral/visibility.h"
+#include "evoral/Range.h"
+#include "evoral/Parameter.h"
+#include "evoral/ParameterDescriptor.h"
+
+namespace Evoral {
+
+class Curve;
+class TypeMap;
+
+/** A single event (time-stamped value) for a control
+ */
+class LIBEVORAL_API ControlEvent {
+public:
+ ControlEvent (double w, double v)
+ : when (w), value (v), coeff (0)
+ {}
+
+ ControlEvent (const ControlEvent& other)
+ : when (other.when), value (other.value), coeff (0)
+ {
+ if (other.coeff) {
+ create_coeffs();
+ for (size_t i = 0; i < 4; ++i)
+ coeff[i] = other.coeff[i];
+ }
+ }
+
+ ~ControlEvent() { if (coeff) delete[] coeff; }
+
+ void create_coeffs() {
+ if (!coeff)
+ coeff = new double[4];
+
+ coeff[0] = coeff[1] = coeff[2] = coeff[3] = 0.0;
+ }
+
+ double when;
+ double value;
+ double* coeff; ///< double[4] allocated by Curve as needed
+};
+
+/** A list (sequence) of time-stamped values for a control
+ */
+class LIBEVORAL_API ControlList
+{
+public:
+ typedef std::list<ControlEvent*> EventList;
+ typedef EventList::iterator iterator;
+ typedef EventList::reverse_iterator reverse_iterator;
+ typedef EventList::const_iterator const_iterator;
+ typedef EventList::const_reverse_iterator const_reverse_iterator;
+
+ ControlList (const Parameter& id, const ParameterDescriptor& desc);
+ ControlList (const ControlList&);
+ ControlList (const ControlList&, double start, double end);
+ virtual ~ControlList();
+
+ virtual boost::shared_ptr<ControlList> create(const Parameter& id, const ParameterDescriptor& desc);
+
+ void dump (std::ostream&);
+
+ ControlList& operator= (const ControlList&);
+ bool operator== (const ControlList&);
+ void copy_events (const ControlList&);
+
+ virtual void freeze();
+ virtual void thaw ();
+ bool frozen() const { return _frozen; }
+
+ const Parameter& parameter() const { return _parameter; }
+ void set_parameter(const Parameter& p) { _parameter = p; }
+
+ const ParameterDescriptor& descriptor() const { return _desc; }
+ void set_descriptor(const ParameterDescriptor& d) { _desc = d; }
+
+ EventList::size_type size() const { return _events.size(); }
+
+ /** @return time-stamp of first or last event in the list */
+ double when (bool at_start) const {
+ Glib::Threads::RWLock::ReaderLock lm (_lock);
+ if (_events.empty()) {
+ return 0.0;
+ }
+ return at_start ? _events.front()->when : _events.back()->when;
+ }
+
+ double length() const {
+ Glib::Threads::RWLock::ReaderLock lm (_lock);
+ return _events.empty() ? 0.0 : _events.back()->when;
+ }
+ bool empty() const { return _events.empty(); }
+
+ void clear ();
+ void x_scale (double factor);
+ bool extend_to (double);
+ void slide (iterator before, double distance);
+ void shift (double before, double distance);
+
+ void y_transform (boost::function<double(double)> callback);
+ void list_merge (ControlList const& other, boost::function<double(double, double)> callback);
+
+ /** add automation events
+ * @param when absolute time in samples
+ * @param value parameter value
+ * @param with_guards if true, add guard-points
+ * @param with_initial if true, add an initial point if the list is empty
+ */
+ virtual void add (double when, double value, bool with_guards=true, bool with_initial=true);
+
+ virtual bool editor_add (double when, double value, bool with_guard);
+
+ /* to be used only for loading pre-sorted data from saved state */
+ void fast_simple_add (double when, double value);
+
+ void erase_range (double start, double end);
+ void erase (iterator);
+ void erase (iterator, iterator);
+ void erase (double, double);
+ bool move_ranges (std::list< RangeMove<double> > const &);
+ void modify (iterator, double, double);
+
+ /** Thin the number of events in this list.
+ *
+ * The thinning factor corresponds to the area of a triangle computed
+ * between three points in the list (time-difference * value-difference).
+ * If the area is large, it indicates significant non-linearity between
+ * the points.
+ *
+ * Time is measured in samples, value is usually normalized to 0..1.
+ *
+ * During automation recording we thin the recorded points using this
+ * value. If a point is sufficiently co-linear with its neighbours (as
+ * defined by the area of the triangle formed by three of them), we will
+ * not include it in the list. The larger the value, the more points are
+ * excluded, so this effectively measures the amount of thinning to be
+ * done.
+ *
+ * @param thinning_factor area-size (default: 20)
+ */
+ void thin (double thinning_factor);
+
+ boost::shared_ptr<ControlList> cut (double, double);
+ boost::shared_ptr<ControlList> copy (double, double);
+
+ /** remove all automation events between the given time range
+ * @param start start of range (inclusive) in audio samples
+ * @param end end of range (inclusive) in audio samples
+ */
+ void clear (double start, double end);
+
+ bool paste (const ControlList&, double position);
+
+ /** truncate the event list after the given time
+ * @param last_coordinate last event to include
+ */
+ void truncate_end (double last_coordinate);
+ /** truncate the event list to the given time
+ * @param overall_length overall length
+ */
+ void truncate_start (double overall_length);
+
+ iterator begin() { return _events.begin(); }
+ const_iterator begin() const { return _events.begin(); }
+ iterator end() { return _events.end(); }
+ const_iterator end() const { return _events.end(); }
+ reverse_iterator rbegin() { return _events.rbegin(); }
+ const_reverse_iterator rbegin() const { return _events.rbegin(); }
+ reverse_iterator rend() { return _events.rend(); }
+ const_reverse_iterator rend() const { return _events.rend(); }
+ ControlEvent* back() { return _events.back(); }
+ const ControlEvent* back() const { return _events.back(); }
+ ControlEvent* front() { return _events.front(); }
+ const ControlEvent* front() const { return _events.front(); }
+
+ std::pair<ControlList::iterator,ControlList::iterator> control_points_adjacent (double when);
+
+ template<class T> void apply_to_points (T& obj, void (T::*method)(const ControlList&)) {
+ Glib::Threads::RWLock::WriterLock lm (_lock);
+ (obj.*method)(*this);
+ }
+
+ /** query value at given time (takes a read-lock, not safe while writing automation)
+ * @param where absolute time in samples
+ * @returns parameter value
+ */
+ double eval (double where) const {
+ Glib::Threads::RWLock::ReaderLock lm (_lock);
+ return unlocked_eval (where);
+ }
+
+ /** realtime safe version of eval, may fail if read-lock cannot be taken
+ * @param where absolute time in samples
+ * @param ok boolean reference if returned value is valid
+ * @returns parameter value
+ */
+ double rt_safe_eval (double where, bool& ok) const {
+
+ Glib::Threads::RWLock::ReaderLock lm (_lock, Glib::Threads::TRY_LOCK);
+
+ if ((ok = lm.locked())) {
+ return unlocked_eval (where);
+ } else {
+ return 0.0;
+ }
+ }
+
+ static inline bool time_comparator (const ControlEvent* a, const ControlEvent* b) {
+ return a->when < b->when;
+ }
+
+ /** Lookup cache for eval functions, range contains equivalent values */
+ struct LookupCache {
+ LookupCache() : left(-1) {}
+ double left; /* leftmost x coordinate used when finding "range" */
+ std::pair<ControlList::const_iterator,ControlList::const_iterator> range;
+ };
+
+ /** Lookup cache for point finding, range contains points after left */
+ struct SearchCache {
+ SearchCache () : left(-1) {}
+ double left; /* leftmost x coordinate used when finding "first" */
+ ControlList::const_iterator first;
+ };
+
+ const EventList& events() const { return _events; }
+
+ // FIXME: const violations for Curve
+ Glib::Threads::RWLock& lock() const { return _lock; }
+ LookupCache& lookup_cache() const { return _lookup_cache; }
+ SearchCache& search_cache() const { return _search_cache; }
+
+ /** Called by locked entry point and various private
+ * locations where we already hold the lock.
+ *
+ * FIXME: Should this be private? Curve needs it..
+ */
+ double unlocked_eval (double x) const;
+
+ bool rt_safe_earliest_event (double start, double& x, double& y, bool start_inclusive=false) const;
+ bool rt_safe_earliest_event_unlocked (double start, double& x, double& y, bool start_inclusive=false) const;
+ bool rt_safe_earliest_event_linear_unlocked (double start, double& x, double& y, bool inclusive) const;
+ bool rt_safe_earliest_event_discrete_unlocked (double start, double& x, double& y, bool inclusive) const;
+
+ void create_curve();
+ void destroy_curve();
+
+ Curve& curve() { assert(_curve); return *_curve; }
+ const Curve& curve() const { assert(_curve); return *_curve; }
+
+ void mark_dirty () const;
+
+ enum InterpolationStyle {
+ Discrete,
+ Linear,
+ Curved, // spline, used for x-fades
+ Logarithmic,
+ Exponential // fader, gain
+ };
+
+ /** query interpolation style of the automation data
+ * @returns Interpolation Style
+ */
+ InterpolationStyle interpolation() const { return _interpolation; }
+
+ /** query default interpolation for parameter-descriptor */
+ virtual InterpolationStyle default_interpolation() const;
+
+ /** set the interpolation style of the automation data.
+ *
+ * This will fail when asking for Logarithmic scale and min,max crosses 0
+ * or Exponential scale with min != 0.
+ *
+ * @param is interpolation style
+ * @returns true if style change was successful
+ */
+ bool set_interpolation (InterpolationStyle is);
+
+ virtual bool touching() const { return false; }
+ virtual bool writing() const { return false; }
+ virtual bool touch_enabled() const { return false; }
+ void start_write_pass (double when);
+ void write_pass_finished (double when, double thinning_factor=0.0);
+ void set_in_write_pass (bool, bool add_point = false, double when = 0.0);
+ bool in_write_pass () const;
+ bool in_new_write_pass () { return new_write_pass; }
+
+ PBD::Signal0<void> WritePassStarted;
+ /** Emitted when mark_dirty() is called on this object */
+ mutable PBD::Signal0<void> Dirty;
+ /** Emitted when our interpolation style changes */
+ PBD::Signal1<void, InterpolationStyle> InterpolationChanged;
+
+ bool operator!= (ControlList const &) const;
+
+ void invalidate_insert_iterator ();
+
+protected:
+
+ /** Called by unlocked_eval() to handle cases of 3 or more control points. */
+ double multipoint_eval (double x) const;
+
+ void build_search_cache_if_necessary (double start) const;
+
+ boost::shared_ptr<ControlList> cut_copy_clear (double, double, int op);
+ bool erase_range_internal (double start, double end, EventList &);
+
+ void maybe_add_insert_guard (double when);
+ iterator erase_from_iterator_to (iterator iter, double when);
+ bool maybe_insert_straight_line (double when, double value);
+
+ virtual void maybe_signal_changed ();
+
+ void _x_scale (double factor);
+
+ mutable LookupCache _lookup_cache;
+ mutable SearchCache _search_cache;
+
+ mutable Glib::Threads::RWLock _lock;
+
+ Parameter _parameter;
+ ParameterDescriptor _desc;
+ InterpolationStyle _interpolation;
+ EventList _events;
+ int8_t _frozen;
+ bool _changed_when_thawed;
+ bool _sort_pending;
+
+ Curve* _curve;
+
+private:
+ iterator most_recent_insert_iterator;
+ double insert_position;
+ bool new_write_pass;
+ bool did_write_during_pass;
+ bool _in_write_pass;
+
+ void unlocked_remove_duplicates ();
+ void unlocked_invalidate_insert_iterator ();
+ void add_guard_point (double when, double offset);
+
+ bool is_sorted () const;
+};
+
+} // namespace Evoral
+
+#endif // EVORAL_CONTROL_LIST_HPP
+
+++ /dev/null
-/*
- * Copyright (C) 2008-2012 Carl Hetherington <carl@carlh.net>
- * Copyright (C) 2008-2014 Paul Davis <paul@linuxaudiosystems.com>
- * Copyright (C) 2008-2015 David Robillard <d@drobilla.net>
- * Copyright (C) 2012-2017 Robin Gareus <robin@gareus.org>
- * Copyright (C) 2015 Nick Mainsbridge <mainsbridge@gmail.com>
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-
-#ifndef EVORAL_CONTROL_LIST_HPP
-#define EVORAL_CONTROL_LIST_HPP
-
-#include <cassert>
-#include <list>
-#include <stdint.h>
-
-#include <boost/pool/pool.hpp>
-#include <boost/pool/pool_alloc.hpp>
-
-#include <glibmm/threads.h>
-
-#include "pbd/signals.h"
-
-#include "evoral/visibility.h"
-#include "evoral/Range.hpp"
-#include "evoral/Parameter.hpp"
-#include "evoral/ParameterDescriptor.hpp"
-
-namespace Evoral {
-
-class Curve;
-class TypeMap;
-
-/** A single event (time-stamped value) for a control
- */
-class LIBEVORAL_API ControlEvent {
-public:
- ControlEvent (double w, double v)
- : when (w), value (v), coeff (0)
- {}
-
- ControlEvent (const ControlEvent& other)
- : when (other.when), value (other.value), coeff (0)
- {
- if (other.coeff) {
- create_coeffs();
- for (size_t i = 0; i < 4; ++i)
- coeff[i] = other.coeff[i];
- }
- }
-
- ~ControlEvent() { if (coeff) delete[] coeff; }
-
- void create_coeffs() {
- if (!coeff)
- coeff = new double[4];
-
- coeff[0] = coeff[1] = coeff[2] = coeff[3] = 0.0;
- }
-
- double when;
- double value;
- double* coeff; ///< double[4] allocated by Curve as needed
-};
-
-/** A list (sequence) of time-stamped values for a control
- */
-class LIBEVORAL_API ControlList
-{
-public:
- typedef std::list<ControlEvent*> EventList;
- typedef EventList::iterator iterator;
- typedef EventList::reverse_iterator reverse_iterator;
- typedef EventList::const_iterator const_iterator;
- typedef EventList::const_reverse_iterator const_reverse_iterator;
-
- ControlList (const Parameter& id, const ParameterDescriptor& desc);
- ControlList (const ControlList&);
- ControlList (const ControlList&, double start, double end);
- virtual ~ControlList();
-
- virtual boost::shared_ptr<ControlList> create(const Parameter& id, const ParameterDescriptor& desc);
-
- void dump (std::ostream&);
-
- ControlList& operator= (const ControlList&);
- bool operator== (const ControlList&);
- void copy_events (const ControlList&);
-
- virtual void freeze();
- virtual void thaw ();
- bool frozen() const { return _frozen; }
-
- const Parameter& parameter() const { return _parameter; }
- void set_parameter(const Parameter& p) { _parameter = p; }
-
- const ParameterDescriptor& descriptor() const { return _desc; }
- void set_descriptor(const ParameterDescriptor& d) { _desc = d; }
-
- EventList::size_type size() const { return _events.size(); }
-
- /** @return time-stamp of first or last event in the list */
- double when (bool at_start) const {
- Glib::Threads::RWLock::ReaderLock lm (_lock);
- if (_events.empty()) {
- return 0.0;
- }
- return at_start ? _events.front()->when : _events.back()->when;
- }
-
- double length() const {
- Glib::Threads::RWLock::ReaderLock lm (_lock);
- return _events.empty() ? 0.0 : _events.back()->when;
- }
- bool empty() const { return _events.empty(); }
-
- void clear ();
- void x_scale (double factor);
- bool extend_to (double);
- void slide (iterator before, double distance);
- void shift (double before, double distance);
-
- void y_transform (boost::function<double(double)> callback);
- void list_merge (ControlList const& other, boost::function<double(double, double)> callback);
-
- /** add automation events
- * @param when absolute time in samples
- * @param value parameter value
- * @param with_guards if true, add guard-points
- * @param with_initial if true, add an initial point if the list is empty
- */
- virtual void add (double when, double value, bool with_guards=true, bool with_initial=true);
-
- virtual bool editor_add (double when, double value, bool with_guard);
-
- /* to be used only for loading pre-sorted data from saved state */
- void fast_simple_add (double when, double value);
-
- void erase_range (double start, double end);
- void erase (iterator);
- void erase (iterator, iterator);
- void erase (double, double);
- bool move_ranges (std::list< RangeMove<double> > const &);
- void modify (iterator, double, double);
-
- /** Thin the number of events in this list.
- *
- * The thinning factor corresponds to the area of a triangle computed
- * between three points in the list (time-difference * value-difference).
- * If the area is large, it indicates significant non-linearity between
- * the points.
- *
- * Time is measured in samples, value is usually normalized to 0..1.
- *
- * During automation recording we thin the recorded points using this
- * value. If a point is sufficiently co-linear with its neighbours (as
- * defined by the area of the triangle formed by three of them), we will
- * not include it in the list. The larger the value, the more points are
- * excluded, so this effectively measures the amount of thinning to be
- * done.
- *
- * @param thinning_factor area-size (default: 20)
- */
- void thin (double thinning_factor);
-
- boost::shared_ptr<ControlList> cut (double, double);
- boost::shared_ptr<ControlList> copy (double, double);
-
- /** remove all automation events between the given time range
- * @param start start of range (inclusive) in audio samples
- * @param end end of range (inclusive) in audio samples
- */
- void clear (double start, double end);
-
- bool paste (const ControlList&, double position);
-
- /** truncate the event list after the given time
- * @param last_coordinate last event to include
- */
- void truncate_end (double last_coordinate);
- /** truncate the event list to the given time
- * @param overall_length overall length
- */
- void truncate_start (double overall_length);
-
- iterator begin() { return _events.begin(); }
- const_iterator begin() const { return _events.begin(); }
- iterator end() { return _events.end(); }
- const_iterator end() const { return _events.end(); }
- reverse_iterator rbegin() { return _events.rbegin(); }
- const_reverse_iterator rbegin() const { return _events.rbegin(); }
- reverse_iterator rend() { return _events.rend(); }
- const_reverse_iterator rend() const { return _events.rend(); }
- ControlEvent* back() { return _events.back(); }
- const ControlEvent* back() const { return _events.back(); }
- ControlEvent* front() { return _events.front(); }
- const ControlEvent* front() const { return _events.front(); }
-
- std::pair<ControlList::iterator,ControlList::iterator> control_points_adjacent (double when);
-
- template<class T> void apply_to_points (T& obj, void (T::*method)(const ControlList&)) {
- Glib::Threads::RWLock::WriterLock lm (_lock);
- (obj.*method)(*this);
- }
-
- /** query value at given time (takes a read-lock, not safe while writing automation)
- * @param where absolute time in samples
- * @returns parameter value
- */
- double eval (double where) const {
- Glib::Threads::RWLock::ReaderLock lm (_lock);
- return unlocked_eval (where);
- }
-
- /** realtime safe version of eval, may fail if read-lock cannot be taken
- * @param where absolute time in samples
- * @param ok boolean reference if returned value is valid
- * @returns parameter value
- */
- double rt_safe_eval (double where, bool& ok) const {
-
- Glib::Threads::RWLock::ReaderLock lm (_lock, Glib::Threads::TRY_LOCK);
-
- if ((ok = lm.locked())) {
- return unlocked_eval (where);
- } else {
- return 0.0;
- }
- }
-
- static inline bool time_comparator (const ControlEvent* a, const ControlEvent* b) {
- return a->when < b->when;
- }
-
- /** Lookup cache for eval functions, range contains equivalent values */
- struct LookupCache {
- LookupCache() : left(-1) {}
- double left; /* leftmost x coordinate used when finding "range" */
- std::pair<ControlList::const_iterator,ControlList::const_iterator> range;
- };
-
- /** Lookup cache for point finding, range contains points after left */
- struct SearchCache {
- SearchCache () : left(-1) {}
- double left; /* leftmost x coordinate used when finding "first" */
- ControlList::const_iterator first;
- };
-
- const EventList& events() const { return _events; }
-
- // FIXME: const violations for Curve
- Glib::Threads::RWLock& lock() const { return _lock; }
- LookupCache& lookup_cache() const { return _lookup_cache; }
- SearchCache& search_cache() const { return _search_cache; }
-
- /** Called by locked entry point and various private
- * locations where we already hold the lock.
- *
- * FIXME: Should this be private? Curve needs it..
- */
- double unlocked_eval (double x) const;
-
- bool rt_safe_earliest_event (double start, double& x, double& y, bool start_inclusive=false) const;
- bool rt_safe_earliest_event_unlocked (double start, double& x, double& y, bool start_inclusive=false) const;
- bool rt_safe_earliest_event_linear_unlocked (double start, double& x, double& y, bool inclusive) const;
- bool rt_safe_earliest_event_discrete_unlocked (double start, double& x, double& y, bool inclusive) const;
-
- void create_curve();
- void destroy_curve();
-
- Curve& curve() { assert(_curve); return *_curve; }
- const Curve& curve() const { assert(_curve); return *_curve; }
-
- void mark_dirty () const;
-
- enum InterpolationStyle {
- Discrete,
- Linear,
- Curved, // spline, used for x-fades
- Logarithmic,
- Exponential // fader, gain
- };
-
- /** query interpolation style of the automation data
- * @returns Interpolation Style
- */
- InterpolationStyle interpolation() const { return _interpolation; }
-
- /** query default interpolation for parameter-descriptor */
- virtual InterpolationStyle default_interpolation() const;
-
- /** set the interpolation style of the automation data.
- *
- * This will fail when asking for Logarithmic scale and min,max crosses 0
- * or Exponential scale with min != 0.
- *
- * @param is interpolation style
- * @returns true if style change was successful
- */
- bool set_interpolation (InterpolationStyle is);
-
- virtual bool touching() const { return false; }
- virtual bool writing() const { return false; }
- virtual bool touch_enabled() const { return false; }
- void start_write_pass (double when);
- void write_pass_finished (double when, double thinning_factor=0.0);
- void set_in_write_pass (bool, bool add_point = false, double when = 0.0);
- bool in_write_pass () const;
- bool in_new_write_pass () { return new_write_pass; }
-
- PBD::Signal0<void> WritePassStarted;
- /** Emitted when mark_dirty() is called on this object */
- mutable PBD::Signal0<void> Dirty;
- /** Emitted when our interpolation style changes */
- PBD::Signal1<void, InterpolationStyle> InterpolationChanged;
-
- bool operator!= (ControlList const &) const;
-
- void invalidate_insert_iterator ();
-
-protected:
-
- /** Called by unlocked_eval() to handle cases of 3 or more control points. */
- double multipoint_eval (double x) const;
-
- void build_search_cache_if_necessary (double start) const;
-
- boost::shared_ptr<ControlList> cut_copy_clear (double, double, int op);
- bool erase_range_internal (double start, double end, EventList &);
-
- void maybe_add_insert_guard (double when);
- iterator erase_from_iterator_to (iterator iter, double when);
- bool maybe_insert_straight_line (double when, double value);
-
- virtual void maybe_signal_changed ();
-
- void _x_scale (double factor);
-
- mutable LookupCache _lookup_cache;
- mutable SearchCache _search_cache;
-
- mutable Glib::Threads::RWLock _lock;
-
- Parameter _parameter;
- ParameterDescriptor _desc;
- InterpolationStyle _interpolation;
- EventList _events;
- int8_t _frozen;
- bool _changed_when_thawed;
- bool _sort_pending;
-
- Curve* _curve;
-
-private:
- iterator most_recent_insert_iterator;
- double insert_position;
- bool new_write_pass;
- bool did_write_during_pass;
- bool _in_write_pass;
-
- void unlocked_remove_duplicates ();
- void unlocked_invalidate_insert_iterator ();
- void add_guard_point (double when, double offset);
-
- bool is_sorted () const;
-};
-
-} // namespace Evoral
-
-#endif // EVORAL_CONTROL_LIST_HPP
-
--- /dev/null
+/*
+ * Copyright (C) 2008-2015 David Robillard <d@drobilla.net>
+ * Copyright (C) 2010-2017 Paul Davis <paul@linuxaudiosystems.com>
+ * Copyright (C) 2010 Carl Hetherington <carl@carlh.net>
+ * Copyright (C) 2015 Robin Gareus <robin@gareus.org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef EVORAL_CONTROLLABLE_HPP
+#define EVORAL_CONTROLLABLE_HPP
+
+#include <set>
+#include <map>
+#include <boost/shared_ptr.hpp>
+#include <boost/utility.hpp>
+#include <glibmm/threads.h>
+#include "pbd/signals.h"
+
+#include "evoral/visibility.h"
+#include "evoral/Parameter.h"
+#include "evoral/ControlList.h"
+
+namespace Evoral {
+
+class Control;
+class ControlEvent;
+
+class LIBEVORAL_API ControlSet : public boost::noncopyable {
+public:
+ ControlSet();
+ ControlSet (const ControlSet&);
+ virtual ~ControlSet() {}
+
+ virtual boost::shared_ptr<Evoral::Control> control_factory(const Evoral::Parameter& id) = 0;
+
+ boost::shared_ptr<Control>
+ control (const Parameter& id, bool create_if_missing=false);
+
+ inline boost::shared_ptr<const Control>
+ control (const Parameter& id) const {
+ const Controls::const_iterator i = _controls.find(id);
+ return (i != _controls.end() ? i->second : boost::shared_ptr<Control>());
+ }
+
+ typedef std::map< Parameter, boost::shared_ptr<Control> > Controls;
+ inline Controls& controls() { return _controls; }
+ inline const Controls& controls() const { return _controls; }
+
+ virtual void add_control(boost::shared_ptr<Control>);
+
+ virtual bool controls_empty() const { return _controls.size() == 0; }
+ virtual void clear_controls();
+
+ void what_has_data(std::set<Parameter>&) const;
+
+ Glib::Threads::Mutex& control_lock() const { return _control_lock; }
+
+protected:
+ virtual void control_list_marked_dirty () {}
+ virtual void control_list_interpolation_changed (Parameter, ControlList::InterpolationStyle) {}
+
+ mutable Glib::Threads::Mutex _control_lock;
+ Controls _controls;
+
+ PBD::ScopedConnectionList _list_connections;
+
+private:
+
+ PBD::ScopedConnectionList _control_connections;
+};
+
+
+} // namespace Evoral
+
+#endif // EVORAL_CONTROLLABLE_HPP
+++ /dev/null
-/*
- * Copyright (C) 2008-2015 David Robillard <d@drobilla.net>
- * Copyright (C) 2010-2017 Paul Davis <paul@linuxaudiosystems.com>
- * Copyright (C) 2010 Carl Hetherington <carl@carlh.net>
- * Copyright (C) 2015 Robin Gareus <robin@gareus.org>
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-
-#ifndef EVORAL_CONTROLLABLE_HPP
-#define EVORAL_CONTROLLABLE_HPP
-
-#include <set>
-#include <map>
-#include <boost/shared_ptr.hpp>
-#include <boost/utility.hpp>
-#include <glibmm/threads.h>
-#include "pbd/signals.h"
-
-#include "evoral/visibility.h"
-#include "evoral/Parameter.hpp"
-#include "evoral/ControlList.hpp"
-
-namespace Evoral {
-
-class Control;
-class ControlEvent;
-
-class LIBEVORAL_API ControlSet : public boost::noncopyable {
-public:
- ControlSet();
- ControlSet (const ControlSet&);
- virtual ~ControlSet() {}
-
- virtual boost::shared_ptr<Evoral::Control> control_factory(const Evoral::Parameter& id) = 0;
-
- boost::shared_ptr<Control>
- control (const Parameter& id, bool create_if_missing=false);
-
- inline boost::shared_ptr<const Control>
- control (const Parameter& id) const {
- const Controls::const_iterator i = _controls.find(id);
- return (i != _controls.end() ? i->second : boost::shared_ptr<Control>());
- }
-
- typedef std::map< Parameter, boost::shared_ptr<Control> > Controls;
- inline Controls& controls() { return _controls; }
- inline const Controls& controls() const { return _controls; }
-
- virtual void add_control(boost::shared_ptr<Control>);
-
- virtual bool controls_empty() const { return _controls.size() == 0; }
- virtual void clear_controls();
-
- void what_has_data(std::set<Parameter>&) const;
-
- Glib::Threads::Mutex& control_lock() const { return _control_lock; }
-
-protected:
- virtual void control_list_marked_dirty () {}
- virtual void control_list_interpolation_changed (Parameter, ControlList::InterpolationStyle) {}
-
- mutable Glib::Threads::Mutex _control_lock;
- Controls _controls;
-
- PBD::ScopedConnectionList _list_connections;
-
-private:
-
- PBD::ScopedConnectionList _control_connections;
-};
-
-
-} // namespace Evoral
-
-#endif // EVORAL_CONTROLLABLE_HPP
--- /dev/null
+/*
+ * Copyright (C) 2008-2016 David Robillard <d@drobilla.net>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef EVORAL_CURVE_HPP
+#define EVORAL_CURVE_HPP
+
+#include <inttypes.h>
+#include <boost/utility.hpp>
+
+#include "evoral/visibility.h"
+
+namespace Evoral {
+
+class ControlList;
+
+class LIBEVORAL_API Curve : public boost::noncopyable
+{
+public:
+ Curve (const ControlList& cl);
+
+ bool rt_safe_get_vector (double x0, double x1, float *arg, int32_t veclen) const;
+ void get_vector (double x0, double x1, float *arg, int32_t veclen) const;
+
+ void solve () const;
+
+ void mark_dirty() const { _dirty = true; }
+
+private:
+ double multipoint_eval (double x) const;
+
+ void _get_vector (double x0, double x1, float *arg, int32_t veclen) const;
+
+ mutable bool _dirty;
+ const ControlList& _list;
+};
+
+} // namespace Evoral
+
+#endif // EVORAL_CURVE_HPP
+
+++ /dev/null
-/*
- * Copyright (C) 2008-2016 David Robillard <d@drobilla.net>
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-
-#ifndef EVORAL_CURVE_HPP
-#define EVORAL_CURVE_HPP
-
-#include <inttypes.h>
-#include <boost/utility.hpp>
-
-#include "evoral/visibility.h"
-
-namespace Evoral {
-
-class ControlList;
-
-class LIBEVORAL_API Curve : public boost::noncopyable
-{
-public:
- Curve (const ControlList& cl);
-
- bool rt_safe_get_vector (double x0, double x1, float *arg, int32_t veclen) const;
- void get_vector (double x0, double x1, float *arg, int32_t veclen) const;
-
- void solve () const;
-
- void mark_dirty() const { _dirty = true; }
-
-private:
- double multipoint_eval (double x) const;
-
- void _get_vector (double x0, double x1, float *arg, int32_t veclen) const;
-
- mutable bool _dirty;
- const ControlList& _list;
-};
-
-} // namespace Evoral
-
-#endif // EVORAL_CURVE_HPP
-
--- /dev/null
+/*
+ * Copyright (C) 2008-2016 David Robillard <d@drobilla.net>
+ * Copyright (C) 2009-2013 Paul Davis <paul@linuxaudiosystems.com>
+ * Copyright (C) 2009 Hans Baier <hansfbaier@googlemail.com>
+ * Copyright (C) 2010 Carl Hetherington <carl@carlh.net>
+ * Copyright (C) 2015-2016 Robin Gareus <robin@gareus.org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef EVORAL_EVENT_HPP
+#define EVORAL_EVENT_HPP
+
+#include <cstdlib>
+#include <cstring>
+#include <sstream>
+#include <stdint.h>
+
+#include "evoral/midi_events.h"
+#include "evoral/types.h"
+#include "evoral/visibility.h"
+
+/** If this is not defined, all methods of MidiEvent are RT safe
+ * but MidiEvent will never deep copy and (depending on the scenario)
+ * may not be usable in STL containers, signals, etc.
+ */
+#define EVORAL_EVENT_ALLOC 1
+
+namespace Evoral {
+
+LIBEVORAL_API event_id_t event_id_counter();
+LIBEVORAL_API event_id_t next_event_id();
+LIBEVORAL_API void init_event_id_counter(event_id_t n);
+
+/** An event (much like a type generic jack_midi_event_t)
+ *
+ * Template parameter Time is the type of the time stamp used for this event.
+ */
+template<typename Time>
+class LIBEVORAL_API Event {
+public:
+#ifdef EVORAL_EVENT_ALLOC
+ Event(EventType type=NO_EVENT, Time time=Time(), uint32_t size=0, uint8_t* buf=NULL, bool alloc=false);
+
+ Event(EventType type, Time time, uint32_t size, const uint8_t* buf);
+
+ /** Copy \a copy.
+ *
+ * If \a alloc is true, the buffer will be copied and this method
+ * is NOT REALTIME SAFE. Otherwise both events share a buffer and
+ * memory management semantics are the caller's problem.
+ */
+ Event(const Event& copy, bool alloc);
+
+ ~Event();
+
+ void assign(const Event& other);
+
+ void set(const uint8_t* buf, uint32_t size, Time t);
+
+ inline bool operator==(const Event& other) const {
+ if (_type != other._type || _time != other._time || _size != other._size) {
+ return false;
+ }
+ return !memcmp(_buf, other._buf, _size);
+ }
+
+ inline bool operator!=(const Event& other) const { return ! operator==(other); }
+
+ inline bool owns_buffer() const { return _owns_buf; }
+
+ /** set event data (e.g. midi data)
+ * @param size number of bytes
+ * @param buf raw 8bit data
+ * @param own set to true if the buffer owns the data (copy, allocate/free) or false to reference previously allocated data.
+ */
+ inline void set_buffer(uint32_t size, uint8_t* buf, bool own) {
+ if (_owns_buf) {
+ free(_buf);
+ _buf = NULL;
+ }
+ _size = size;
+ _buf = buf;
+ _owns_buf = own;
+ }
+
+ inline void realloc(uint32_t size) {
+ if (_owns_buf) {
+ if (size > _size)
+ _buf = (uint8_t*) ::realloc(_buf, size);
+ } else {
+ _buf = (uint8_t*) ::malloc(size);
+ _owns_buf = true;
+ }
+
+ _size = size;
+ }
+
+ inline void clear() {
+ _type = NO_EVENT;
+ _time = Time();
+ _size = 0;
+ _buf = NULL;
+ }
+
+#endif // EVORAL_EVENT_ALLOC
+
+ inline EventType event_type() const { return _type; }
+ inline Time time() const { return _time; }
+ inline uint32_t size() const { return _size; }
+ inline const uint8_t* buffer() const { return _buf; }
+ inline uint8_t* buffer() { return _buf; }
+
+ inline void set_event_type(EventType t) { _type = t; }
+
+ inline void set_time(Time t) { _time = t; }
+
+ inline event_id_t id() const { return _id; }
+ inline void set_id(event_id_t n) { _id = n; }
+
+ /* The following methods are type specific and only make sense for the
+ correct event type. It is the caller's responsibility to only call
+ methods which make sense for the given event type. Currently this means
+ they all only make sense for MIDI, but built-in support may be added for
+ other protocols in the future, or the internal representation may change
+ to be protocol agnostic. */
+
+ uint8_t type() const { return _buf[0] & 0xF0; }
+ uint8_t channel() const { return _buf[0] & 0x0F; }
+ bool is_note_on() const { return type() == MIDI_CMD_NOTE_ON; }
+ bool is_note_off() const { return type() == MIDI_CMD_NOTE_OFF; }
+ bool is_note() const { return is_note_on() || is_note_off(); }
+ bool is_poly_pressure() const { return type() == MIDI_CMD_NOTE_PRESSURE; }
+ bool is_channel_pressure() const { return type() == MIDI_CMD_CHANNEL_PRESSURE; }
+ bool is_cc() const { return type() == MIDI_CMD_CONTROL; }
+ bool is_pgm_change() const { return type() == MIDI_CMD_PGM_CHANGE; }
+ bool is_pitch_bender() const { return type() == MIDI_CMD_BENDER; }
+ bool is_channel_event() const { return (0x80 <= type()) && (type() <= 0xE0); }
+ bool is_smf_meta_event() const { return _buf[0] == 0xFF; }
+ bool is_sysex() const { return _buf[0] == 0xF0 || _buf[0] == 0xF7; }
+ bool is_spp() const { return _buf[0] == 0xF2 && size() == 1; }
+ bool is_mtc_quarter() const { return _buf[0] == 0xF1 && size() == 1; }
+ bool is_mtc_full() const { return (size() == 10 &&
+ _buf[0] == 0xF0 && _buf[1] == 0x7F &&
+ _buf[3] == 0x01 && _buf[4] == 0x01); }
+
+ uint8_t note() const { return _buf[1]; }
+ uint8_t velocity() const { return _buf[2]; }
+ uint8_t poly_note() const { return _buf[1]; }
+ uint8_t poly_pressure() const { return _buf[2]; }
+ uint8_t channel_pressure() const { return _buf[1]; }
+ uint8_t cc_number() const { return _buf[1]; }
+ uint8_t cc_value() const { return _buf[2]; }
+ uint8_t pgm_number() const { return _buf[1]; }
+ uint8_t pitch_bender_lsb() const { return _buf[1]; }
+ uint8_t pitch_bender_msb() const { return _buf[2]; }
+ uint16_t pitch_bender_value() const { return ((0x7F & _buf[2]) << 7 | (0x7F & _buf[1])); }
+
+ void set_channel(uint8_t channel) { _buf[0] = (0xF0 & _buf[0]) | (0x0F & channel); }
+ void set_type(uint8_t type) { _buf[0] = (0x0F & _buf[0]) | (0xF0 & type); }
+ void set_note(uint8_t num) { _buf[1] = num; }
+ void set_velocity(uint8_t val) { _buf[2] = val; }
+ void set_cc_number(uint8_t num) { _buf[1] = num; }
+ void set_cc_value(uint8_t val) { _buf[2] = val; }
+ void set_pgm_number(uint8_t num) { _buf[1] = num; }
+
+ uint16_t value() const {
+ switch (type()) {
+ case MIDI_CMD_CONTROL:
+ return cc_value();
+ case MIDI_CMD_BENDER:
+ return pitch_bender_value();
+ case MIDI_CMD_NOTE_PRESSURE:
+ return poly_pressure();
+ case MIDI_CMD_CHANNEL_PRESSURE:
+ return channel_pressure();
+ case MIDI_CMD_PGM_CHANGE:
+ return pgm_number();
+ default:
+ return 0;
+ }
+ }
+
+protected:
+ EventType _type; ///< Type of event (application relative, NOT MIDI 'type')
+ Time _time; ///< Time stamp of event
+ uint32_t _size; ///< Size of buffer in bytes
+ uint8_t* _buf; ///< Event contents (e.g. raw MIDI data)
+ event_id_t _id; ///< Unique event ID
+#ifdef EVORAL_EVENT_ALLOC
+ bool _owns_buf; ///< Whether buffer is locally allocated
+#endif
+};
+
+template<typename Time>
+/*LIBEVORAL_API*/ std::ostream& operator<<(std::ostream& o, const Evoral::Event<Time>& ev) {
+ o << "Event #" << ev.id() << " type = " << ev.event_type() << " @ " << ev.time();
+ o << std::hex;
+ for (uint32_t n = 0; n < ev.size(); ++n) {
+ o << ' ' << (int) ev.buffer()[n];
+ }
+ o << std::dec;
+ return o;
+}
+
+} // namespace Evoral
+
+#endif // EVORAL_EVENT_HPP
+
+++ /dev/null
-/*
- * Copyright (C) 2008-2016 David Robillard <d@drobilla.net>
- * Copyright (C) 2009-2013 Paul Davis <paul@linuxaudiosystems.com>
- * Copyright (C) 2009 Hans Baier <hansfbaier@googlemail.com>
- * Copyright (C) 2010 Carl Hetherington <carl@carlh.net>
- * Copyright (C) 2015-2016 Robin Gareus <robin@gareus.org>
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-
-#ifndef EVORAL_EVENT_HPP
-#define EVORAL_EVENT_HPP
-
-#include <cstdlib>
-#include <cstring>
-#include <sstream>
-#include <stdint.h>
-
-#include "evoral/midi_events.h"
-#include "evoral/types.hpp"
-#include "evoral/visibility.h"
-
-/** If this is not defined, all methods of MidiEvent are RT safe
- * but MidiEvent will never deep copy and (depending on the scenario)
- * may not be usable in STL containers, signals, etc.
- */
-#define EVORAL_EVENT_ALLOC 1
-
-namespace Evoral {
-
-LIBEVORAL_API event_id_t event_id_counter();
-LIBEVORAL_API event_id_t next_event_id();
-LIBEVORAL_API void init_event_id_counter(event_id_t n);
-
-/** An event (much like a type generic jack_midi_event_t)
- *
- * Template parameter Time is the type of the time stamp used for this event.
- */
-template<typename Time>
-class LIBEVORAL_API Event {
-public:
-#ifdef EVORAL_EVENT_ALLOC
- Event(EventType type=NO_EVENT, Time time=Time(), uint32_t size=0, uint8_t* buf=NULL, bool alloc=false);
-
- Event(EventType type, Time time, uint32_t size, const uint8_t* buf);
-
- /** Copy \a copy.
- *
- * If \a alloc is true, the buffer will be copied and this method
- * is NOT REALTIME SAFE. Otherwise both events share a buffer and
- * memory management semantics are the caller's problem.
- */
- Event(const Event& copy, bool alloc);
-
- ~Event();
-
- void assign(const Event& other);
-
- void set(const uint8_t* buf, uint32_t size, Time t);
-
- inline bool operator==(const Event& other) const {
- if (_type != other._type || _time != other._time || _size != other._size) {
- return false;
- }
- return !memcmp(_buf, other._buf, _size);
- }
-
- inline bool operator!=(const Event& other) const { return ! operator==(other); }
-
- inline bool owns_buffer() const { return _owns_buf; }
-
- /** set event data (e.g. midi data)
- * @param size number of bytes
- * @param buf raw 8bit data
- * @param own set to true if the buffer owns the data (copy, allocate/free) or false to reference previously allocated data.
- */
- inline void set_buffer(uint32_t size, uint8_t* buf, bool own) {
- if (_owns_buf) {
- free(_buf);
- _buf = NULL;
- }
- _size = size;
- _buf = buf;
- _owns_buf = own;
- }
-
- inline void realloc(uint32_t size) {
- if (_owns_buf) {
- if (size > _size)
- _buf = (uint8_t*) ::realloc(_buf, size);
- } else {
- _buf = (uint8_t*) ::malloc(size);
- _owns_buf = true;
- }
-
- _size = size;
- }
-
- inline void clear() {
- _type = NO_EVENT;
- _time = Time();
- _size = 0;
- _buf = NULL;
- }
-
-#endif // EVORAL_EVENT_ALLOC
-
- inline EventType event_type() const { return _type; }
- inline Time time() const { return _time; }
- inline uint32_t size() const { return _size; }
- inline const uint8_t* buffer() const { return _buf; }
- inline uint8_t* buffer() { return _buf; }
-
- inline void set_event_type(EventType t) { _type = t; }
-
- inline void set_time(Time t) { _time = t; }
-
- inline event_id_t id() const { return _id; }
- inline void set_id(event_id_t n) { _id = n; }
-
- /* The following methods are type specific and only make sense for the
- correct event type. It is the caller's responsibility to only call
- methods which make sense for the given event type. Currently this means
- they all only make sense for MIDI, but built-in support may be added for
- other protocols in the future, or the internal representation may change
- to be protocol agnostic. */
-
- uint8_t type() const { return _buf[0] & 0xF0; }
- uint8_t channel() const { return _buf[0] & 0x0F; }
- bool is_note_on() const { return type() == MIDI_CMD_NOTE_ON; }
- bool is_note_off() const { return type() == MIDI_CMD_NOTE_OFF; }
- bool is_note() const { return is_note_on() || is_note_off(); }
- bool is_poly_pressure() const { return type() == MIDI_CMD_NOTE_PRESSURE; }
- bool is_channel_pressure() const { return type() == MIDI_CMD_CHANNEL_PRESSURE; }
- bool is_cc() const { return type() == MIDI_CMD_CONTROL; }
- bool is_pgm_change() const { return type() == MIDI_CMD_PGM_CHANGE; }
- bool is_pitch_bender() const { return type() == MIDI_CMD_BENDER; }
- bool is_channel_event() const { return (0x80 <= type()) && (type() <= 0xE0); }
- bool is_smf_meta_event() const { return _buf[0] == 0xFF; }
- bool is_sysex() const { return _buf[0] == 0xF0 || _buf[0] == 0xF7; }
- bool is_spp() const { return _buf[0] == 0xF2 && size() == 1; }
- bool is_mtc_quarter() const { return _buf[0] == 0xF1 && size() == 1; }
- bool is_mtc_full() const { return (size() == 10 &&
- _buf[0] == 0xF0 && _buf[1] == 0x7F &&
- _buf[3] == 0x01 && _buf[4] == 0x01); }
-
- uint8_t note() const { return _buf[1]; }
- uint8_t velocity() const { return _buf[2]; }
- uint8_t poly_note() const { return _buf[1]; }
- uint8_t poly_pressure() const { return _buf[2]; }
- uint8_t channel_pressure() const { return _buf[1]; }
- uint8_t cc_number() const { return _buf[1]; }
- uint8_t cc_value() const { return _buf[2]; }
- uint8_t pgm_number() const { return _buf[1]; }
- uint8_t pitch_bender_lsb() const { return _buf[1]; }
- uint8_t pitch_bender_msb() const { return _buf[2]; }
- uint16_t pitch_bender_value() const { return ((0x7F & _buf[2]) << 7 | (0x7F & _buf[1])); }
-
- void set_channel(uint8_t channel) { _buf[0] = (0xF0 & _buf[0]) | (0x0F & channel); }
- void set_type(uint8_t type) { _buf[0] = (0x0F & _buf[0]) | (0xF0 & type); }
- void set_note(uint8_t num) { _buf[1] = num; }
- void set_velocity(uint8_t val) { _buf[2] = val; }
- void set_cc_number(uint8_t num) { _buf[1] = num; }
- void set_cc_value(uint8_t val) { _buf[2] = val; }
- void set_pgm_number(uint8_t num) { _buf[1] = num; }
-
- uint16_t value() const {
- switch (type()) {
- case MIDI_CMD_CONTROL:
- return cc_value();
- case MIDI_CMD_BENDER:
- return pitch_bender_value();
- case MIDI_CMD_NOTE_PRESSURE:
- return poly_pressure();
- case MIDI_CMD_CHANNEL_PRESSURE:
- return channel_pressure();
- case MIDI_CMD_PGM_CHANGE:
- return pgm_number();
- default:
- return 0;
- }
- }
-
-protected:
- EventType _type; ///< Type of event (application relative, NOT MIDI 'type')
- Time _time; ///< Time stamp of event
- uint32_t _size; ///< Size of buffer in bytes
- uint8_t* _buf; ///< Event contents (e.g. raw MIDI data)
- event_id_t _id; ///< Unique event ID
-#ifdef EVORAL_EVENT_ALLOC
- bool _owns_buf; ///< Whether buffer is locally allocated
-#endif
-};
-
-template<typename Time>
-/*LIBEVORAL_API*/ std::ostream& operator<<(std::ostream& o, const Evoral::Event<Time>& ev) {
- o << "Event #" << ev.id() << " type = " << ev.event_type() << " @ " << ev.time();
- o << std::hex;
- for (uint32_t n = 0; n < ev.size(); ++n) {
- o << ' ' << (int) ev.buffer()[n];
- }
- o << std::dec;
- return o;
-}
-
-} // namespace Evoral
-
-#endif // EVORAL_EVENT_HPP
-
--- /dev/null
+/*
+ * Copyright (C) 2009-2013 Paul Davis <paul@linuxaudiosystems.com>
+ * Copyright (C) 2009-2015 David Robillard <d@drobilla.net>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef EVORAL_EVENT_LIST_HPP
+#define EVORAL_EVENT_LIST_HPP
+
+#include <list>
+
+#include "evoral/Event.h"
+#include "evoral/EventSink.h"
+#include "evoral/visibility.h"
+
+namespace Evoral {
+
+
+/** A list of events (generic time-stamped binary "blobs").
+ *
+ * Used when we need an unsorted list of Events that is also an EventSink. Absolutely nothing more.
+ */
+template<typename Time>
+class /*LIBEVORAL_API*/ EventList : public std::list<Evoral::Event<Time> *>, public Evoral::EventSink<Time> {
+public:
+ EventList() {}
+
+ uint32_t write(Time time, EventType type, uint32_t size, const uint8_t* buf) {
+ this->push_back(new Evoral::Event<Time>(
+ type, time, size, const_cast<uint8_t*>(buf), true)); // Event copies buffer
+ return size;
+ }
+};
+
+
+} // namespace Evoral
+
+#endif // EVORAL_EVENT_LIST_HPP
+++ /dev/null
-/*
- * Copyright (C) 2009-2013 Paul Davis <paul@linuxaudiosystems.com>
- * Copyright (C) 2009-2015 David Robillard <d@drobilla.net>
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-
-#ifndef EVORAL_EVENT_LIST_HPP
-#define EVORAL_EVENT_LIST_HPP
-
-#include <list>
-
-#include "evoral/Event.hpp"
-#include "evoral/EventSink.hpp"
-#include "evoral/visibility.h"
-
-namespace Evoral {
-
-
-/** A list of events (generic time-stamped binary "blobs").
- *
- * Used when we need an unsorted list of Events that is also an EventSink. Absolutely nothing more.
- */
-template<typename Time>
-class /*LIBEVORAL_API*/ EventList : public std::list<Evoral::Event<Time> *>, public Evoral::EventSink<Time> {
-public:
- EventList() {}
-
- uint32_t write(Time time, EventType type, uint32_t size, const uint8_t* buf) {
- this->push_back(new Evoral::Event<Time>(
- type, time, size, const_cast<uint8_t*>(buf), true)); // Event copies buffer
- return size;
- }
-};
-
-
-} // namespace Evoral
-
-#endif // EVORAL_EVENT_LIST_HPP
--- /dev/null
+/*
+ * Copyright (C) 2008-2011 David Robillard <d@drobilla.net>
+ * Copyright (C) 2008-2013 Paul Davis <paul@linuxaudiosystems.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef EVORAL_EVENT_SINK_HPP
+#define EVORAL_EVENT_SINK_HPP
+
+#include "evoral/visibility.h"
+#include "evoral/types.h"
+
+namespace Evoral {
+
+/** Pure virtual base for anything you can write events to.
+ */
+template<typename Time>
+class /*LIBEVORAL_API*/ EventSink {
+public:
+ virtual ~EventSink() {}
+ virtual uint32_t write(Time time, EventType type, uint32_t size, const uint8_t* buf) = 0;
+};
+
+
+} // namespace Evoral
+
+#endif // EVORAL_EVENT_SINK_HPP
+
+++ /dev/null
-/*
- * Copyright (C) 2008-2011 David Robillard <d@drobilla.net>
- * Copyright (C) 2008-2013 Paul Davis <paul@linuxaudiosystems.com>
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-
-#ifndef EVORAL_EVENT_SINK_HPP
-#define EVORAL_EVENT_SINK_HPP
-
-#include "evoral/visibility.h"
-#include "evoral/types.hpp"
-
-namespace Evoral {
-
-/** Pure virtual base for anything you can write events to.
- */
-template<typename Time>
-class /*LIBEVORAL_API*/ EventSink {
-public:
- virtual ~EventSink() {}
- virtual uint32_t write(Time time, EventType type, uint32_t size, const uint8_t* buf) = 0;
-};
-
-
-} // namespace Evoral
-
-#endif // EVORAL_EVENT_SINK_HPP
-
--- /dev/null
+/*
+ * Copyright (C) 2016 David Robillard <d@drobilla.net>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef EVORAL_MIDI_XML_HPP
+#define EVORAL_MIDI_XML_HPP
+
+#include "evoral/Event.h"
+#include "pbd/xml++.h"
+
+namespace Evoral {
+namespace MIDIXML {
+
+template<typename Time>
+bool
+xml_to_midi(const XMLNode& node, Evoral::Event<Time>& ev)
+{
+ if (node.name() == "ControlChange") {
+ ev.set_type(MIDI_CMD_CONTROL);
+ ev.set_cc_number(atoi(node.property("Control")->value().c_str()));
+ ev.set_cc_value(atoi(node.property("Value")->value().c_str()));
+ return true;
+ } else if (node.name() == "ProgramChange") {
+ ev.set_type(MIDI_CMD_PGM_CHANGE);
+ ev.set_pgm_number(atoi(node.property("Number")->value().c_str()));
+ return true;
+ }
+
+ return false;
+}
+
+template<typename Time>
+boost::shared_ptr<XMLNode>
+midi_to_xml(const Evoral::Event<Time>& ev)
+{
+ XMLNode* result = 0;
+
+ switch (ev.type()) {
+ case MIDI_CMD_CONTROL:
+ result = new XMLNode("ControlChange");
+ result->add_property("Channel", long(ev.channel()));
+ result->add_property("Control", long(ev.cc_number()));
+ result->add_property("Value", long(ev.cc_value()));
+ break;
+
+ case MIDI_CMD_PGM_CHANGE:
+ result = new XMLNode("ProgramChange");
+ result->add_property("Channel", long(ev.channel()));
+ result->add_property("Number", long(ev.pgm_number()));
+ break;
+
+ case MIDI_CMD_NOTE_ON:
+ result = new XMLNode("NoteOn");
+ result->add_property("Channel", long(ev.channel()));
+ result->add_property("Note", long(ev.note()));
+ result->add_property("Velocity", long(ev.velocity()));
+ break;
+
+ case MIDI_CMD_NOTE_OFF:
+ result = new XMLNode("NoteOff");
+ result->add_property("Channel", long(ev.channel()));
+ result->add_property("Note", long(ev.note()));
+ result->add_property("Velocity", long(ev.velocity()));
+ break;
+
+ case MIDI_CMD_BENDER:
+ result = new XMLNode("PitchBendChange");
+ result->add_property("Channel", long(ev.channel()));
+ result->add_property("Value", long(ev.pitch_bender_value()));
+ break;
+
+ default:
+ return boost::shared_ptr<XMLNode>();
+ }
+
+ return boost::shared_ptr<XMLNode>(result);
+}
+
+} // namespace MIDIXML
+} // namespace Evoral
+
+#endif // EVORAL_MIDI_XML_HPP
+++ /dev/null
-/*
- * Copyright (C) 2016 David Robillard <d@drobilla.net>
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-
-#ifndef EVORAL_MIDI_XML_HPP
-#define EVORAL_MIDI_XML_HPP
-
-#include "evoral/Event.hpp"
-#include "pbd/xml++.h"
-
-namespace Evoral {
-namespace MIDIXML {
-
-template<typename Time>
-bool
-xml_to_midi(const XMLNode& node, Evoral::Event<Time>& ev)
-{
- if (node.name() == "ControlChange") {
- ev.set_type(MIDI_CMD_CONTROL);
- ev.set_cc_number(atoi(node.property("Control")->value().c_str()));
- ev.set_cc_value(atoi(node.property("Value")->value().c_str()));
- return true;
- } else if (node.name() == "ProgramChange") {
- ev.set_type(MIDI_CMD_PGM_CHANGE);
- ev.set_pgm_number(atoi(node.property("Number")->value().c_str()));
- return true;
- }
-
- return false;
-}
-
-template<typename Time>
-boost::shared_ptr<XMLNode>
-midi_to_xml(const Evoral::Event<Time>& ev)
-{
- XMLNode* result = 0;
-
- switch (ev.type()) {
- case MIDI_CMD_CONTROL:
- result = new XMLNode("ControlChange");
- result->add_property("Channel", long(ev.channel()));
- result->add_property("Control", long(ev.cc_number()));
- result->add_property("Value", long(ev.cc_value()));
- break;
-
- case MIDI_CMD_PGM_CHANGE:
- result = new XMLNode("ProgramChange");
- result->add_property("Channel", long(ev.channel()));
- result->add_property("Number", long(ev.pgm_number()));
- break;
-
- case MIDI_CMD_NOTE_ON:
- result = new XMLNode("NoteOn");
- result->add_property("Channel", long(ev.channel()));
- result->add_property("Note", long(ev.note()));
- result->add_property("Velocity", long(ev.velocity()));
- break;
-
- case MIDI_CMD_NOTE_OFF:
- result = new XMLNode("NoteOff");
- result->add_property("Channel", long(ev.channel()));
- result->add_property("Note", long(ev.note()));
- result->add_property("Velocity", long(ev.velocity()));
- break;
-
- case MIDI_CMD_BENDER:
- result = new XMLNode("PitchBendChange");
- result->add_property("Channel", long(ev.channel()));
- result->add_property("Value", long(ev.pitch_bender_value()));
- break;
-
- default:
- return boost::shared_ptr<XMLNode>();
- }
-
- return boost::shared_ptr<XMLNode>(result);
-}
-
-} // namespace MIDIXML
-} // namespace Evoral
-
-#endif // EVORAL_MIDI_XML_HPP
--- /dev/null
+/*
+ * Copyright (C) 2008-2016 David Robillard <d@drobilla.net>
+ * Copyright (C) 2009-2014 Paul Davis <paul@linuxaudiosystems.com>
+ * Copyright (C) 2014 John Emmas <john@creativepost.co.uk>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef EVORAL_NOTE_HPP
+#define EVORAL_NOTE_HPP
+
+#include <algorithm>
+#include <assert.h>
+#include <glib.h>
+#include <stdint.h>
+
+#include "evoral/visibility.h"
+#include "evoral/Event.h"
+
+namespace Evoral {
+
+/** An abstract (protocol agnostic) note.
+ *
+ * Currently a note is defined as (on event, length, off event).
+ */
+template<typename Time>
+#ifdef COMPILER_MSVC
+class LIBEVORAL_LOCAL Note {
+#else
+class LIBEVORAL_TEMPLATE_API Note {
+#endif
+public:
+ Note(uint8_t chan=0, Time time=Time(), Time len=Time(), uint8_t note=0, uint8_t vel=0x40);
+ Note(const Note<Time>& copy);
+ ~Note();
+
+ inline bool operator==(const Note<Time>& other) {
+ return time() == other.time() &&
+ note() == other.note() &&
+ length() == other.length() &&
+ velocity() == other.velocity() &&
+ off_velocity() == other.off_velocity() &&
+ channel() == other.channel();
+ }
+
+ inline event_id_t id() const { return _on_event.id(); }
+ void set_id (event_id_t);
+
+ inline Time time() const { return _on_event.time(); }
+ inline Time end_time() const { return _off_event.time(); }
+ inline uint8_t note() const { return _on_event.note(); }
+ inline uint8_t velocity() const { return _on_event.velocity(); }
+ inline uint8_t off_velocity() const { return _off_event.velocity(); }
+ inline Time length() const { return _off_event.time() - _on_event.time(); }
+ inline uint8_t channel() const {
+ assert(_on_event.channel() == _off_event.channel());
+ return _on_event.channel();
+ }
+
+private:
+ const Note<Time>& operator=(const Note<Time>& copy); // undefined (unsafe)
+
+ inline int clamp(int val, int low, int high) {
+ return std::min (std::max (val, low), high);
+ }
+
+public:
+ inline void set_time(Time t) {
+ _off_event.set_time(t + length());
+ _on_event.set_time(t);
+ }
+ inline void set_note(uint8_t n) {
+ const uint8_t nn = clamp(n, 0, 127);
+ _on_event.buffer()[1] = nn;
+ _off_event.buffer()[1] = nn;
+ }
+ inline void set_velocity(uint8_t n) {
+ _on_event.buffer()[2] = clamp(n, 0, 127);
+ }
+ inline void set_off_velocity(uint8_t n) {
+ _off_event.buffer()[2] = clamp(n, 0, 127);
+ }
+ inline void set_length(Time l) {
+ _off_event.set_time(_on_event.time() + l);
+ }
+ inline void set_channel(uint8_t c) {
+ const uint8_t cc = clamp(c, 0, 16);
+ _on_event.set_channel(cc);
+ _off_event.set_channel(cc);
+ }
+
+ inline Event<Time>& on_event() { return _on_event; }
+ inline const Event<Time>& on_event() const { return _on_event; }
+ inline Event<Time>& off_event() { return _off_event; }
+ inline const Event<Time>& off_event() const { return _off_event; }
+
+private:
+ // Event buffers are self-contained
+ Event<Time> _on_event;
+ Event<Time> _off_event;
+};
+
+template<typename Time>
+/*LIBEVORAL_API*/ std::ostream& operator<<(std::ostream& o, const Evoral::Note<Time>& n) {
+ o << "Note #" << n.id() << ": pitch = " << (int) n.note()
+ << " @ " << n.time() << " .. " << n.end_time()
+ << " velocity " << (int) n.velocity()
+ << " chn " << (int) n.channel();
+ return o;
+}
+
+} // namespace Evoral
+
+#ifdef COMPILER_MSVC
+#include "../src/Note.impl"
+#endif
+
+#endif // EVORAL_NOTE_HPP
+
+++ /dev/null
-/*
- * Copyright (C) 2008-2016 David Robillard <d@drobilla.net>
- * Copyright (C) 2009-2014 Paul Davis <paul@linuxaudiosystems.com>
- * Copyright (C) 2014 John Emmas <john@creativepost.co.uk>
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-
-#ifndef EVORAL_NOTE_HPP
-#define EVORAL_NOTE_HPP
-
-#include <algorithm>
-#include <assert.h>
-#include <glib.h>
-#include <stdint.h>
-
-#include "evoral/visibility.h"
-#include "evoral/Event.hpp"
-
-namespace Evoral {
-
-/** An abstract (protocol agnostic) note.
- *
- * Currently a note is defined as (on event, length, off event).
- */
-template<typename Time>
-#ifdef COMPILER_MSVC
-class LIBEVORAL_LOCAL Note {
-#else
-class LIBEVORAL_TEMPLATE_API Note {
-#endif
-public:
- Note(uint8_t chan=0, Time time=Time(), Time len=Time(), uint8_t note=0, uint8_t vel=0x40);
- Note(const Note<Time>& copy);
- ~Note();
-
- inline bool operator==(const Note<Time>& other) {
- return time() == other.time() &&
- note() == other.note() &&
- length() == other.length() &&
- velocity() == other.velocity() &&
- off_velocity() == other.off_velocity() &&
- channel() == other.channel();
- }
-
- inline event_id_t id() const { return _on_event.id(); }
- void set_id (event_id_t);
-
- inline Time time() const { return _on_event.time(); }
- inline Time end_time() const { return _off_event.time(); }
- inline uint8_t note() const { return _on_event.note(); }
- inline uint8_t velocity() const { return _on_event.velocity(); }
- inline uint8_t off_velocity() const { return _off_event.velocity(); }
- inline Time length() const { return _off_event.time() - _on_event.time(); }
- inline uint8_t channel() const {
- assert(_on_event.channel() == _off_event.channel());
- return _on_event.channel();
- }
-
-private:
- const Note<Time>& operator=(const Note<Time>& copy); // undefined (unsafe)
-
- inline int clamp(int val, int low, int high) {
- return std::min (std::max (val, low), high);
- }
-
-public:
- inline void set_time(Time t) {
- _off_event.set_time(t + length());
- _on_event.set_time(t);
- }
- inline void set_note(uint8_t n) {
- const uint8_t nn = clamp(n, 0, 127);
- _on_event.buffer()[1] = nn;
- _off_event.buffer()[1] = nn;
- }
- inline void set_velocity(uint8_t n) {
- _on_event.buffer()[2] = clamp(n, 0, 127);
- }
- inline void set_off_velocity(uint8_t n) {
- _off_event.buffer()[2] = clamp(n, 0, 127);
- }
- inline void set_length(Time l) {
- _off_event.set_time(_on_event.time() + l);
- }
- inline void set_channel(uint8_t c) {
- const uint8_t cc = clamp(c, 0, 16);
- _on_event.set_channel(cc);
- _off_event.set_channel(cc);
- }
-
- inline Event<Time>& on_event() { return _on_event; }
- inline const Event<Time>& on_event() const { return _on_event; }
- inline Event<Time>& off_event() { return _off_event; }
- inline const Event<Time>& off_event() const { return _off_event; }
-
-private:
- // Event buffers are self-contained
- Event<Time> _on_event;
- Event<Time> _off_event;
-};
-
-template<typename Time>
-/*LIBEVORAL_API*/ std::ostream& operator<<(std::ostream& o, const Evoral::Note<Time>& n) {
- o << "Note #" << n.id() << ": pitch = " << (int) n.note()
- << " @ " << n.time() << " .. " << n.end_time()
- << " velocity " << (int) n.velocity()
- << " chn " << (int) n.channel();
- return o;
-}
-
-} // namespace Evoral
-
-#ifdef COMPILER_MSVC
-#include "../src/Note.impl"
-#endif
-
-#endif // EVORAL_NOTE_HPP
-
--- /dev/null
+/*
+ * Copyright (C) 2008-2016 David Robillard <d@drobilla.net>
+ * Copyright (C) 2008-2018 Paul Davis <paul@linuxaudiosystems.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef EVORAL_PARAMETER_HPP
+#define EVORAL_PARAMETER_HPP
+
+#include <string>
+#include <map>
+#include <stdint.h>
+#include <boost/shared_ptr.hpp>
+
+#include "evoral/visibility.h"
+#include "evoral/types.h"
+
+namespace Evoral {
+
+/** ID of a [play|record|automate]able parameter.
+ *
+ * A parameter is defined by (type, id, channel). Type is an integer which
+ * can be used in any way by the application (e.g. cast to a custom enum,
+ * map to/from a URI, etc). ID is type specific (e.g. MIDI controller #).
+ *
+ * This class defines a < operator which is a strict weak ordering, so
+ * Parameter may be stored in a std::set, used as a std::map key, etc.
+ */
+class LIBEVORAL_API Parameter
+{
+public:
+ inline Parameter(ParameterType type, uint8_t channel=0, uint32_t id=0)
+ : _type(type), _id(id), _channel(channel)
+ {}
+
+ inline ParameterType type() const { return _type; }
+ inline uint8_t channel() const { return _channel; }
+ inline uint32_t id() const { return _id; }
+
+ /** Equivalence operator
+ * It is obvious from the definition that this operator
+ * is transitive, as required by stict weak ordering
+ * (see: http://www.sgi.com/tech/stl/StrictWeakOrdering.html)
+ */
+ inline bool operator==(const Parameter& id) const {
+ return (_type == id._type && _channel == id._channel && _id == id._id );
+ }
+
+ inline bool operator!=(const Parameter& id) const {
+ return !operator==(id);
+ }
+
+ /** Strict weak ordering
+ * See: http://www.sgi.com/tech/stl/StrictWeakOrdering.html
+ * Sort Parameters first according to type then to channel and lastly to ID.
+ */
+ inline bool operator<(const Parameter& other) const {
+ if (_type < other._type) {
+ return true;
+ } else if (_type == other._type && _channel < other._channel) {
+ return true;
+ } else if (_type == other._type && _channel == other._channel && _id < other._id ) {
+ return true;
+ }
+
+ return false;
+ }
+
+ inline operator bool() const { return (_type != 0); }
+
+private:
+ ParameterType _type;
+ uint32_t _id;
+ uint8_t _channel;
+};
+
+} // namespace Evoral
+
+namespace std {
+std::ostream& operator<< (std::ostream &str, Evoral::Parameter const &);
+}
+
+#endif // EVORAL_PARAMETER_HPP
+
+++ /dev/null
-/*
- * Copyright (C) 2008-2016 David Robillard <d@drobilla.net>
- * Copyright (C) 2008-2018 Paul Davis <paul@linuxaudiosystems.com>
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-
-#ifndef EVORAL_PARAMETER_HPP
-#define EVORAL_PARAMETER_HPP
-
-#include <string>
-#include <map>
-#include <stdint.h>
-#include <boost/shared_ptr.hpp>
-
-#include "evoral/visibility.h"
-#include "evoral/types.hpp"
-
-namespace Evoral {
-
-/** ID of a [play|record|automate]able parameter.
- *
- * A parameter is defined by (type, id, channel). Type is an integer which
- * can be used in any way by the application (e.g. cast to a custom enum,
- * map to/from a URI, etc). ID is type specific (e.g. MIDI controller #).
- *
- * This class defines a < operator which is a strict weak ordering, so
- * Parameter may be stored in a std::set, used as a std::map key, etc.
- */
-class LIBEVORAL_API Parameter
-{
-public:
- inline Parameter(ParameterType type, uint8_t channel=0, uint32_t id=0)
- : _type(type), _id(id), _channel(channel)
- {}
-
- inline ParameterType type() const { return _type; }
- inline uint8_t channel() const { return _channel; }
- inline uint32_t id() const { return _id; }
-
- /** Equivalence operator
- * It is obvious from the definition that this operator
- * is transitive, as required by stict weak ordering
- * (see: http://www.sgi.com/tech/stl/StrictWeakOrdering.html)
- */
- inline bool operator==(const Parameter& id) const {
- return (_type == id._type && _channel == id._channel && _id == id._id );
- }
-
- inline bool operator!=(const Parameter& id) const {
- return !operator==(id);
- }
-
- /** Strict weak ordering
- * See: http://www.sgi.com/tech/stl/StrictWeakOrdering.html
- * Sort Parameters first according to type then to channel and lastly to ID.
- */
- inline bool operator<(const Parameter& other) const {
- if (_type < other._type) {
- return true;
- } else if (_type == other._type && _channel < other._channel) {
- return true;
- } else if (_type == other._type && _channel == other._channel && _id < other._id ) {
- return true;
- }
-
- return false;
- }
-
- inline operator bool() const { return (_type != 0); }
-
-private:
- ParameterType _type;
- uint32_t _id;
- uint8_t _channel;
-};
-
-} // namespace Evoral
-
-namespace std {
-std::ostream& operator<< (std::ostream &str, Evoral::Parameter const &);
-}
-
-#endif // EVORAL_PARAMETER_HPP
-
--- /dev/null
+/*
+ * Copyright (C) 2014 David Robillard <d@drobilla.net>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef EVORAL_PARAMETER_DESCRIPTOR_HPP
+#define EVORAL_PARAMETER_DESCRIPTOR_HPP
+
+namespace Evoral {
+
+/** Description of the value range of a parameter or control. */
+struct ParameterDescriptor
+{
+ ParameterDescriptor()
+ : normal(0.0)
+ , lower(0.0)
+ , upper(1.0)
+ , toggled(false)
+ , logarithmic(false)
+ , rangesteps (0)
+ {}
+
+ float normal; ///< Default value
+ float lower; ///< Minimum value (in Hz, for frequencies)
+ float upper; ///< Maximum value (in Hz, for frequencies)
+ bool toggled; ///< True iff parameter is boolean
+ bool logarithmic; ///< True for log-scale parameters
+ unsigned int rangesteps; ///< number of steps, [min,max] (inclusive). <= 1 means continuous. == 2 only min, max. For integer controls this is usually (1 + max - min)
+};
+
+} // namespace Evoral
+
+#endif // EVORAL_PARAMETER_DESCRIPTOR_HPP
+++ /dev/null
-/*
- * Copyright (C) 2014 David Robillard <d@drobilla.net>
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-
-#ifndef EVORAL_PARAMETER_DESCRIPTOR_HPP
-#define EVORAL_PARAMETER_DESCRIPTOR_HPP
-
-namespace Evoral {
-
-/** Description of the value range of a parameter or control. */
-struct ParameterDescriptor
-{
- ParameterDescriptor()
- : normal(0.0)
- , lower(0.0)
- , upper(1.0)
- , toggled(false)
- , logarithmic(false)
- , rangesteps (0)
- {}
-
- float normal; ///< Default value
- float lower; ///< Minimum value (in Hz, for frequencies)
- float upper; ///< Maximum value (in Hz, for frequencies)
- bool toggled; ///< True iff parameter is boolean
- bool logarithmic; ///< True for log-scale parameters
- unsigned int rangesteps; ///< number of steps, [min,max] (inclusive). <= 1 means continuous. == 2 only min, max. For integer controls this is usually (1 + max - min)
-};
-
-} // namespace Evoral
-
-#endif // EVORAL_PARAMETER_DESCRIPTOR_HPP
--- /dev/null
+/*
+ * Copyright (C) 2011 Carl Hetherington <carl@carlh.net>
+ * Copyright (C) 2011-2013 Paul Davis <paul@linuxaudiosystems.com>
+ * Copyright (C) 2011-2016 David Robillard <d@drobilla.net>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef EVORAL_PATCH_CHANGE_HPP
+#define EVORAL_PATCH_CHANGE_HPP
+
+#include "evoral/visibility.h"
+#include "evoral/Event.h"
+#include "evoral/Event.h"
+
+namespace Evoral {
+
+/** Event representing a `patch change', composed of a LSB and MSB
+ * bank select and then a program change.
+ */
+template<typename Time>
+class /*LIBEVORAL_API*/ PatchChange
+{
+public:
+ /** @param t Time.
+ * @param c Channel.
+ * @param p Program change number (counted from 0).
+ * @param b Bank number (counted from 0, 14-bit).
+ */
+ PatchChange (Time t, uint8_t c, uint8_t p, int b)
+ : _bank_change_msb (MIDI_EVENT, t, 3, 0, true)
+ , _bank_change_lsb (MIDI_EVENT, t, 3, 0, true)
+ , _program_change (MIDI_EVENT, t, 2, 0, true)
+ {
+ _bank_change_msb.buffer()[0] = MIDI_CMD_CONTROL | c;
+ _bank_change_msb.buffer()[1] = MIDI_CTL_MSB_BANK;
+ _bank_change_msb.buffer()[2] = (b >> 7) & 0x7f;
+
+ _bank_change_lsb.buffer()[0] = MIDI_CMD_CONTROL | c;
+ _bank_change_lsb.buffer()[1] = MIDI_CTL_LSB_BANK;
+ _bank_change_lsb.buffer()[2] = b & 0x7f;
+
+ _program_change.buffer()[0] = MIDI_CMD_PGM_CHANGE | c;
+ _program_change.buffer()[1] = p;
+ }
+
+ PatchChange (const PatchChange & other)
+ : _bank_change_msb (other._bank_change_msb, true)
+ , _bank_change_lsb (other._bank_change_lsb, true)
+ , _program_change (other._program_change, true)
+ {
+ set_id (other.id ());
+ }
+
+ event_id_t id () const {
+ return _program_change.id ();
+ }
+
+ void set_id (event_id_t id) {
+ _bank_change_msb.set_id (id);
+ _bank_change_lsb.set_id (id);
+ _program_change.set_id (id);
+ }
+
+ Time time () const {
+ return _program_change.time ();
+ }
+
+ void set_time (Time t) {
+ _bank_change_msb.set_time (t);
+ _bank_change_lsb.set_time (t);
+ _program_change.set_time (t);
+ }
+
+ void set_channel (uint8_t c) {
+ _bank_change_msb.buffer()[0] &= 0xf0;
+ _bank_change_msb.buffer()[0] |= c;
+ _bank_change_lsb.buffer()[0] &= 0xf0;
+ _bank_change_lsb.buffer()[0] |= c;
+ _program_change.buffer()[0] &= 0xf0;
+ _program_change.buffer()[0] |= c;
+ }
+
+ uint8_t program () const {
+ return _program_change.buffer()[1];
+ }
+
+ void set_program (uint8_t p) {
+ _program_change.buffer()[1] = p;
+ }
+
+ int bank () const {
+ return (bank_msb() << 7) | bank_lsb();
+ }
+
+ void set_bank (int b) {
+ _bank_change_msb.buffer()[2] = (b >> 7) & 0x7f;
+ _bank_change_lsb.buffer()[2] = b & 0x7f;
+ }
+
+ uint8_t bank_msb () const {
+ return _bank_change_msb.buffer()[2];
+ }
+
+ uint8_t bank_lsb () const {
+ return _bank_change_lsb.buffer()[2];
+ }
+
+ uint8_t channel () const { return _program_change.buffer()[0] & 0xf; }
+
+ inline bool operator< (const PatchChange<Time>& o) const {
+ if (time() != o.time()) {
+ return time() < o.time();
+ }
+
+ if (bank() != o.bank()) {
+ return bank() < o.bank();
+ }
+
+ return (program() < o.program());
+ }
+
+ inline bool operator== (const PatchChange<Time>& o) const {
+ return (time() == o.time() && program() == o.program() && bank() == o.bank());
+ }
+
+ /** The PatchChange is made up of messages() MIDI messages; this method returns them by index.
+ * @param i index of message to return.
+ */
+ Event<Time> const & message (int i) const {
+ switch (i) {
+ case 0:
+ return _bank_change_msb;
+ case 1:
+ return _bank_change_lsb;
+ case 2:
+ return _program_change;
+ default:
+ abort(); /*NOTREACHED*/
+ return _program_change;
+ }
+ }
+
+ /** @return Number of MIDI messages that make up this change */
+ int messages () const {
+ return 3;
+ }
+
+private:
+ Event<Time> _bank_change_msb;
+ Event<Time> _bank_change_lsb;
+ Event<Time> _program_change;
+};
+
+}
+
+template<typename Time>
+/*LIBEVORAL_API*/ std::ostream& operator<< (std::ostream& o, const Evoral::PatchChange<Time>& p) {
+ o << "Patch Change " << p.id() << " @ " << p.time() << " bank " << (int) p.bank() << " program " << (int) p.program();
+ return o;
+}
+
+#endif
+++ /dev/null
-/*
- * Copyright (C) 2011 Carl Hetherington <carl@carlh.net>
- * Copyright (C) 2011-2013 Paul Davis <paul@linuxaudiosystems.com>
- * Copyright (C) 2011-2016 David Robillard <d@drobilla.net>
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-
-#ifndef EVORAL_PATCH_CHANGE_HPP
-#define EVORAL_PATCH_CHANGE_HPP
-
-#include "evoral/visibility.h"
-#include "evoral/Event.hpp"
-#include "evoral/Event.hpp"
-
-namespace Evoral {
-
-/** Event representing a `patch change', composed of a LSB and MSB
- * bank select and then a program change.
- */
-template<typename Time>
-class /*LIBEVORAL_API*/ PatchChange
-{
-public:
- /** @param t Time.
- * @param c Channel.
- * @param p Program change number (counted from 0).
- * @param b Bank number (counted from 0, 14-bit).
- */
- PatchChange (Time t, uint8_t c, uint8_t p, int b)
- : _bank_change_msb (MIDI_EVENT, t, 3, 0, true)
- , _bank_change_lsb (MIDI_EVENT, t, 3, 0, true)
- , _program_change (MIDI_EVENT, t, 2, 0, true)
- {
- _bank_change_msb.buffer()[0] = MIDI_CMD_CONTROL | c;
- _bank_change_msb.buffer()[1] = MIDI_CTL_MSB_BANK;
- _bank_change_msb.buffer()[2] = (b >> 7) & 0x7f;
-
- _bank_change_lsb.buffer()[0] = MIDI_CMD_CONTROL | c;
- _bank_change_lsb.buffer()[1] = MIDI_CTL_LSB_BANK;
- _bank_change_lsb.buffer()[2] = b & 0x7f;
-
- _program_change.buffer()[0] = MIDI_CMD_PGM_CHANGE | c;
- _program_change.buffer()[1] = p;
- }
-
- PatchChange (const PatchChange & other)
- : _bank_change_msb (other._bank_change_msb, true)
- , _bank_change_lsb (other._bank_change_lsb, true)
- , _program_change (other._program_change, true)
- {
- set_id (other.id ());
- }
-
- event_id_t id () const {
- return _program_change.id ();
- }
-
- void set_id (event_id_t id) {
- _bank_change_msb.set_id (id);
- _bank_change_lsb.set_id (id);
- _program_change.set_id (id);
- }
-
- Time time () const {
- return _program_change.time ();
- }
-
- void set_time (Time t) {
- _bank_change_msb.set_time (t);
- _bank_change_lsb.set_time (t);
- _program_change.set_time (t);
- }
-
- void set_channel (uint8_t c) {
- _bank_change_msb.buffer()[0] &= 0xf0;
- _bank_change_msb.buffer()[0] |= c;
- _bank_change_lsb.buffer()[0] &= 0xf0;
- _bank_change_lsb.buffer()[0] |= c;
- _program_change.buffer()[0] &= 0xf0;
- _program_change.buffer()[0] |= c;
- }
-
- uint8_t program () const {
- return _program_change.buffer()[1];
- }
-
- void set_program (uint8_t p) {
- _program_change.buffer()[1] = p;
- }
-
- int bank () const {
- return (bank_msb() << 7) | bank_lsb();
- }
-
- void set_bank (int b) {
- _bank_change_msb.buffer()[2] = (b >> 7) & 0x7f;
- _bank_change_lsb.buffer()[2] = b & 0x7f;
- }
-
- uint8_t bank_msb () const {
- return _bank_change_msb.buffer()[2];
- }
-
- uint8_t bank_lsb () const {
- return _bank_change_lsb.buffer()[2];
- }
-
- uint8_t channel () const { return _program_change.buffer()[0] & 0xf; }
-
- inline bool operator< (const PatchChange<Time>& o) const {
- if (time() != o.time()) {
- return time() < o.time();
- }
-
- if (bank() != o.bank()) {
- return bank() < o.bank();
- }
-
- return (program() < o.program());
- }
-
- inline bool operator== (const PatchChange<Time>& o) const {
- return (time() == o.time() && program() == o.program() && bank() == o.bank());
- }
-
- /** The PatchChange is made up of messages() MIDI messages; this method returns them by index.
- * @param i index of message to return.
- */
- Event<Time> const & message (int i) const {
- switch (i) {
- case 0:
- return _bank_change_msb;
- case 1:
- return _bank_change_lsb;
- case 2:
- return _program_change;
- default:
- abort(); /*NOTREACHED*/
- return _program_change;
- }
- }
-
- /** @return Number of MIDI messages that make up this change */
- int messages () const {
- return 3;
- }
-
-private:
- Event<Time> _bank_change_msb;
- Event<Time> _bank_change_lsb;
- Event<Time> _program_change;
-};
-
-}
-
-template<typename Time>
-/*LIBEVORAL_API*/ std::ostream& operator<< (std::ostream& o, const Evoral::PatchChange<Time>& p) {
- o << "Patch Change " << p.id() << " @ " << p.time() << " bank " << (int) p.bank() << " program " << (int) p.program();
- return o;
-}
-
-#endif
--- /dev/null
+/*
+ * Copyright (C) 2012 Carl Hetherington <carl@carlh.net>
+ * Copyright (C) 2013-2016 Paul Davis <paul@linuxaudiosystems.com>
+ * Copyright (C) 2014 Colin Fletcher <colin.m.fletcher@googlemail.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef EVORAL_RANGE_HPP
+#define EVORAL_RANGE_HPP
+
+#include <list>
+#include <assert.h>
+#include <iostream>
+#include "evoral/visibility.h"
+
+
+
+namespace Evoral {
+
+enum /*LIBEVORAL_API*/ OverlapType {
+ OverlapNone, // no overlap
+ OverlapInternal, // the overlap is 100% within the object
+ OverlapStart, // overlap covers start, but ends within
+ OverlapEnd, // overlap begins within and covers end
+ OverlapExternal // overlap extends to (at least) begin+end
+};
+
+template<typename T>
+/*LIBEVORAL_API*/ OverlapType coverage (T sa, T ea, T sb, T eb) {
+ /* OverlapType returned reflects how the second (B)
+ * range overlaps the first (A).
+ *
+ * The diagram shows the OverlapType of each possible relative
+ * placement of A and B.
+ *
+ * Notes:
+ * Internal: the start and end points cannot coincide
+ * External: the start and end points can coincide
+ * Start: end points can coincide
+ * End: start points can coincide
+ *
+ * Internal disallows start and end point equality, and thus implies
+ * that there are two disjoint portions of A which do not overlap B.
+ *
+ * A: |---|
+ * B starts before A
+ * B: |-| None
+ * B: |--| Start
+ * B: |----| Start
+ * B: |------| External
+ * B: |--------| External
+ * B starts equal to A
+ * B: |-| Start
+ * B: |---| External
+ * B: |----| External
+ * B starts inside A
+ * B: |-| Internal
+ * B: |--| End
+ * B: |---| End
+ * B starts at end of A
+ * B: |--| End
+ * B starts after A
+ * B: |-| None
+ * A: |---|
+ */
+
+
+ if (sa > ea) {
+ // seems we are sometimes called with negative length ranges
+ return OverlapNone;
+ }
+
+ if (sb > eb) {
+ // seems we are sometimes called with negative length ranges
+ return OverlapNone;
+ }
+
+ if (sb < sa) { // B starts before A
+ if (eb < sa) {
+ return OverlapNone;
+ } else if (eb == sa) {
+ return OverlapStart;
+ } else { // eb > sa
+ if (eb < ea) {
+ return OverlapStart;
+ } else if (eb == ea) {
+ return OverlapExternal;
+ } else {
+ return OverlapExternal;
+ }
+ }
+ } else if (sb == sa) { // B starts equal to A
+ if (eb < ea) {
+ return OverlapStart;
+ } else if (eb == ea) {
+ return OverlapExternal;
+ } else { // eb > ea
+ return OverlapExternal;
+ }
+ } else { // sb > sa
+ if (eb < ea) {
+ return OverlapInternal;
+ } else if (eb == ea) {
+ return OverlapEnd;
+ } else { // eb > ea
+ if (sb < ea) { // B starts inside A
+ return OverlapEnd;
+ } else if (sb == ea) { // B starts at end of A
+ return OverlapEnd;
+ } else { // sb > ea, B starts after A
+ return OverlapNone;
+ }
+ }
+ }
+
+ std::cerr << "unknown overlap type!" << sa << ", " << ea << "; " << sb << ", " << eb << std::endl;
+ assert(!"unknown overlap type!");
+ return OverlapNone;
+}
+
+/** Type to describe a time range */
+template<typename T>
+struct /*LIBEVORAL_API*/ Range {
+ Range (T f, T t) : from (f), to (t) {}
+ T from; ///< start of the range
+ T to; ///< end of the range (inclusive: to lies inside the range)
+ bool empty() const { return from == to; }
+ T length() const { return to - from + 1; }
+
+ /** for a T, return a mapping of it into the range (used for
+ * looping). If the argument is earlier than or equal to the end of
+ * this range, do nothing.
+ */
+ T squish (T t) const {
+ if (t > to) {
+ t = (from + ((t - from) % length()));
+ }
+ return t;
+ }
+};
+
+template<typename T>
+bool operator== (Range<T> a, Range<T> b) {
+ return a.from == b.from && a.to == b.to;
+}
+
+template<typename T>
+class /*LIBEVORAL_API*/ RangeList {
+public:
+ RangeList () : _dirty (false) {}
+
+ typedef std::list<Range<T> > List;
+
+ List const & get () {
+ coalesce ();
+ return _list;
+ }
+
+ void add (Range<T> const & range) {
+ _dirty = true;
+ _list.push_back (range);
+ }
+
+ bool empty () const {
+ return _list.empty ();
+ }
+
+ void coalesce () {
+ if (!_dirty) {
+ return;
+ }
+
+ restart:
+ for (typename List::iterator i = _list.begin(); i != _list.end(); ++i) {
+ for (typename List::iterator j = _list.begin(); j != _list.end(); ++j) {
+
+ if (i == j) {
+ continue;
+ }
+
+ if (coverage (i->from, i->to, j->from, j->to) != OverlapNone) {
+ i->from = std::min (i->from, j->from);
+ i->to = std::max (i->to, j->to);
+ _list.erase (j);
+ goto restart;
+ }
+ }
+ }
+
+ _dirty = false;
+ }
+
+private:
+
+ List _list;
+ bool _dirty;
+};
+
+/** Type to describe the movement of a time range */
+template<typename T>
+struct /*LIBEVORAL_API*/ RangeMove {
+ RangeMove (T f, double l, T t) : from (f), length (l), to (t) {}
+ T from; ///< start of the range
+ double length; ///< length of the range
+ T to; ///< new start of the range
+};
+
+/** Subtract the ranges in `sub' from that in `range',
+ * returning the result.
+ */
+template<typename T>
+RangeList<T> subtract (Range<T> range, RangeList<T> sub)
+{
+ /* Start with the input range */
+ RangeList<T> result;
+ result.add (range);
+
+ if (sub.empty () || range.empty()) {
+ return result;
+ }
+
+ typename RangeList<T>::List s = sub.get ();
+
+ /* The basic idea here is to keep a list of the result ranges, and subtract
+ the bits of `sub' from them one by one.
+ */
+
+ for (typename RangeList<T>::List::const_iterator i = s.begin(); i != s.end(); ++i) {
+
+ /* Here's where we'll put the new current result after subtracting *i from it */
+ RangeList<T> new_result;
+
+ typename RangeList<T>::List r = result.get ();
+
+ /* Work on all parts of the current result using this range *i */
+ for (typename RangeList<T>::List::const_iterator j = r.begin(); j != r.end(); ++j) {
+
+ switch (coverage (j->from, j->to, i->from, i->to)) {
+ case OverlapNone:
+ /* The thing we're subtracting (*i) does not overlap this bit of the result (*j),
+ so pass it through.
+ */
+ new_result.add (*j);
+ break;
+ case OverlapInternal:
+ /* Internal overlap of the thing we're subtracting (*i) from this bit of the result,
+ so we should end up with two bits of (*j) left over, from the start of (*j) to
+ the start of (*i), and from the end of (*i) to the end of (*j).
+ */
+ assert (j->from < i->from);
+ assert (j->to > i->to);
+ new_result.add (Range<T> (j->from, i->from - 1));
+ new_result.add (Range<T> (i->to + 1, j->to));
+ break;
+ case OverlapStart:
+ /* The bit we're subtracting (*i) overlaps the start of the bit of the result (*j),
+ * so we keep only the part of of (*j) from after the end of (*i)
+ */
+ assert (i->to < j->to);
+ new_result.add (Range<T> (i->to + 1, j->to));
+ break;
+ case OverlapEnd:
+ /* The bit we're subtracting (*i) overlaps the end of the bit of the result (*j),
+ * so we keep only the part of of (*j) from before the start of (*i)
+ */
+ assert (j->from < i->from);
+ new_result.add (Range<T> (j->from, i->from - 1));
+ break;
+ case OverlapExternal:
+ /* total overlap of the bit we're subtracting with the result bit, so the
+ result bit is completely removed; do nothing */
+ break;
+ }
+ }
+
+ new_result.coalesce ();
+ result = new_result;
+ }
+
+ return result;
+}
+
+}
+
+#endif
+++ /dev/null
-/*
- * Copyright (C) 2012 Carl Hetherington <carl@carlh.net>
- * Copyright (C) 2013-2016 Paul Davis <paul@linuxaudiosystems.com>
- * Copyright (C) 2014 Colin Fletcher <colin.m.fletcher@googlemail.com>
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-
-#ifndef EVORAL_RANGE_HPP
-#define EVORAL_RANGE_HPP
-
-#include <list>
-#include <assert.h>
-#include <iostream>
-#include "evoral/visibility.h"
-
-
-
-namespace Evoral {
-
-enum /*LIBEVORAL_API*/ OverlapType {
- OverlapNone, // no overlap
- OverlapInternal, // the overlap is 100% within the object
- OverlapStart, // overlap covers start, but ends within
- OverlapEnd, // overlap begins within and covers end
- OverlapExternal // overlap extends to (at least) begin+end
-};
-
-template<typename T>
-/*LIBEVORAL_API*/ OverlapType coverage (T sa, T ea, T sb, T eb) {
- /* OverlapType returned reflects how the second (B)
- * range overlaps the first (A).
- *
- * The diagram shows the OverlapType of each possible relative
- * placement of A and B.
- *
- * Notes:
- * Internal: the start and end points cannot coincide
- * External: the start and end points can coincide
- * Start: end points can coincide
- * End: start points can coincide
- *
- * Internal disallows start and end point equality, and thus implies
- * that there are two disjoint portions of A which do not overlap B.
- *
- * A: |---|
- * B starts before A
- * B: |-| None
- * B: |--| Start
- * B: |----| Start
- * B: |------| External
- * B: |--------| External
- * B starts equal to A
- * B: |-| Start
- * B: |---| External
- * B: |----| External
- * B starts inside A
- * B: |-| Internal
- * B: |--| End
- * B: |---| End
- * B starts at end of A
- * B: |--| End
- * B starts after A
- * B: |-| None
- * A: |---|
- */
-
-
- if (sa > ea) {
- // seems we are sometimes called with negative length ranges
- return OverlapNone;
- }
-
- if (sb > eb) {
- // seems we are sometimes called with negative length ranges
- return OverlapNone;
- }
-
- if (sb < sa) { // B starts before A
- if (eb < sa) {
- return OverlapNone;
- } else if (eb == sa) {
- return OverlapStart;
- } else { // eb > sa
- if (eb < ea) {
- return OverlapStart;
- } else if (eb == ea) {
- return OverlapExternal;
- } else {
- return OverlapExternal;
- }
- }
- } else if (sb == sa) { // B starts equal to A
- if (eb < ea) {
- return OverlapStart;
- } else if (eb == ea) {
- return OverlapExternal;
- } else { // eb > ea
- return OverlapExternal;
- }
- } else { // sb > sa
- if (eb < ea) {
- return OverlapInternal;
- } else if (eb == ea) {
- return OverlapEnd;
- } else { // eb > ea
- if (sb < ea) { // B starts inside A
- return OverlapEnd;
- } else if (sb == ea) { // B starts at end of A
- return OverlapEnd;
- } else { // sb > ea, B starts after A
- return OverlapNone;
- }
- }
- }
-
- std::cerr << "unknown overlap type!" << sa << ", " << ea << "; " << sb << ", " << eb << std::endl;
- assert(!"unknown overlap type!");
- return OverlapNone;
-}
-
-/** Type to describe a time range */
-template<typename T>
-struct /*LIBEVORAL_API*/ Range {
- Range (T f, T t) : from (f), to (t) {}
- T from; ///< start of the range
- T to; ///< end of the range (inclusive: to lies inside the range)
- bool empty() const { return from == to; }
- T length() const { return to - from + 1; }
-
- /** for a T, return a mapping of it into the range (used for
- * looping). If the argument is earlier than or equal to the end of
- * this range, do nothing.
- */
- T squish (T t) const {
- if (t > to) {
- t = (from + ((t - from) % length()));
- }
- return t;
- }
-};
-
-template<typename T>
-bool operator== (Range<T> a, Range<T> b) {
- return a.from == b.from && a.to == b.to;
-}
-
-template<typename T>
-class /*LIBEVORAL_API*/ RangeList {
-public:
- RangeList () : _dirty (false) {}
-
- typedef std::list<Range<T> > List;
-
- List const & get () {
- coalesce ();
- return _list;
- }
-
- void add (Range<T> const & range) {
- _dirty = true;
- _list.push_back (range);
- }
-
- bool empty () const {
- return _list.empty ();
- }
-
- void coalesce () {
- if (!_dirty) {
- return;
- }
-
- restart:
- for (typename List::iterator i = _list.begin(); i != _list.end(); ++i) {
- for (typename List::iterator j = _list.begin(); j != _list.end(); ++j) {
-
- if (i == j) {
- continue;
- }
-
- if (coverage (i->from, i->to, j->from, j->to) != OverlapNone) {
- i->from = std::min (i->from, j->from);
- i->to = std::max (i->to, j->to);
- _list.erase (j);
- goto restart;
- }
- }
- }
-
- _dirty = false;
- }
-
-private:
-
- List _list;
- bool _dirty;
-};
-
-/** Type to describe the movement of a time range */
-template<typename T>
-struct /*LIBEVORAL_API*/ RangeMove {
- RangeMove (T f, double l, T t) : from (f), length (l), to (t) {}
- T from; ///< start of the range
- double length; ///< length of the range
- T to; ///< new start of the range
-};
-
-/** Subtract the ranges in `sub' from that in `range',
- * returning the result.
- */
-template<typename T>
-RangeList<T> subtract (Range<T> range, RangeList<T> sub)
-{
- /* Start with the input range */
- RangeList<T> result;
- result.add (range);
-
- if (sub.empty () || range.empty()) {
- return result;
- }
-
- typename RangeList<T>::List s = sub.get ();
-
- /* The basic idea here is to keep a list of the result ranges, and subtract
- the bits of `sub' from them one by one.
- */
-
- for (typename RangeList<T>::List::const_iterator i = s.begin(); i != s.end(); ++i) {
-
- /* Here's where we'll put the new current result after subtracting *i from it */
- RangeList<T> new_result;
-
- typename RangeList<T>::List r = result.get ();
-
- /* Work on all parts of the current result using this range *i */
- for (typename RangeList<T>::List::const_iterator j = r.begin(); j != r.end(); ++j) {
-
- switch (coverage (j->from, j->to, i->from, i->to)) {
- case OverlapNone:
- /* The thing we're subtracting (*i) does not overlap this bit of the result (*j),
- so pass it through.
- */
- new_result.add (*j);
- break;
- case OverlapInternal:
- /* Internal overlap of the thing we're subtracting (*i) from this bit of the result,
- so we should end up with two bits of (*j) left over, from the start of (*j) to
- the start of (*i), and from the end of (*i) to the end of (*j).
- */
- assert (j->from < i->from);
- assert (j->to > i->to);
- new_result.add (Range<T> (j->from, i->from - 1));
- new_result.add (Range<T> (i->to + 1, j->to));
- break;
- case OverlapStart:
- /* The bit we're subtracting (*i) overlaps the start of the bit of the result (*j),
- * so we keep only the part of of (*j) from after the end of (*i)
- */
- assert (i->to < j->to);
- new_result.add (Range<T> (i->to + 1, j->to));
- break;
- case OverlapEnd:
- /* The bit we're subtracting (*i) overlaps the end of the bit of the result (*j),
- * so we keep only the part of of (*j) from before the start of (*i)
- */
- assert (j->from < i->from);
- new_result.add (Range<T> (j->from, i->from - 1));
- break;
- case OverlapExternal:
- /* total overlap of the bit we're subtracting with the result bit, so the
- result bit is completely removed; do nothing */
- break;
- }
- }
-
- new_result.coalesce ();
- result = new_result;
- }
-
- return result;
-}
-
-}
-
-#endif
--- /dev/null
+/*
+ * Copyright (C) 2008-2014 David Robillard <d@drobilla.net>
+ * Copyright (C) 2009-2017 Paul Davis <paul@linuxaudiosystems.com>
+ * Copyright (C) 2009 Hans Baier <hansfbaier@googlemail.com>
+ * Copyright (C) 2014-2016 Robin Gareus <robin@gareus.org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef EVORAL_SMF_HPP
+#define EVORAL_SMF_HPP
+
+#include <glibmm/threads.h>
+#include <set>
+
+#include "evoral/visibility.h"
+#include "evoral/types.h"
+
+struct smf_struct;
+struct smf_track_struct;
+struct smf_tempo_struct;
+typedef smf_struct smf_t;
+typedef smf_track_struct smf_track_t;
+typedef smf_tempo_struct smf_tempo_t;
+
+namespace Evoral {
+
+/** Standard Midi File.
+ * Currently only tempo-based time of a given PPQN is supported.
+ *
+ * For WRITING: this object specifically wraps a type0 file or a type1 file with only a
+ * single track. It has no support at this time for a type1 file with multiple
+ * tracks.
+ *
+ * For READING: this object can read a single arbitrary track from a type1
+ * file, or the single track of a type0 file. It has no support at this time
+ * for reading more than 1 track.
+ */
+class LIBEVORAL_API SMF {
+public:
+ class FileError : public std::exception {
+ public:
+ FileError (std::string const & n) : _file_name (n) {}
+ ~FileError () throw () {}
+ const char* what() const throw() { return "Unknown SMF error"; }
+ std::string file_name () const { return _file_name; }
+ private:
+ std::string _file_name;
+ };
+
+ SMF();
+ virtual ~SMF();
+
+ static bool test(const std::string& path);
+ int open(const std::string& path, int track=1);
+ // XXX 19200 = 10 * Timecode::BBT_Time::ticks_per_beat
+ int create(const std::string& path, int track=1, uint16_t ppqn=19200);
+ void close();
+
+ void seek_to_start() const;
+ int seek_to_track(int track);
+
+ int read_event(uint32_t* delta_t, uint32_t* size, uint8_t** buf, event_id_t* note_id) const;
+
+ uint16_t num_tracks() const;
+ uint16_t ppqn() const;
+ bool is_empty() const { return _empty; }
+
+ void begin_write();
+ void append_event_delta(uint32_t delta_t, uint32_t size, const uint8_t* buf, event_id_t note_id);
+ void end_write(std::string const &);
+
+ void flush() {};
+
+ double round_to_file_precision (double val) const;
+
+ bool is_type0 () const { return _type0; }
+ std::set<uint8_t> channels () const { return _type0channels; }
+ void track_names (std::vector<std::string>&) const;
+ void instrument_names (std::vector<std::string>&) const;
+
+ int num_tempos () const;
+
+ /* This is exactly modelled on smf_tempo_t */
+ struct Tempo {
+ size_t time_pulses;
+ double time_seconds;
+ int microseconds_per_quarter_note;
+ int numerator;
+ int denominator;
+ int clocks_per_click;
+ int notes_per_note;
+
+ Tempo ()
+ : time_pulses (0)
+ , time_seconds (0)
+ , microseconds_per_quarter_note (-1)
+ , numerator (-1)
+ , denominator (-1)
+ , clocks_per_click (-1)
+ , notes_per_note (-1) {}
+ Tempo (smf_tempo_t*);
+
+ double tempo() const {
+ return 60.0 * (1000000.0 / (double) microseconds_per_quarter_note);
+ }
+ };
+
+ Tempo* tempo_at_smf_pulse (size_t smf_pulse) const;
+ Tempo* tempo_at_seconds (double seconds) const;
+ Tempo* nth_tempo (size_t n) const;
+
+ private:
+ smf_t* _smf;
+ smf_track_t* _smf_track;
+ bool _empty; ///< true iff file contains(non-empty) events
+ mutable Glib::Threads::Mutex _smf_lock;
+
+ bool _type0;
+ std::set<uint8_t> _type0channels;
+};
+
+}; /* namespace Evoral */
+
+#endif /* EVORAL_SMF_HPP */
+++ /dev/null
-/*
- * Copyright (C) 2008-2014 David Robillard <d@drobilla.net>
- * Copyright (C) 2009-2017 Paul Davis <paul@linuxaudiosystems.com>
- * Copyright (C) 2009 Hans Baier <hansfbaier@googlemail.com>
- * Copyright (C) 2014-2016 Robin Gareus <robin@gareus.org>
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-
-#ifndef EVORAL_SMF_HPP
-#define EVORAL_SMF_HPP
-
-#include <glibmm/threads.h>
-#include <set>
-
-#include "evoral/visibility.h"
-#include "evoral/types.hpp"
-
-struct smf_struct;
-struct smf_track_struct;
-struct smf_tempo_struct;
-typedef smf_struct smf_t;
-typedef smf_track_struct smf_track_t;
-typedef smf_tempo_struct smf_tempo_t;
-
-namespace Evoral {
-
-/** Standard Midi File.
- * Currently only tempo-based time of a given PPQN is supported.
- *
- * For WRITING: this object specifically wraps a type0 file or a type1 file with only a
- * single track. It has no support at this time for a type1 file with multiple
- * tracks.
- *
- * For READING: this object can read a single arbitrary track from a type1
- * file, or the single track of a type0 file. It has no support at this time
- * for reading more than 1 track.
- */
-class LIBEVORAL_API SMF {
-public:
- class FileError : public std::exception {
- public:
- FileError (std::string const & n) : _file_name (n) {}
- ~FileError () throw () {}
- const char* what() const throw() { return "Unknown SMF error"; }
- std::string file_name () const { return _file_name; }
- private:
- std::string _file_name;
- };
-
- SMF();
- virtual ~SMF();
-
- static bool test(const std::string& path);
- int open(const std::string& path, int track=1);
- // XXX 19200 = 10 * Timecode::BBT_Time::ticks_per_beat
- int create(const std::string& path, int track=1, uint16_t ppqn=19200);
- void close();
-
- void seek_to_start() const;
- int seek_to_track(int track);
-
- int read_event(uint32_t* delta_t, uint32_t* size, uint8_t** buf, event_id_t* note_id) const;
-
- uint16_t num_tracks() const;
- uint16_t ppqn() const;
- bool is_empty() const { return _empty; }
-
- void begin_write();
- void append_event_delta(uint32_t delta_t, uint32_t size, const uint8_t* buf, event_id_t note_id);
- void end_write(std::string const &);
-
- void flush() {};
-
- double round_to_file_precision (double val) const;
-
- bool is_type0 () const { return _type0; }
- std::set<uint8_t> channels () const { return _type0channels; }
- void track_names (std::vector<std::string>&) const;
- void instrument_names (std::vector<std::string>&) const;
-
- int num_tempos () const;
-
- /* This is exactly modelled on smf_tempo_t */
- struct Tempo {
- size_t time_pulses;
- double time_seconds;
- int microseconds_per_quarter_note;
- int numerator;
- int denominator;
- int clocks_per_click;
- int notes_per_note;
-
- Tempo ()
- : time_pulses (0)
- , time_seconds (0)
- , microseconds_per_quarter_note (-1)
- , numerator (-1)
- , denominator (-1)
- , clocks_per_click (-1)
- , notes_per_note (-1) {}
- Tempo (smf_tempo_t*);
-
- double tempo() const {
- return 60.0 * (1000000.0 / (double) microseconds_per_quarter_note);
- }
- };
-
- Tempo* tempo_at_smf_pulse (size_t smf_pulse) const;
- Tempo* tempo_at_seconds (double seconds) const;
- Tempo* nth_tempo (size_t n) const;
-
- private:
- smf_t* _smf;
- smf_track_t* _smf_track;
- bool _empty; ///< true iff file contains(non-empty) events
- mutable Glib::Threads::Mutex _smf_lock;
-
- bool _type0;
- std::set<uint8_t> _type0channels;
-};
-
-}; /* namespace Evoral */
-
-#endif /* EVORAL_SMF_HPP */
--- /dev/null
+/*
+ * Copyright (C) 2008-2011 David Robillard <d@drobilla.net>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef EVORAL_SMF_READER_HPP
+#define EVORAL_SMF_READER_HPP
+
+#include <exception>
+#include <stdexcept>
+#include <string>
+#include <inttypes.h>
+
+#include "evoral/visibility.h"
+
+namespace Evoral {
+
+
+/** Standard MIDI File (Type 0) Reader
+ *
+ * Currently this only reads SMF files with tempo-based timing.
+ */
+class LIBEVORAL_API SMFReader {
+public:
+ class PrematureEOF : public std::exception {
+ const char* what() const throw() { return "Unexpected end of file"; }
+ };
+ class CorruptFile : public std::exception {
+ const char* what() const throw() { return "Corrupted file"; }
+ };
+ class UnsupportedTime : public std::exception {
+ const char* what() const throw() { return "Unsupported time stamp type (SMPTE)"; }
+ };
+
+ SMFReader(const std::string& filename="");
+ ~SMFReader();
+
+ bool open(const std::string& filename) throw (std::logic_error, UnsupportedTime);
+
+ bool seek_to_track(unsigned track) throw (std::logic_error);
+
+ const std::string& filename() const { return _filename; };
+
+ uint16_t type() const { return _type; }
+ uint16_t ppqn() const { return _ppqn; }
+ uint16_t num_tracks() const { return _num_tracks; }
+
+ int read_event(size_t buf_len,
+ uint8_t* buf,
+ uint32_t* ev_size,
+ uint32_t* ev_delta_time)
+ throw (std::logic_error, PrematureEOF, CorruptFile);
+
+ void close();
+
+ static uint32_t read_var_len(FILE* fd) throw (PrematureEOF);
+
+protected:
+ /** size of SMF header, including MTrk chunk header */
+ static const uint32_t HEADER_SIZE = 22;
+
+ std::string _filename;
+ FILE* _fd;
+ uint16_t _type;
+ uint16_t _ppqn;
+ uint16_t _num_tracks;
+ uint32_t _track;
+ uint32_t _track_size;
+};
+
+
+} // namespace Evoral
+
+#endif // EVORAL_SMF_READER_HPP
+
+++ /dev/null
-/*
- * Copyright (C) 2008-2011 David Robillard <d@drobilla.net>
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-
-#ifndef EVORAL_SMF_READER_HPP
-#define EVORAL_SMF_READER_HPP
-
-#include <exception>
-#include <stdexcept>
-#include <string>
-#include <inttypes.h>
-
-#include "evoral/visibility.h"
-
-namespace Evoral {
-
-
-/** Standard MIDI File (Type 0) Reader
- *
- * Currently this only reads SMF files with tempo-based timing.
- */
-class LIBEVORAL_API SMFReader {
-public:
- class PrematureEOF : public std::exception {
- const char* what() const throw() { return "Unexpected end of file"; }
- };
- class CorruptFile : public std::exception {
- const char* what() const throw() { return "Corrupted file"; }
- };
- class UnsupportedTime : public std::exception {
- const char* what() const throw() { return "Unsupported time stamp type (SMPTE)"; }
- };
-
- SMFReader(const std::string& filename="");
- ~SMFReader();
-
- bool open(const std::string& filename) throw (std::logic_error, UnsupportedTime);
-
- bool seek_to_track(unsigned track) throw (std::logic_error);
-
- const std::string& filename() const { return _filename; };
-
- uint16_t type() const { return _type; }
- uint16_t ppqn() const { return _ppqn; }
- uint16_t num_tracks() const { return _num_tracks; }
-
- int read_event(size_t buf_len,
- uint8_t* buf,
- uint32_t* ev_size,
- uint32_t* ev_delta_time)
- throw (std::logic_error, PrematureEOF, CorruptFile);
-
- void close();
-
- static uint32_t read_var_len(FILE* fd) throw (PrematureEOF);
-
-protected:
- /** size of SMF header, including MTrk chunk header */
- static const uint32_t HEADER_SIZE = 22;
-
- std::string _filename;
- FILE* _fd;
- uint16_t _type;
- uint16_t _ppqn;
- uint16_t _num_tracks;
- uint32_t _track;
- uint32_t _track_size;
-};
-
-
-} // namespace Evoral
-
-#endif // EVORAL_SMF_READER_HPP
-
--- /dev/null
+/*
+ * Copyright (C) 2008-2016 David Robillard <d@drobilla.net>
+ * Copyright (C) 2009-2012 Hans Baier <hansfbaier@googlemail.com>
+ * Copyright (C) 2009-2013 Paul Davis <paul@linuxaudiosystems.com>
+ * Copyright (C) 2010-2011 Carl Hetherington <carl@carlh.net>
+ * Copyright (C) 2013-2015 John Emmas <john@creativepost.co.uk>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef EVORAL_SEQUENCE_HPP
+#define EVORAL_SEQUENCE_HPP
+
+#include <vector>
+#include <queue>
+#include <set>
+#include <list>
+#include <utility>
+#include <boost/shared_ptr.hpp>
+#include <glibmm/threads.h>
+
+#include "evoral/visibility.h"
+#include "evoral/Note.h"
+#include "evoral/ControlSet.h"
+#include "evoral/ControlList.h"
+#include "evoral/PatchChange.h"
+
+namespace Evoral {
+
+class Parameter;
+class TypeMap;
+template<typename Time> class EventSink;
+template<typename Time> class Note;
+template<typename Time> class Event;
+
+/** An iterator over (the x axis of) a 2-d double coordinate space.
+ */
+class /*LIBEVORAL_API*/ ControlIterator {
+public:
+ ControlIterator(boost::shared_ptr<const ControlList> al, double ax, double ay)
+ : list(al)
+ , x(ax)
+ , y(ay)
+ {}
+
+ boost::shared_ptr<const ControlList> list;
+ double x;
+ double y;
+};
+
+
+/** This is a higher level view of events, with separate representations for
+ * notes (instead of just unassociated note on/off events) and controller data.
+ * Controller data is represented as a list of time-stamped float values. */
+template<typename Time>
+class LIBEVORAL_API Sequence : virtual public ControlSet {
+public:
+ Sequence(const TypeMap& type_map);
+ Sequence(const Sequence<Time>& other);
+
+protected:
+ struct WriteLockImpl {
+ WriteLockImpl(Glib::Threads::RWLock& s, Glib::Threads::Mutex& c)
+ : sequence_lock(new Glib::Threads::RWLock::WriterLock(s))
+ , control_lock(new Glib::Threads::Mutex::Lock(c)) { }
+ ~WriteLockImpl() {
+ delete sequence_lock;
+ delete control_lock;
+ }
+ Glib::Threads::RWLock::WriterLock* sequence_lock;
+ Glib::Threads::Mutex::Lock* control_lock;
+ };
+
+public:
+
+ typedef typename boost::shared_ptr<Evoral::Note<Time> > NotePtr;
+ typedef typename boost::weak_ptr<Evoral::Note<Time> > WeakNotePtr;
+ typedef typename boost::shared_ptr<const Evoral::Note<Time> > constNotePtr;
+
+ typedef boost::shared_ptr<Glib::Threads::RWLock::ReaderLock> ReadLock;
+ typedef boost::shared_ptr<WriteLockImpl> WriteLock;
+
+ virtual ReadLock read_lock() const { return ReadLock(new Glib::Threads::RWLock::ReaderLock(_lock)); }
+ virtual WriteLock write_lock() { return WriteLock(new WriteLockImpl(_lock, _control_lock)); }
+
+ void clear();
+
+ bool percussive() const { return _percussive; }
+ void set_percussive(bool p) { _percussive = p; }
+
+ void start_write();
+ bool writing() const { return _writing; }
+
+ enum StuckNoteOption {
+ Relax,
+ DeleteStuckNotes,
+ ResolveStuckNotes
+ };
+
+ void end_write (StuckNoteOption, Time when = Time());
+
+ void append(const Event<Time>& ev, Evoral::event_id_t evid);
+
+ const TypeMap& type_map() const { return _type_map; }
+
+ inline size_t n_notes() const { return _notes.size(); }
+ inline bool empty() const { return _notes.empty() && _sysexes.empty() && _patch_changes.empty() && ControlSet::controls_empty(); }
+
+ inline static bool note_time_comparator(const boost::shared_ptr< const Note<Time> >& a,
+ const boost::shared_ptr< const Note<Time> >& b) {
+ return a->time() < b->time();
+ }
+
+ struct NoteNumberComparator {
+ inline bool operator()(const boost::shared_ptr< const Note<Time> > a,
+ const boost::shared_ptr< const Note<Time> > b) const {
+ return a->note() < b->note();
+ }
+ };
+
+ struct EarlierNoteComparator {
+ inline bool operator()(const boost::shared_ptr< const Note<Time> > a,
+ const boost::shared_ptr< const Note<Time> > b) const {
+ return a->time() < b->time();
+ }
+ };
+
+#if 0 // NOT USED
+ struct LaterNoteComparator {
+ typedef const Note<Time>* value_type;
+ inline bool operator()(const boost::shared_ptr< const Note<Time> > a,
+ const boost::shared_ptr< const Note<Time> > b) const {
+ return a->time() > b->time();
+ }
+ };
+#endif
+
+ struct LaterNoteEndComparator {
+ typedef const Note<Time>* value_type;
+ inline bool operator()(const boost::shared_ptr< const Note<Time> > a,
+ const boost::shared_ptr< const Note<Time> > b) const {
+ return a->end_time().to_double() > b->end_time().to_double();
+ }
+ };
+
+ typedef std::multiset<NotePtr, EarlierNoteComparator> Notes;
+ inline Notes& notes() { return _notes; }
+ inline const Notes& notes() const { return _notes; }
+
+ enum NoteOperator {
+ PitchEqual,
+ PitchLessThan,
+ PitchLessThanOrEqual,
+ PitchGreater,
+ PitchGreaterThanOrEqual,
+ VelocityEqual,
+ VelocityLessThan,
+ VelocityLessThanOrEqual,
+ VelocityGreater,
+ VelocityGreaterThanOrEqual,
+ };
+
+ void get_notes (Notes&, NoteOperator, uint8_t val, int chan_mask = 0) const;
+
+ void remove_overlapping_notes ();
+ void trim_overlapping_notes ();
+ void remove_duplicate_notes ();
+
+ enum OverlapPitchResolution {
+ LastOnFirstOff,
+ FirstOnFirstOff
+ };
+
+ bool overlapping_pitches_accepted() const { return _overlapping_pitches_accepted; }
+ void overlapping_pitches_accepted(bool yn) { _overlapping_pitches_accepted = yn; }
+ OverlapPitchResolution overlap_pitch_resolution() const { return _overlap_pitch_resolution; }
+ void set_overlap_pitch_resolution(OverlapPitchResolution opr);
+
+ void set_notes (const typename Sequence<Time>::Notes& n);
+
+ typedef boost::shared_ptr< Event<Time> > SysExPtr;
+ typedef boost::shared_ptr<const Event<Time> > constSysExPtr;
+
+ struct EarlierSysExComparator {
+ inline bool operator() (constSysExPtr a, constSysExPtr b) const {
+ return a->time() < b->time();
+ }
+ };
+
+ typedef std::multiset<SysExPtr, EarlierSysExComparator> SysExes;
+ inline SysExes& sysexes() { return _sysexes; }
+ inline const SysExes& sysexes() const { return _sysexes; }
+
+ typedef boost::shared_ptr<PatchChange<Time> > PatchChangePtr;
+ typedef boost::shared_ptr<const PatchChange<Time> > constPatchChangePtr;
+
+ struct EarlierPatchChangeComparator {
+ inline bool operator() (constPatchChangePtr a, constPatchChangePtr b) const {
+ return a->time() < b->time();
+ }
+ };
+
+ typedef std::multiset<PatchChangePtr, EarlierPatchChangeComparator> PatchChanges;
+ inline PatchChanges& patch_changes () { return _patch_changes; }
+ inline const PatchChanges& patch_changes () const { return _patch_changes; }
+
+ void dump (std::ostream&) const;
+
+private:
+ typedef std::priority_queue<NotePtr, std::deque<NotePtr>, LaterNoteEndComparator> ActiveNotes;
+public:
+
+ /** Read iterator */
+ class LIBEVORAL_API const_iterator {
+ public:
+ const_iterator();
+ const_iterator(const Sequence<Time>& seq,
+ Time t,
+ bool force_discrete,
+ const std::set<Evoral::Parameter>& filtered,
+ const std::set<WeakNotePtr>* active_notes=NULL);
+
+ inline bool valid() const { return !_is_end && _event; }
+
+ void invalidate(std::set<WeakNotePtr>* notes);
+
+ const Event<Time>& operator*() const { return *_event; }
+ const boost::shared_ptr< const Event<Time> > operator->() const { return _event; }
+
+ const const_iterator& operator++(); // prefix only
+
+ bool operator==(const const_iterator& other) const;
+ bool operator!=(const const_iterator& other) const { return ! operator==(other); }
+
+ const_iterator& operator=(const const_iterator& other);
+
+ private:
+ friend class Sequence<Time>;
+
+ Time choose_next(Time earliest_t);
+ void set_event();
+
+ typedef std::vector<ControlIterator> ControlIterators;
+ enum MIDIMessageType { NIL, NOTE_ON, NOTE_OFF, CONTROL, SYSEX, PATCH_CHANGE };
+
+ const Sequence<Time>* _seq;
+ boost::shared_ptr< Event<Time> > _event;
+ mutable ActiveNotes _active_notes;
+ /** If the iterator is pointing at a patch change, this is the index of the
+ * sub-message within that change.
+ */
+ int _active_patch_change_message;
+ MIDIMessageType _type;
+ bool _is_end;
+ typename Sequence::ReadLock _lock;
+ typename Notes::const_iterator _note_iter;
+ typename SysExes::const_iterator _sysex_iter;
+ typename PatchChanges::const_iterator _patch_change_iter;
+ ControlIterators _control_iters;
+ ControlIterators::iterator _control_iter;
+ bool _force_discrete;
+ };
+
+ const_iterator begin (
+ Time t = Time(),
+ bool force_discrete = false,
+ const std::set<Evoral::Parameter>& f = std::set<Evoral::Parameter>(),
+ const std::set<WeakNotePtr>* active_notes = NULL) const {
+ return const_iterator (*this, t, force_discrete, f, active_notes);
+ }
+
+ const const_iterator& end() const { return _end_iter; }
+
+ // CONST iterator implementations (x3)
+ typename Notes::const_iterator note_lower_bound (Time t) const;
+ typename PatchChanges::const_iterator patch_change_lower_bound (Time t) const;
+ typename SysExes::const_iterator sysex_lower_bound (Time t) const;
+
+ // NON-CONST iterator implementations (x3)
+ typename Notes::iterator note_lower_bound (Time t);
+ typename PatchChanges::iterator patch_change_lower_bound (Time t);
+ typename SysExes::iterator sysex_lower_bound (Time t);
+
+ bool control_to_midi_event(boost::shared_ptr< Event<Time> >& ev,
+ const ControlIterator& iter) const;
+
+ bool edited() const { return _edited; }
+ void set_edited(bool yn) { _edited = yn; }
+
+ bool overlaps (const NotePtr& ev,
+ const NotePtr& ignore_this_note) const;
+ bool contains (const NotePtr& ev) const;
+
+ bool add_note_unlocked (const NotePtr note, void* arg = 0);
+ void remove_note_unlocked(const constNotePtr note);
+
+ void add_patch_change_unlocked (const PatchChangePtr);
+ void remove_patch_change_unlocked (const constPatchChangePtr);
+
+ void add_sysex_unlocked (const SysExPtr);
+ void remove_sysex_unlocked (const SysExPtr);
+
+ uint8_t lowest_note() const { return _lowest_note; }
+ uint8_t highest_note() const { return _highest_note; }
+
+
+protected:
+ bool _edited;
+ bool _overlapping_pitches_accepted;
+ OverlapPitchResolution _overlap_pitch_resolution;
+ mutable Glib::Threads::RWLock _lock;
+ bool _writing;
+
+ virtual int resolve_overlaps_unlocked (const NotePtr, void* /* arg */ = 0) {
+ return 0;
+ }
+
+ typedef std::multiset<NotePtr, NoteNumberComparator> Pitches;
+ inline Pitches& pitches(uint8_t chan) { return _pitches[chan&0xf]; }
+ inline const Pitches& pitches(uint8_t chan) const { return _pitches[chan&0xf]; }
+
+ virtual void control_list_marked_dirty ();
+
+private:
+ friend class const_iterator;
+
+ bool overlaps_unlocked (const NotePtr& ev, const NotePtr& ignore_this_note) const;
+ bool contains_unlocked (const NotePtr& ev) const;
+
+ void append_note_on_unlocked(const Event<Time>& event, Evoral::event_id_t);
+ void append_note_off_unlocked(const Event<Time>& event);
+ void append_control_unlocked(const Parameter& param, Time time, double value, Evoral::event_id_t);
+ void append_sysex_unlocked(const Event<Time>& ev, Evoral::event_id_t);
+ void append_patch_change_unlocked(const PatchChange<Time>&, Evoral::event_id_t);
+
+ void get_notes_by_pitch (Notes&, NoteOperator, uint8_t val, int chan_mask = 0) const;
+ void get_notes_by_velocity (Notes&, NoteOperator, uint8_t val, int chan_mask = 0) const;
+
+ const TypeMap& _type_map;
+
+ Notes _notes; // notes indexed by time
+ Pitches _pitches[16]; // notes indexed by channel+pitch
+ SysExes _sysexes;
+ PatchChanges _patch_changes;
+
+ typedef std::multiset<NotePtr, EarlierNoteComparator> WriteNotes;
+ WriteNotes _write_notes[16];
+
+ /** Current bank number on each channel so that we know what
+ * to put in PatchChange events when program changes are
+ * seen.
+ */
+ int _bank[16];
+
+ const const_iterator _end_iter;
+ bool _percussive;
+
+ uint8_t _lowest_note;
+ uint8_t _highest_note;
+};
+
+
+} // namespace Evoral
+
+template<typename Time> /*LIBEVORAL_API*/ std::ostream& operator<<(std::ostream& o, const Evoral::Sequence<Time>& s) { s.dump (o); return o; }
+
+
+#endif // EVORAL_SEQUENCE_HPP
+
+++ /dev/null
-/*
- * Copyright (C) 2008-2016 David Robillard <d@drobilla.net>
- * Copyright (C) 2009-2012 Hans Baier <hansfbaier@googlemail.com>
- * Copyright (C) 2009-2013 Paul Davis <paul@linuxaudiosystems.com>
- * Copyright (C) 2010-2011 Carl Hetherington <carl@carlh.net>
- * Copyright (C) 2013-2015 John Emmas <john@creativepost.co.uk>
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-
-#ifndef EVORAL_SEQUENCE_HPP
-#define EVORAL_SEQUENCE_HPP
-
-#include <vector>
-#include <queue>
-#include <set>
-#include <list>
-#include <utility>
-#include <boost/shared_ptr.hpp>
-#include <glibmm/threads.h>
-
-#include "evoral/visibility.h"
-#include "evoral/Note.hpp"
-#include "evoral/ControlSet.hpp"
-#include "evoral/ControlList.hpp"
-#include "evoral/PatchChange.hpp"
-
-namespace Evoral {
-
-class Parameter;
-class TypeMap;
-template<typename Time> class EventSink;
-template<typename Time> class Note;
-template<typename Time> class Event;
-
-/** An iterator over (the x axis of) a 2-d double coordinate space.
- */
-class /*LIBEVORAL_API*/ ControlIterator {
-public:
- ControlIterator(boost::shared_ptr<const ControlList> al, double ax, double ay)
- : list(al)
- , x(ax)
- , y(ay)
- {}
-
- boost::shared_ptr<const ControlList> list;
- double x;
- double y;
-};
-
-
-/** This is a higher level view of events, with separate representations for
- * notes (instead of just unassociated note on/off events) and controller data.
- * Controller data is represented as a list of time-stamped float values. */
-template<typename Time>
-class LIBEVORAL_API Sequence : virtual public ControlSet {
-public:
- Sequence(const TypeMap& type_map);
- Sequence(const Sequence<Time>& other);
-
-protected:
- struct WriteLockImpl {
- WriteLockImpl(Glib::Threads::RWLock& s, Glib::Threads::Mutex& c)
- : sequence_lock(new Glib::Threads::RWLock::WriterLock(s))
- , control_lock(new Glib::Threads::Mutex::Lock(c)) { }
- ~WriteLockImpl() {
- delete sequence_lock;
- delete control_lock;
- }
- Glib::Threads::RWLock::WriterLock* sequence_lock;
- Glib::Threads::Mutex::Lock* control_lock;
- };
-
-public:
-
- typedef typename boost::shared_ptr<Evoral::Note<Time> > NotePtr;
- typedef typename boost::weak_ptr<Evoral::Note<Time> > WeakNotePtr;
- typedef typename boost::shared_ptr<const Evoral::Note<Time> > constNotePtr;
-
- typedef boost::shared_ptr<Glib::Threads::RWLock::ReaderLock> ReadLock;
- typedef boost::shared_ptr<WriteLockImpl> WriteLock;
-
- virtual ReadLock read_lock() const { return ReadLock(new Glib::Threads::RWLock::ReaderLock(_lock)); }
- virtual WriteLock write_lock() { return WriteLock(new WriteLockImpl(_lock, _control_lock)); }
-
- void clear();
-
- bool percussive() const { return _percussive; }
- void set_percussive(bool p) { _percussive = p; }
-
- void start_write();
- bool writing() const { return _writing; }
-
- enum StuckNoteOption {
- Relax,
- DeleteStuckNotes,
- ResolveStuckNotes
- };
-
- void end_write (StuckNoteOption, Time when = Time());
-
- void append(const Event<Time>& ev, Evoral::event_id_t evid);
-
- const TypeMap& type_map() const { return _type_map; }
-
- inline size_t n_notes() const { return _notes.size(); }
- inline bool empty() const { return _notes.empty() && _sysexes.empty() && _patch_changes.empty() && ControlSet::controls_empty(); }
-
- inline static bool note_time_comparator(const boost::shared_ptr< const Note<Time> >& a,
- const boost::shared_ptr< const Note<Time> >& b) {
- return a->time() < b->time();
- }
-
- struct NoteNumberComparator {
- inline bool operator()(const boost::shared_ptr< const Note<Time> > a,
- const boost::shared_ptr< const Note<Time> > b) const {
- return a->note() < b->note();
- }
- };
-
- struct EarlierNoteComparator {
- inline bool operator()(const boost::shared_ptr< const Note<Time> > a,
- const boost::shared_ptr< const Note<Time> > b) const {
- return a->time() < b->time();
- }
- };
-
-#if 0 // NOT USED
- struct LaterNoteComparator {
- typedef const Note<Time>* value_type;
- inline bool operator()(const boost::shared_ptr< const Note<Time> > a,
- const boost::shared_ptr< const Note<Time> > b) const {
- return a->time() > b->time();
- }
- };
-#endif
-
- struct LaterNoteEndComparator {
- typedef const Note<Time>* value_type;
- inline bool operator()(const boost::shared_ptr< const Note<Time> > a,
- const boost::shared_ptr< const Note<Time> > b) const {
- return a->end_time().to_double() > b->end_time().to_double();
- }
- };
-
- typedef std::multiset<NotePtr, EarlierNoteComparator> Notes;
- inline Notes& notes() { return _notes; }
- inline const Notes& notes() const { return _notes; }
-
- enum NoteOperator {
- PitchEqual,
- PitchLessThan,
- PitchLessThanOrEqual,
- PitchGreater,
- PitchGreaterThanOrEqual,
- VelocityEqual,
- VelocityLessThan,
- VelocityLessThanOrEqual,
- VelocityGreater,
- VelocityGreaterThanOrEqual,
- };
-
- void get_notes (Notes&, NoteOperator, uint8_t val, int chan_mask = 0) const;
-
- void remove_overlapping_notes ();
- void trim_overlapping_notes ();
- void remove_duplicate_notes ();
-
- enum OverlapPitchResolution {
- LastOnFirstOff,
- FirstOnFirstOff
- };
-
- bool overlapping_pitches_accepted() const { return _overlapping_pitches_accepted; }
- void overlapping_pitches_accepted(bool yn) { _overlapping_pitches_accepted = yn; }
- OverlapPitchResolution overlap_pitch_resolution() const { return _overlap_pitch_resolution; }
- void set_overlap_pitch_resolution(OverlapPitchResolution opr);
-
- void set_notes (const typename Sequence<Time>::Notes& n);
-
- typedef boost::shared_ptr< Event<Time> > SysExPtr;
- typedef boost::shared_ptr<const Event<Time> > constSysExPtr;
-
- struct EarlierSysExComparator {
- inline bool operator() (constSysExPtr a, constSysExPtr b) const {
- return a->time() < b->time();
- }
- };
-
- typedef std::multiset<SysExPtr, EarlierSysExComparator> SysExes;
- inline SysExes& sysexes() { return _sysexes; }
- inline const SysExes& sysexes() const { return _sysexes; }
-
- typedef boost::shared_ptr<PatchChange<Time> > PatchChangePtr;
- typedef boost::shared_ptr<const PatchChange<Time> > constPatchChangePtr;
-
- struct EarlierPatchChangeComparator {
- inline bool operator() (constPatchChangePtr a, constPatchChangePtr b) const {
- return a->time() < b->time();
- }
- };
-
- typedef std::multiset<PatchChangePtr, EarlierPatchChangeComparator> PatchChanges;
- inline PatchChanges& patch_changes () { return _patch_changes; }
- inline const PatchChanges& patch_changes () const { return _patch_changes; }
-
- void dump (std::ostream&) const;
-
-private:
- typedef std::priority_queue<NotePtr, std::deque<NotePtr>, LaterNoteEndComparator> ActiveNotes;
-public:
-
- /** Read iterator */
- class LIBEVORAL_API const_iterator {
- public:
- const_iterator();
- const_iterator(const Sequence<Time>& seq,
- Time t,
- bool force_discrete,
- const std::set<Evoral::Parameter>& filtered,
- const std::set<WeakNotePtr>* active_notes=NULL);
-
- inline bool valid() const { return !_is_end && _event; }
-
- void invalidate(std::set<WeakNotePtr>* notes);
-
- const Event<Time>& operator*() const { return *_event; }
- const boost::shared_ptr< const Event<Time> > operator->() const { return _event; }
-
- const const_iterator& operator++(); // prefix only
-
- bool operator==(const const_iterator& other) const;
- bool operator!=(const const_iterator& other) const { return ! operator==(other); }
-
- const_iterator& operator=(const const_iterator& other);
-
- private:
- friend class Sequence<Time>;
-
- Time choose_next(Time earliest_t);
- void set_event();
-
- typedef std::vector<ControlIterator> ControlIterators;
- enum MIDIMessageType { NIL, NOTE_ON, NOTE_OFF, CONTROL, SYSEX, PATCH_CHANGE };
-
- const Sequence<Time>* _seq;
- boost::shared_ptr< Event<Time> > _event;
- mutable ActiveNotes _active_notes;
- /** If the iterator is pointing at a patch change, this is the index of the
- * sub-message within that change.
- */
- int _active_patch_change_message;
- MIDIMessageType _type;
- bool _is_end;
- typename Sequence::ReadLock _lock;
- typename Notes::const_iterator _note_iter;
- typename SysExes::const_iterator _sysex_iter;
- typename PatchChanges::const_iterator _patch_change_iter;
- ControlIterators _control_iters;
- ControlIterators::iterator _control_iter;
- bool _force_discrete;
- };
-
- const_iterator begin (
- Time t = Time(),
- bool force_discrete = false,
- const std::set<Evoral::Parameter>& f = std::set<Evoral::Parameter>(),
- const std::set<WeakNotePtr>* active_notes = NULL) const {
- return const_iterator (*this, t, force_discrete, f, active_notes);
- }
-
- const const_iterator& end() const { return _end_iter; }
-
- // CONST iterator implementations (x3)
- typename Notes::const_iterator note_lower_bound (Time t) const;
- typename PatchChanges::const_iterator patch_change_lower_bound (Time t) const;
- typename SysExes::const_iterator sysex_lower_bound (Time t) const;
-
- // NON-CONST iterator implementations (x3)
- typename Notes::iterator note_lower_bound (Time t);
- typename PatchChanges::iterator patch_change_lower_bound (Time t);
- typename SysExes::iterator sysex_lower_bound (Time t);
-
- bool control_to_midi_event(boost::shared_ptr< Event<Time> >& ev,
- const ControlIterator& iter) const;
-
- bool edited() const { return _edited; }
- void set_edited(bool yn) { _edited = yn; }
-
- bool overlaps (const NotePtr& ev,
- const NotePtr& ignore_this_note) const;
- bool contains (const NotePtr& ev) const;
-
- bool add_note_unlocked (const NotePtr note, void* arg = 0);
- void remove_note_unlocked(const constNotePtr note);
-
- void add_patch_change_unlocked (const PatchChangePtr);
- void remove_patch_change_unlocked (const constPatchChangePtr);
-
- void add_sysex_unlocked (const SysExPtr);
- void remove_sysex_unlocked (const SysExPtr);
-
- uint8_t lowest_note() const { return _lowest_note; }
- uint8_t highest_note() const { return _highest_note; }
-
-
-protected:
- bool _edited;
- bool _overlapping_pitches_accepted;
- OverlapPitchResolution _overlap_pitch_resolution;
- mutable Glib::Threads::RWLock _lock;
- bool _writing;
-
- virtual int resolve_overlaps_unlocked (const NotePtr, void* /* arg */ = 0) {
- return 0;
- }
-
- typedef std::multiset<NotePtr, NoteNumberComparator> Pitches;
- inline Pitches& pitches(uint8_t chan) { return _pitches[chan&0xf]; }
- inline const Pitches& pitches(uint8_t chan) const { return _pitches[chan&0xf]; }
-
- virtual void control_list_marked_dirty ();
-
-private:
- friend class const_iterator;
-
- bool overlaps_unlocked (const NotePtr& ev, const NotePtr& ignore_this_note) const;
- bool contains_unlocked (const NotePtr& ev) const;
-
- void append_note_on_unlocked(const Event<Time>& event, Evoral::event_id_t);
- void append_note_off_unlocked(const Event<Time>& event);
- void append_control_unlocked(const Parameter& param, Time time, double value, Evoral::event_id_t);
- void append_sysex_unlocked(const Event<Time>& ev, Evoral::event_id_t);
- void append_patch_change_unlocked(const PatchChange<Time>&, Evoral::event_id_t);
-
- void get_notes_by_pitch (Notes&, NoteOperator, uint8_t val, int chan_mask = 0) const;
- void get_notes_by_velocity (Notes&, NoteOperator, uint8_t val, int chan_mask = 0) const;
-
- const TypeMap& _type_map;
-
- Notes _notes; // notes indexed by time
- Pitches _pitches[16]; // notes indexed by channel+pitch
- SysExes _sysexes;
- PatchChanges _patch_changes;
-
- typedef std::multiset<NotePtr, EarlierNoteComparator> WriteNotes;
- WriteNotes _write_notes[16];
-
- /** Current bank number on each channel so that we know what
- * to put in PatchChange events when program changes are
- * seen.
- */
- int _bank[16];
-
- const const_iterator _end_iter;
- bool _percussive;
-
- uint8_t _lowest_note;
- uint8_t _highest_note;
-};
-
-
-} // namespace Evoral
-
-template<typename Time> /*LIBEVORAL_API*/ std::ostream& operator<<(std::ostream& o, const Evoral::Sequence<Time>& s) { s.dump (o); return o; }
-
-
-#endif // EVORAL_SEQUENCE_HPP
-
--- /dev/null
+/*
+ * Copyright (C) 2009-2014 David Robillard <d@drobilla.net>
+ * Copyright (C) 2010-2012 Carl Hetherington <carl@carlh.net>
+ * Copyright (C) 2011-2014 Paul Davis <paul@linuxaudiosystems.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef EVORAL_TIME_CONVERTER_HPP
+#define EVORAL_TIME_CONVERTER_HPP
+
+#include "evoral/visibility.h"
+
+namespace Evoral {
+
+/** A bidirectional converter between two different time units.
+ *
+ * Think of the conversion method names as if they are written in-between
+ * the two template parameters (i.e. "A <name> B").
+ *
+ * _origin_b should be the origin for conversion in the units of B.
+ * That is, there is some point in time _origin_b, such that:
+ *
+ * to() converts a time _origin_b + a into an offset from _origin_b in units of B.
+ * from() converts a time _origin_b + b into an offset from _origin_b in units of A.
+ */
+template<typename A, typename B>
+class LIBEVORAL_TEMPLATE_API TimeConverter {
+public:
+ TimeConverter () : _origin_b (0) {}
+ TimeConverter (B ob) : _origin_b (ob) {}
+ virtual ~TimeConverter();
+
+ /** Convert A time to B time (A to B) */
+ virtual B to(A a) const = 0;
+
+ /** Convert B time to A time (A from B) */
+ virtual A from(B b) const = 0;
+
+ B origin_b () const {
+ return _origin_b;
+ }
+
+ void set_origin_b (B o) {
+ _origin_b = o;
+ }
+
+protected:
+ B _origin_b;
+};
+
+
+/** A stub TimeConverter that simple statically casts between types.
+ * _origin_b has no bearing here, as there is no time conversion
+ * going on.
+ */
+template<typename A, typename B>
+class LIBEVORAL_TEMPLATE_API IdentityConverter : public TimeConverter<A,B> {
+ public:
+ IdentityConverter() {}
+
+ B to(A a) const;
+ A from(B b) const;
+};
+
+
+} // namespace Evoral
+
+#endif // EVORAL_TIME_CONVERTER_HPP
+++ /dev/null
-/*
- * Copyright (C) 2009-2014 David Robillard <d@drobilla.net>
- * Copyright (C) 2010-2012 Carl Hetherington <carl@carlh.net>
- * Copyright (C) 2011-2014 Paul Davis <paul@linuxaudiosystems.com>
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-
-#ifndef EVORAL_TIME_CONVERTER_HPP
-#define EVORAL_TIME_CONVERTER_HPP
-
-#include "evoral/visibility.h"
-
-namespace Evoral {
-
-/** A bidirectional converter between two different time units.
- *
- * Think of the conversion method names as if they are written in-between
- * the two template parameters (i.e. "A <name> B").
- *
- * _origin_b should be the origin for conversion in the units of B.
- * That is, there is some point in time _origin_b, such that:
- *
- * to() converts a time _origin_b + a into an offset from _origin_b in units of B.
- * from() converts a time _origin_b + b into an offset from _origin_b in units of A.
- */
-template<typename A, typename B>
-class LIBEVORAL_TEMPLATE_API TimeConverter {
-public:
- TimeConverter () : _origin_b (0) {}
- TimeConverter (B ob) : _origin_b (ob) {}
- virtual ~TimeConverter();
-
- /** Convert A time to B time (A to B) */
- virtual B to(A a) const = 0;
-
- /** Convert B time to A time (A from B) */
- virtual A from(B b) const = 0;
-
- B origin_b () const {
- return _origin_b;
- }
-
- void set_origin_b (B o) {
- _origin_b = o;
- }
-
-protected:
- B _origin_b;
-};
-
-
-/** A stub TimeConverter that simple statically casts between types.
- * _origin_b has no bearing here, as there is no time conversion
- * going on.
- */
-template<typename A, typename B>
-class LIBEVORAL_TEMPLATE_API IdentityConverter : public TimeConverter<A,B> {
- public:
- IdentityConverter() {}
-
- B to(A a) const;
- A from(B b) const;
-};
-
-
-} // namespace Evoral
-
-#endif // EVORAL_TIME_CONVERTER_HPP
--- /dev/null
+/*
+ * Copyright (C) 2008-2016 David Robillard <d@drobilla.net>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef EVORAL_TYPE_MAP_HPP
+#define EVORAL_TYPE_MAP_HPP
+
+#include <stdint.h>
+
+#include <string>
+
+#include "evoral/visibility.h"
+#include "evoral/types.h"
+
+namespace Evoral {
+
+class Parameter;
+class ParameterDescriptor;
+
+/** The applications passes one of these which provide the implementation
+ * with required information about event types in an opaque, type neutral way
+ */
+class /*LIBEVORAL_API*/ TypeMap {
+public:
+ virtual ~TypeMap() {}
+
+ /** Return true iff the type is a MIDI event.
+ * The contents of the event will be used for specific ID
+ */
+ virtual bool type_is_midi(uint32_t type) const = 0;
+
+ /** Return the MIDI type (ie status byte with channel 0) for a
+ * parameter, or 0 if parameter can not be expressed as a MIDI event
+ */
+ virtual uint8_t parameter_midi_type(const Parameter& param) const = 0;
+
+ /** The parameter type for the given MIDI event. */
+ virtual ParameterType midi_parameter_type(const uint8_t* buf, uint32_t len) const = 0;
+
+ /** Return the description of a parameter. */
+ virtual ParameterDescriptor descriptor(const Parameter& param) const = 0;
+
+ virtual std::string to_symbol(const Parameter& param) const = 0;
+};
+
+} // namespace Evoral
+
+#endif // EVORAL_TYPE_MAP_HPP
+++ /dev/null
-/*
- * Copyright (C) 2008-2016 David Robillard <d@drobilla.net>
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-
-#ifndef EVORAL_TYPE_MAP_HPP
-#define EVORAL_TYPE_MAP_HPP
-
-#include <stdint.h>
-
-#include <string>
-
-#include "evoral/visibility.h"
-#include "evoral/types.hpp"
-
-namespace Evoral {
-
-class Parameter;
-class ParameterDescriptor;
-
-/** The applications passes one of these which provide the implementation
- * with required information about event types in an opaque, type neutral way
- */
-class /*LIBEVORAL_API*/ TypeMap {
-public:
- virtual ~TypeMap() {}
-
- /** Return true iff the type is a MIDI event.
- * The contents of the event will be used for specific ID
- */
- virtual bool type_is_midi(uint32_t type) const = 0;
-
- /** Return the MIDI type (ie status byte with channel 0) for a
- * parameter, or 0 if parameter can not be expressed as a MIDI event
- */
- virtual uint8_t parameter_midi_type(const Parameter& param) const = 0;
-
- /** The parameter type for the given MIDI event. */
- virtual ParameterType midi_parameter_type(const uint8_t* buf, uint32_t len) const = 0;
-
- /** Return the description of a parameter. */
- virtual ParameterDescriptor descriptor(const Parameter& param) const = 0;
-
- virtual std::string to_symbol(const Parameter& param) const = 0;
-};
-
-} // namespace Evoral
-
-#endif // EVORAL_TYPE_MAP_HPP
--- /dev/null
+/*
+ * Copyright (C) 2008-2012 Carl Hetherington <carl@carlh.net>
+ * Copyright (C) 2008-2016 David Robillard <d@drobilla.net>
+ * Copyright (C) 2009-2015 Paul Davis <paul@linuxaudiosystems.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef EVORAL_TYPES_HPP
+#define EVORAL_TYPES_HPP
+
+#include <float.h>
+#include <math.h>
+#include <stdint.h>
+
+#include <iostream>
+#include <limits>
+#include <list>
+
+#include "evoral/visibility.h"
+#include "pbd/debug.h"
+
+namespace Evoral {
+
+/** ID of an event (note or other). This must be operable on by glib
+ atomic ops
+*/
+typedef int32_t event_id_t;
+
+/** Type of an event (opaque, mapped by application, e.g. MIDI).
+ *
+ * Event types are really an arbitrary integer provided by the type map, and it
+ * is safe to use values not in this enum, but this enum exists so the compiler
+ * can catch mistakes like setting the event type to a MIDI status byte. Event
+ * types come from the type map and describe a format/protocol like MIDI, and
+ * must not be confused with the payload (such as a note on or CC change).
+ * There is a static value for MIDI as this type is handled specially by
+ * various parts of Evoral.
+ */
+enum EventType {
+ NO_EVENT,
+ MIDI_EVENT
+};
+
+/** Type of a parameter (opaque, mapped by application, e.g. gain) */
+typedef uint32_t ParameterType;
+
+class Beats;
+
+} // namespace Evoral
+
+namespace PBD {
+ namespace DEBUG {
+ LIBEVORAL_API extern DebugBits Sequence;
+ LIBEVORAL_API extern DebugBits Note;
+ LIBEVORAL_API extern DebugBits ControlList;
+ }
+}
+
+#endif // EVORAL_TYPES_HPP
+++ /dev/null
-/*
- * Copyright (C) 2008-2012 Carl Hetherington <carl@carlh.net>
- * Copyright (C) 2008-2016 David Robillard <d@drobilla.net>
- * Copyright (C) 2009-2015 Paul Davis <paul@linuxaudiosystems.com>
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-
-#ifndef EVORAL_TYPES_HPP
-#define EVORAL_TYPES_HPP
-
-#include <float.h>
-#include <math.h>
-#include <stdint.h>
-
-#include <iostream>
-#include <limits>
-#include <list>
-
-#include "evoral/visibility.h"
-#include "pbd/debug.h"
-
-namespace Evoral {
-
-/** ID of an event (note or other). This must be operable on by glib
- atomic ops
-*/
-typedef int32_t event_id_t;
-
-/** Type of an event (opaque, mapped by application, e.g. MIDI).
- *
- * Event types are really an arbitrary integer provided by the type map, and it
- * is safe to use values not in this enum, but this enum exists so the compiler
- * can catch mistakes like setting the event type to a MIDI status byte. Event
- * types come from the type map and describe a format/protocol like MIDI, and
- * must not be confused with the payload (such as a note on or CC change).
- * There is a static value for MIDI as this type is handled specially by
- * various parts of Evoral.
- */
-enum EventType {
- NO_EVENT,
- MIDI_EVENT
-};
-
-/** Type of a parameter (opaque, mapped by application, e.g. gain) */
-typedef uint32_t ParameterType;
-
-class Beats;
-
-} // namespace Evoral
-
-namespace PBD {
- namespace DEBUG {
- LIBEVORAL_API extern DebugBits Sequence;
- LIBEVORAL_API extern DebugBits Note;
- LIBEVORAL_API extern DebugBits ControlList;
- }
-}
-
-#endif // EVORAL_TYPES_HPP
--- /dev/null
+/*
+ * Copyright (C) 2008-2016 David Robillard <d@drobilla.net>
+ * Copyright (C) 2010-2014 Paul Davis <paul@linuxaudiosystems.com>
+ * Copyright (C) 2010 Carl Hetherington <carl@carlh.net>
+ * Copyright (C) 2015-2017 Robin Gareus <robin@gareus.org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include <iostream>
+
+#include "evoral/Control.h"
+#include "evoral/ControlList.h"
+#include "evoral/ParameterDescriptor.h"
+#include "evoral/TypeMap.h"
+
+namespace Evoral {
+
+Control::Control(const Parameter& parameter,
+ const ParameterDescriptor& desc,
+ boost::shared_ptr<ControlList> list)
+ : _parameter(parameter)
+ , _user_value(desc.normal)
+{
+ set_list (list);
+}
+
+
+/** Get the currently effective value (ie the one that corresponds to current output)
+ */
+double
+Control::get_double (bool from_list, double frame) const
+{
+ if (from_list) {
+ return _list->eval(frame);
+ } else {
+ return _user_value;
+ }
+}
+
+
+void
+Control::set_double (double value, double frame, bool to_list)
+{
+ _user_value = value;
+
+ /* if we're in a write pass, the automation watcher will determine the
+ values and add them to the list, so we we don't need to bother.
+ */
+
+ if (to_list && (!_list->in_write_pass() || _list->descriptor().toggled)) {
+ _list->add (frame, value, false);
+ }
+}
+
+
+void
+Control::set_list(boost::shared_ptr<ControlList> list)
+{
+ _list_marked_dirty_connection.disconnect ();
+
+ _list = list;
+
+ if (_list) {
+ _list->Dirty.connect_same_thread (_list_marked_dirty_connection, boost::bind (&Control::list_marked_dirty, this));
+ }
+}
+
+void
+Control::list_marked_dirty ()
+{
+ ListMarkedDirty (); /* EMIT SIGNAL */
+}
+
+} // namespace Evoral
+
+++ /dev/null
-/*
- * Copyright (C) 2008-2016 David Robillard <d@drobilla.net>
- * Copyright (C) 2010-2014 Paul Davis <paul@linuxaudiosystems.com>
- * Copyright (C) 2010 Carl Hetherington <carl@carlh.net>
- * Copyright (C) 2015-2017 Robin Gareus <robin@gareus.org>
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-
-#include <iostream>
-
-#include "evoral/Control.hpp"
-#include "evoral/ControlList.hpp"
-#include "evoral/ParameterDescriptor.hpp"
-#include "evoral/TypeMap.hpp"
-
-namespace Evoral {
-
-Control::Control(const Parameter& parameter,
- const ParameterDescriptor& desc,
- boost::shared_ptr<ControlList> list)
- : _parameter(parameter)
- , _user_value(desc.normal)
-{
- set_list (list);
-}
-
-
-/** Get the currently effective value (ie the one that corresponds to current output)
- */
-double
-Control::get_double (bool from_list, double frame) const
-{
- if (from_list) {
- return _list->eval(frame);
- } else {
- return _user_value;
- }
-}
-
-
-void
-Control::set_double (double value, double frame, bool to_list)
-{
- _user_value = value;
-
- /* if we're in a write pass, the automation watcher will determine the
- values and add them to the list, so we we don't need to bother.
- */
-
- if (to_list && (!_list->in_write_pass() || _list->descriptor().toggled)) {
- _list->add (frame, value, false);
- }
-}
-
-
-void
-Control::set_list(boost::shared_ptr<ControlList> list)
-{
- _list_marked_dirty_connection.disconnect ();
-
- _list = list;
-
- if (_list) {
- _list->Dirty.connect_same_thread (_list_marked_dirty_connection, boost::bind (&Control::list_marked_dirty, this));
- }
-}
-
-void
-Control::list_marked_dirty ()
-{
- ListMarkedDirty (); /* EMIT SIGNAL */
-}
-
-} // namespace Evoral
-
--- /dev/null
+/*
+ * Copyright (C) 2008-2009 Hans Baier <hansfbaier@googlemail.com>
+ * Copyright (C) 2008-2012 Carl Hetherington <carl@carlh.net>
+ * Copyright (C) 2008-2015 David Robillard <d@drobilla.net>
+ * Copyright (C) 2010-2017 Paul Davis <paul@linuxaudiosystems.com>
+ * Copyright (C) 2014-2018 Robin Gareus <robin@gareus.org>
+ * Copyright (C) 2014 Ben Loftis <ben@harrisonconsoles.com>
+ * Copyright (C) 2015 Nick Mainsbridge <mainsbridge@gmail.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include <cmath>
+
+#ifdef COMPILER_MSVC
+#include <float.h>
+
+// 'std::isnan()' is not available in MSVC.
+#define isnan_local(val) (bool)_isnan((double)val)
+#else
+#define isnan_local std::isnan
+#endif
+
+#define GUARD_POINT_DELTA 64
+
+#include <cassert>
+#include <cmath>
+#include <iostream>
+#include <utility>
+
+#include "evoral/ControlList.h"
+#include "evoral/Curve.h"
+#include "evoral/ParameterDescriptor.h"
+#include "evoral/TypeMap.h"
+#include "evoral/types.h"
+
+#include "pbd/control_math.h"
+#include "pbd/compose.h"
+#include "pbd/debug.h"
+
+using namespace std;
+using namespace PBD;
+
+namespace Evoral {
+
+inline bool event_time_less_than (ControlEvent* a, ControlEvent* b)
+{
+ return a->when < b->when;
+}
+
+ControlList::ControlList (const Parameter& id, const ParameterDescriptor& desc)
+ : _parameter(id)
+ , _desc(desc)
+ , _interpolation (default_interpolation ())
+ , _curve(0)
+{
+ _frozen = 0;
+ _changed_when_thawed = false;
+ _lookup_cache.left = -1;
+ _lookup_cache.range.first = _events.end();
+ _lookup_cache.range.second = _events.end();
+ _search_cache.left = -1;
+ _search_cache.first = _events.end();
+ _sort_pending = false;
+ new_write_pass = true;
+ _in_write_pass = false;
+ did_write_during_pass = false;
+ insert_position = -1;
+ most_recent_insert_iterator = _events.end();
+}
+
+ControlList::ControlList (const ControlList& other)
+ : _parameter(other._parameter)
+ , _desc(other._desc)
+ , _interpolation(other._interpolation)
+ , _curve(0)
+{
+ _frozen = 0;
+ _changed_when_thawed = false;
+ _lookup_cache.range.first = _events.end();
+ _lookup_cache.range.second = _events.end();
+ _search_cache.first = _events.end();
+ _sort_pending = false;
+ new_write_pass = true;
+ _in_write_pass = false;
+ did_write_during_pass = false;
+ insert_position = -1;
+ most_recent_insert_iterator = _events.end();
+
+ // XXX copy_events() emits Dirty, but this is just assignment copy/construction
+ copy_events (other);
+}
+
+ControlList::ControlList (const ControlList& other, double start, double end)
+ : _parameter(other._parameter)
+ , _desc(other._desc)
+ , _interpolation(other._interpolation)
+ , _curve(0)
+{
+ _frozen = 0;
+ _changed_when_thawed = false;
+ _lookup_cache.range.first = _events.end();
+ _lookup_cache.range.second = _events.end();
+ _search_cache.first = _events.end();
+ _sort_pending = false;
+
+ /* now grab the relevant points, and shift them back if necessary */
+
+ boost::shared_ptr<ControlList> section = const_cast<ControlList*>(&other)->copy (start, end);
+
+ if (!section->empty()) {
+ // XXX copy_events() emits Dirty, but this is just assignment copy/construction
+ copy_events (*(section.get()));
+ }
+
+ new_write_pass = true;
+ _in_write_pass = false;
+ did_write_during_pass = false;
+ insert_position = -1;
+ most_recent_insert_iterator = _events.end();
+
+ mark_dirty ();
+}
+
+ControlList::~ControlList()
+{
+ for (EventList::iterator x = _events.begin(); x != _events.end(); ++x) {
+ delete (*x);
+ }
+ _events.clear ();
+
+ delete _curve;
+}
+
+boost::shared_ptr<ControlList>
+ControlList::create(const Parameter& id, const ParameterDescriptor& desc)
+{
+ return boost::shared_ptr<ControlList>(new ControlList(id, desc));
+}
+
+bool
+ControlList::operator== (const ControlList& other)
+{
+ return _events == other._events;
+}
+
+ControlList&
+ControlList::operator= (const ControlList& other)
+{
+ if (this != &other) {
+ /* list should be frozen before assignment */
+ assert (_frozen > 0);
+ _changed_when_thawed = false;
+ _sort_pending = false;
+
+ insert_position = other.insert_position;
+ new_write_pass = true;
+ _in_write_pass = false;
+ did_write_during_pass = false;
+ insert_position = -1;
+
+ _parameter = other._parameter;
+ _desc = other._desc;
+ _interpolation = other._interpolation;
+
+ copy_events (other);
+ }
+
+ return *this;
+}
+
+void
+ControlList::copy_events (const ControlList& other)
+{
+ {
+ Glib::Threads::RWLock::WriterLock lm (_lock);
+ for (EventList::iterator x = _events.begin(); x != _events.end(); ++x) {
+ delete (*x);
+ }
+ _events.clear ();
+ Glib::Threads::RWLock::ReaderLock olm (other._lock);
+ for (const_iterator i = other.begin(); i != other.end(); ++i) {
+ _events.push_back (new ControlEvent ((*i)->when, (*i)->value));
+ }
+ unlocked_invalidate_insert_iterator ();
+ mark_dirty ();
+ }
+ maybe_signal_changed ();
+}
+
+void
+ControlList::create_curve()
+{
+ _curve = new Curve(*this);
+}
+
+void
+ControlList::destroy_curve()
+{
+ delete _curve;
+ _curve = NULL;
+}
+
+ControlList::InterpolationStyle
+ControlList::default_interpolation () const
+{
+ if (_desc.toggled) {
+ return Discrete;
+ } else if (_desc.logarithmic) {
+ return Logarithmic;
+ }
+ return Linear;
+}
+
+void
+ControlList::maybe_signal_changed ()
+{
+ mark_dirty ();
+
+ if (_frozen) {
+ _changed_when_thawed = true;
+ }
+}
+
+void
+ControlList::clear ()
+{
+ {
+ Glib::Threads::RWLock::WriterLock lm (_lock);
+ for (EventList::iterator x = _events.begin(); x != _events.end(); ++x) {
+ delete (*x);
+ }
+ _events.clear ();
+ unlocked_invalidate_insert_iterator ();
+ mark_dirty ();
+ }
+
+ maybe_signal_changed ();
+}
+
+void
+ControlList::x_scale (double factor)
+{
+ Glib::Threads::RWLock::WriterLock lm (_lock);
+ _x_scale (factor);
+}
+
+bool
+ControlList::extend_to (double when)
+{
+ Glib::Threads::RWLock::WriterLock lm (_lock);
+ if (_events.empty() || _events.back()->when == when) {
+ return false;
+ }
+ double factor = when / _events.back()->when;
+ _x_scale (factor);
+ return true;
+}
+
+void
+ControlList::y_transform (boost::function<double(double)> callback)
+{
+ {
+ Glib::Threads::RWLock::WriterLock lm (_lock);
+ for (iterator i = _events.begin(); i != _events.end(); ++i) {
+ (*i)->value = callback ((*i)->value);
+ }
+ mark_dirty ();
+ }
+ maybe_signal_changed ();
+}
+
+void
+ControlList::list_merge (ControlList const& other, boost::function<double(double, double)> callback)
+{
+ {
+ Glib::Threads::RWLock::WriterLock lm (_lock);
+ EventList nel;
+ /* First scale existing events, copy into a new list.
+ * The original list is needed later to interpolate
+ * for new events only present in the master list.
+ */
+ for (iterator i = _events.begin(); i != _events.end(); ++i) {
+ float val = callback ((*i)->value, other.eval ((*i)->when));
+ nel.push_back (new ControlEvent ((*i)->when , val));
+ }
+ /* Now add events which are only present in the master-list. */
+ const EventList& evl (other.events());
+ for (const_iterator i = evl.begin(); i != evl.end(); ++i) {
+ bool found = false;
+ // TODO: optimize, remember last matching iterator (lists are sorted)
+ for (iterator j = _events.begin(); j != _events.end(); ++j) {
+ if ((*i)->when == (*j)->when) {
+ found = true;
+ break;
+ }
+ }
+ /* skip events that have already been merge in the first pass */
+ if (found) {
+ continue;
+ }
+ float val = callback (unlocked_eval ((*i)->when), (*i)->value);
+ nel.push_back (new ControlEvent ((*i)->when, val));
+ }
+ nel.sort (event_time_less_than);
+
+ for (EventList::iterator x = _events.begin(); x != _events.end(); ++x) {
+ delete (*x);
+ }
+ _events.clear ();
+ _events = nel;
+
+ unlocked_remove_duplicates ();
+ unlocked_invalidate_insert_iterator ();
+ mark_dirty ();
+ }
+ maybe_signal_changed ();
+}
+
+void
+ControlList::_x_scale (double factor)
+{
+ for (iterator i = _events.begin(); i != _events.end(); ++i) {
+ (*i)->when *= factor;
+ }
+
+ mark_dirty ();
+}
+
+struct ControlEventTimeComparator {
+ bool operator() (ControlEvent* a, ControlEvent* b) {
+ return a->when < b->when;
+ }
+};
+
+void
+ControlList::thin (double thinning_factor)
+{
+ if (thinning_factor == 0.0 || _desc.toggled) {
+ return;
+ }
+
+ assert (is_sorted ());
+
+ bool changed = false;
+
+ {
+ Glib::Threads::RWLock::WriterLock lm (_lock);
+
+ ControlEvent* prevprev = 0;
+ ControlEvent* cur = 0;
+ ControlEvent* prev = 0;
+ iterator pprev;
+ int counter = 0;
+
+ DEBUG_TRACE (DEBUG::ControlList, string_compose ("@%1 thin from %2 events\n", this, _events.size()));
+
+ for (iterator i = _events.begin(); i != _events.end(); ++i) {
+
+ cur = *i;
+ counter++;
+
+ if (counter > 2) {
+
+ /* compute the area of the triangle formed by 3 points
+ */
+
+ double area = fabs ((prevprev->when * (prev->value - cur->value)) +
+ (prev->when * (cur->value - prevprev->value)) +
+ (cur->when * (prevprev->value - prev->value)));
+
+ if (area < thinning_factor) {
+ iterator tmp = pprev;
+
+ /* pprev will change to current
+ i is incremented to the next event
+ as we loop.
+ */
+
+ pprev = i;
+ _events.erase (tmp);
+ changed = true;
+ continue;
+ }
+ }
+
+ prevprev = prev;
+ prev = cur;
+ pprev = i;
+ }
+
+ DEBUG_TRACE (DEBUG::ControlList, string_compose ("@%1 thin => %2 events\n", this, _events.size()));
+
+ if (changed) {
+ unlocked_invalidate_insert_iterator ();
+ mark_dirty ();
+ }
+ }
+
+ if (changed) {
+ maybe_signal_changed ();
+ }
+}
+
+void
+ControlList::fast_simple_add (double when, double value)
+{
+ Glib::Threads::RWLock::WriterLock lm (_lock);
+ /* to be used only for loading pre-sorted data from saved state */
+ _events.insert (_events.end(), new ControlEvent (when, value));
+
+ mark_dirty ();
+ if (_frozen) {
+ _sort_pending = true;
+ }
+}
+
+void
+ControlList::invalidate_insert_iterator ()
+{
+ Glib::Threads::RWLock::WriterLock lm (_lock);
+ unlocked_invalidate_insert_iterator ();
+}
+
+void
+ControlList::unlocked_invalidate_insert_iterator ()
+{
+ most_recent_insert_iterator = _events.end();
+}
+
+void
+ControlList::unlocked_remove_duplicates ()
+{
+ if (_events.size() < 2) {
+ return;
+ }
+ iterator i = _events.begin();
+ iterator prev = i++;
+ while (i != _events.end()) {
+ if ((*prev)->when == (*i)->when && (*prev)->value == (*i)->value) {
+ i = _events.erase (i);
+ } else {
+ ++prev;
+ ++i;
+ }
+ }
+}
+
+void
+ControlList::start_write_pass (double when)
+{
+ Glib::Threads::RWLock::WriterLock lm (_lock);
+
+ DEBUG_TRACE (DEBUG::ControlList, string_compose ("%1: setup write pass @ %2\n", this, when));
+
+ insert_position = when;
+
+ /* leave the insert iterator invalid, so that we will do the lookup
+ of where it should be in a "lazy" way - deferring it until
+ we actually add the first point (which may never happen).
+ */
+
+ unlocked_invalidate_insert_iterator ();
+
+ /* except if we're already in an active write-pass.
+ *
+ * invalid iterator == end() the iterator is set to the correct
+ * position in ControlList::add IFF (_in_write_pass && new_write_pass)
+ */
+ if (_in_write_pass && !new_write_pass) {
+#if 1
+ add_guard_point (when, 0); // also sets most_recent_insert_iterator
+#else
+ const ControlEvent cp (when, 0.0);
+ most_recent_insert_iterator = lower_bound (_events.begin(), _events.end(), &cp, time_comparator);
+#endif
+ }
+}
+
+void
+ControlList::write_pass_finished (double /*when*/, double thinning_factor)
+{
+ DEBUG_TRACE (DEBUG::ControlList, "write pass finished\n");
+
+ if (did_write_during_pass) {
+ thin (thinning_factor);
+ did_write_during_pass = false;
+ }
+ new_write_pass = true;
+ _in_write_pass = false;
+}
+
+void
+ControlList::set_in_write_pass (bool yn, bool add_point, double when)
+{
+ DEBUG_TRACE (DEBUG::ControlList, string_compose ("now in write pass @ %1, add point ? %2\n", when, add_point));
+
+ _in_write_pass = yn;
+
+ if (yn && add_point) {
+ Glib::Threads::RWLock::WriterLock lm (_lock);
+ add_guard_point (when, 0);
+ }
+}
+
+void
+ControlList::add_guard_point (double when, double offset)
+{
+ // caller needs to hold writer-lock
+ if (offset < 0 && when < offset) {
+ return;
+ }
+ assert (offset <= 0);
+
+ if (offset != 0) {
+ /* check if there are points between when + offset .. when */
+ ControlEvent cp (when + offset, 0.0);
+ iterator s;
+ iterator e;
+ if ((s = lower_bound (_events.begin(), _events.end(), &cp, time_comparator)) != _events.end()) {
+ cp.when = when;
+ e = lower_bound (_events.begin(), _events.end(), &cp, time_comparator);
+ if (s != e) {
+ DEBUG_TRACE (DEBUG::ControlList, string_compose ("@%1 add_guard_point, none added, found event between %2 and %3\n", this, when - offset, when));
+ return;
+ }
+ }
+ }
+
+ /* don't do this again till the next write pass,
+ * unless we're not in a write-pass (transport stopped)
+ */
+ if (_in_write_pass && new_write_pass) {
+ WritePassStarted (); /* EMIT SIGNAL w/WriteLock */
+ new_write_pass = false;
+ }
+
+ when += offset;
+
+ ControlEvent cp (when, 0.0);
+ most_recent_insert_iterator = lower_bound (_events.begin(), _events.end(), &cp, time_comparator);
+
+ double eval_value = unlocked_eval (when);
+
+ if (most_recent_insert_iterator == _events.end()) {
+
+ DEBUG_TRACE (DEBUG::ControlList, string_compose ("@%1 insert iterator at end, adding eval-value there %2\n", this, eval_value));
+ _events.push_back (new ControlEvent (when, eval_value));
+ /* leave insert iterator at the end */
+
+ } else if ((*most_recent_insert_iterator)->when == when) {
+
+ DEBUG_TRACE (DEBUG::ControlList, string_compose ("@%1 insert iterator at existing point, setting eval-value there %2\n", this, eval_value));
+
+ /* most_recent_insert_iterator points to a control event
+ already at the insert position, so there is
+ nothing to do.
+
+ ... except ...
+
+ advance most_recent_insert_iterator so that the "real"
+ insert occurs in the right place, since it
+ points to the control event just inserted.
+ */
+
+ ++most_recent_insert_iterator;
+ } else {
+
+ /* insert a new control event at the right spot */
+
+ DEBUG_TRACE (DEBUG::ControlList, string_compose ("@%1 insert eval-value %2 just before iterator @ %3\n",
+ this, eval_value, (*most_recent_insert_iterator)->when));
+
+ most_recent_insert_iterator = _events.insert (most_recent_insert_iterator, new ControlEvent (when, eval_value));
+
+ /* advance most_recent_insert_iterator so that the "real"
+ * insert occurs in the right place, since it
+ * points to the control event just inserted.
+ */
+
+ ++most_recent_insert_iterator;
+ }
+}
+
+bool
+ControlList::in_write_pass () const
+{
+ return _in_write_pass;
+}
+
+bool
+ControlList::editor_add (double when, double value, bool with_guard)
+{
+ /* this is for making changes from a graphical line editor */
+ {
+ Glib::Threads::RWLock::WriterLock lm (_lock);
+
+ ControlEvent cp (when, 0.0f);
+ iterator i = lower_bound (_events.begin(), _events.end(), &cp, time_comparator);
+
+ if (i != _events.end () && (*i)->when == when) {
+ return false;
+ }
+
+ /* clamp new value to allowed range */
+ value = std::min ((double)_desc.upper, std::max ((double)_desc.lower, value));
+
+ if (_events.empty()) {
+
+ /* as long as the point we're adding is not at zero,
+ * add an "anchor" point there.
+ */
+
+ if (when >= 1) {
+ _events.insert (_events.end(), new ControlEvent (0, value));
+ DEBUG_TRACE (DEBUG::ControlList, string_compose ("@%1 added value %2 at zero\n", this, value));
+ }
+ }
+
+ insert_position = when;
+ if (with_guard) {
+ add_guard_point (when, -GUARD_POINT_DELTA);
+ maybe_add_insert_guard (when);
+ i = lower_bound (_events.begin(), _events.end(), &cp, time_comparator);
+ }
+
+ iterator result;
+ DEBUG_TRACE (DEBUG::ControlList, string_compose ("editor_add: actually add when= %1 value= %2\n", when, value));
+ result = _events.insert (i, new ControlEvent (when, value));
+
+ if (i == result) {
+ return false;
+ }
+
+ mark_dirty ();
+ }
+ maybe_signal_changed ();
+
+ return true;
+}
+
+void
+ControlList::maybe_add_insert_guard (double when)
+{
+ // caller needs to hold writer-lock
+ if (most_recent_insert_iterator != _events.end()) {
+ if ((*most_recent_insert_iterator)->when - when > GUARD_POINT_DELTA) {
+ /* Next control point is some distance from where our new point is
+ going to go, so add a new point to avoid changing the shape of
+ the line too much. The insert iterator needs to point to the
+ new control point so that our insert will happen correctly. */
+ most_recent_insert_iterator = _events.insert ( most_recent_insert_iterator,
+ new ControlEvent (when + GUARD_POINT_DELTA, (*most_recent_insert_iterator)->value));
+
+ DEBUG_TRACE (DEBUG::ControlList, string_compose ("@%1 added insert guard point @ %2 = %3\n",
+ this, when + GUARD_POINT_DELTA,
+ (*most_recent_insert_iterator)->value));
+ }
+ }
+}
+
+/** If we would just be adding to a straight line, move the previous point instead. */
+bool
+ControlList::maybe_insert_straight_line (double when, double value)
+{
+ // caller needs to hold writer-lock
+ if (_events.empty()) {
+ return false;
+ }
+
+ if (_events.back()->value == value) {
+ // Point b at the final point, which we know exists
+ EventList::iterator b = _events.end();
+ --b;
+ if (b == _events.begin()) {
+ return false; // No previous point
+ }
+
+ // Check the previous point's value
+ --b;
+ if ((*b)->value == value) {
+ /* At least two points with the exact same value (straight
+ line), just move the final point to the new time. */
+ _events.back()->when = when;
+ DEBUG_TRACE (DEBUG::ControlList, string_compose ("final value of %1 moved to %2\n", value, when));
+ return true;
+ }
+ }
+ return false;
+}
+
+ControlList::iterator
+ControlList::erase_from_iterator_to (iterator iter, double when)
+{
+ // caller needs to hold writer-lock
+ while (iter != _events.end()) {
+ if ((*iter)->when < when) {
+ DEBUG_TRACE (DEBUG::ControlList, string_compose ("@%1 erase existing @ %2\n", this, (*iter)->when));
+ delete *iter;
+ iter = _events.erase (iter);
+ continue;
+ } else if ((*iter)->when >= when) {
+ break;
+ }
+ ++iter;
+ }
+ return iter;
+}
+
+/* this is for making changes from some kind of user interface or
+ * control surface (GUI, MIDI, OSC etc)
+ */
+void
+ControlList::add (double when, double value, bool with_guards, bool with_initial)
+{
+ /* clamp new value to allowed range */
+ value = std::min ((double)_desc.upper, std::max ((double)_desc.lower, value));
+
+ DEBUG_TRACE (DEBUG::ControlList,
+ string_compose ("@%1 add %2 at %3 guards = %4 write pass = %5 (new? %6) at end? %7\n",
+ this, value, when, with_guards, _in_write_pass, new_write_pass,
+ (most_recent_insert_iterator == _events.end())));
+ {
+ Glib::Threads::RWLock::WriterLock lm (_lock);
+ ControlEvent cp (when, 0.0f);
+ iterator insertion_point;
+
+ if (_events.empty() && with_initial) {
+
+ /* empty: add an "anchor" point if the point we're adding past time 0 */
+
+ if (when >= 1) {
+ if (_desc.toggled) {
+ const double opp_val = ((value < 0.5) ? 1.0 : 0.0);
+ _events.insert (_events.end(), new ControlEvent (0, opp_val));
+ DEBUG_TRACE (DEBUG::ControlList, string_compose ("@%1 added toggled value %2 at zero\n", this, opp_val));
+
+ } else {
+ _events.insert (_events.end(), new ControlEvent (0, value));
+ DEBUG_TRACE (DEBUG::ControlList, string_compose ("@%1 added default value %2 at zero\n", this, _desc.normal));
+ }
+ }
+ }
+
+ if (_in_write_pass && new_write_pass) {
+
+ /* first write in a write pass: add guard point if requested */
+
+ if (with_guards) {
+ add_guard_point (insert_position, 0);
+ did_write_during_pass = true;
+ } else {
+ /* not adding a guard, but we need to set iterator appropriately */
+ const ControlEvent cp (when, 0.0);
+ most_recent_insert_iterator = lower_bound (_events.begin(), _events.end(), &cp, time_comparator);
+ }
+ WritePassStarted (); /* EMIT SIGNAL w/WriteLock */
+ new_write_pass = false;
+
+ } else if (_in_write_pass &&
+ (most_recent_insert_iterator == _events.end() || when > (*most_recent_insert_iterator)->when)) {
+
+ /* in write pass: erase from most recent insert to now */
+
+ if (most_recent_insert_iterator != _events.end()) {
+ /* advance to avoid deleting the last inserted point itself. */
+ ++most_recent_insert_iterator;
+ }
+
+ if (with_guards) {
+ most_recent_insert_iterator = erase_from_iterator_to (most_recent_insert_iterator, when + GUARD_POINT_DELTA);
+ maybe_add_insert_guard (when);
+ } else {
+ most_recent_insert_iterator = erase_from_iterator_to(most_recent_insert_iterator, when);
+ }
+
+ } else if (!_in_write_pass) {
+
+ /* not in a write pass: figure out the iterator we should insert in front of */
+
+ DEBUG_TRACE (DEBUG::ControlList, string_compose ("compute(b) MRI for position %1\n", when));
+ ControlEvent cp (when, 0.0f);
+ most_recent_insert_iterator = lower_bound (_events.begin(), _events.end(), &cp, time_comparator);
+ }
+
+ /* OK, now we're really ready to add a new point */
+
+ if (most_recent_insert_iterator == _events.end()) {
+ DEBUG_TRACE (DEBUG::ControlList, string_compose ("@%1 appending new point at end\n", this));
+
+ const bool done = maybe_insert_straight_line (when, value);
+ if (!done) {
+ _events.push_back (new ControlEvent (when, value));
+ DEBUG_TRACE (DEBUG::ControlList, string_compose ("\tactually appended, size now %1\n", _events.size()));
+ }
+
+ most_recent_insert_iterator = _events.end();
+ --most_recent_insert_iterator;
+
+ } else if ((*most_recent_insert_iterator)->when == when) {
+
+ if ((*most_recent_insert_iterator)->value != value) {
+ DEBUG_TRACE (DEBUG::ControlList, string_compose ("@%1 reset existing point to new value %2\n", this, value));
+
+ /* only one point allowed per time point, so add a guard point
+ * before it if needed then reset the value of the point.
+ */
+
+ (*most_recent_insert_iterator)->value = value;
+
+ /* if we modified the final value, then its as
+ * if we inserted a new point as far as the
+ * next addition, so make sure we know that.
+ */
+
+ if (_events.back()->when == when) {
+ most_recent_insert_iterator = _events.end();
+ }
+
+ } else {
+ DEBUG_TRACE (DEBUG::ControlList, string_compose ("@%1 same time %2, same value value %3\n", this, when, value));
+ }
+
+ } else {
+ DEBUG_TRACE (DEBUG::ControlList, string_compose ("@%1 insert new point at %2 at iterator at %3\n", this, when, (*most_recent_insert_iterator)->when));
+ bool done = false;
+ /* check for possible straight line here until maybe_insert_straight_line () handles the insert iterator properly*/
+ if (most_recent_insert_iterator != _events.begin ()) {
+ bool have_point2 = false;
+ --most_recent_insert_iterator;
+ const bool have_point1 = (*most_recent_insert_iterator)->value == value;
+
+ if (most_recent_insert_iterator != _events.begin ()) {
+ --most_recent_insert_iterator;
+ have_point2 = (*most_recent_insert_iterator)->value == value;
+ ++most_recent_insert_iterator;
+ }
+
+ if (have_point1 && have_point2) {
+ DEBUG_TRACE (DEBUG::ControlList, string_compose ("@%1 no change: move existing at %3 to %2\n", this, when, (*most_recent_insert_iterator)->when));
+ (*most_recent_insert_iterator)->when = when;
+ done = true;
+ } else {
+ ++most_recent_insert_iterator;
+ }
+ }
+
+ /* if the transport is stopped, add guard points */
+ if (!done && !_in_write_pass) {
+ add_guard_point (when, -GUARD_POINT_DELTA);
+ maybe_add_insert_guard (when);
+ } else if (with_guards) {
+ maybe_add_insert_guard (when);
+ }
+
+ if (!done) {
+ EventList::iterator x = _events.insert (most_recent_insert_iterator, new ControlEvent (when, value));
+ DEBUG_TRACE (DEBUG::ControlList, string_compose ("@%1 inserted new value before MRI, size now %2\n", this, _events.size()));
+ most_recent_insert_iterator = x;
+ }
+ }
+
+ mark_dirty ();
+ }
+
+ maybe_signal_changed ();
+}
+
+void
+ControlList::erase (iterator i)
+{
+ {
+ Glib::Threads::RWLock::WriterLock lm (_lock);
+ if (most_recent_insert_iterator == i) {
+ unlocked_invalidate_insert_iterator ();
+ }
+ _events.erase (i);
+ mark_dirty ();
+ }
+ maybe_signal_changed ();
+}
+
+void
+ControlList::erase (iterator start, iterator end)
+{
+ {
+ Glib::Threads::RWLock::WriterLock lm (_lock);
+ _events.erase (start, end);
+ unlocked_invalidate_insert_iterator ();
+ mark_dirty ();
+ }
+ maybe_signal_changed ();
+}
+
+/** Erase the first event which matches the given time and value */
+void
+ControlList::erase (double when, double value)
+{
+ {
+ Glib::Threads::RWLock::WriterLock lm (_lock);
+
+ iterator i = begin ();
+ while (i != end() && ((*i)->when != when || (*i)->value != value)) {
+ ++i;
+ }
+
+ if (i != end ()) {
+ _events.erase (i);
+ if (most_recent_insert_iterator == i) {
+ unlocked_invalidate_insert_iterator ();
+ }
+ }
+
+ mark_dirty ();
+ }
+
+ maybe_signal_changed ();
+}
+
+void
+ControlList::erase_range (double start, double endt)
+{
+ bool erased = false;
+
+ {
+ Glib::Threads::RWLock::WriterLock lm (_lock);
+ erased = erase_range_internal (start, endt, _events);
+
+ if (erased) {
+ mark_dirty ();
+ }
+
+ }
+
+ if (erased) {
+ maybe_signal_changed ();
+ }
+}
+
+bool
+ControlList::erase_range_internal (double start, double endt, EventList & events)
+{
+ bool erased = false;
+ ControlEvent cp (start, 0.0f);
+ iterator s;
+ iterator e;
+
+ if ((s = lower_bound (events.begin(), events.end(), &cp, time_comparator)) != events.end()) {
+ cp.when = endt;
+ e = upper_bound (events.begin(), events.end(), &cp, time_comparator);
+ events.erase (s, e);
+ if (s != e) {
+ unlocked_invalidate_insert_iterator ();
+ erased = true;
+ }
+ }
+
+ return erased;
+}
+
+void
+ControlList::slide (iterator before, double distance)
+{
+ {
+ Glib::Threads::RWLock::WriterLock lm (_lock);
+
+ if (before == _events.end()) {
+ return;
+ }
+
+ while (before != _events.end()) {
+ (*before)->when += distance;
+ ++before;
+ }
+
+ mark_dirty ();
+ }
+
+ maybe_signal_changed ();
+}
+
+void
+ControlList::shift (double pos, double frames)
+{
+ {
+ Glib::Threads::RWLock::WriterLock lm (_lock);
+ double v0, v1;
+ if (frames < 0) {
+ /* Route::shift () with negative shift is used
+ * for "remove time". The time [pos.. pos-frames] is removed.
+ * and everyhing after, moved backwards.
+ */
+ v0 = unlocked_eval (pos);
+ v1 = unlocked_eval (pos - frames);
+ erase_range_internal (pos, pos - frames, _events);
+ } else {
+ v0 = v1 = unlocked_eval (pos);
+ }
+
+ bool dst_guard_exists = false;
+
+ for (iterator i = _events.begin(); i != _events.end(); ++i) {
+ if ((*i)->when == pos) {
+ dst_guard_exists = true;
+ }
+ if ((*i)->when >= pos) {
+ (*i)->when += frames;
+ }
+ }
+
+ /* add guard-points to retain shape, if needed */
+ if (frames > 0) {
+ ControlEvent cp (pos, 0.0);
+ iterator s = lower_bound (_events.begin(), _events.end(), &cp, time_comparator);
+ if (s != _events.end ()) {
+ _events.insert (s, new ControlEvent (pos, v0));
+ }
+ pos += frames;
+ } else if (frames < 0 && pos > 0) {
+ ControlEvent cp (pos - 1, 0.0);
+ iterator s = lower_bound (_events.begin(), _events.end(), &cp, time_comparator);
+ if (s != _events.end ()) {
+ _events.insert (s, new ControlEvent (pos - 1, v0));
+ }
+ }
+ if (!dst_guard_exists) {
+ ControlEvent cp (pos, 0.0);
+ iterator s = lower_bound (_events.begin(), _events.end(), &cp, time_comparator);
+ _events.insert (s, new ControlEvent (pos, s == _events.end () ? v0 : v1));
+ }
+
+
+ mark_dirty ();
+ }
+
+ maybe_signal_changed ();
+}
+
+void
+ControlList::modify (iterator iter, double when, double val)
+{
+ /* note: we assume higher level logic is in place to avoid this
+ * reordering the time-order of control events in the list. ie. all
+ * points after *iter are later than when.
+ */
+
+ /* catch possible float/double rounding errors from higher levels */
+ val = std::min ((double)_desc.upper, std::max ((double)_desc.lower, val));
+
+ {
+ Glib::Threads::RWLock::WriterLock lm (_lock);
+
+ (*iter)->when = when;
+ (*iter)->value = val;
+ if (isnan_local (val)) {
+ abort ();
+ }
+
+ if (!_frozen) {
+ _events.sort (event_time_less_than);
+ unlocked_remove_duplicates ();
+ unlocked_invalidate_insert_iterator ();
+ } else {
+ _sort_pending = true;
+ }
+
+ mark_dirty ();
+ }
+
+ maybe_signal_changed ();
+}
+
+std::pair<ControlList::iterator,ControlList::iterator>
+ControlList::control_points_adjacent (double xval)
+{
+ Glib::Threads::RWLock::ReaderLock lm (_lock);
+ iterator i;
+ ControlEvent cp (xval, 0.0f);
+ std::pair<iterator,iterator> ret;
+
+ ret.first = _events.end();
+ ret.second = _events.end();
+
+ for (i = lower_bound (_events.begin(), _events.end(), &cp, time_comparator); i != _events.end(); ++i) {
+
+ if (ret.first == _events.end()) {
+ if ((*i)->when >= xval) {
+ if (i != _events.begin()) {
+ ret.first = i;
+ --ret.first;
+ } else {
+ return ret;
+ }
+ }
+ }
+
+ if ((*i)->when > xval) {
+ ret.second = i;
+ break;
+ }
+ }
+
+ return ret;
+}
+
+void
+ControlList::freeze ()
+{
+ _frozen++;
+}
+
+void
+ControlList::thaw ()
+{
+ assert(_frozen > 0);
+
+ if (--_frozen > 0) {
+ return;
+ }
+
+ {
+ Glib::Threads::RWLock::WriterLock lm (_lock);
+
+ if (_sort_pending) {
+ _events.sort (event_time_less_than);
+ unlocked_remove_duplicates ();
+ unlocked_invalidate_insert_iterator ();
+ _sort_pending = false;
+ }
+ }
+}
+
+void
+ControlList::mark_dirty () const
+{
+ _lookup_cache.left = -1;
+ _lookup_cache.range.first = _events.end();
+ _lookup_cache.range.second = _events.end();
+ _search_cache.left = -1;
+ _search_cache.first = _events.end();
+
+ if (_curve) {
+ _curve->mark_dirty();
+ }
+
+ Dirty (); /* EMIT SIGNAL */
+}
+
+void
+ControlList::truncate_end (double last_coordinate)
+{
+ {
+ Glib::Threads::RWLock::WriterLock lm (_lock);
+ ControlEvent cp (last_coordinate, 0);
+ ControlList::reverse_iterator i;
+ double last_val;
+
+ if (_events.empty()) {
+ return;
+ }
+
+ if (last_coordinate == _events.back()->when) {
+ return;
+ }
+
+ if (last_coordinate > _events.back()->when) {
+
+ /* extending end:
+ */
+
+ iterator foo = _events.begin();
+ bool lessthantwo;
+
+ if (foo == _events.end()) {
+ lessthantwo = true;
+ } else if (++foo == _events.end()) {
+ lessthantwo = true;
+ } else {
+ lessthantwo = false;
+ }
+
+ if (lessthantwo) {
+ /* less than 2 points: add a new point */
+ _events.push_back (new ControlEvent (last_coordinate, _events.back()->value));
+ } else {
+
+ /* more than 2 points: check to see if the last 2 values
+ are equal. if so, just move the position of the
+ last point. otherwise, add a new point.
+ */
+
+ iterator penultimate = _events.end();
+ --penultimate; /* points at last point */
+ --penultimate; /* points at the penultimate point */
+
+ if (_events.back()->value == (*penultimate)->value) {
+ _events.back()->when = last_coordinate;
+ } else {
+ _events.push_back (new ControlEvent (last_coordinate, _events.back()->value));
+ }
+ }
+
+ } else {
+
+ /* shortening end */
+
+ last_val = unlocked_eval (last_coordinate);
+ last_val = max ((double) _desc.lower, last_val);
+ last_val = min ((double) _desc.upper, last_val);
+
+ i = _events.rbegin();
+
+ /* make i point to the last control point */
+
+ ++i;
+
+ /* now go backwards, removing control points that are
+ beyond the new last coordinate.
+ */
+
+ // FIXME: SLOW! (size() == O(n))
+
+ uint32_t sz = _events.size();
+
+ while (i != _events.rend() && sz > 2) {
+ ControlList::reverse_iterator tmp;
+
+ tmp = i;
+ ++tmp;
+
+ if ((*i)->when < last_coordinate) {
+ break;
+ }
+
+ _events.erase (i.base());
+ --sz;
+
+ i = tmp;
+ }
+
+ _events.back()->when = last_coordinate;
+ _events.back()->value = last_val;
+ }
+
+ unlocked_invalidate_insert_iterator ();
+ mark_dirty();
+ }
+
+ maybe_signal_changed ();
+}
+
+void
+ControlList::truncate_start (double overall_length)
+{
+ {
+ Glib::Threads::RWLock::WriterLock lm (_lock);
+ iterator i;
+ double first_legal_value;
+ double first_legal_coordinate;
+
+ if (_events.empty()) {
+ /* nothing to truncate */
+ return;
+ } else if (overall_length == _events.back()->when) {
+ /* no change in overall length */
+ return;
+ }
+
+ if (overall_length > _events.back()->when) {
+
+ /* growing at front: duplicate first point. shift all others */
+
+ double shift = overall_length - _events.back()->when;
+ uint32_t np;
+
+ for (np = 0, i = _events.begin(); i != _events.end(); ++i, ++np) {
+ (*i)->when += shift;
+ }
+
+ if (np < 2) {
+
+ /* less than 2 points: add a new point */
+ _events.push_front (new ControlEvent (0, _events.front()->value));
+
+ } else {
+
+ /* more than 2 points: check to see if the first 2 values
+ are equal. if so, just move the position of the
+ first point. otherwise, add a new point.
+ */
+
+ iterator second = _events.begin();
+ ++second; /* points at the second point */
+
+ if (_events.front()->value == (*second)->value) {
+ /* first segment is flat, just move start point back to zero */
+ _events.front()->when = 0;
+ } else {
+ /* leave non-flat segment in place, add a new leading point. */
+ _events.push_front (new ControlEvent (0, _events.front()->value));
+ }
+ }
+
+ } else {
+
+ /* shrinking at front */
+
+ first_legal_coordinate = _events.back()->when - overall_length;
+ first_legal_value = unlocked_eval (first_legal_coordinate);
+ first_legal_value = max ((double)_desc.lower, first_legal_value);
+ first_legal_value = min ((double)_desc.upper, first_legal_value);
+
+ /* remove all events earlier than the new "front" */
+
+ i = _events.begin();
+
+ while (i != _events.end() && !_events.empty()) {
+ ControlList::iterator tmp;
+
+ tmp = i;
+ ++tmp;
+
+ if ((*i)->when > first_legal_coordinate) {
+ break;
+ }
+
+ _events.erase (i);
+
+ i = tmp;
+ }
+
+
+ /* shift all remaining points left to keep their same
+ relative position
+ */
+
+ for (i = _events.begin(); i != _events.end(); ++i) {
+ (*i)->when -= first_legal_coordinate;
+ }
+
+ /* add a new point for the interpolated new value */
+
+ _events.push_front (new ControlEvent (0, first_legal_value));
+ }
+
+ unlocked_invalidate_insert_iterator ();
+ mark_dirty();
+ }
+
+ maybe_signal_changed ();
+}
+
+double
+ControlList::unlocked_eval (double x) const
+{
+ pair<EventList::iterator,EventList::iterator> range;
+ int32_t npoints;
+ double lpos, upos;
+ double lval, uval;
+ double fraction;
+
+ const_iterator length_check_iter = _events.begin();
+ for (npoints = 0; npoints < 4; ++npoints, ++length_check_iter) {
+ if (length_check_iter == _events.end()) {
+ break;
+ }
+ }
+
+ switch (npoints) {
+ case 0:
+ return _desc.normal;
+
+ case 1:
+ return _events.front()->value;
+
+ case 2:
+ if (x >= _events.back()->when) {
+ return _events.back()->value;
+ } else if (x <= _events.front()->when) {
+ return _events.front()->value;
+ }
+
+ lpos = _events.front()->when;
+ lval = _events.front()->value;
+ upos = _events.back()->when;
+ uval = _events.back()->value;
+
+ fraction = (double) (x - lpos) / (double) (upos - lpos);
+
+ switch (_interpolation) {
+ case Discrete:
+ return lval;
+ case Logarithmic:
+ return interpolate_logarithmic (lval, uval, fraction, _desc.lower, _desc.upper);
+ case Exponential:
+ return interpolate_gain (lval, uval, fraction, _desc.upper);
+ case Curved:
+ /* only used x-fade curves, never direct eval */
+ assert (0);
+ default: // Linear
+ return interpolate_linear (lval, uval, fraction);
+ }
+
+ default:
+ if (x >= _events.back()->when) {
+ return _events.back()->value;
+ } else if (x <= _events.front()->when) {
+ return _events.front()->value;
+ }
+
+ return multipoint_eval (x);
+ }
+
+ abort(); /*NOTREACHED*/ /* stupid gcc */
+ return _desc.normal;
+}
+
+double
+ControlList::multipoint_eval (double x) const
+{
+ double upos, lpos;
+ double uval, lval;
+ double fraction;
+
+ /* "Stepped" lookup (no interpolation) */
+ /* FIXME: no cache. significant? */
+ if (_interpolation == Discrete) {
+ const ControlEvent cp (x, 0);
+ EventList::const_iterator i = lower_bound (_events.begin(), _events.end(), &cp, time_comparator);
+
+ // shouldn't have made it to multipoint_eval
+ assert(i != _events.end());
+
+ if (i == _events.begin() || (*i)->when == x)
+ return (*i)->value;
+ else
+ return (*(--i))->value;
+ }
+
+ /* Only do the range lookup if x is in a different range than last time
+ * this was called (or if the lookup cache has been marked "dirty" (left<0) */
+ if ((_lookup_cache.left < 0) ||
+ ((_lookup_cache.left > x) ||
+ (_lookup_cache.range.first == _events.end()) ||
+ ((*_lookup_cache.range.second)->when < x))) {
+
+ const ControlEvent cp (x, 0);
+
+ _lookup_cache.range = equal_range (_events.begin(), _events.end(), &cp, time_comparator);
+ }
+
+ pair<const_iterator,const_iterator> range = _lookup_cache.range;
+
+ if (range.first == range.second) {
+
+ /* x does not exist within the list as a control point */
+
+ _lookup_cache.left = x;
+
+ if (range.first != _events.begin()) {
+ --range.first;
+ lpos = (*range.first)->when;
+ lval = (*range.first)->value;
+ } else {
+ /* we're before the first point */
+ // return _default_value;
+ return _events.front()->value;
+ }
+
+ if (range.second == _events.end()) {
+ /* we're after the last point */
+ return _events.back()->value;
+ }
+
+ upos = (*range.second)->when;
+ uval = (*range.second)->value;
+
+ fraction = (double) (x - lpos) / (double) (upos - lpos);
+
+ switch (_interpolation) {
+ case Logarithmic:
+ return interpolate_logarithmic (lval, uval, fraction, _desc.lower, _desc.upper);
+ case Exponential:
+ return interpolate_gain (lval, uval, fraction, _desc.upper);
+ case Discrete:
+ /* should not reach here */
+ assert (0);
+ case Curved:
+ /* only used x-fade curves, never direct eval */
+ assert (0);
+ default: // Linear
+ return interpolate_linear (lval, uval, fraction);
+ break;
+ }
+ assert (0);
+ }
+
+ /* x is a control point in the data */
+ _lookup_cache.left = -1;
+ return (*range.first)->value;
+}
+
+void
+ControlList::build_search_cache_if_necessary (double start) const
+{
+ if (_events.empty()) {
+ /* Empty, nothing to cache, move to end. */
+ _search_cache.first = _events.end();
+ _search_cache.left = 0;
+ return;
+ } else if ((_search_cache.left < 0) || (_search_cache.left > start)) {
+ /* Marked dirty (left < 0), or we're too far forward, re-search. */
+
+ const ControlEvent start_point (start, 0);
+
+ _search_cache.first = lower_bound (_events.begin(), _events.end(), &start_point, time_comparator);
+ _search_cache.left = start;
+ }
+
+ /* We now have a search cache that is not too far right, but it may be too
+ far left and need to be advanced. */
+
+ while (_search_cache.first != end() && (*_search_cache.first)->when < start) {
+ ++_search_cache.first;
+ }
+ _search_cache.left = start;
+}
+
+/** Get the earliest event after \a start using the current interpolation style.
+ *
+ * If an event is found, \a x and \a y are set to its coordinates.
+ *
+ * \param inclusive Include events with timestamp exactly equal to \a start
+ * \return true if event is found (and \a x and \a y are valid).
+ */
+bool
+ControlList::rt_safe_earliest_event (double start, double& x, double& y, bool inclusive) const
+{
+ // FIXME: It would be nice if this was unnecessary..
+ Glib::Threads::RWLock::ReaderLock lm(_lock, Glib::Threads::TRY_LOCK);
+ if (!lm.locked()) {
+ return false;
+ }
+
+ return rt_safe_earliest_event_unlocked (start, x, y, inclusive);
+}
+
+
+/** Get the earliest event after \a start using the current interpolation style.
+ *
+ * If an event is found, \a x and \a y are set to its coordinates.
+ *
+ * \param inclusive Include events with timestamp exactly equal to \a start
+ * \return true if event is found (and \a x and \a y are valid).
+ */
+bool
+ControlList::rt_safe_earliest_event_unlocked (double start, double& x, double& y, bool inclusive) const
+{
+ if (_interpolation == Discrete) {
+ return rt_safe_earliest_event_discrete_unlocked(start, x, y, inclusive);
+ } else {
+ return rt_safe_earliest_event_linear_unlocked(start, x, y, inclusive);
+ }
+}
+
+
+/** Get the earliest event after \a start without interpolation.
+ *
+ * If an event is found, \a x and \a y are set to its coordinates.
+ *
+ * \param inclusive Include events with timestamp exactly equal to \a start
+ * \return true if event is found (and \a x and \a y are valid).
+ */
+bool
+ControlList::rt_safe_earliest_event_discrete_unlocked (double start, double& x, double& y, bool inclusive) const
+{
+ build_search_cache_if_necessary (start);
+
+ if (_search_cache.first != _events.end()) {
+ const ControlEvent* const first = *_search_cache.first;
+
+ const bool past_start = (inclusive ? first->when >= start : first->when > start);
+
+ /* Earliest points is in range, return it */
+ if (past_start) {
+
+ x = first->when;
+ y = first->value;
+
+ /* Move left of cache to this point
+ * (Optimize for immediate call this cycle within range) */
+ _search_cache.left = x;
+ ++_search_cache.first;
+
+ assert(x >= start);
+ return true;
+
+ } else {
+ return false;
+ }
+
+ /* No points in range */
+ } else {
+ return false;
+ }
+}
+
+/** Get the earliest time the line crosses an integer (Linear interpolation).
+ *
+ * If an event is found, \a x and \a y are set to its coordinates.
+ *
+ * \param inclusive Include events with timestamp exactly equal to \a start
+ * \return true if event is found (and \a x and \a y are valid).
+ */
+bool
+ControlList::rt_safe_earliest_event_linear_unlocked (double start, double& x, double& y, bool inclusive) const
+{
+ // cout << "earliest_event(start: " << start << ", x: " << x << ", y: " << y << ", inclusive: " << inclusive << ")" << endl;
+
+ const_iterator length_check_iter = _events.begin();
+ if (_events.empty()) { // 0 events
+ return false;
+ } else if (_events.end() == ++length_check_iter) { // 1 event
+ return rt_safe_earliest_event_discrete_unlocked (start, x, y, inclusive);
+ }
+
+ // Hack to avoid infinitely repeating the same event
+ build_search_cache_if_necessary (start);
+
+ if (_search_cache.first != _events.end()) {
+
+ const ControlEvent* first = NULL;
+ const ControlEvent* next = NULL;
+
+ if (_search_cache.first == _events.begin() || (*_search_cache.first)->when <= start) {
+ /* Step is after first */
+ first = *_search_cache.first;
+ ++_search_cache.first;
+ if (_search_cache.first == _events.end()) {
+ return false;
+ }
+ next = *_search_cache.first;
+
+ } else {
+ /* Step is before first */
+ const_iterator prev = _search_cache.first;
+ --prev;
+ first = *prev;
+ next = *_search_cache.first;
+ }
+
+ if (inclusive && first->when == start) {
+ x = first->when;
+ y = first->value;
+ /* Move left of cache to this point
+ * (Optimize for immediate call this cycle within range) */
+ _search_cache.left = x;
+ return true;
+ } else if (next->when < start || (!inclusive && next->when == start)) {
+ /* "Next" is before the start, no points left. */
+ return false;
+ }
+
+ if (fabs(first->value - next->value) <= 1) {
+ if (next->when > start) {
+ x = next->when;
+ y = next->value;
+ /* Move left of cache to this point
+ * (Optimize for immediate call this cycle within range) */
+ _search_cache.left = x;
+ return true;
+ } else {
+ return false;
+ }
+ }
+
+ const double slope = (next->value - first->value) / (double)(next->when - first->when);
+ //cerr << "start y: " << start_y << endl;
+
+ //y = first->value + (slope * fabs(start - first->when));
+ y = first->value;
+
+ if (first->value < next->value) // ramping up
+ y = ceil(y);
+ else // ramping down
+ y = floor(y);
+
+ x = first->when + (y - first->value) / (double)slope;
+
+ while ((inclusive && x < start) || (x <= start && y != next->value)) {
+
+ if (first->value < next->value) // ramping up
+ y += 1.0;
+ else // ramping down
+ y -= 1.0;
+
+ x = first->when + (y - first->value) / (double)slope;
+ }
+
+ /*cerr << first->value << " @ " << first->when << " ... "
+ << next->value << " @ " << next->when
+ << " = " << y << " @ " << x << endl;*/
+
+ assert( (y >= first->value && y <= next->value)
+ || (y <= first->value && y >= next->value) );
+
+
+ const bool past_start = (inclusive ? x >= start : x > start);
+ if (past_start) {
+ /* Move left of cache to this point
+ * (Optimize for immediate call this cycle within range) */
+ _search_cache.left = x;
+ assert(inclusive ? x >= start : x > start);
+ return true;
+ } else {
+ if (inclusive) {
+ x = next->when;
+ } else {
+ x = start;
+ }
+ _search_cache.left = x;
+ return true;
+ }
+
+ } else {
+ /* No points in the future, so no steps (towards them) in the future */
+ return false;
+ }
+}
+
+
+/** @param start Start position in model coordinates.
+ * @param end End position in model coordinates.
+ * @param op 0 = cut, 1 = copy, 2 = clear.
+ */
+boost::shared_ptr<ControlList>
+ControlList::cut_copy_clear (double start, double end, int op)
+{
+ boost::shared_ptr<ControlList> nal = create (_parameter, _desc);
+ iterator s, e;
+ ControlEvent cp (start, 0.0);
+
+ {
+ Glib::Threads::RWLock::WriterLock lm (_lock);
+
+ /* first, determine s & e, two iterators that define the range of points
+ affected by this operation
+ */
+
+ if ((s = lower_bound (_events.begin(), _events.end(), &cp, time_comparator)) == _events.end()) {
+ return nal;
+ }
+
+ /* and the last that is at or after `end' */
+ cp.when = end;
+ e = upper_bound (_events.begin(), _events.end(), &cp, time_comparator);
+
+
+ /* if "start" isn't the location of an existing point,
+ evaluate the curve to get a value for the start. Add a point to
+ both the existing event list, and if its not a "clear" operation,
+ to the copy ("nal") as well.
+
+ Note that the time positions of the points in each list are different
+ because we want the copy ("nal") to have a zero time reference.
+ */
+
+
+ /* before we begin any cut/clear operations, get the value of the curve
+ at "end".
+ */
+
+ double end_value = unlocked_eval (end);
+
+ if ((*s)->when != start) {
+
+ double val = unlocked_eval (start);
+
+ if (op == 0) { // cut
+ if (start > _events.front()->when) {
+ _events.insert (s, (new ControlEvent (start, val)));
+ }
+ }
+
+ if (op != 2) { // ! clear
+ nal->_events.push_back (new ControlEvent (0, val));
+ }
+ }
+
+ for (iterator x = s; x != e; ) {
+
+ /* adjust new points to be relative to start, which
+ has been set to zero.
+ */
+
+ if (op != 2) {
+ nal->_events.push_back (new ControlEvent ((*x)->when - start, (*x)->value));
+ }
+
+ if (op != 1) {
+ x = _events.erase (x);
+ } else {
+ ++x;
+ }
+ }
+
+ if (e == _events.end() || (*e)->when != end) {
+
+ /* only add a boundary point if there is a point after "end"
+ */
+
+ if (op == 0 && (e != _events.end() && end < (*e)->when)) { // cut
+ _events.insert (e, new ControlEvent (end, end_value));
+ }
+
+ if (op != 2 && (e != _events.end() && end < (*e)->when)) { // cut/copy
+ nal->_events.push_back (new ControlEvent (end - start, end_value));
+ }
+ }
+
+ unlocked_invalidate_insert_iterator ();
+ mark_dirty ();
+ }
+
+ if (op != 1) {
+ maybe_signal_changed ();
+ }
+
+ return nal;
+}
+
+
+boost::shared_ptr<ControlList>
+ControlList::cut (double start, double end)
+{
+ return cut_copy_clear (start, end, 0);
+}
+
+boost::shared_ptr<ControlList>
+ControlList::copy (double start, double end)
+{
+ return cut_copy_clear (start, end, 1);
+}
+
+void
+ControlList::clear (double start, double end)
+{
+ cut_copy_clear (start, end, 2);
+}
+
+/** @param pos Position in model coordinates */
+bool
+ControlList::paste (const ControlList& alist, double pos)
+{
+ if (alist._events.empty()) {
+ return false;
+ }
+
+ {
+ Glib::Threads::RWLock::WriterLock lm (_lock);
+ iterator where;
+ iterator prev;
+ double end = 0;
+ ControlEvent cp (pos, 0.0);
+
+ where = upper_bound (_events.begin(), _events.end(), &cp, time_comparator);
+
+ for (const_iterator i = alist.begin();i != alist.end(); ++i) {
+ double value = (*i)->value;
+ if (alist.parameter() != parameter()) {
+ const ParameterDescriptor& src_desc = alist.descriptor();
+
+ // This does not work for logscale and will probably also not do
+ // the right thing for integer_step and sr_dependent parameters.
+ //
+ // TODO various flags from from ARDOUR::ParameterDescriptor
+ // to Evoral::ParameterDescriptor
+
+ value -= src_desc.lower; // translate to 0-relative
+ value /= (src_desc.upper - src_desc.lower); // normalize range
+ value *= (_desc.upper - _desc.lower); // scale to our range
+ value += _desc.lower; // translate to our offset
+ if (_desc.toggled) {
+ value = (value < 0.5) ? 0.0 : 1.0;
+ }
+ /* catch possible rounding errors */
+ value = std::min ((double)_desc.upper, std::max ((double)_desc.lower, value));
+ }
+ _events.insert (where, new ControlEvent((*i)->when + pos, value));
+ end = (*i)->when + pos;
+ }
+
+
+ /* move all points after the insertion along the timeline by
+ the correct amount.
+ */
+
+ while (where != _events.end()) {
+ iterator tmp;
+ if ((*where)->when <= end) {
+ tmp = where;
+ ++tmp;
+ _events.erase(where);
+ where = tmp;
+
+ } else {
+ break;
+ }
+ }
+
+ unlocked_invalidate_insert_iterator ();
+ mark_dirty ();
+ }
+
+ maybe_signal_changed ();
+ return true;
+}
+
+/** Move automation around according to a list of region movements.
+ * @param return true if anything was changed, otherwise false (ie nothing needed changing)
+ */
+bool
+ControlList::move_ranges (const list< RangeMove<double> >& movements)
+{
+ typedef list< RangeMove<double> > RangeMoveList;
+
+ {
+ Glib::Threads::RWLock::WriterLock lm (_lock);
+
+ /* a copy of the events list before we started moving stuff around */
+ EventList old_events = _events;
+
+ /* clear the source and destination ranges in the new list */
+ bool things_erased = false;
+ for (RangeMoveList::const_iterator i = movements.begin (); i != movements.end (); ++i) {
+
+ if (erase_range_internal (i->from, i->from + i->length, _events)) {
+ things_erased = true;
+ }
+
+ if (erase_range_internal (i->to, i->to + i->length, _events)) {
+ things_erased = true;
+ }
+ }
+
+ /* if nothing was erased, there is nothing to do */
+ if (!things_erased) {
+ return false;
+ }
+
+ /* copy the events into the new list */
+ for (RangeMoveList::const_iterator i = movements.begin (); i != movements.end (); ++i) {
+ iterator j = old_events.begin ();
+ const double limit = i->from + i->length;
+ const double dx = i->to - i->from;
+ while (j != old_events.end () && (*j)->when <= limit) {
+ if ((*j)->when >= i->from) {
+ ControlEvent* ev = new ControlEvent (**j);
+ ev->when += dx;
+ _events.push_back (ev);
+ }
+ ++j;
+ }
+ }
+
+ if (!_frozen) {
+ _events.sort (event_time_less_than);
+ unlocked_remove_duplicates ();
+ unlocked_invalidate_insert_iterator ();
+ } else {
+ _sort_pending = true;
+ }
+
+ mark_dirty ();
+ }
+
+ maybe_signal_changed ();
+ return true;
+}
+
+bool
+ControlList::set_interpolation (InterpolationStyle s)
+{
+ if (_interpolation == s) {
+ return true;
+ }
+
+ switch (s) {
+ case Logarithmic:
+ if (_desc.lower * _desc.upper <= 0 || _desc.upper <= _desc.lower) {
+ return false;
+ }
+ break;
+ case Exponential:
+ if (_desc.lower != 0 || _desc.upper <= _desc.lower) {
+ return false;
+ }
+ default:
+ break;
+ }
+
+ _interpolation = s;
+ InterpolationChanged (s); /* EMIT SIGNAL */
+ return true;
+}
+
+bool
+ControlList::operator!= (ControlList const & other) const
+{
+ if (_events.size() != other._events.size()) {
+ return true;
+ }
+
+ EventList::const_iterator i = _events.begin ();
+ EventList::const_iterator j = other._events.begin ();
+
+ while (i != _events.end() && (*i)->when == (*j)->when && (*i)->value == (*j)->value) {
+ ++i;
+ ++j;
+ }
+
+ if (i != _events.end ()) {
+ return true;
+ }
+
+ return (
+ _parameter != other._parameter ||
+ _interpolation != other._interpolation ||
+ _desc.lower != other._desc.lower ||
+ _desc.upper != other._desc.upper ||
+ _desc.normal != other._desc.normal
+ );
+}
+
+bool
+ControlList::is_sorted () const
+{
+ Glib::Threads::RWLock::ReaderLock lm (_lock);
+ if (_events.size () == 0) {
+ return true;
+ }
+ const_iterator i = _events.begin();
+ const_iterator n = i;
+ while (++n != _events.end ()) {
+ if (event_time_less_than(*n,*i)) {
+ return false;
+ }
+ ++i;
+ }
+ return true;
+}
+
+void
+ControlList::dump (ostream& o)
+{
+ /* NOT LOCKED ... for debugging only */
+
+ for (EventList::iterator x = _events.begin(); x != _events.end(); ++x) {
+ o << (*x)->value << " @ " << (uint64_t) (*x)->when << endl;
+ }
+}
+
+} // namespace Evoral
+
+++ /dev/null
-/*
- * Copyright (C) 2008-2009 Hans Baier <hansfbaier@googlemail.com>
- * Copyright (C) 2008-2012 Carl Hetherington <carl@carlh.net>
- * Copyright (C) 2008-2015 David Robillard <d@drobilla.net>
- * Copyright (C) 2010-2017 Paul Davis <paul@linuxaudiosystems.com>
- * Copyright (C) 2014-2018 Robin Gareus <robin@gareus.org>
- * Copyright (C) 2014 Ben Loftis <ben@harrisonconsoles.com>
- * Copyright (C) 2015 Nick Mainsbridge <mainsbridge@gmail.com>
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-
-#include <cmath>
-
-#ifdef COMPILER_MSVC
-#include <float.h>
-
-// 'std::isnan()' is not available in MSVC.
-#define isnan_local(val) (bool)_isnan((double)val)
-#else
-#define isnan_local std::isnan
-#endif
-
-#define GUARD_POINT_DELTA 64
-
-#include <cassert>
-#include <cmath>
-#include <iostream>
-#include <utility>
-
-#include "evoral/ControlList.hpp"
-#include "evoral/Curve.hpp"
-#include "evoral/ParameterDescriptor.hpp"
-#include "evoral/TypeMap.hpp"
-#include "evoral/types.hpp"
-
-#include "pbd/control_math.h"
-#include "pbd/compose.h"
-#include "pbd/debug.h"
-
-using namespace std;
-using namespace PBD;
-
-namespace Evoral {
-
-inline bool event_time_less_than (ControlEvent* a, ControlEvent* b)
-{
- return a->when < b->when;
-}
-
-ControlList::ControlList (const Parameter& id, const ParameterDescriptor& desc)
- : _parameter(id)
- , _desc(desc)
- , _interpolation (default_interpolation ())
- , _curve(0)
-{
- _frozen = 0;
- _changed_when_thawed = false;
- _lookup_cache.left = -1;
- _lookup_cache.range.first = _events.end();
- _lookup_cache.range.second = _events.end();
- _search_cache.left = -1;
- _search_cache.first = _events.end();
- _sort_pending = false;
- new_write_pass = true;
- _in_write_pass = false;
- did_write_during_pass = false;
- insert_position = -1;
- most_recent_insert_iterator = _events.end();
-}
-
-ControlList::ControlList (const ControlList& other)
- : _parameter(other._parameter)
- , _desc(other._desc)
- , _interpolation(other._interpolation)
- , _curve(0)
-{
- _frozen = 0;
- _changed_when_thawed = false;
- _lookup_cache.range.first = _events.end();
- _lookup_cache.range.second = _events.end();
- _search_cache.first = _events.end();
- _sort_pending = false;
- new_write_pass = true;
- _in_write_pass = false;
- did_write_during_pass = false;
- insert_position = -1;
- most_recent_insert_iterator = _events.end();
-
- // XXX copy_events() emits Dirty, but this is just assignment copy/construction
- copy_events (other);
-}
-
-ControlList::ControlList (const ControlList& other, double start, double end)
- : _parameter(other._parameter)
- , _desc(other._desc)
- , _interpolation(other._interpolation)
- , _curve(0)
-{
- _frozen = 0;
- _changed_when_thawed = false;
- _lookup_cache.range.first = _events.end();
- _lookup_cache.range.second = _events.end();
- _search_cache.first = _events.end();
- _sort_pending = false;
-
- /* now grab the relevant points, and shift them back if necessary */
-
- boost::shared_ptr<ControlList> section = const_cast<ControlList*>(&other)->copy (start, end);
-
- if (!section->empty()) {
- // XXX copy_events() emits Dirty, but this is just assignment copy/construction
- copy_events (*(section.get()));
- }
-
- new_write_pass = true;
- _in_write_pass = false;
- did_write_during_pass = false;
- insert_position = -1;
- most_recent_insert_iterator = _events.end();
-
- mark_dirty ();
-}
-
-ControlList::~ControlList()
-{
- for (EventList::iterator x = _events.begin(); x != _events.end(); ++x) {
- delete (*x);
- }
- _events.clear ();
-
- delete _curve;
-}
-
-boost::shared_ptr<ControlList>
-ControlList::create(const Parameter& id, const ParameterDescriptor& desc)
-{
- return boost::shared_ptr<ControlList>(new ControlList(id, desc));
-}
-
-bool
-ControlList::operator== (const ControlList& other)
-{
- return _events == other._events;
-}
-
-ControlList&
-ControlList::operator= (const ControlList& other)
-{
- if (this != &other) {
- /* list should be frozen before assignment */
- assert (_frozen > 0);
- _changed_when_thawed = false;
- _sort_pending = false;
-
- insert_position = other.insert_position;
- new_write_pass = true;
- _in_write_pass = false;
- did_write_during_pass = false;
- insert_position = -1;
-
- _parameter = other._parameter;
- _desc = other._desc;
- _interpolation = other._interpolation;
-
- copy_events (other);
- }
-
- return *this;
-}
-
-void
-ControlList::copy_events (const ControlList& other)
-{
- {
- Glib::Threads::RWLock::WriterLock lm (_lock);
- for (EventList::iterator x = _events.begin(); x != _events.end(); ++x) {
- delete (*x);
- }
- _events.clear ();
- Glib::Threads::RWLock::ReaderLock olm (other._lock);
- for (const_iterator i = other.begin(); i != other.end(); ++i) {
- _events.push_back (new ControlEvent ((*i)->when, (*i)->value));
- }
- unlocked_invalidate_insert_iterator ();
- mark_dirty ();
- }
- maybe_signal_changed ();
-}
-
-void
-ControlList::create_curve()
-{
- _curve = new Curve(*this);
-}
-
-void
-ControlList::destroy_curve()
-{
- delete _curve;
- _curve = NULL;
-}
-
-ControlList::InterpolationStyle
-ControlList::default_interpolation () const
-{
- if (_desc.toggled) {
- return Discrete;
- } else if (_desc.logarithmic) {
- return Logarithmic;
- }
- return Linear;
-}
-
-void
-ControlList::maybe_signal_changed ()
-{
- mark_dirty ();
-
- if (_frozen) {
- _changed_when_thawed = true;
- }
-}
-
-void
-ControlList::clear ()
-{
- {
- Glib::Threads::RWLock::WriterLock lm (_lock);
- for (EventList::iterator x = _events.begin(); x != _events.end(); ++x) {
- delete (*x);
- }
- _events.clear ();
- unlocked_invalidate_insert_iterator ();
- mark_dirty ();
- }
-
- maybe_signal_changed ();
-}
-
-void
-ControlList::x_scale (double factor)
-{
- Glib::Threads::RWLock::WriterLock lm (_lock);
- _x_scale (factor);
-}
-
-bool
-ControlList::extend_to (double when)
-{
- Glib::Threads::RWLock::WriterLock lm (_lock);
- if (_events.empty() || _events.back()->when == when) {
- return false;
- }
- double factor = when / _events.back()->when;
- _x_scale (factor);
- return true;
-}
-
-void
-ControlList::y_transform (boost::function<double(double)> callback)
-{
- {
- Glib::Threads::RWLock::WriterLock lm (_lock);
- for (iterator i = _events.begin(); i != _events.end(); ++i) {
- (*i)->value = callback ((*i)->value);
- }
- mark_dirty ();
- }
- maybe_signal_changed ();
-}
-
-void
-ControlList::list_merge (ControlList const& other, boost::function<double(double, double)> callback)
-{
- {
- Glib::Threads::RWLock::WriterLock lm (_lock);
- EventList nel;
- /* First scale existing events, copy into a new list.
- * The original list is needed later to interpolate
- * for new events only present in the master list.
- */
- for (iterator i = _events.begin(); i != _events.end(); ++i) {
- float val = callback ((*i)->value, other.eval ((*i)->when));
- nel.push_back (new ControlEvent ((*i)->when , val));
- }
- /* Now add events which are only present in the master-list. */
- const EventList& evl (other.events());
- for (const_iterator i = evl.begin(); i != evl.end(); ++i) {
- bool found = false;
- // TODO: optimize, remember last matching iterator (lists are sorted)
- for (iterator j = _events.begin(); j != _events.end(); ++j) {
- if ((*i)->when == (*j)->when) {
- found = true;
- break;
- }
- }
- /* skip events that have already been merge in the first pass */
- if (found) {
- continue;
- }
- float val = callback (unlocked_eval ((*i)->when), (*i)->value);
- nel.push_back (new ControlEvent ((*i)->when, val));
- }
- nel.sort (event_time_less_than);
-
- for (EventList::iterator x = _events.begin(); x != _events.end(); ++x) {
- delete (*x);
- }
- _events.clear ();
- _events = nel;
-
- unlocked_remove_duplicates ();
- unlocked_invalidate_insert_iterator ();
- mark_dirty ();
- }
- maybe_signal_changed ();
-}
-
-void
-ControlList::_x_scale (double factor)
-{
- for (iterator i = _events.begin(); i != _events.end(); ++i) {
- (*i)->when *= factor;
- }
-
- mark_dirty ();
-}
-
-struct ControlEventTimeComparator {
- bool operator() (ControlEvent* a, ControlEvent* b) {
- return a->when < b->when;
- }
-};
-
-void
-ControlList::thin (double thinning_factor)
-{
- if (thinning_factor == 0.0 || _desc.toggled) {
- return;
- }
-
- assert (is_sorted ());
-
- bool changed = false;
-
- {
- Glib::Threads::RWLock::WriterLock lm (_lock);
-
- ControlEvent* prevprev = 0;
- ControlEvent* cur = 0;
- ControlEvent* prev = 0;
- iterator pprev;
- int counter = 0;
-
- DEBUG_TRACE (DEBUG::ControlList, string_compose ("@%1 thin from %2 events\n", this, _events.size()));
-
- for (iterator i = _events.begin(); i != _events.end(); ++i) {
-
- cur = *i;
- counter++;
-
- if (counter > 2) {
-
- /* compute the area of the triangle formed by 3 points
- */
-
- double area = fabs ((prevprev->when * (prev->value - cur->value)) +
- (prev->when * (cur->value - prevprev->value)) +
- (cur->when * (prevprev->value - prev->value)));
-
- if (area < thinning_factor) {
- iterator tmp = pprev;
-
- /* pprev will change to current
- i is incremented to the next event
- as we loop.
- */
-
- pprev = i;
- _events.erase (tmp);
- changed = true;
- continue;
- }
- }
-
- prevprev = prev;
- prev = cur;
- pprev = i;
- }
-
- DEBUG_TRACE (DEBUG::ControlList, string_compose ("@%1 thin => %2 events\n", this, _events.size()));
-
- if (changed) {
- unlocked_invalidate_insert_iterator ();
- mark_dirty ();
- }
- }
-
- if (changed) {
- maybe_signal_changed ();
- }
-}
-
-void
-ControlList::fast_simple_add (double when, double value)
-{
- Glib::Threads::RWLock::WriterLock lm (_lock);
- /* to be used only for loading pre-sorted data from saved state */
- _events.insert (_events.end(), new ControlEvent (when, value));
-
- mark_dirty ();
- if (_frozen) {
- _sort_pending = true;
- }
-}
-
-void
-ControlList::invalidate_insert_iterator ()
-{
- Glib::Threads::RWLock::WriterLock lm (_lock);
- unlocked_invalidate_insert_iterator ();
-}
-
-void
-ControlList::unlocked_invalidate_insert_iterator ()
-{
- most_recent_insert_iterator = _events.end();
-}
-
-void
-ControlList::unlocked_remove_duplicates ()
-{
- if (_events.size() < 2) {
- return;
- }
- iterator i = _events.begin();
- iterator prev = i++;
- while (i != _events.end()) {
- if ((*prev)->when == (*i)->when && (*prev)->value == (*i)->value) {
- i = _events.erase (i);
- } else {
- ++prev;
- ++i;
- }
- }
-}
-
-void
-ControlList::start_write_pass (double when)
-{
- Glib::Threads::RWLock::WriterLock lm (_lock);
-
- DEBUG_TRACE (DEBUG::ControlList, string_compose ("%1: setup write pass @ %2\n", this, when));
-
- insert_position = when;
-
- /* leave the insert iterator invalid, so that we will do the lookup
- of where it should be in a "lazy" way - deferring it until
- we actually add the first point (which may never happen).
- */
-
- unlocked_invalidate_insert_iterator ();
-
- /* except if we're already in an active write-pass.
- *
- * invalid iterator == end() the iterator is set to the correct
- * position in ControlList::add IFF (_in_write_pass && new_write_pass)
- */
- if (_in_write_pass && !new_write_pass) {
-#if 1
- add_guard_point (when, 0); // also sets most_recent_insert_iterator
-#else
- const ControlEvent cp (when, 0.0);
- most_recent_insert_iterator = lower_bound (_events.begin(), _events.end(), &cp, time_comparator);
-#endif
- }
-}
-
-void
-ControlList::write_pass_finished (double /*when*/, double thinning_factor)
-{
- DEBUG_TRACE (DEBUG::ControlList, "write pass finished\n");
-
- if (did_write_during_pass) {
- thin (thinning_factor);
- did_write_during_pass = false;
- }
- new_write_pass = true;
- _in_write_pass = false;
-}
-
-void
-ControlList::set_in_write_pass (bool yn, bool add_point, double when)
-{
- DEBUG_TRACE (DEBUG::ControlList, string_compose ("now in write pass @ %1, add point ? %2\n", when, add_point));
-
- _in_write_pass = yn;
-
- if (yn && add_point) {
- Glib::Threads::RWLock::WriterLock lm (_lock);
- add_guard_point (when, 0);
- }
-}
-
-void
-ControlList::add_guard_point (double when, double offset)
-{
- // caller needs to hold writer-lock
- if (offset < 0 && when < offset) {
- return;
- }
- assert (offset <= 0);
-
- if (offset != 0) {
- /* check if there are points between when + offset .. when */
- ControlEvent cp (when + offset, 0.0);
- iterator s;
- iterator e;
- if ((s = lower_bound (_events.begin(), _events.end(), &cp, time_comparator)) != _events.end()) {
- cp.when = when;
- e = lower_bound (_events.begin(), _events.end(), &cp, time_comparator);
- if (s != e) {
- DEBUG_TRACE (DEBUG::ControlList, string_compose ("@%1 add_guard_point, none added, found event between %2 and %3\n", this, when - offset, when));
- return;
- }
- }
- }
-
- /* don't do this again till the next write pass,
- * unless we're not in a write-pass (transport stopped)
- */
- if (_in_write_pass && new_write_pass) {
- WritePassStarted (); /* EMIT SIGNAL w/WriteLock */
- new_write_pass = false;
- }
-
- when += offset;
-
- ControlEvent cp (when, 0.0);
- most_recent_insert_iterator = lower_bound (_events.begin(), _events.end(), &cp, time_comparator);
-
- double eval_value = unlocked_eval (when);
-
- if (most_recent_insert_iterator == _events.end()) {
-
- DEBUG_TRACE (DEBUG::ControlList, string_compose ("@%1 insert iterator at end, adding eval-value there %2\n", this, eval_value));
- _events.push_back (new ControlEvent (when, eval_value));
- /* leave insert iterator at the end */
-
- } else if ((*most_recent_insert_iterator)->when == when) {
-
- DEBUG_TRACE (DEBUG::ControlList, string_compose ("@%1 insert iterator at existing point, setting eval-value there %2\n", this, eval_value));
-
- /* most_recent_insert_iterator points to a control event
- already at the insert position, so there is
- nothing to do.
-
- ... except ...
-
- advance most_recent_insert_iterator so that the "real"
- insert occurs in the right place, since it
- points to the control event just inserted.
- */
-
- ++most_recent_insert_iterator;
- } else {
-
- /* insert a new control event at the right spot */
-
- DEBUG_TRACE (DEBUG::ControlList, string_compose ("@%1 insert eval-value %2 just before iterator @ %3\n",
- this, eval_value, (*most_recent_insert_iterator)->when));
-
- most_recent_insert_iterator = _events.insert (most_recent_insert_iterator, new ControlEvent (when, eval_value));
-
- /* advance most_recent_insert_iterator so that the "real"
- * insert occurs in the right place, since it
- * points to the control event just inserted.
- */
-
- ++most_recent_insert_iterator;
- }
-}
-
-bool
-ControlList::in_write_pass () const
-{
- return _in_write_pass;
-}
-
-bool
-ControlList::editor_add (double when, double value, bool with_guard)
-{
- /* this is for making changes from a graphical line editor */
- {
- Glib::Threads::RWLock::WriterLock lm (_lock);
-
- ControlEvent cp (when, 0.0f);
- iterator i = lower_bound (_events.begin(), _events.end(), &cp, time_comparator);
-
- if (i != _events.end () && (*i)->when == when) {
- return false;
- }
-
- /* clamp new value to allowed range */
- value = std::min ((double)_desc.upper, std::max ((double)_desc.lower, value));
-
- if (_events.empty()) {
-
- /* as long as the point we're adding is not at zero,
- * add an "anchor" point there.
- */
-
- if (when >= 1) {
- _events.insert (_events.end(), new ControlEvent (0, value));
- DEBUG_TRACE (DEBUG::ControlList, string_compose ("@%1 added value %2 at zero\n", this, value));
- }
- }
-
- insert_position = when;
- if (with_guard) {
- add_guard_point (when, -GUARD_POINT_DELTA);
- maybe_add_insert_guard (when);
- i = lower_bound (_events.begin(), _events.end(), &cp, time_comparator);
- }
-
- iterator result;
- DEBUG_TRACE (DEBUG::ControlList, string_compose ("editor_add: actually add when= %1 value= %2\n", when, value));
- result = _events.insert (i, new ControlEvent (when, value));
-
- if (i == result) {
- return false;
- }
-
- mark_dirty ();
- }
- maybe_signal_changed ();
-
- return true;
-}
-
-void
-ControlList::maybe_add_insert_guard (double when)
-{
- // caller needs to hold writer-lock
- if (most_recent_insert_iterator != _events.end()) {
- if ((*most_recent_insert_iterator)->when - when > GUARD_POINT_DELTA) {
- /* Next control point is some distance from where our new point is
- going to go, so add a new point to avoid changing the shape of
- the line too much. The insert iterator needs to point to the
- new control point so that our insert will happen correctly. */
- most_recent_insert_iterator = _events.insert ( most_recent_insert_iterator,
- new ControlEvent (when + GUARD_POINT_DELTA, (*most_recent_insert_iterator)->value));
-
- DEBUG_TRACE (DEBUG::ControlList, string_compose ("@%1 added insert guard point @ %2 = %3\n",
- this, when + GUARD_POINT_DELTA,
- (*most_recent_insert_iterator)->value));
- }
- }
-}
-
-/** If we would just be adding to a straight line, move the previous point instead. */
-bool
-ControlList::maybe_insert_straight_line (double when, double value)
-{
- // caller needs to hold writer-lock
- if (_events.empty()) {
- return false;
- }
-
- if (_events.back()->value == value) {
- // Point b at the final point, which we know exists
- EventList::iterator b = _events.end();
- --b;
- if (b == _events.begin()) {
- return false; // No previous point
- }
-
- // Check the previous point's value
- --b;
- if ((*b)->value == value) {
- /* At least two points with the exact same value (straight
- line), just move the final point to the new time. */
- _events.back()->when = when;
- DEBUG_TRACE (DEBUG::ControlList, string_compose ("final value of %1 moved to %2\n", value, when));
- return true;
- }
- }
- return false;
-}
-
-ControlList::iterator
-ControlList::erase_from_iterator_to (iterator iter, double when)
-{
- // caller needs to hold writer-lock
- while (iter != _events.end()) {
- if ((*iter)->when < when) {
- DEBUG_TRACE (DEBUG::ControlList, string_compose ("@%1 erase existing @ %2\n", this, (*iter)->when));
- delete *iter;
- iter = _events.erase (iter);
- continue;
- } else if ((*iter)->when >= when) {
- break;
- }
- ++iter;
- }
- return iter;
-}
-
-/* this is for making changes from some kind of user interface or
- * control surface (GUI, MIDI, OSC etc)
- */
-void
-ControlList::add (double when, double value, bool with_guards, bool with_initial)
-{
- /* clamp new value to allowed range */
- value = std::min ((double)_desc.upper, std::max ((double)_desc.lower, value));
-
- DEBUG_TRACE (DEBUG::ControlList,
- string_compose ("@%1 add %2 at %3 guards = %4 write pass = %5 (new? %6) at end? %7\n",
- this, value, when, with_guards, _in_write_pass, new_write_pass,
- (most_recent_insert_iterator == _events.end())));
- {
- Glib::Threads::RWLock::WriterLock lm (_lock);
- ControlEvent cp (when, 0.0f);
- iterator insertion_point;
-
- if (_events.empty() && with_initial) {
-
- /* empty: add an "anchor" point if the point we're adding past time 0 */
-
- if (when >= 1) {
- if (_desc.toggled) {
- const double opp_val = ((value < 0.5) ? 1.0 : 0.0);
- _events.insert (_events.end(), new ControlEvent (0, opp_val));
- DEBUG_TRACE (DEBUG::ControlList, string_compose ("@%1 added toggled value %2 at zero\n", this, opp_val));
-
- } else {
- _events.insert (_events.end(), new ControlEvent (0, value));
- DEBUG_TRACE (DEBUG::ControlList, string_compose ("@%1 added default value %2 at zero\n", this, _desc.normal));
- }
- }
- }
-
- if (_in_write_pass && new_write_pass) {
-
- /* first write in a write pass: add guard point if requested */
-
- if (with_guards) {
- add_guard_point (insert_position, 0);
- did_write_during_pass = true;
- } else {
- /* not adding a guard, but we need to set iterator appropriately */
- const ControlEvent cp (when, 0.0);
- most_recent_insert_iterator = lower_bound (_events.begin(), _events.end(), &cp, time_comparator);
- }
- WritePassStarted (); /* EMIT SIGNAL w/WriteLock */
- new_write_pass = false;
-
- } else if (_in_write_pass &&
- (most_recent_insert_iterator == _events.end() || when > (*most_recent_insert_iterator)->when)) {
-
- /* in write pass: erase from most recent insert to now */
-
- if (most_recent_insert_iterator != _events.end()) {
- /* advance to avoid deleting the last inserted point itself. */
- ++most_recent_insert_iterator;
- }
-
- if (with_guards) {
- most_recent_insert_iterator = erase_from_iterator_to (most_recent_insert_iterator, when + GUARD_POINT_DELTA);
- maybe_add_insert_guard (when);
- } else {
- most_recent_insert_iterator = erase_from_iterator_to(most_recent_insert_iterator, when);
- }
-
- } else if (!_in_write_pass) {
-
- /* not in a write pass: figure out the iterator we should insert in front of */
-
- DEBUG_TRACE (DEBUG::ControlList, string_compose ("compute(b) MRI for position %1\n", when));
- ControlEvent cp (when, 0.0f);
- most_recent_insert_iterator = lower_bound (_events.begin(), _events.end(), &cp, time_comparator);
- }
-
- /* OK, now we're really ready to add a new point */
-
- if (most_recent_insert_iterator == _events.end()) {
- DEBUG_TRACE (DEBUG::ControlList, string_compose ("@%1 appending new point at end\n", this));
-
- const bool done = maybe_insert_straight_line (when, value);
- if (!done) {
- _events.push_back (new ControlEvent (when, value));
- DEBUG_TRACE (DEBUG::ControlList, string_compose ("\tactually appended, size now %1\n", _events.size()));
- }
-
- most_recent_insert_iterator = _events.end();
- --most_recent_insert_iterator;
-
- } else if ((*most_recent_insert_iterator)->when == when) {
-
- if ((*most_recent_insert_iterator)->value != value) {
- DEBUG_TRACE (DEBUG::ControlList, string_compose ("@%1 reset existing point to new value %2\n", this, value));
-
- /* only one point allowed per time point, so add a guard point
- * before it if needed then reset the value of the point.
- */
-
- (*most_recent_insert_iterator)->value = value;
-
- /* if we modified the final value, then its as
- * if we inserted a new point as far as the
- * next addition, so make sure we know that.
- */
-
- if (_events.back()->when == when) {
- most_recent_insert_iterator = _events.end();
- }
-
- } else {
- DEBUG_TRACE (DEBUG::ControlList, string_compose ("@%1 same time %2, same value value %3\n", this, when, value));
- }
-
- } else {
- DEBUG_TRACE (DEBUG::ControlList, string_compose ("@%1 insert new point at %2 at iterator at %3\n", this, when, (*most_recent_insert_iterator)->when));
- bool done = false;
- /* check for possible straight line here until maybe_insert_straight_line () handles the insert iterator properly*/
- if (most_recent_insert_iterator != _events.begin ()) {
- bool have_point2 = false;
- --most_recent_insert_iterator;
- const bool have_point1 = (*most_recent_insert_iterator)->value == value;
-
- if (most_recent_insert_iterator != _events.begin ()) {
- --most_recent_insert_iterator;
- have_point2 = (*most_recent_insert_iterator)->value == value;
- ++most_recent_insert_iterator;
- }
-
- if (have_point1 && have_point2) {
- DEBUG_TRACE (DEBUG::ControlList, string_compose ("@%1 no change: move existing at %3 to %2\n", this, when, (*most_recent_insert_iterator)->when));
- (*most_recent_insert_iterator)->when = when;
- done = true;
- } else {
- ++most_recent_insert_iterator;
- }
- }
-
- /* if the transport is stopped, add guard points */
- if (!done && !_in_write_pass) {
- add_guard_point (when, -GUARD_POINT_DELTA);
- maybe_add_insert_guard (when);
- } else if (with_guards) {
- maybe_add_insert_guard (when);
- }
-
- if (!done) {
- EventList::iterator x = _events.insert (most_recent_insert_iterator, new ControlEvent (when, value));
- DEBUG_TRACE (DEBUG::ControlList, string_compose ("@%1 inserted new value before MRI, size now %2\n", this, _events.size()));
- most_recent_insert_iterator = x;
- }
- }
-
- mark_dirty ();
- }
-
- maybe_signal_changed ();
-}
-
-void
-ControlList::erase (iterator i)
-{
- {
- Glib::Threads::RWLock::WriterLock lm (_lock);
- if (most_recent_insert_iterator == i) {
- unlocked_invalidate_insert_iterator ();
- }
- _events.erase (i);
- mark_dirty ();
- }
- maybe_signal_changed ();
-}
-
-void
-ControlList::erase (iterator start, iterator end)
-{
- {
- Glib::Threads::RWLock::WriterLock lm (_lock);
- _events.erase (start, end);
- unlocked_invalidate_insert_iterator ();
- mark_dirty ();
- }
- maybe_signal_changed ();
-}
-
-/** Erase the first event which matches the given time and value */
-void
-ControlList::erase (double when, double value)
-{
- {
- Glib::Threads::RWLock::WriterLock lm (_lock);
-
- iterator i = begin ();
- while (i != end() && ((*i)->when != when || (*i)->value != value)) {
- ++i;
- }
-
- if (i != end ()) {
- _events.erase (i);
- if (most_recent_insert_iterator == i) {
- unlocked_invalidate_insert_iterator ();
- }
- }
-
- mark_dirty ();
- }
-
- maybe_signal_changed ();
-}
-
-void
-ControlList::erase_range (double start, double endt)
-{
- bool erased = false;
-
- {
- Glib::Threads::RWLock::WriterLock lm (_lock);
- erased = erase_range_internal (start, endt, _events);
-
- if (erased) {
- mark_dirty ();
- }
-
- }
-
- if (erased) {
- maybe_signal_changed ();
- }
-}
-
-bool
-ControlList::erase_range_internal (double start, double endt, EventList & events)
-{
- bool erased = false;
- ControlEvent cp (start, 0.0f);
- iterator s;
- iterator e;
-
- if ((s = lower_bound (events.begin(), events.end(), &cp, time_comparator)) != events.end()) {
- cp.when = endt;
- e = upper_bound (events.begin(), events.end(), &cp, time_comparator);
- events.erase (s, e);
- if (s != e) {
- unlocked_invalidate_insert_iterator ();
- erased = true;
- }
- }
-
- return erased;
-}
-
-void
-ControlList::slide (iterator before, double distance)
-{
- {
- Glib::Threads::RWLock::WriterLock lm (_lock);
-
- if (before == _events.end()) {
- return;
- }
-
- while (before != _events.end()) {
- (*before)->when += distance;
- ++before;
- }
-
- mark_dirty ();
- }
-
- maybe_signal_changed ();
-}
-
-void
-ControlList::shift (double pos, double frames)
-{
- {
- Glib::Threads::RWLock::WriterLock lm (_lock);
- double v0, v1;
- if (frames < 0) {
- /* Route::shift () with negative shift is used
- * for "remove time". The time [pos.. pos-frames] is removed.
- * and everyhing after, moved backwards.
- */
- v0 = unlocked_eval (pos);
- v1 = unlocked_eval (pos - frames);
- erase_range_internal (pos, pos - frames, _events);
- } else {
- v0 = v1 = unlocked_eval (pos);
- }
-
- bool dst_guard_exists = false;
-
- for (iterator i = _events.begin(); i != _events.end(); ++i) {
- if ((*i)->when == pos) {
- dst_guard_exists = true;
- }
- if ((*i)->when >= pos) {
- (*i)->when += frames;
- }
- }
-
- /* add guard-points to retain shape, if needed */
- if (frames > 0) {
- ControlEvent cp (pos, 0.0);
- iterator s = lower_bound (_events.begin(), _events.end(), &cp, time_comparator);
- if (s != _events.end ()) {
- _events.insert (s, new ControlEvent (pos, v0));
- }
- pos += frames;
- } else if (frames < 0 && pos > 0) {
- ControlEvent cp (pos - 1, 0.0);
- iterator s = lower_bound (_events.begin(), _events.end(), &cp, time_comparator);
- if (s != _events.end ()) {
- _events.insert (s, new ControlEvent (pos - 1, v0));
- }
- }
- if (!dst_guard_exists) {
- ControlEvent cp (pos, 0.0);
- iterator s = lower_bound (_events.begin(), _events.end(), &cp, time_comparator);
- _events.insert (s, new ControlEvent (pos, s == _events.end () ? v0 : v1));
- }
-
-
- mark_dirty ();
- }
-
- maybe_signal_changed ();
-}
-
-void
-ControlList::modify (iterator iter, double when, double val)
-{
- /* note: we assume higher level logic is in place to avoid this
- * reordering the time-order of control events in the list. ie. all
- * points after *iter are later than when.
- */
-
- /* catch possible float/double rounding errors from higher levels */
- val = std::min ((double)_desc.upper, std::max ((double)_desc.lower, val));
-
- {
- Glib::Threads::RWLock::WriterLock lm (_lock);
-
- (*iter)->when = when;
- (*iter)->value = val;
- if (isnan_local (val)) {
- abort ();
- }
-
- if (!_frozen) {
- _events.sort (event_time_less_than);
- unlocked_remove_duplicates ();
- unlocked_invalidate_insert_iterator ();
- } else {
- _sort_pending = true;
- }
-
- mark_dirty ();
- }
-
- maybe_signal_changed ();
-}
-
-std::pair<ControlList::iterator,ControlList::iterator>
-ControlList::control_points_adjacent (double xval)
-{
- Glib::Threads::RWLock::ReaderLock lm (_lock);
- iterator i;
- ControlEvent cp (xval, 0.0f);
- std::pair<iterator,iterator> ret;
-
- ret.first = _events.end();
- ret.second = _events.end();
-
- for (i = lower_bound (_events.begin(), _events.end(), &cp, time_comparator); i != _events.end(); ++i) {
-
- if (ret.first == _events.end()) {
- if ((*i)->when >= xval) {
- if (i != _events.begin()) {
- ret.first = i;
- --ret.first;
- } else {
- return ret;
- }
- }
- }
-
- if ((*i)->when > xval) {
- ret.second = i;
- break;
- }
- }
-
- return ret;
-}
-
-void
-ControlList::freeze ()
-{
- _frozen++;
-}
-
-void
-ControlList::thaw ()
-{
- assert(_frozen > 0);
-
- if (--_frozen > 0) {
- return;
- }
-
- {
- Glib::Threads::RWLock::WriterLock lm (_lock);
-
- if (_sort_pending) {
- _events.sort (event_time_less_than);
- unlocked_remove_duplicates ();
- unlocked_invalidate_insert_iterator ();
- _sort_pending = false;
- }
- }
-}
-
-void
-ControlList::mark_dirty () const
-{
- _lookup_cache.left = -1;
- _lookup_cache.range.first = _events.end();
- _lookup_cache.range.second = _events.end();
- _search_cache.left = -1;
- _search_cache.first = _events.end();
-
- if (_curve) {
- _curve->mark_dirty();
- }
-
- Dirty (); /* EMIT SIGNAL */
-}
-
-void
-ControlList::truncate_end (double last_coordinate)
-{
- {
- Glib::Threads::RWLock::WriterLock lm (_lock);
- ControlEvent cp (last_coordinate, 0);
- ControlList::reverse_iterator i;
- double last_val;
-
- if (_events.empty()) {
- return;
- }
-
- if (last_coordinate == _events.back()->when) {
- return;
- }
-
- if (last_coordinate > _events.back()->when) {
-
- /* extending end:
- */
-
- iterator foo = _events.begin();
- bool lessthantwo;
-
- if (foo == _events.end()) {
- lessthantwo = true;
- } else if (++foo == _events.end()) {
- lessthantwo = true;
- } else {
- lessthantwo = false;
- }
-
- if (lessthantwo) {
- /* less than 2 points: add a new point */
- _events.push_back (new ControlEvent (last_coordinate, _events.back()->value));
- } else {
-
- /* more than 2 points: check to see if the last 2 values
- are equal. if so, just move the position of the
- last point. otherwise, add a new point.
- */
-
- iterator penultimate = _events.end();
- --penultimate; /* points at last point */
- --penultimate; /* points at the penultimate point */
-
- if (_events.back()->value == (*penultimate)->value) {
- _events.back()->when = last_coordinate;
- } else {
- _events.push_back (new ControlEvent (last_coordinate, _events.back()->value));
- }
- }
-
- } else {
-
- /* shortening end */
-
- last_val = unlocked_eval (last_coordinate);
- last_val = max ((double) _desc.lower, last_val);
- last_val = min ((double) _desc.upper, last_val);
-
- i = _events.rbegin();
-
- /* make i point to the last control point */
-
- ++i;
-
- /* now go backwards, removing control points that are
- beyond the new last coordinate.
- */
-
- // FIXME: SLOW! (size() == O(n))
-
- uint32_t sz = _events.size();
-
- while (i != _events.rend() && sz > 2) {
- ControlList::reverse_iterator tmp;
-
- tmp = i;
- ++tmp;
-
- if ((*i)->when < last_coordinate) {
- break;
- }
-
- _events.erase (i.base());
- --sz;
-
- i = tmp;
- }
-
- _events.back()->when = last_coordinate;
- _events.back()->value = last_val;
- }
-
- unlocked_invalidate_insert_iterator ();
- mark_dirty();
- }
-
- maybe_signal_changed ();
-}
-
-void
-ControlList::truncate_start (double overall_length)
-{
- {
- Glib::Threads::RWLock::WriterLock lm (_lock);
- iterator i;
- double first_legal_value;
- double first_legal_coordinate;
-
- if (_events.empty()) {
- /* nothing to truncate */
- return;
- } else if (overall_length == _events.back()->when) {
- /* no change in overall length */
- return;
- }
-
- if (overall_length > _events.back()->when) {
-
- /* growing at front: duplicate first point. shift all others */
-
- double shift = overall_length - _events.back()->when;
- uint32_t np;
-
- for (np = 0, i = _events.begin(); i != _events.end(); ++i, ++np) {
- (*i)->when += shift;
- }
-
- if (np < 2) {
-
- /* less than 2 points: add a new point */
- _events.push_front (new ControlEvent (0, _events.front()->value));
-
- } else {
-
- /* more than 2 points: check to see if the first 2 values
- are equal. if so, just move the position of the
- first point. otherwise, add a new point.
- */
-
- iterator second = _events.begin();
- ++second; /* points at the second point */
-
- if (_events.front()->value == (*second)->value) {
- /* first segment is flat, just move start point back to zero */
- _events.front()->when = 0;
- } else {
- /* leave non-flat segment in place, add a new leading point. */
- _events.push_front (new ControlEvent (0, _events.front()->value));
- }
- }
-
- } else {
-
- /* shrinking at front */
-
- first_legal_coordinate = _events.back()->when - overall_length;
- first_legal_value = unlocked_eval (first_legal_coordinate);
- first_legal_value = max ((double)_desc.lower, first_legal_value);
- first_legal_value = min ((double)_desc.upper, first_legal_value);
-
- /* remove all events earlier than the new "front" */
-
- i = _events.begin();
-
- while (i != _events.end() && !_events.empty()) {
- ControlList::iterator tmp;
-
- tmp = i;
- ++tmp;
-
- if ((*i)->when > first_legal_coordinate) {
- break;
- }
-
- _events.erase (i);
-
- i = tmp;
- }
-
-
- /* shift all remaining points left to keep their same
- relative position
- */
-
- for (i = _events.begin(); i != _events.end(); ++i) {
- (*i)->when -= first_legal_coordinate;
- }
-
- /* add a new point for the interpolated new value */
-
- _events.push_front (new ControlEvent (0, first_legal_value));
- }
-
- unlocked_invalidate_insert_iterator ();
- mark_dirty();
- }
-
- maybe_signal_changed ();
-}
-
-double
-ControlList::unlocked_eval (double x) const
-{
- pair<EventList::iterator,EventList::iterator> range;
- int32_t npoints;
- double lpos, upos;
- double lval, uval;
- double fraction;
-
- const_iterator length_check_iter = _events.begin();
- for (npoints = 0; npoints < 4; ++npoints, ++length_check_iter) {
- if (length_check_iter == _events.end()) {
- break;
- }
- }
-
- switch (npoints) {
- case 0:
- return _desc.normal;
-
- case 1:
- return _events.front()->value;
-
- case 2:
- if (x >= _events.back()->when) {
- return _events.back()->value;
- } else if (x <= _events.front()->when) {
- return _events.front()->value;
- }
-
- lpos = _events.front()->when;
- lval = _events.front()->value;
- upos = _events.back()->when;
- uval = _events.back()->value;
-
- fraction = (double) (x - lpos) / (double) (upos - lpos);
-
- switch (_interpolation) {
- case Discrete:
- return lval;
- case Logarithmic:
- return interpolate_logarithmic (lval, uval, fraction, _desc.lower, _desc.upper);
- case Exponential:
- return interpolate_gain (lval, uval, fraction, _desc.upper);
- case Curved:
- /* only used x-fade curves, never direct eval */
- assert (0);
- default: // Linear
- return interpolate_linear (lval, uval, fraction);
- }
-
- default:
- if (x >= _events.back()->when) {
- return _events.back()->value;
- } else if (x <= _events.front()->when) {
- return _events.front()->value;
- }
-
- return multipoint_eval (x);
- }
-
- abort(); /*NOTREACHED*/ /* stupid gcc */
- return _desc.normal;
-}
-
-double
-ControlList::multipoint_eval (double x) const
-{
- double upos, lpos;
- double uval, lval;
- double fraction;
-
- /* "Stepped" lookup (no interpolation) */
- /* FIXME: no cache. significant? */
- if (_interpolation == Discrete) {
- const ControlEvent cp (x, 0);
- EventList::const_iterator i = lower_bound (_events.begin(), _events.end(), &cp, time_comparator);
-
- // shouldn't have made it to multipoint_eval
- assert(i != _events.end());
-
- if (i == _events.begin() || (*i)->when == x)
- return (*i)->value;
- else
- return (*(--i))->value;
- }
-
- /* Only do the range lookup if x is in a different range than last time
- * this was called (or if the lookup cache has been marked "dirty" (left<0) */
- if ((_lookup_cache.left < 0) ||
- ((_lookup_cache.left > x) ||
- (_lookup_cache.range.first == _events.end()) ||
- ((*_lookup_cache.range.second)->when < x))) {
-
- const ControlEvent cp (x, 0);
-
- _lookup_cache.range = equal_range (_events.begin(), _events.end(), &cp, time_comparator);
- }
-
- pair<const_iterator,const_iterator> range = _lookup_cache.range;
-
- if (range.first == range.second) {
-
- /* x does not exist within the list as a control point */
-
- _lookup_cache.left = x;
-
- if (range.first != _events.begin()) {
- --range.first;
- lpos = (*range.first)->when;
- lval = (*range.first)->value;
- } else {
- /* we're before the first point */
- // return _default_value;
- return _events.front()->value;
- }
-
- if (range.second == _events.end()) {
- /* we're after the last point */
- return _events.back()->value;
- }
-
- upos = (*range.second)->when;
- uval = (*range.second)->value;
-
- fraction = (double) (x - lpos) / (double) (upos - lpos);
-
- switch (_interpolation) {
- case Logarithmic:
- return interpolate_logarithmic (lval, uval, fraction, _desc.lower, _desc.upper);
- case Exponential:
- return interpolate_gain (lval, uval, fraction, _desc.upper);
- case Discrete:
- /* should not reach here */
- assert (0);
- case Curved:
- /* only used x-fade curves, never direct eval */
- assert (0);
- default: // Linear
- return interpolate_linear (lval, uval, fraction);
- break;
- }
- assert (0);
- }
-
- /* x is a control point in the data */
- _lookup_cache.left = -1;
- return (*range.first)->value;
-}
-
-void
-ControlList::build_search_cache_if_necessary (double start) const
-{
- if (_events.empty()) {
- /* Empty, nothing to cache, move to end. */
- _search_cache.first = _events.end();
- _search_cache.left = 0;
- return;
- } else if ((_search_cache.left < 0) || (_search_cache.left > start)) {
- /* Marked dirty (left < 0), or we're too far forward, re-search. */
-
- const ControlEvent start_point (start, 0);
-
- _search_cache.first = lower_bound (_events.begin(), _events.end(), &start_point, time_comparator);
- _search_cache.left = start;
- }
-
- /* We now have a search cache that is not too far right, but it may be too
- far left and need to be advanced. */
-
- while (_search_cache.first != end() && (*_search_cache.first)->when < start) {
- ++_search_cache.first;
- }
- _search_cache.left = start;
-}
-
-/** Get the earliest event after \a start using the current interpolation style.
- *
- * If an event is found, \a x and \a y are set to its coordinates.
- *
- * \param inclusive Include events with timestamp exactly equal to \a start
- * \return true if event is found (and \a x and \a y are valid).
- */
-bool
-ControlList::rt_safe_earliest_event (double start, double& x, double& y, bool inclusive) const
-{
- // FIXME: It would be nice if this was unnecessary..
- Glib::Threads::RWLock::ReaderLock lm(_lock, Glib::Threads::TRY_LOCK);
- if (!lm.locked()) {
- return false;
- }
-
- return rt_safe_earliest_event_unlocked (start, x, y, inclusive);
-}
-
-
-/** Get the earliest event after \a start using the current interpolation style.
- *
- * If an event is found, \a x and \a y are set to its coordinates.
- *
- * \param inclusive Include events with timestamp exactly equal to \a start
- * \return true if event is found (and \a x and \a y are valid).
- */
-bool
-ControlList::rt_safe_earliest_event_unlocked (double start, double& x, double& y, bool inclusive) const
-{
- if (_interpolation == Discrete) {
- return rt_safe_earliest_event_discrete_unlocked(start, x, y, inclusive);
- } else {
- return rt_safe_earliest_event_linear_unlocked(start, x, y, inclusive);
- }
-}
-
-
-/** Get the earliest event after \a start without interpolation.
- *
- * If an event is found, \a x and \a y are set to its coordinates.
- *
- * \param inclusive Include events with timestamp exactly equal to \a start
- * \return true if event is found (and \a x and \a y are valid).
- */
-bool
-ControlList::rt_safe_earliest_event_discrete_unlocked (double start, double& x, double& y, bool inclusive) const
-{
- build_search_cache_if_necessary (start);
-
- if (_search_cache.first != _events.end()) {
- const ControlEvent* const first = *_search_cache.first;
-
- const bool past_start = (inclusive ? first->when >= start : first->when > start);
-
- /* Earliest points is in range, return it */
- if (past_start) {
-
- x = first->when;
- y = first->value;
-
- /* Move left of cache to this point
- * (Optimize for immediate call this cycle within range) */
- _search_cache.left = x;
- ++_search_cache.first;
-
- assert(x >= start);
- return true;
-
- } else {
- return false;
- }
-
- /* No points in range */
- } else {
- return false;
- }
-}
-
-/** Get the earliest time the line crosses an integer (Linear interpolation).
- *
- * If an event is found, \a x and \a y are set to its coordinates.
- *
- * \param inclusive Include events with timestamp exactly equal to \a start
- * \return true if event is found (and \a x and \a y are valid).
- */
-bool
-ControlList::rt_safe_earliest_event_linear_unlocked (double start, double& x, double& y, bool inclusive) const
-{
- // cout << "earliest_event(start: " << start << ", x: " << x << ", y: " << y << ", inclusive: " << inclusive << ")" << endl;
-
- const_iterator length_check_iter = _events.begin();
- if (_events.empty()) { // 0 events
- return false;
- } else if (_events.end() == ++length_check_iter) { // 1 event
- return rt_safe_earliest_event_discrete_unlocked (start, x, y, inclusive);
- }
-
- // Hack to avoid infinitely repeating the same event
- build_search_cache_if_necessary (start);
-
- if (_search_cache.first != _events.end()) {
-
- const ControlEvent* first = NULL;
- const ControlEvent* next = NULL;
-
- if (_search_cache.first == _events.begin() || (*_search_cache.first)->when <= start) {
- /* Step is after first */
- first = *_search_cache.first;
- ++_search_cache.first;
- if (_search_cache.first == _events.end()) {
- return false;
- }
- next = *_search_cache.first;
-
- } else {
- /* Step is before first */
- const_iterator prev = _search_cache.first;
- --prev;
- first = *prev;
- next = *_search_cache.first;
- }
-
- if (inclusive && first->when == start) {
- x = first->when;
- y = first->value;
- /* Move left of cache to this point
- * (Optimize for immediate call this cycle within range) */
- _search_cache.left = x;
- return true;
- } else if (next->when < start || (!inclusive && next->when == start)) {
- /* "Next" is before the start, no points left. */
- return false;
- }
-
- if (fabs(first->value - next->value) <= 1) {
- if (next->when > start) {
- x = next->when;
- y = next->value;
- /* Move left of cache to this point
- * (Optimize for immediate call this cycle within range) */
- _search_cache.left = x;
- return true;
- } else {
- return false;
- }
- }
-
- const double slope = (next->value - first->value) / (double)(next->when - first->when);
- //cerr << "start y: " << start_y << endl;
-
- //y = first->value + (slope * fabs(start - first->when));
- y = first->value;
-
- if (first->value < next->value) // ramping up
- y = ceil(y);
- else // ramping down
- y = floor(y);
-
- x = first->when + (y - first->value) / (double)slope;
-
- while ((inclusive && x < start) || (x <= start && y != next->value)) {
-
- if (first->value < next->value) // ramping up
- y += 1.0;
- else // ramping down
- y -= 1.0;
-
- x = first->when + (y - first->value) / (double)slope;
- }
-
- /*cerr << first->value << " @ " << first->when << " ... "
- << next->value << " @ " << next->when
- << " = " << y << " @ " << x << endl;*/
-
- assert( (y >= first->value && y <= next->value)
- || (y <= first->value && y >= next->value) );
-
-
- const bool past_start = (inclusive ? x >= start : x > start);
- if (past_start) {
- /* Move left of cache to this point
- * (Optimize for immediate call this cycle within range) */
- _search_cache.left = x;
- assert(inclusive ? x >= start : x > start);
- return true;
- } else {
- if (inclusive) {
- x = next->when;
- } else {
- x = start;
- }
- _search_cache.left = x;
- return true;
- }
-
- } else {
- /* No points in the future, so no steps (towards them) in the future */
- return false;
- }
-}
-
-
-/** @param start Start position in model coordinates.
- * @param end End position in model coordinates.
- * @param op 0 = cut, 1 = copy, 2 = clear.
- */
-boost::shared_ptr<ControlList>
-ControlList::cut_copy_clear (double start, double end, int op)
-{
- boost::shared_ptr<ControlList> nal = create (_parameter, _desc);
- iterator s, e;
- ControlEvent cp (start, 0.0);
-
- {
- Glib::Threads::RWLock::WriterLock lm (_lock);
-
- /* first, determine s & e, two iterators that define the range of points
- affected by this operation
- */
-
- if ((s = lower_bound (_events.begin(), _events.end(), &cp, time_comparator)) == _events.end()) {
- return nal;
- }
-
- /* and the last that is at or after `end' */
- cp.when = end;
- e = upper_bound (_events.begin(), _events.end(), &cp, time_comparator);
-
-
- /* if "start" isn't the location of an existing point,
- evaluate the curve to get a value for the start. Add a point to
- both the existing event list, and if its not a "clear" operation,
- to the copy ("nal") as well.
-
- Note that the time positions of the points in each list are different
- because we want the copy ("nal") to have a zero time reference.
- */
-
-
- /* before we begin any cut/clear operations, get the value of the curve
- at "end".
- */
-
- double end_value = unlocked_eval (end);
-
- if ((*s)->when != start) {
-
- double val = unlocked_eval (start);
-
- if (op == 0) { // cut
- if (start > _events.front()->when) {
- _events.insert (s, (new ControlEvent (start, val)));
- }
- }
-
- if (op != 2) { // ! clear
- nal->_events.push_back (new ControlEvent (0, val));
- }
- }
-
- for (iterator x = s; x != e; ) {
-
- /* adjust new points to be relative to start, which
- has been set to zero.
- */
-
- if (op != 2) {
- nal->_events.push_back (new ControlEvent ((*x)->when - start, (*x)->value));
- }
-
- if (op != 1) {
- x = _events.erase (x);
- } else {
- ++x;
- }
- }
-
- if (e == _events.end() || (*e)->when != end) {
-
- /* only add a boundary point if there is a point after "end"
- */
-
- if (op == 0 && (e != _events.end() && end < (*e)->when)) { // cut
- _events.insert (e, new ControlEvent (end, end_value));
- }
-
- if (op != 2 && (e != _events.end() && end < (*e)->when)) { // cut/copy
- nal->_events.push_back (new ControlEvent (end - start, end_value));
- }
- }
-
- unlocked_invalidate_insert_iterator ();
- mark_dirty ();
- }
-
- if (op != 1) {
- maybe_signal_changed ();
- }
-
- return nal;
-}
-
-
-boost::shared_ptr<ControlList>
-ControlList::cut (double start, double end)
-{
- return cut_copy_clear (start, end, 0);
-}
-
-boost::shared_ptr<ControlList>
-ControlList::copy (double start, double end)
-{
- return cut_copy_clear (start, end, 1);
-}
-
-void
-ControlList::clear (double start, double end)
-{
- cut_copy_clear (start, end, 2);
-}
-
-/** @param pos Position in model coordinates */
-bool
-ControlList::paste (const ControlList& alist, double pos)
-{
- if (alist._events.empty()) {
- return false;
- }
-
- {
- Glib::Threads::RWLock::WriterLock lm (_lock);
- iterator where;
- iterator prev;
- double end = 0;
- ControlEvent cp (pos, 0.0);
-
- where = upper_bound (_events.begin(), _events.end(), &cp, time_comparator);
-
- for (const_iterator i = alist.begin();i != alist.end(); ++i) {
- double value = (*i)->value;
- if (alist.parameter() != parameter()) {
- const ParameterDescriptor& src_desc = alist.descriptor();
-
- // This does not work for logscale and will probably also not do
- // the right thing for integer_step and sr_dependent parameters.
- //
- // TODO various flags from from ARDOUR::ParameterDescriptor
- // to Evoral::ParameterDescriptor
-
- value -= src_desc.lower; // translate to 0-relative
- value /= (src_desc.upper - src_desc.lower); // normalize range
- value *= (_desc.upper - _desc.lower); // scale to our range
- value += _desc.lower; // translate to our offset
- if (_desc.toggled) {
- value = (value < 0.5) ? 0.0 : 1.0;
- }
- /* catch possible rounding errors */
- value = std::min ((double)_desc.upper, std::max ((double)_desc.lower, value));
- }
- _events.insert (where, new ControlEvent((*i)->when + pos, value));
- end = (*i)->when + pos;
- }
-
-
- /* move all points after the insertion along the timeline by
- the correct amount.
- */
-
- while (where != _events.end()) {
- iterator tmp;
- if ((*where)->when <= end) {
- tmp = where;
- ++tmp;
- _events.erase(where);
- where = tmp;
-
- } else {
- break;
- }
- }
-
- unlocked_invalidate_insert_iterator ();
- mark_dirty ();
- }
-
- maybe_signal_changed ();
- return true;
-}
-
-/** Move automation around according to a list of region movements.
- * @param return true if anything was changed, otherwise false (ie nothing needed changing)
- */
-bool
-ControlList::move_ranges (const list< RangeMove<double> >& movements)
-{
- typedef list< RangeMove<double> > RangeMoveList;
-
- {
- Glib::Threads::RWLock::WriterLock lm (_lock);
-
- /* a copy of the events list before we started moving stuff around */
- EventList old_events = _events;
-
- /* clear the source and destination ranges in the new list */
- bool things_erased = false;
- for (RangeMoveList::const_iterator i = movements.begin (); i != movements.end (); ++i) {
-
- if (erase_range_internal (i->from, i->from + i->length, _events)) {
- things_erased = true;
- }
-
- if (erase_range_internal (i->to, i->to + i->length, _events)) {
- things_erased = true;
- }
- }
-
- /* if nothing was erased, there is nothing to do */
- if (!things_erased) {
- return false;
- }
-
- /* copy the events into the new list */
- for (RangeMoveList::const_iterator i = movements.begin (); i != movements.end (); ++i) {
- iterator j = old_events.begin ();
- const double limit = i->from + i->length;
- const double dx = i->to - i->from;
- while (j != old_events.end () && (*j)->when <= limit) {
- if ((*j)->when >= i->from) {
- ControlEvent* ev = new ControlEvent (**j);
- ev->when += dx;
- _events.push_back (ev);
- }
- ++j;
- }
- }
-
- if (!_frozen) {
- _events.sort (event_time_less_than);
- unlocked_remove_duplicates ();
- unlocked_invalidate_insert_iterator ();
- } else {
- _sort_pending = true;
- }
-
- mark_dirty ();
- }
-
- maybe_signal_changed ();
- return true;
-}
-
-bool
-ControlList::set_interpolation (InterpolationStyle s)
-{
- if (_interpolation == s) {
- return true;
- }
-
- switch (s) {
- case Logarithmic:
- if (_desc.lower * _desc.upper <= 0 || _desc.upper <= _desc.lower) {
- return false;
- }
- break;
- case Exponential:
- if (_desc.lower != 0 || _desc.upper <= _desc.lower) {
- return false;
- }
- default:
- break;
- }
-
- _interpolation = s;
- InterpolationChanged (s); /* EMIT SIGNAL */
- return true;
-}
-
-bool
-ControlList::operator!= (ControlList const & other) const
-{
- if (_events.size() != other._events.size()) {
- return true;
- }
-
- EventList::const_iterator i = _events.begin ();
- EventList::const_iterator j = other._events.begin ();
-
- while (i != _events.end() && (*i)->when == (*j)->when && (*i)->value == (*j)->value) {
- ++i;
- ++j;
- }
-
- if (i != _events.end ()) {
- return true;
- }
-
- return (
- _parameter != other._parameter ||
- _interpolation != other._interpolation ||
- _desc.lower != other._desc.lower ||
- _desc.upper != other._desc.upper ||
- _desc.normal != other._desc.normal
- );
-}
-
-bool
-ControlList::is_sorted () const
-{
- Glib::Threads::RWLock::ReaderLock lm (_lock);
- if (_events.size () == 0) {
- return true;
- }
- const_iterator i = _events.begin();
- const_iterator n = i;
- while (++n != _events.end ()) {
- if (event_time_less_than(*n,*i)) {
- return false;
- }
- ++i;
- }
- return true;
-}
-
-void
-ControlList::dump (ostream& o)
-{
- /* NOT LOCKED ... for debugging only */
-
- for (EventList::iterator x = _events.begin(); x != _events.end(); ++x) {
- o << (*x)->value << " @ " << (uint64_t) (*x)->when << endl;
- }
-}
-
-} // namespace Evoral
-
--- /dev/null
+/*
+ * Copyright (C) 2008-2014 David Robillard <d@drobilla.net>
+ * Copyright (C) 2010-2018 Paul Davis <paul@linuxaudiosystems.com>
+ * Copyright (C) 2010 Carl Hetherington <carl@carlh.net>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include <iostream>
+#include <limits>
+#include "evoral/ControlSet.h"
+#include "evoral/ControlList.h"
+#include "evoral/Control.h"
+#include "evoral/Event.h"
+
+using namespace std;
+
+namespace Evoral {
+
+
+ControlSet::ControlSet()
+{
+}
+
+ControlSet::ControlSet (const ControlSet&)
+ : noncopyable ()
+{
+ /* derived class must copy controls */
+}
+
+void
+ControlSet::add_control(boost::shared_ptr<Control> ac)
+{
+ _controls[ac->parameter()] = ac;
+
+ ac->ListMarkedDirty.connect_same_thread (_control_connections, boost::bind (&ControlSet::control_list_marked_dirty, this));
+
+ if (ac->list()) {
+ ac->list()->InterpolationChanged.connect_same_thread (
+ _list_connections,
+ boost::bind (&ControlSet::control_list_interpolation_changed,
+ this, ac->parameter(), _1));
+ }
+}
+
+void
+ControlSet::what_has_data (set<Parameter>& s) const
+{
+ Glib::Threads::Mutex::Lock lm (_control_lock);
+
+ for (Controls::const_iterator li = _controls.begin(); li != _controls.end(); ++li) {
+ if (li->second->list() && !li->second->list()->empty()) {
+ s.insert (li->first);
+ }
+ }
+}
+
+/** If a control for the given parameter does not exist and \a create_if_missing is true,
+ * a control will be created, added to this set, and returned.
+ * If \a create_if_missing is false this function may return null.
+ */
+boost::shared_ptr<Control>
+ControlSet::control (const Parameter& parameter, bool create_if_missing)
+{
+ Controls::iterator i = _controls.find(parameter);
+
+ if (i != _controls.end()) {
+ return i->second;
+
+ } else if (create_if_missing) {
+ boost::shared_ptr<Control> ac(control_factory(parameter));
+ add_control(ac);
+ return ac;
+
+ } else {
+ return boost::shared_ptr<Control>();
+ }
+}
+
+void
+ControlSet::clear_controls ()
+{
+ Glib::Threads::Mutex::Lock lm (_control_lock);
+
+ _control_connections.drop_connections ();
+ _list_connections.drop_connections ();
+
+ for (Controls::iterator li = _controls.begin(); li != _controls.end(); ++li) {
+ if (li->second->list()) {
+ li->second->list()->clear();
+ }
+ }
+}
+
+} // namespace Evoral
+
+/* No good place for this so just put it here */
+
+std::ostream&
+std::operator<< (std::ostream & str, Evoral::Parameter const & p)
+{
+ return str << p.type() << '-' << p.id() << '-' << (int) p.channel();
+}
+++ /dev/null
-/*
- * Copyright (C) 2008-2014 David Robillard <d@drobilla.net>
- * Copyright (C) 2010-2018 Paul Davis <paul@linuxaudiosystems.com>
- * Copyright (C) 2010 Carl Hetherington <carl@carlh.net>
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-
-#include <iostream>
-#include <limits>
-#include "evoral/ControlSet.hpp"
-#include "evoral/ControlList.hpp"
-#include "evoral/Control.hpp"
-#include "evoral/Event.hpp"
-
-using namespace std;
-
-namespace Evoral {
-
-
-ControlSet::ControlSet()
-{
-}
-
-ControlSet::ControlSet (const ControlSet&)
- : noncopyable ()
-{
- /* derived class must copy controls */
-}
-
-void
-ControlSet::add_control(boost::shared_ptr<Control> ac)
-{
- _controls[ac->parameter()] = ac;
-
- ac->ListMarkedDirty.connect_same_thread (_control_connections, boost::bind (&ControlSet::control_list_marked_dirty, this));
-
- if (ac->list()) {
- ac->list()->InterpolationChanged.connect_same_thread (
- _list_connections,
- boost::bind (&ControlSet::control_list_interpolation_changed,
- this, ac->parameter(), _1));
- }
-}
-
-void
-ControlSet::what_has_data (set<Parameter>& s) const
-{
- Glib::Threads::Mutex::Lock lm (_control_lock);
-
- for (Controls::const_iterator li = _controls.begin(); li != _controls.end(); ++li) {
- if (li->second->list() && !li->second->list()->empty()) {
- s.insert (li->first);
- }
- }
-}
-
-/** If a control for the given parameter does not exist and \a create_if_missing is true,
- * a control will be created, added to this set, and returned.
- * If \a create_if_missing is false this function may return null.
- */
-boost::shared_ptr<Control>
-ControlSet::control (const Parameter& parameter, bool create_if_missing)
-{
- Controls::iterator i = _controls.find(parameter);
-
- if (i != _controls.end()) {
- return i->second;
-
- } else if (create_if_missing) {
- boost::shared_ptr<Control> ac(control_factory(parameter));
- add_control(ac);
- return ac;
-
- } else {
- return boost::shared_ptr<Control>();
- }
-}
-
-void
-ControlSet::clear_controls ()
-{
- Glib::Threads::Mutex::Lock lm (_control_lock);
-
- _control_connections.drop_connections ();
- _list_connections.drop_connections ();
-
- for (Controls::iterator li = _controls.begin(); li != _controls.end(); ++li) {
- if (li->second->list()) {
- li->second->list()->clear();
- }
- }
-}
-
-} // namespace Evoral
-
-/* No good place for this so just put it here */
-
-std::ostream&
-std::operator<< (std::ostream & str, Evoral::Parameter const & p)
-{
- return str << p.type() << '-' << p.id() << '-' << (int) p.channel();
-}
--- /dev/null
+/*
+ * Copyright (C) 2008-2013 Paul Davis <paul@linuxaudiosystems.com>
+ * Copyright (C) 2008-2016 David Robillard <d@drobilla.net>
+ * Copyright (C) 2010-2012 Carl Hetherington <carl@carlh.net>
+ * Copyright (C) 2012-2018 Robin Gareus <robin@gareus.org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include <iostream>
+#include <float.h>
+#include <cmath>
+#include <climits>
+#include <cfloat>
+#include <cmath>
+#include <vector>
+
+#include <glibmm/threads.h>
+
+#include "pbd/control_math.h"
+
+#include "evoral/Curve.h"
+#include "evoral/ControlList.h"
+
+using namespace std;
+using namespace sigc;
+
+namespace Evoral {
+
+
+Curve::Curve (const ControlList& cl)
+ : _dirty (true)
+ , _list (cl)
+{
+}
+
+void
+Curve::solve () const
+{
+ uint32_t npoints;
+
+ if (!_dirty) {
+ return;
+ }
+
+ if ((npoints = _list.events().size()) > 2) {
+
+ /* Compute coefficients needed to efficiently compute a constrained spline
+ curve. See "Constrained Cubic Spline Interpolation" by CJC Kruger
+ (www.korf.co.uk/spline.pdf) for more details.
+ */
+
+ vector<double> x(npoints);
+ vector<double> y(npoints);
+ uint32_t i;
+ ControlList::EventList::const_iterator xx;
+
+ for (i = 0, xx = _list.events().begin(); xx != _list.events().end(); ++xx, ++i) {
+ x[i] = (double) (*xx)->when;
+ y[i] = (double) (*xx)->value;
+ }
+
+ double lp0, lp1, fpone;
+
+ lp0 = (x[1] - x[0])/(y[1] - y[0]);
+ lp1 = (x[2] - x[1])/(y[2] - y[1]);
+
+ if (lp0*lp1 < 0) {
+ fpone = 0;
+ } else {
+ fpone = 2 / (lp1 + lp0);
+ }
+
+ double fplast = 0;
+
+ for (i = 0, xx = _list.events().begin(); xx != _list.events().end(); ++xx, ++i) {
+
+ double xdelta; /* gcc is wrong about possible uninitialized use */
+ double xdelta2; /* ditto */
+ double ydelta; /* ditto */
+ double fppL, fppR;
+ double fpi;
+
+ if (i > 0) {
+ xdelta = x[i] - x[i-1];
+ xdelta2 = xdelta * xdelta;
+ ydelta = y[i] - y[i-1];
+ }
+
+ /* compute (constrained) first derivatives */
+
+ if (i == 0) {
+
+ /* first segment */
+
+ fplast = ((3 * (y[1] - y[0]) / (2 * (x[1] - x[0]))) - (fpone * 0.5));
+
+ /* we don't store coefficients for i = 0 */
+
+ continue;
+
+ } else if (i == npoints - 1) {
+
+ /* last segment */
+
+ fpi = ((3 * ydelta) / (2 * xdelta)) - (fplast * 0.5);
+
+ } else {
+
+ /* all other segments */
+
+ double slope_before = ((x[i+1] - x[i]) / (y[i+1] - y[i]));
+ double slope_after = (xdelta / ydelta);
+
+ if (slope_after * slope_before < 0.0) {
+ /* slope changed sign */
+ fpi = 0.0;
+ } else {
+ fpi = 2 / (slope_before + slope_after);
+ }
+ }
+
+ /* compute second derivative for either side of control point `i' */
+
+ fppL = (((-2 * (fpi + (2 * fplast))) / (xdelta))) +
+ ((6 * ydelta) / xdelta2);
+
+ fppR = (2 * ((2 * fpi) + fplast) / xdelta) -
+ ((6 * ydelta) / xdelta2);
+
+ /* compute polynomial coefficients */
+
+ double b, c, d;
+
+ d = (fppR - fppL) / (6 * xdelta);
+ c = ((x[i] * fppL) - (x[i-1] * fppR))/(2 * xdelta);
+
+ double xim12, xim13;
+ double xi2, xi3;
+
+ xim12 = x[i-1] * x[i-1]; /* "x[i-1] squared" */
+ xim13 = xim12 * x[i-1]; /* "x[i-1] cubed" */
+ xi2 = x[i] * x[i]; /* "x[i] squared" */
+ xi3 = xi2 * x[i]; /* "x[i] cubed" */
+
+ b = (ydelta - (c * (xi2 - xim12)) - (d * (xi3 - xim13))) / xdelta;
+
+ /* store */
+
+ (*xx)->create_coeffs();
+ (*xx)->coeff[0] = y[i-1] - (b * x[i-1]) - (c * xim12) - (d * xim13);
+ (*xx)->coeff[1] = b;
+ (*xx)->coeff[2] = c;
+ (*xx)->coeff[3] = d;
+
+ fplast = fpi;
+ }
+
+ }
+
+ _dirty = false;
+}
+
+bool
+Curve::rt_safe_get_vector (double x0, double x1, float *vec, int32_t veclen) const
+{
+ Glib::Threads::RWLock::ReaderLock lm(_list.lock(), Glib::Threads::TRY_LOCK);
+
+ if (!lm.locked()) {
+ return false;
+ } else {
+ _get_vector (x0, x1, vec, veclen);
+ return true;
+ }
+}
+
+void
+Curve::get_vector (double x0, double x1, float *vec, int32_t veclen) const
+{
+ Glib::Threads::RWLock::ReaderLock lm(_list.lock());
+ _get_vector (x0, x1, vec, veclen);
+}
+
+void
+Curve::_get_vector (double x0, double x1, float *vec, int32_t veclen) const
+{
+ double rx, lx, hx, max_x, min_x;
+ int32_t i;
+ int32_t original_veclen;
+ int32_t npoints;
+
+ if (veclen == 0) {
+ return;
+ }
+
+ if ((npoints = _list.events().size()) == 0) {
+ /* no events in list, so just fill the entire array with the default value */
+ for (int32_t i = 0; i < veclen; ++i) {
+ vec[i] = _list.descriptor().normal;
+ }
+ return;
+ }
+
+ if (npoints == 1) {
+ for (int32_t i = 0; i < veclen; ++i) {
+ vec[i] = _list.events().front()->value;
+ }
+ return;
+ }
+
+ /* events is now known not to be empty */
+
+ max_x = _list.events().back()->when;
+ min_x = _list.events().front()->when;
+
+ if (x0 > max_x) {
+ /* totally past the end - just fill the entire array with the final value */
+ for (int32_t i = 0; i < veclen; ++i) {
+ vec[i] = _list.events().back()->value;
+ }
+ return;
+ }
+
+ if (x1 < min_x) {
+ /* totally before the first event - fill the entire array with
+ * the initial value.
+ */
+ for (int32_t i = 0; i < veclen; ++i) {
+ vec[i] = _list.events().front()->value;
+ }
+ return;
+ }
+
+ original_veclen = veclen;
+
+ if (x0 < min_x) {
+
+ /* fill some beginning section of the array with the
+ initial (used to be default) value
+ */
+
+ double frac = (min_x - x0) / (x1 - x0);
+ int64_t fill_len = (int64_t) floor (veclen * frac);
+
+ fill_len = min (fill_len, (int64_t)veclen);
+
+ for (i = 0; i < fill_len; ++i) {
+ vec[i] = _list.events().front()->value;
+ }
+
+ veclen -= fill_len;
+ vec += fill_len;
+ }
+
+ if (veclen && x1 > max_x) {
+
+ /* fill some end section of the array with the default or final value */
+
+ double frac = (x1 - max_x) / (x1 - x0);
+ int64_t fill_len = (int64_t) floor (original_veclen * frac);
+ float val;
+
+ fill_len = min (fill_len, (int64_t)veclen);
+ val = _list.events().back()->value;
+
+ for (i = veclen - fill_len; i < veclen; ++i) {
+ vec[i] = val;
+ }
+
+ veclen -= fill_len;
+ }
+
+ lx = max (min_x, x0);
+ hx = min (max_x, x1);
+
+ if (npoints == 2) {
+
+ const double lpos = _list.events().front()->when;
+ const double lval = _list.events().front()->value;
+ const double upos = _list.events().back()->when;
+ const double uval = _list.events().back()->value;
+
+ /* dx that we are using */
+ if (veclen > 1) {
+ const double dx_num = hx - lx;
+ const double dx_den = veclen - 1;
+ const double lower = _list.descriptor().lower;
+ const double upper = _list.descriptor().upper;
+
+ /* gradient of the line */
+ const double m_num = uval - lval;
+ const double m_den = upos - lpos;
+ /* y intercept of the line */
+ const double c = uval - (m_num * upos / m_den);
+
+ switch (_list.interpolation()) {
+ case ControlList::Logarithmic:
+ for (int i = 0; i < veclen; ++i) {
+ const double fraction = (lx - lpos + i * dx_num / dx_den) / m_den;
+ vec[i] = interpolate_logarithmic (lval, uval, fraction, lower, upper);
+ }
+ break;
+ case ControlList::Exponential:
+ for (int i = 0; i < veclen; ++i) {
+ const double fraction = (lx - lpos + i * dx_num / dx_den) / m_den;
+ vec[i] = interpolate_gain (lval, uval, fraction, upper);
+ }
+ break;
+ case ControlList::Discrete:
+ // any discrete vector curves somewhere?
+ assert (0);
+ case ControlList::Curved:
+ /* no 2 point spline */
+ /* fallthrough */
+ default: // Linear:
+ for (int i = 0; i < veclen; ++i) {
+ vec[i] = (lx * (m_num / m_den) + m_num * i * dx_num / (m_den * dx_den)) + c;
+ }
+ break;
+ }
+ } else {
+ double fraction = (lx - lpos) / (upos - lpos);
+ switch (_list.interpolation()) {
+ case ControlList::Logarithmic:
+ vec[0] = interpolate_logarithmic (lval, uval, fraction, _list.descriptor().lower, _list.descriptor().upper);
+ break;
+ case ControlList::Exponential:
+ vec[0] = interpolate_gain (lval, uval, fraction, _list.descriptor().upper);
+ break;
+ case ControlList::Discrete:
+ // any discrete vector curves somewhere?
+ assert (0);
+ case ControlList::Curved:
+ /* no 2 point spline */
+ /* fallthrough */
+ default: // Linear:
+ vec[0] = interpolate_linear (lval, uval, fraction);
+ break;
+ }
+ }
+
+ return;
+ }
+
+ if (_dirty) {
+ solve ();
+ }
+
+ rx = lx;
+
+ double dx = 0;
+ if (veclen > 1) {
+ dx = (hx - lx) / (veclen - 1);
+ }
+
+ for (i = 0; i < veclen; ++i, rx += dx) {
+ vec[i] = multipoint_eval (rx);
+ }
+}
+
+double
+Curve::multipoint_eval (double x) const
+{
+ pair<ControlList::EventList::const_iterator,ControlList::EventList::const_iterator> range;
+
+ ControlList::LookupCache& lookup_cache = _list.lookup_cache();
+
+ if ((lookup_cache.left < 0) ||
+ ((lookup_cache.left > x) ||
+ (lookup_cache.range.first == _list.events().end()) ||
+ ((*lookup_cache.range.second)->when < x))) {
+
+ ControlEvent cp (x, 0.0);
+
+ lookup_cache.range = equal_range (_list.events().begin(), _list.events().end(), &cp, ControlList::time_comparator);
+ }
+
+ range = lookup_cache.range;
+
+ /* EITHER
+
+ a) x is an existing control point, so first == existing point, second == next point
+
+ OR
+
+ b) x is between control points, so range is empty (first == second, points to where
+ to insert x)
+
+ */
+
+ if (range.first == range.second) {
+
+ /* x does not exist within the list as a control point */
+
+ lookup_cache.left = x;
+
+ if (range.first == _list.events().begin()) {
+ /* we're before the first point */
+ // return default_value;
+ return _list.events().front()->value;
+ }
+
+ if (range.second == _list.events().end()) {
+ /* we're after the last point */
+ return _list.events().back()->value;
+ }
+
+ ControlEvent* after = (*range.second);
+ range.second--;
+ ControlEvent* before = (*range.second);
+
+ double vdelta = after->value - before->value;
+
+ if (vdelta == 0.0) {
+ return before->value;
+ }
+
+ double tdelta = x - before->when;
+ double trange = after->when - before->when;
+
+ switch (_list.interpolation()) {
+ case ControlList::Discrete:
+ return before->value;
+ case ControlList::Logarithmic:
+ return interpolate_logarithmic (before->value, after->value, tdelta / trange, _list.descriptor().lower, _list.descriptor().upper);
+ case ControlList::Exponential:
+ return interpolate_gain (before->value, after->value, tdelta / trange, _list.descriptor().upper);
+ case ControlList::Curved:
+ if (after->coeff) {
+ ControlEvent* ev = after;
+ double x2 = x * x;
+ return ev->coeff[0] + (ev->coeff[1] * x) + (ev->coeff[2] * x2) + (ev->coeff[3] * x2 * x);
+ }
+ /* fallthrough */
+ case ControlList::Linear:
+ return before->value + (vdelta * (tdelta / trange));
+ }
+ }
+
+ /* x is a control point in the data */
+ /* invalidate the cached range because its not usable */
+ lookup_cache.left = -1;
+ return (*range.first)->value;
+}
+
+} // namespace Evoral
+++ /dev/null
-/*
- * Copyright (C) 2008-2013 Paul Davis <paul@linuxaudiosystems.com>
- * Copyright (C) 2008-2016 David Robillard <d@drobilla.net>
- * Copyright (C) 2010-2012 Carl Hetherington <carl@carlh.net>
- * Copyright (C) 2012-2018 Robin Gareus <robin@gareus.org>
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-
-#include <iostream>
-#include <float.h>
-#include <cmath>
-#include <climits>
-#include <cfloat>
-#include <cmath>
-#include <vector>
-
-#include <glibmm/threads.h>
-
-#include "pbd/control_math.h"
-
-#include "evoral/Curve.hpp"
-#include "evoral/ControlList.hpp"
-
-using namespace std;
-using namespace sigc;
-
-namespace Evoral {
-
-
-Curve::Curve (const ControlList& cl)
- : _dirty (true)
- , _list (cl)
-{
-}
-
-void
-Curve::solve () const
-{
- uint32_t npoints;
-
- if (!_dirty) {
- return;
- }
-
- if ((npoints = _list.events().size()) > 2) {
-
- /* Compute coefficients needed to efficiently compute a constrained spline
- curve. See "Constrained Cubic Spline Interpolation" by CJC Kruger
- (www.korf.co.uk/spline.pdf) for more details.
- */
-
- vector<double> x(npoints);
- vector<double> y(npoints);
- uint32_t i;
- ControlList::EventList::const_iterator xx;
-
- for (i = 0, xx = _list.events().begin(); xx != _list.events().end(); ++xx, ++i) {
- x[i] = (double) (*xx)->when;
- y[i] = (double) (*xx)->value;
- }
-
- double lp0, lp1, fpone;
-
- lp0 = (x[1] - x[0])/(y[1] - y[0]);
- lp1 = (x[2] - x[1])/(y[2] - y[1]);
-
- if (lp0*lp1 < 0) {
- fpone = 0;
- } else {
- fpone = 2 / (lp1 + lp0);
- }
-
- double fplast = 0;
-
- for (i = 0, xx = _list.events().begin(); xx != _list.events().end(); ++xx, ++i) {
-
- double xdelta; /* gcc is wrong about possible uninitialized use */
- double xdelta2; /* ditto */
- double ydelta; /* ditto */
- double fppL, fppR;
- double fpi;
-
- if (i > 0) {
- xdelta = x[i] - x[i-1];
- xdelta2 = xdelta * xdelta;
- ydelta = y[i] - y[i-1];
- }
-
- /* compute (constrained) first derivatives */
-
- if (i == 0) {
-
- /* first segment */
-
- fplast = ((3 * (y[1] - y[0]) / (2 * (x[1] - x[0]))) - (fpone * 0.5));
-
- /* we don't store coefficients for i = 0 */
-
- continue;
-
- } else if (i == npoints - 1) {
-
- /* last segment */
-
- fpi = ((3 * ydelta) / (2 * xdelta)) - (fplast * 0.5);
-
- } else {
-
- /* all other segments */
-
- double slope_before = ((x[i+1] - x[i]) / (y[i+1] - y[i]));
- double slope_after = (xdelta / ydelta);
-
- if (slope_after * slope_before < 0.0) {
- /* slope changed sign */
- fpi = 0.0;
- } else {
- fpi = 2 / (slope_before + slope_after);
- }
- }
-
- /* compute second derivative for either side of control point `i' */
-
- fppL = (((-2 * (fpi + (2 * fplast))) / (xdelta))) +
- ((6 * ydelta) / xdelta2);
-
- fppR = (2 * ((2 * fpi) + fplast) / xdelta) -
- ((6 * ydelta) / xdelta2);
-
- /* compute polynomial coefficients */
-
- double b, c, d;
-
- d = (fppR - fppL) / (6 * xdelta);
- c = ((x[i] * fppL) - (x[i-1] * fppR))/(2 * xdelta);
-
- double xim12, xim13;
- double xi2, xi3;
-
- xim12 = x[i-1] * x[i-1]; /* "x[i-1] squared" */
- xim13 = xim12 * x[i-1]; /* "x[i-1] cubed" */
- xi2 = x[i] * x[i]; /* "x[i] squared" */
- xi3 = xi2 * x[i]; /* "x[i] cubed" */
-
- b = (ydelta - (c * (xi2 - xim12)) - (d * (xi3 - xim13))) / xdelta;
-
- /* store */
-
- (*xx)->create_coeffs();
- (*xx)->coeff[0] = y[i-1] - (b * x[i-1]) - (c * xim12) - (d * xim13);
- (*xx)->coeff[1] = b;
- (*xx)->coeff[2] = c;
- (*xx)->coeff[3] = d;
-
- fplast = fpi;
- }
-
- }
-
- _dirty = false;
-}
-
-bool
-Curve::rt_safe_get_vector (double x0, double x1, float *vec, int32_t veclen) const
-{
- Glib::Threads::RWLock::ReaderLock lm(_list.lock(), Glib::Threads::TRY_LOCK);
-
- if (!lm.locked()) {
- return false;
- } else {
- _get_vector (x0, x1, vec, veclen);
- return true;
- }
-}
-
-void
-Curve::get_vector (double x0, double x1, float *vec, int32_t veclen) const
-{
- Glib::Threads::RWLock::ReaderLock lm(_list.lock());
- _get_vector (x0, x1, vec, veclen);
-}
-
-void
-Curve::_get_vector (double x0, double x1, float *vec, int32_t veclen) const
-{
- double rx, lx, hx, max_x, min_x;
- int32_t i;
- int32_t original_veclen;
- int32_t npoints;
-
- if (veclen == 0) {
- return;
- }
-
- if ((npoints = _list.events().size()) == 0) {
- /* no events in list, so just fill the entire array with the default value */
- for (int32_t i = 0; i < veclen; ++i) {
- vec[i] = _list.descriptor().normal;
- }
- return;
- }
-
- if (npoints == 1) {
- for (int32_t i = 0; i < veclen; ++i) {
- vec[i] = _list.events().front()->value;
- }
- return;
- }
-
- /* events is now known not to be empty */
-
- max_x = _list.events().back()->when;
- min_x = _list.events().front()->when;
-
- if (x0 > max_x) {
- /* totally past the end - just fill the entire array with the final value */
- for (int32_t i = 0; i < veclen; ++i) {
- vec[i] = _list.events().back()->value;
- }
- return;
- }
-
- if (x1 < min_x) {
- /* totally before the first event - fill the entire array with
- * the initial value.
- */
- for (int32_t i = 0; i < veclen; ++i) {
- vec[i] = _list.events().front()->value;
- }
- return;
- }
-
- original_veclen = veclen;
-
- if (x0 < min_x) {
-
- /* fill some beginning section of the array with the
- initial (used to be default) value
- */
-
- double frac = (min_x - x0) / (x1 - x0);
- int64_t fill_len = (int64_t) floor (veclen * frac);
-
- fill_len = min (fill_len, (int64_t)veclen);
-
- for (i = 0; i < fill_len; ++i) {
- vec[i] = _list.events().front()->value;
- }
-
- veclen -= fill_len;
- vec += fill_len;
- }
-
- if (veclen && x1 > max_x) {
-
- /* fill some end section of the array with the default or final value */
-
- double frac = (x1 - max_x) / (x1 - x0);
- int64_t fill_len = (int64_t) floor (original_veclen * frac);
- float val;
-
- fill_len = min (fill_len, (int64_t)veclen);
- val = _list.events().back()->value;
-
- for (i = veclen - fill_len; i < veclen; ++i) {
- vec[i] = val;
- }
-
- veclen -= fill_len;
- }
-
- lx = max (min_x, x0);
- hx = min (max_x, x1);
-
- if (npoints == 2) {
-
- const double lpos = _list.events().front()->when;
- const double lval = _list.events().front()->value;
- const double upos = _list.events().back()->when;
- const double uval = _list.events().back()->value;
-
- /* dx that we are using */
- if (veclen > 1) {
- const double dx_num = hx - lx;
- const double dx_den = veclen - 1;
- const double lower = _list.descriptor().lower;
- const double upper = _list.descriptor().upper;
-
- /* gradient of the line */
- const double m_num = uval - lval;
- const double m_den = upos - lpos;
- /* y intercept of the line */
- const double c = uval - (m_num * upos / m_den);
-
- switch (_list.interpolation()) {
- case ControlList::Logarithmic:
- for (int i = 0; i < veclen; ++i) {
- const double fraction = (lx - lpos + i * dx_num / dx_den) / m_den;
- vec[i] = interpolate_logarithmic (lval, uval, fraction, lower, upper);
- }
- break;
- case ControlList::Exponential:
- for (int i = 0; i < veclen; ++i) {
- const double fraction = (lx - lpos + i * dx_num / dx_den) / m_den;
- vec[i] = interpolate_gain (lval, uval, fraction, upper);
- }
- break;
- case ControlList::Discrete:
- // any discrete vector curves somewhere?
- assert (0);
- case ControlList::Curved:
- /* no 2 point spline */
- /* fallthrough */
- default: // Linear:
- for (int i = 0; i < veclen; ++i) {
- vec[i] = (lx * (m_num / m_den) + m_num * i * dx_num / (m_den * dx_den)) + c;
- }
- break;
- }
- } else {
- double fraction = (lx - lpos) / (upos - lpos);
- switch (_list.interpolation()) {
- case ControlList::Logarithmic:
- vec[0] = interpolate_logarithmic (lval, uval, fraction, _list.descriptor().lower, _list.descriptor().upper);
- break;
- case ControlList::Exponential:
- vec[0] = interpolate_gain (lval, uval, fraction, _list.descriptor().upper);
- break;
- case ControlList::Discrete:
- // any discrete vector curves somewhere?
- assert (0);
- case ControlList::Curved:
- /* no 2 point spline */
- /* fallthrough */
- default: // Linear:
- vec[0] = interpolate_linear (lval, uval, fraction);
- break;
- }
- }
-
- return;
- }
-
- if (_dirty) {
- solve ();
- }
-
- rx = lx;
-
- double dx = 0;
- if (veclen > 1) {
- dx = (hx - lx) / (veclen - 1);
- }
-
- for (i = 0; i < veclen; ++i, rx += dx) {
- vec[i] = multipoint_eval (rx);
- }
-}
-
-double
-Curve::multipoint_eval (double x) const
-{
- pair<ControlList::EventList::const_iterator,ControlList::EventList::const_iterator> range;
-
- ControlList::LookupCache& lookup_cache = _list.lookup_cache();
-
- if ((lookup_cache.left < 0) ||
- ((lookup_cache.left > x) ||
- (lookup_cache.range.first == _list.events().end()) ||
- ((*lookup_cache.range.second)->when < x))) {
-
- ControlEvent cp (x, 0.0);
-
- lookup_cache.range = equal_range (_list.events().begin(), _list.events().end(), &cp, ControlList::time_comparator);
- }
-
- range = lookup_cache.range;
-
- /* EITHER
-
- a) x is an existing control point, so first == existing point, second == next point
-
- OR
-
- b) x is between control points, so range is empty (first == second, points to where
- to insert x)
-
- */
-
- if (range.first == range.second) {
-
- /* x does not exist within the list as a control point */
-
- lookup_cache.left = x;
-
- if (range.first == _list.events().begin()) {
- /* we're before the first point */
- // return default_value;
- return _list.events().front()->value;
- }
-
- if (range.second == _list.events().end()) {
- /* we're after the last point */
- return _list.events().back()->value;
- }
-
- ControlEvent* after = (*range.second);
- range.second--;
- ControlEvent* before = (*range.second);
-
- double vdelta = after->value - before->value;
-
- if (vdelta == 0.0) {
- return before->value;
- }
-
- double tdelta = x - before->when;
- double trange = after->when - before->when;
-
- switch (_list.interpolation()) {
- case ControlList::Discrete:
- return before->value;
- case ControlList::Logarithmic:
- return interpolate_logarithmic (before->value, after->value, tdelta / trange, _list.descriptor().lower, _list.descriptor().upper);
- case ControlList::Exponential:
- return interpolate_gain (before->value, after->value, tdelta / trange, _list.descriptor().upper);
- case ControlList::Curved:
- if (after->coeff) {
- ControlEvent* ev = after;
- double x2 = x * x;
- return ev->coeff[0] + (ev->coeff[1] * x) + (ev->coeff[2] * x2) + (ev->coeff[3] * x2 * x);
- }
- /* fallthrough */
- case ControlList::Linear:
- return before->value + (vdelta * (tdelta / trange));
- }
- }
-
- /* x is a control point in the data */
- /* invalidate the cached range because its not usable */
- lookup_cache.left = -1;
- return (*range.first)->value;
-}
-
-} // namespace Evoral
--- /dev/null
+/*
+ * Copyright (C) 2008-2016 David Robillard <d@drobilla.net>
+ * Copyright (C) 2009-2017 Paul Davis <paul@linuxaudiosystems.com>
+ * Copyright (C) 2010 Carl Hetherington <carl@carlh.net>
+ * Copyright (C) 2014-2015 Robin Gareus <robin@gareus.org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include <glib.h>
+
+#include "temporal/beats.h"
+#include "evoral/Event.h"
+
+namespace Evoral {
+
+static event_id_t _event_id_counter = 0;
+
+event_id_t
+event_id_counter()
+{
+ return g_atomic_int_get (&_event_id_counter);
+}
+
+void
+init_event_id_counter(event_id_t n)
+{
+ g_atomic_int_set (&_event_id_counter, n);
+}
+
+event_id_t
+next_event_id ()
+{
+ /* TODO: handle 31bit overflow , event_id_t is an int32_t,
+ * and libsmf only supports loading uint32_t vlq's, see smf_extract_vlq()
+ *
+ * event-IDs only have to be unique per .mid file.
+ * Previously (Ardour 4.2ish) Ardour re-generated those IDs when loading the
+ * file but that lead to .mid files being modified on every load/save.
+ *
+ * current user-record: is event-counter="276390506" (just abov 2^28)
+ */
+ return g_atomic_int_add (&_event_id_counter, 1);
+}
+
+#ifdef EVORAL_EVENT_ALLOC
+
+template<typename Timestamp>
+Event<Timestamp>::Event(EventType type, Timestamp time, uint32_t size, uint8_t* buf, bool alloc)
+ : _type(type)
+ , _time(time)
+ , _size(size)
+ , _buf(buf)
+ , _id(-1)
+ , _owns_buf(alloc)
+{
+ if (alloc) {
+ _buf = (uint8_t*)malloc(_size);
+ if (buf) {
+ memcpy(_buf, buf, _size);
+ } else {
+ memset(_buf, 0, _size);
+ }
+ }
+}
+
+template<typename Timestamp>
+Event<Timestamp>::Event(EventType type,
+ Timestamp time,
+ uint32_t size,
+ const uint8_t* buf)
+ : _type(type)
+ , _time(time)
+ , _size(size)
+ , _buf((uint8_t*)malloc(size))
+ , _id(-1)
+ , _owns_buf(true)
+{
+ memcpy(_buf, buf, _size);
+}
+
+template<typename Timestamp>
+Event<Timestamp>::Event(const Event& copy, bool owns_buf)
+ : _type(copy._type)
+ , _time(copy._time)
+ , _size(copy._size)
+ , _buf(copy._buf)
+ , _id (next_event_id ())
+ , _owns_buf(owns_buf)
+{
+ if (owns_buf) {
+ _buf = (uint8_t*)malloc(_size);
+ if (copy._buf) {
+ memcpy(_buf, copy._buf, _size);
+ } else {
+ memset(_buf, 0, _size);
+ }
+ }
+}
+
+template<typename Timestamp>
+Event<Timestamp>::~Event() {
+ if (_owns_buf) {
+ free(_buf);
+ }
+}
+
+template<typename Timestamp>
+void
+Event<Timestamp>::assign(const Event& other)
+{
+ _id = other._id;
+ _type = other._type;
+ _time = other._time;
+ _owns_buf = other._owns_buf;
+ if (_owns_buf) {
+ if (other._buf) {
+ if (other._size > _size) {
+ _buf = (uint8_t*)::realloc(_buf, other._size);
+ }
+ memcpy(_buf, other._buf, other._size);
+ } else {
+ free(_buf);
+ _buf = NULL;
+ }
+ } else {
+ _buf = other._buf;
+ }
+
+ _size = other._size;
+}
+
+template<typename Timestamp>
+void
+Event<Timestamp>::set (const uint8_t* buf, uint32_t size, Timestamp t)
+{
+ if (_owns_buf) {
+ if (_size < size) {
+ _buf = (uint8_t*) ::realloc(_buf, size);
+ }
+ memcpy (_buf, buf, size);
+ } else {
+ /* XXX this is really dangerous given the
+ const-ness of buf. The API should really
+ intervene here.
+ */
+ _buf = const_cast<uint8_t*> (buf);
+ }
+
+ _time = t;
+ _size = size;
+}
+
+#endif // EVORAL_EVENT_ALLOC
+
+template class Event<Temporal::Beats>;
+template class Event<double>;
+template class Event<int64_t>;
+
+} // namespace Evoral
+
+++ /dev/null
-/*
- * Copyright (C) 2008-2016 David Robillard <d@drobilla.net>
- * Copyright (C) 2009-2017 Paul Davis <paul@linuxaudiosystems.com>
- * Copyright (C) 2010 Carl Hetherington <carl@carlh.net>
- * Copyright (C) 2014-2015 Robin Gareus <robin@gareus.org>
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-
-#include <glib.h>
-
-#include "temporal/beats.h"
-#include "evoral/Event.hpp"
-
-namespace Evoral {
-
-static event_id_t _event_id_counter = 0;
-
-event_id_t
-event_id_counter()
-{
- return g_atomic_int_get (&_event_id_counter);
-}
-
-void
-init_event_id_counter(event_id_t n)
-{
- g_atomic_int_set (&_event_id_counter, n);
-}
-
-event_id_t
-next_event_id ()
-{
- /* TODO: handle 31bit overflow , event_id_t is an int32_t,
- * and libsmf only supports loading uint32_t vlq's, see smf_extract_vlq()
- *
- * event-IDs only have to be unique per .mid file.
- * Previously (Ardour 4.2ish) Ardour re-generated those IDs when loading the
- * file but that lead to .mid files being modified on every load/save.
- *
- * current user-record: is event-counter="276390506" (just abov 2^28)
- */
- return g_atomic_int_add (&_event_id_counter, 1);
-}
-
-#ifdef EVORAL_EVENT_ALLOC
-
-template<typename Timestamp>
-Event<Timestamp>::Event(EventType type, Timestamp time, uint32_t size, uint8_t* buf, bool alloc)
- : _type(type)
- , _time(time)
- , _size(size)
- , _buf(buf)
- , _id(-1)
- , _owns_buf(alloc)
-{
- if (alloc) {
- _buf = (uint8_t*)malloc(_size);
- if (buf) {
- memcpy(_buf, buf, _size);
- } else {
- memset(_buf, 0, _size);
- }
- }
-}
-
-template<typename Timestamp>
-Event<Timestamp>::Event(EventType type,
- Timestamp time,
- uint32_t size,
- const uint8_t* buf)
- : _type(type)
- , _time(time)
- , _size(size)
- , _buf((uint8_t*)malloc(size))
- , _id(-1)
- , _owns_buf(true)
-{
- memcpy(_buf, buf, _size);
-}
-
-template<typename Timestamp>
-Event<Timestamp>::Event(const Event& copy, bool owns_buf)
- : _type(copy._type)
- , _time(copy._time)
- , _size(copy._size)
- , _buf(copy._buf)
- , _id (next_event_id ())
- , _owns_buf(owns_buf)
-{
- if (owns_buf) {
- _buf = (uint8_t*)malloc(_size);
- if (copy._buf) {
- memcpy(_buf, copy._buf, _size);
- } else {
- memset(_buf, 0, _size);
- }
- }
-}
-
-template<typename Timestamp>
-Event<Timestamp>::~Event() {
- if (_owns_buf) {
- free(_buf);
- }
-}
-
-template<typename Timestamp>
-void
-Event<Timestamp>::assign(const Event& other)
-{
- _id = other._id;
- _type = other._type;
- _time = other._time;
- _owns_buf = other._owns_buf;
- if (_owns_buf) {
- if (other._buf) {
- if (other._size > _size) {
- _buf = (uint8_t*)::realloc(_buf, other._size);
- }
- memcpy(_buf, other._buf, other._size);
- } else {
- free(_buf);
- _buf = NULL;
- }
- } else {
- _buf = other._buf;
- }
-
- _size = other._size;
-}
-
-template<typename Timestamp>
-void
-Event<Timestamp>::set (const uint8_t* buf, uint32_t size, Timestamp t)
-{
- if (_owns_buf) {
- if (_size < size) {
- _buf = (uint8_t*) ::realloc(_buf, size);
- }
- memcpy (_buf, buf, size);
- } else {
- /* XXX this is really dangerous given the
- const-ness of buf. The API should really
- intervene here.
- */
- _buf = const_cast<uint8_t*> (buf);
- }
-
- _time = t;
- _size = size;
-}
-
-#endif // EVORAL_EVENT_ALLOC
-
-template class Event<Temporal::Beats>;
-template class Event<double>;
-template class Event<int64_t>;
-
-} // namespace Evoral
-
--- /dev/null
+/*
+ * Copyright (C) 2008-2016 David Robillard <d@drobilla.net>
+ * Copyright (C) 2009-2017 Paul Davis <paul@linuxaudiosystems.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include <cassert>
+#include <iostream>
+#include <limits>
+#include <glib.h>
+#ifndef COMPILER_MSVC
+#include "evoral/Note.h"
+#endif
+
+#include "temporal/beats.h"
+
+namespace Evoral {
+
+template<typename Time>
+Note<Time>::Note(uint8_t chan, Time t, Time l, uint8_t n, uint8_t v)
+ : _on_event (MIDI_EVENT, t, 3, NULL, true)
+ , _off_event (MIDI_EVENT, t + l, 3, NULL, true)
+{
+ assert(chan < 16);
+
+ _on_event.buffer()[0] = MIDI_CMD_NOTE_ON + chan;
+ _on_event.buffer()[1] = n;
+ _on_event.buffer()[2] = v;
+
+ _off_event.buffer()[0] = MIDI_CMD_NOTE_OFF + chan;
+ _off_event.buffer()[1] = n;
+ _off_event.buffer()[2] = 0x40;
+
+ assert(time() == t);
+ assert(length() == l);
+ assert(note() == n);
+ assert(velocity() == v);
+ assert(_on_event.channel() == _off_event.channel());
+ assert(channel() == chan);
+}
+
+
+template<typename Time>
+Note<Time>::Note(const Note<Time>& copy)
+ : _on_event(copy._on_event, true)
+ , _off_event(copy._off_event, true)
+{
+ assert(_on_event.buffer());
+ assert(_off_event.buffer());
+ /*
+ assert(copy._on_event.size == 3);
+ _on_event.buffer = _on_event_buffer;
+ memcpy(_on_event_buffer, copy._on_event_buffer, 3);
+
+ assert(copy._off_event.size == 3);
+ _off_event.buffer = _off_event_buffer;
+ memcpy(_off_event_buffer, copy._off_event_buffer, 3);
+ */
+
+ assert(time() == copy.time());
+ assert(end_time() == copy.end_time());
+ assert(length() == copy.length());
+ assert(note() == copy.note());
+ assert(velocity() == copy.velocity());
+ assert(_on_event.channel() == _off_event.channel());
+ assert(channel() == copy.channel());
+}
+
+template<typename Time>
+Note<Time>::~Note()
+{
+}
+
+template<typename Time> void
+Note<Time>::set_id (event_id_t id)
+{
+ _on_event.set_id (id);
+ _off_event.set_id (id);
+}
+
+template class Note<Temporal::Beats>;
+
+} // namespace Evoral
+
+++ /dev/null
-/*
- * Copyright (C) 2008-2016 David Robillard <d@drobilla.net>
- * Copyright (C) 2009-2017 Paul Davis <paul@linuxaudiosystems.com>
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-
-#include <cassert>
-#include <iostream>
-#include <limits>
-#include <glib.h>
-#ifndef COMPILER_MSVC
-#include "evoral/Note.hpp"
-#endif
-
-#include "temporal/beats.h"
-
-namespace Evoral {
-
-template<typename Time>
-Note<Time>::Note(uint8_t chan, Time t, Time l, uint8_t n, uint8_t v)
- : _on_event (MIDI_EVENT, t, 3, NULL, true)
- , _off_event (MIDI_EVENT, t + l, 3, NULL, true)
-{
- assert(chan < 16);
-
- _on_event.buffer()[0] = MIDI_CMD_NOTE_ON + chan;
- _on_event.buffer()[1] = n;
- _on_event.buffer()[2] = v;
-
- _off_event.buffer()[0] = MIDI_CMD_NOTE_OFF + chan;
- _off_event.buffer()[1] = n;
- _off_event.buffer()[2] = 0x40;
-
- assert(time() == t);
- assert(length() == l);
- assert(note() == n);
- assert(velocity() == v);
- assert(_on_event.channel() == _off_event.channel());
- assert(channel() == chan);
-}
-
-
-template<typename Time>
-Note<Time>::Note(const Note<Time>& copy)
- : _on_event(copy._on_event, true)
- , _off_event(copy._off_event, true)
-{
- assert(_on_event.buffer());
- assert(_off_event.buffer());
- /*
- assert(copy._on_event.size == 3);
- _on_event.buffer = _on_event_buffer;
- memcpy(_on_event_buffer, copy._on_event_buffer, 3);
-
- assert(copy._off_event.size == 3);
- _off_event.buffer = _off_event_buffer;
- memcpy(_off_event_buffer, copy._off_event_buffer, 3);
- */
-
- assert(time() == copy.time());
- assert(end_time() == copy.end_time());
- assert(length() == copy.length());
- assert(note() == copy.note());
- assert(velocity() == copy.velocity());
- assert(_on_event.channel() == _off_event.channel());
- assert(channel() == copy.channel());
-}
-
-template<typename Time>
-Note<Time>::~Note()
-{
-}
-
-template<typename Time> void
-Note<Time>::set_id (event_id_t id)
-{
- _on_event.set_id (id);
- _off_event.set_id (id);
-}
-
-template class Note<Temporal::Beats>;
-
-} // namespace Evoral
-
--- /dev/null
+/*
+ * Copyright (C) 2008-2015 David Robillard <d@drobilla.net>
+ * Copyright (C) 2009-2018 Paul Davis <paul@linuxaudiosystems.com>
+ * Copyright (C) 2009 Hans Baier <hansfbaier@googlemail.com>
+ * Copyright (C) 2010-2012 Carl Hetherington <carl@carlh.net>
+ * Copyright (C) 2014-2018 Robin Gareus <robin@gareus.org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include <algorithm>
+#include <cassert>
+#include <cmath>
+#include <iostream>
+#include <stdint.h>
+
+#include <glib/gstdio.h>
+
+#include "libsmf/smf.h"
+
+#include "evoral/Event.h"
+#include "evoral/SMF.h"
+#include "evoral/midi_util.h"
+
+#ifdef COMPILER_MSVC
+extern double round(double x);
+#endif
+
+using namespace std;
+
+namespace Evoral {
+
+SMF::SMF()
+ : _smf (0)
+ , _smf_track (0)
+ , _empty (true)
+ , _type0 (false)
+ {};
+
+SMF::~SMF()
+{
+ close ();
+}
+
+uint16_t
+SMF::num_tracks() const
+{
+ Glib::Threads::Mutex::Lock lm (_smf_lock);
+ return _smf ? _smf->number_of_tracks : 0;
+}
+
+uint16_t
+SMF::ppqn() const
+{
+ Glib::Threads::Mutex::Lock lm (_smf_lock);
+ return _smf->ppqn;
+}
+
+/** Seek to the specified track (1-based indexing)
+ * \return 0 on success
+ */
+int
+SMF::seek_to_track(int track)
+{
+ Glib::Threads::Mutex::Lock lm (_smf_lock);
+ _smf_track = smf_get_track_by_number(_smf, track);
+ if (_smf_track != NULL) {
+ _smf_track->next_event_number = (_smf_track->number_of_events == 0) ? 0 : 1;
+ return 0;
+ } else {
+ return -1;
+ }
+}
+
+/** Attempt to open the SMF file just to see if it is valid.
+ *
+ * \return true on success
+ * false on failure
+ */
+bool
+SMF::test(const std::string& path)
+{
+ FILE* f = g_fopen(path.c_str(), "r");
+ if (f == 0) {
+ return false;
+ }
+
+ smf_t* test_smf = smf_load(f);
+ fclose(f);
+
+ if (!test_smf) {
+ return false;
+ }
+ if (test_smf) {
+ smf_delete(test_smf);
+ }
+ return true;
+}
+
+/** Attempt to open the SMF file for reading and/or writing.
+ *
+ * \return 0 on success
+ * -1 if the file can not be opened or created
+ * -2 if the file exists but specified track does not exist
+ */
+int
+SMF::open(const std::string& path, int track)
+{
+ Glib::Threads::Mutex::Lock lm (_smf_lock);
+
+ _type0 = false;
+ _type0channels.clear ();
+
+ assert(track >= 1);
+ if (_smf) {
+ smf_delete(_smf);
+ }
+
+ FILE* f = g_fopen(path.c_str(), "r");
+ if (f == 0) {
+ return -1;
+ } else if ((_smf = smf_load(f)) == 0) {
+ fclose(f);
+ return -1;
+ } else if ((_smf_track = smf_get_track_by_number(_smf, track)) == 0) {
+ fclose(f);
+ return -2;
+ }
+
+ //cerr << "Track " << track << " # events: " << _smf_track->number_of_events << endl;
+ if (_smf_track->number_of_events == 0) {
+ _smf_track->next_event_number = 0;
+ _empty = true;
+ } else {
+ _smf_track->next_event_number = 1;
+ _empty = false;
+ }
+
+ fclose(f);
+
+ lm.release ();
+ if (_smf->format == 0 && _smf->number_of_tracks == 1 && !_empty) {
+ // type-0 file: scan file for # of used channels.
+ int ret;
+ uint32_t delta_t = 0;
+ uint32_t size = 0;
+ uint8_t* buf = NULL;
+ event_id_t event_id = 0;
+ seek_to_start();
+ while ((ret = read_event (&delta_t, &size, &buf, &event_id)) >= 0) {
+ if (ret == 0) {
+ continue;
+ }
+ if (size == 0) {
+ break;
+ }
+ uint8_t type = buf[0] & 0xf0;
+ uint8_t chan = buf[0] & 0x0f;
+ if (type < 0x80 || type > 0xE0) {
+ continue;
+ }
+ _type0channels.insert(chan);
+ }
+ _type0 = true;
+ seek_to_start();
+ }
+ return 0;
+}
+
+
+/** Attempt to create a new SMF file for reading and/or writing.
+ *
+ * \return 0 on success
+ * -1 if the file can not be created
+ * -2 if the track can not be created
+ */
+int
+SMF::create(const std::string& path, int track, uint16_t ppqn)
+{
+ Glib::Threads::Mutex::Lock lm (_smf_lock);
+
+ assert(track >= 1);
+ if (_smf) {
+ smf_delete(_smf);
+ }
+
+ _smf = smf_new();
+
+ if (_smf == NULL) {
+ return -1;
+ }
+
+ if (smf_set_ppqn(_smf, ppqn) != 0) {
+ return -1;
+ }
+
+ for (int i = 0; i < track; ++i) {
+ _smf_track = smf_track_new();
+ if (!_smf_track) {
+ return -2;
+ }
+ smf_add_track(_smf, _smf_track);
+ }
+
+ _smf_track = smf_get_track_by_number(_smf, track);
+ if (!_smf_track)
+ return -2;
+
+ _smf_track->next_event_number = 0;
+
+ {
+ /* put a stub file on disk */
+
+ FILE* f = g_fopen (path.c_str(), "w+");
+ if (f == 0) {
+ return -1;
+ }
+
+ if (smf_save (_smf, f)) {
+ fclose (f);
+ return -1;
+ }
+ fclose (f);
+ }
+
+ _empty = true;
+ _type0 = false;
+ _type0channels.clear ();
+
+ return 0;
+}
+
+void
+SMF::close()
+{
+ Glib::Threads::Mutex::Lock lm (_smf_lock);
+
+ if (_smf) {
+ smf_delete(_smf);
+ _smf = 0;
+ _smf_track = 0;
+ _type0 = false;
+ _type0channels.clear ();
+ }
+}
+
+void
+SMF::seek_to_start() const
+{
+ Glib::Threads::Mutex::Lock lm (_smf_lock);
+ if (_smf_track) {
+ _smf_track->next_event_number = std::min(_smf_track->number_of_events, (size_t)1);
+ } else {
+ cerr << "WARNING: SMF seek_to_start() with no track" << endl;
+ }
+}
+
+/** Read an event from the current position in file.
+ *
+ * File position MUST be at the beginning of a delta time, or this will die very messily.
+ * ev.buffer must be of size ev.size, and large enough for the event. The returned event
+ * will have it's time field set to it's delta time, in SMF tempo-based ticks, using the
+ * rate given by ppqn() (it is the caller's responsibility to calculate a real time).
+ *
+ * \a buf must be a pointer to a buffer allocated with malloc, or a pointer to NULL.
+ * \a size must be the capacity of \a buf. If it is not large enough, \a buf will
+ * be reallocated and *size will be set to the new size of buf.
+ *
+ * if the event is a meta-event and is an Evoral Note ID, then \a note_id will be set
+ * to the value of the NoteID; otherwise, meta-events will set \a note_id to -1.
+ *
+ * \return event length (including status byte) on success, 0 if event was
+ * a meta event, or -1 on EOF (or end of track).
+ */
+int
+SMF::read_event(uint32_t* delta_t, uint32_t* size, uint8_t** buf, event_id_t* note_id) const
+{
+ Glib::Threads::Mutex::Lock lm (_smf_lock);
+
+ smf_event_t* event;
+
+ assert(delta_t);
+ assert(size);
+ assert(buf);
+ assert(note_id);
+
+ if ((event = smf_track_get_next_event(_smf_track)) != NULL) {
+
+ *delta_t = event->delta_time_pulses;
+
+ if (smf_event_is_metadata(event)) {
+ *note_id = -1; // "no note id in this meta-event */
+
+ if (event->midi_buffer[1] == 0x7f) { // Sequencer-specific
+
+ uint32_t evsize;
+ uint32_t lenlen;
+
+ if (smf_extract_vlq (&event->midi_buffer[2], event->midi_buffer_length-2, &evsize, &lenlen) == 0) {
+
+ if (event->midi_buffer[2+lenlen] == 0x99 && // Evoral
+ event->midi_buffer[3+lenlen] == 0x1) { // Evoral Note ID
+
+ uint32_t id;
+ uint32_t idlen;
+
+ if (smf_extract_vlq (&event->midi_buffer[4+lenlen], event->midi_buffer_length-(4+lenlen), &id, &idlen) == 0) {
+ *note_id = id;
+ }
+ }
+ }
+ }
+ return 0; /* this is a meta-event */
+ }
+
+ int event_size = event->midi_buffer_length;
+ assert(event_size > 0);
+
+ // Make sure we have enough scratch buffer
+ if (*size < (unsigned)event_size) {
+ *buf = (uint8_t*)realloc(*buf, event_size);
+ }
+ assert (*buf);
+ memcpy(*buf, event->midi_buffer, size_t(event_size));
+ *size = event_size;
+ if (((*buf)[0] & 0xF0) == 0x90 && (*buf)[2] == 0) {
+ /* normalize note on with velocity 0 to proper note off */
+ (*buf)[0] = 0x80 | ((*buf)[0] & 0x0F); /* note off */
+ (*buf)[2] = 0x40; /* default velocity */
+ }
+
+ if (!midi_event_is_valid(*buf, *size)) {
+ cerr << "WARNING: SMF ignoring illegal MIDI event" << endl;
+ *size = 0;
+ return -1;
+ }
+
+ /* printf("SMF::read_event @ %u: ", *delta_t);
+ for (size_t i = 0; i < *size; ++i) {
+ printf("%X ", (*buf)[i]);
+ } printf("\n") */
+
+ return event_size;
+ } else {
+ return -1;
+ }
+}
+
+void
+SMF::append_event_delta(uint32_t delta_t, uint32_t size, const uint8_t* buf, event_id_t note_id)
+{
+ Glib::Threads::Mutex::Lock lm (_smf_lock);
+
+ if (size == 0) {
+ return;
+ }
+
+ /* printf("SMF::append_event_delta @ %u:", delta_t);
+ for (size_t i = 0; i < size; ++i) {
+ printf("%X ", buf[i]);
+ } printf("\n"); */
+
+ switch (buf[0]) {
+ case 0xf1:
+ case 0xf2:
+ case 0xf3:
+ case 0xf4:
+ case 0xf5:
+ case 0xf6:
+ case 0xf8:
+ case 0xf9:
+ case 0xfa:
+ case 0xfb:
+ case 0xfc:
+ case 0xfd:
+ case 0xfe:
+ case 0xff:
+ /* System Real Time or System Common event: not valid in SMF
+ */
+ return;
+ }
+
+ if (!midi_event_is_valid(buf, size)) {
+ cerr << "WARNING: SMF ignoring illegal MIDI event" << endl;
+ return;
+ }
+
+ smf_event_t* event;
+
+ /* XXX july 2010: currently only store event ID's for notes, program changes and bank changes
+ */
+
+ uint8_t const c = buf[0] & 0xf0;
+ bool const store_id = (
+ c == MIDI_CMD_NOTE_ON ||
+ c == MIDI_CMD_NOTE_OFF ||
+ c == MIDI_CMD_NOTE_PRESSURE ||
+ c == MIDI_CMD_PGM_CHANGE ||
+ (c == MIDI_CMD_CONTROL && (buf[1] == MIDI_CTL_MSB_BANK || buf[1] == MIDI_CTL_LSB_BANK))
+ );
+
+ if (store_id && note_id >= 0) {
+ int idlen;
+ int lenlen;
+ uint8_t idbuf[16];
+ uint8_t lenbuf[16];
+
+ event = smf_event_new ();
+ assert(event != NULL);
+
+ /* generate VLQ representation of note ID */
+ idlen = smf_format_vlq (idbuf, sizeof(idbuf), note_id);
+
+ /* generate VLQ representation of meta event length,
+ which is the idlen + 2 bytes (Evoral type ID plus Note ID type)
+ */
+
+ lenlen = smf_format_vlq (lenbuf, sizeof(lenbuf), idlen+2);
+
+ event->midi_buffer_length = 2 + lenlen + 2 + idlen;
+ /* this should be allocated by malloc(3) because libsmf will
+ call free(3) on it
+ */
+ event->midi_buffer = (uint8_t*) malloc (sizeof(uint8_t) * event->midi_buffer_length);
+
+ event->midi_buffer[0] = 0xff; // Meta-event
+ event->midi_buffer[1] = 0x7f; // Sequencer-specific
+ memcpy (&event->midi_buffer[2], lenbuf, lenlen);
+ event->midi_buffer[2+lenlen] = 0x99; // Evoral type ID
+ event->midi_buffer[3+lenlen] = 0x1; // Evoral type Note ID
+ memcpy (&event->midi_buffer[4+lenlen], idbuf, idlen);
+
+ assert(_smf_track);
+ smf_track_add_event_delta_pulses(_smf_track, event, 0);
+ }
+
+ event = smf_event_new_from_pointer(buf, size);
+ assert(event != NULL);
+
+ assert(_smf_track);
+ smf_track_add_event_delta_pulses(_smf_track, event, delta_t);
+ _empty = false;
+}
+
+void
+SMF::begin_write()
+{
+ Glib::Threads::Mutex::Lock lm (_smf_lock);
+
+ assert(_smf_track);
+ smf_track_delete(_smf_track);
+
+ _smf_track = smf_track_new();
+ assert(_smf_track);
+
+ smf_add_track(_smf, _smf_track);
+ assert(_smf->number_of_tracks == 1);
+}
+
+void
+SMF::end_write(string const & path)
+{
+ Glib::Threads::Mutex::Lock lm (_smf_lock);
+
+ if (!_smf) {
+ return;
+ }
+
+ FILE* f = g_fopen (path.c_str(), "w+");
+ if (f == 0) {
+ throw FileError (path);
+ }
+
+ if (smf_save(_smf, f) != 0) {
+ fclose(f);
+ throw FileError (path);
+ }
+
+ fclose(f);
+}
+
+double
+SMF::round_to_file_precision (double val) const
+{
+ double div = ppqn();
+
+ return round (val * div) / div;
+}
+
+void
+SMF::track_names(vector<string>& names) const
+{
+ if (!_smf) {
+ return;
+ }
+
+ names.clear ();
+
+ Glib::Threads::Mutex::Lock lm (_smf_lock);
+
+ for (uint16_t n = 0; n < _smf->number_of_tracks; ++n) {
+ smf_track_t* trk = smf_get_track_by_number (_smf, n+1);
+ if (!trk) {
+ names.push_back (string());
+ } else {
+ if (trk->name) {
+ names.push_back (trk->name);
+ } else {
+ names.push_back (string());
+ }
+ }
+ }
+}
+
+void
+SMF::instrument_names(vector<string>& names) const
+{
+ if (!_smf) {
+ return;
+ }
+
+ names.clear ();
+
+ Glib::Threads::Mutex::Lock lm (_smf_lock);
+
+ for (uint16_t n = 0; n < _smf->number_of_tracks; ++n) {
+ smf_track_t* trk = smf_get_track_by_number (_smf, n+1);
+ if (!trk) {
+ names.push_back (string());
+ } else {
+ if (trk->instrument) {
+ names.push_back (trk->instrument);
+ } else {
+ names.push_back (string());
+ }
+ }
+ }
+}
+
+SMF::Tempo::Tempo (smf_tempo_t* smft)
+ : time_pulses (smft->time_pulses)
+ , time_seconds (smft->time_seconds)
+ , microseconds_per_quarter_note (smft->microseconds_per_quarter_note)
+ , numerator (smft->numerator)
+ , denominator (smft->denominator)
+ , clocks_per_click (smft->clocks_per_click)
+ , notes_per_note (smft->notes_per_note)
+{
+}
+
+int
+SMF::num_tempos () const
+{
+ assert (_smf);
+ return smf_get_tempo_count (_smf);
+}
+
+SMF::Tempo*
+SMF::tempo_at_smf_pulse (size_t smf_pulse) const
+{
+ smf_tempo_t* t = smf_get_tempo_by_seconds (_smf, smf_pulse);
+ if (!t) {
+ return 0;
+ }
+ return new Tempo (t);
+}
+
+SMF::Tempo*
+SMF::tempo_at_seconds (double seconds) const
+{
+ smf_tempo_t* t = smf_get_tempo_by_seconds (_smf, seconds);
+ if (!t) {
+ return 0;
+ }
+ return new Tempo (t);
+}
+
+SMF::Tempo*
+SMF::nth_tempo (size_t n) const
+{
+ assert (_smf);
+
+ smf_tempo_t* t = smf_get_tempo_by_number (_smf, n);
+ if (!t) {
+ return 0;
+ }
+
+ return new Tempo (t);
+}
+
+} // namespace Evoral
+++ /dev/null
-/*
- * Copyright (C) 2008-2015 David Robillard <d@drobilla.net>
- * Copyright (C) 2009-2018 Paul Davis <paul@linuxaudiosystems.com>
- * Copyright (C) 2009 Hans Baier <hansfbaier@googlemail.com>
- * Copyright (C) 2010-2012 Carl Hetherington <carl@carlh.net>
- * Copyright (C) 2014-2018 Robin Gareus <robin@gareus.org>
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-
-#include <algorithm>
-#include <cassert>
-#include <cmath>
-#include <iostream>
-#include <stdint.h>
-
-#include <glib/gstdio.h>
-
-#include "libsmf/smf.h"
-
-#include "evoral/Event.hpp"
-#include "evoral/SMF.hpp"
-#include "evoral/midi_util.h"
-
-#ifdef COMPILER_MSVC
-extern double round(double x);
-#endif
-
-using namespace std;
-
-namespace Evoral {
-
-SMF::SMF()
- : _smf (0)
- , _smf_track (0)
- , _empty (true)
- , _type0 (false)
- {};
-
-SMF::~SMF()
-{
- close ();
-}
-
-uint16_t
-SMF::num_tracks() const
-{
- Glib::Threads::Mutex::Lock lm (_smf_lock);
- return _smf ? _smf->number_of_tracks : 0;
-}
-
-uint16_t
-SMF::ppqn() const
-{
- Glib::Threads::Mutex::Lock lm (_smf_lock);
- return _smf->ppqn;
-}
-
-/** Seek to the specified track (1-based indexing)
- * \return 0 on success
- */
-int
-SMF::seek_to_track(int track)
-{
- Glib::Threads::Mutex::Lock lm (_smf_lock);
- _smf_track = smf_get_track_by_number(_smf, track);
- if (_smf_track != NULL) {
- _smf_track->next_event_number = (_smf_track->number_of_events == 0) ? 0 : 1;
- return 0;
- } else {
- return -1;
- }
-}
-
-/** Attempt to open the SMF file just to see if it is valid.
- *
- * \return true on success
- * false on failure
- */
-bool
-SMF::test(const std::string& path)
-{
- FILE* f = g_fopen(path.c_str(), "r");
- if (f == 0) {
- return false;
- }
-
- smf_t* test_smf = smf_load(f);
- fclose(f);
-
- if (!test_smf) {
- return false;
- }
- if (test_smf) {
- smf_delete(test_smf);
- }
- return true;
-}
-
-/** Attempt to open the SMF file for reading and/or writing.
- *
- * \return 0 on success
- * -1 if the file can not be opened or created
- * -2 if the file exists but specified track does not exist
- */
-int
-SMF::open(const std::string& path, int track)
-{
- Glib::Threads::Mutex::Lock lm (_smf_lock);
-
- _type0 = false;
- _type0channels.clear ();
-
- assert(track >= 1);
- if (_smf) {
- smf_delete(_smf);
- }
-
- FILE* f = g_fopen(path.c_str(), "r");
- if (f == 0) {
- return -1;
- } else if ((_smf = smf_load(f)) == 0) {
- fclose(f);
- return -1;
- } else if ((_smf_track = smf_get_track_by_number(_smf, track)) == 0) {
- fclose(f);
- return -2;
- }
-
- //cerr << "Track " << track << " # events: " << _smf_track->number_of_events << endl;
- if (_smf_track->number_of_events == 0) {
- _smf_track->next_event_number = 0;
- _empty = true;
- } else {
- _smf_track->next_event_number = 1;
- _empty = false;
- }
-
- fclose(f);
-
- lm.release ();
- if (_smf->format == 0 && _smf->number_of_tracks == 1 && !_empty) {
- // type-0 file: scan file for # of used channels.
- int ret;
- uint32_t delta_t = 0;
- uint32_t size = 0;
- uint8_t* buf = NULL;
- event_id_t event_id = 0;
- seek_to_start();
- while ((ret = read_event (&delta_t, &size, &buf, &event_id)) >= 0) {
- if (ret == 0) {
- continue;
- }
- if (size == 0) {
- break;
- }
- uint8_t type = buf[0] & 0xf0;
- uint8_t chan = buf[0] & 0x0f;
- if (type < 0x80 || type > 0xE0) {
- continue;
- }
- _type0channels.insert(chan);
- }
- _type0 = true;
- seek_to_start();
- }
- return 0;
-}
-
-
-/** Attempt to create a new SMF file for reading and/or writing.
- *
- * \return 0 on success
- * -1 if the file can not be created
- * -2 if the track can not be created
- */
-int
-SMF::create(const std::string& path, int track, uint16_t ppqn)
-{
- Glib::Threads::Mutex::Lock lm (_smf_lock);
-
- assert(track >= 1);
- if (_smf) {
- smf_delete(_smf);
- }
-
- _smf = smf_new();
-
- if (_smf == NULL) {
- return -1;
- }
-
- if (smf_set_ppqn(_smf, ppqn) != 0) {
- return -1;
- }
-
- for (int i = 0; i < track; ++i) {
- _smf_track = smf_track_new();
- if (!_smf_track) {
- return -2;
- }
- smf_add_track(_smf, _smf_track);
- }
-
- _smf_track = smf_get_track_by_number(_smf, track);
- if (!_smf_track)
- return -2;
-
- _smf_track->next_event_number = 0;
-
- {
- /* put a stub file on disk */
-
- FILE* f = g_fopen (path.c_str(), "w+");
- if (f == 0) {
- return -1;
- }
-
- if (smf_save (_smf, f)) {
- fclose (f);
- return -1;
- }
- fclose (f);
- }
-
- _empty = true;
- _type0 = false;
- _type0channels.clear ();
-
- return 0;
-}
-
-void
-SMF::close()
-{
- Glib::Threads::Mutex::Lock lm (_smf_lock);
-
- if (_smf) {
- smf_delete(_smf);
- _smf = 0;
- _smf_track = 0;
- _type0 = false;
- _type0channels.clear ();
- }
-}
-
-void
-SMF::seek_to_start() const
-{
- Glib::Threads::Mutex::Lock lm (_smf_lock);
- if (_smf_track) {
- _smf_track->next_event_number = std::min(_smf_track->number_of_events, (size_t)1);
- } else {
- cerr << "WARNING: SMF seek_to_start() with no track" << endl;
- }
-}
-
-/** Read an event from the current position in file.
- *
- * File position MUST be at the beginning of a delta time, or this will die very messily.
- * ev.buffer must be of size ev.size, and large enough for the event. The returned event
- * will have it's time field set to it's delta time, in SMF tempo-based ticks, using the
- * rate given by ppqn() (it is the caller's responsibility to calculate a real time).
- *
- * \a buf must be a pointer to a buffer allocated with malloc, or a pointer to NULL.
- * \a size must be the capacity of \a buf. If it is not large enough, \a buf will
- * be reallocated and *size will be set to the new size of buf.
- *
- * if the event is a meta-event and is an Evoral Note ID, then \a note_id will be set
- * to the value of the NoteID; otherwise, meta-events will set \a note_id to -1.
- *
- * \return event length (including status byte) on success, 0 if event was
- * a meta event, or -1 on EOF (or end of track).
- */
-int
-SMF::read_event(uint32_t* delta_t, uint32_t* size, uint8_t** buf, event_id_t* note_id) const
-{
- Glib::Threads::Mutex::Lock lm (_smf_lock);
-
- smf_event_t* event;
-
- assert(delta_t);
- assert(size);
- assert(buf);
- assert(note_id);
-
- if ((event = smf_track_get_next_event(_smf_track)) != NULL) {
-
- *delta_t = event->delta_time_pulses;
-
- if (smf_event_is_metadata(event)) {
- *note_id = -1; // "no note id in this meta-event */
-
- if (event->midi_buffer[1] == 0x7f) { // Sequencer-specific
-
- uint32_t evsize;
- uint32_t lenlen;
-
- if (smf_extract_vlq (&event->midi_buffer[2], event->midi_buffer_length-2, &evsize, &lenlen) == 0) {
-
- if (event->midi_buffer[2+lenlen] == 0x99 && // Evoral
- event->midi_buffer[3+lenlen] == 0x1) { // Evoral Note ID
-
- uint32_t id;
- uint32_t idlen;
-
- if (smf_extract_vlq (&event->midi_buffer[4+lenlen], event->midi_buffer_length-(4+lenlen), &id, &idlen) == 0) {
- *note_id = id;
- }
- }
- }
- }
- return 0; /* this is a meta-event */
- }
-
- int event_size = event->midi_buffer_length;
- assert(event_size > 0);
-
- // Make sure we have enough scratch buffer
- if (*size < (unsigned)event_size) {
- *buf = (uint8_t*)realloc(*buf, event_size);
- }
- assert (*buf);
- memcpy(*buf, event->midi_buffer, size_t(event_size));
- *size = event_size;
- if (((*buf)[0] & 0xF0) == 0x90 && (*buf)[2] == 0) {
- /* normalize note on with velocity 0 to proper note off */
- (*buf)[0] = 0x80 | ((*buf)[0] & 0x0F); /* note off */
- (*buf)[2] = 0x40; /* default velocity */
- }
-
- if (!midi_event_is_valid(*buf, *size)) {
- cerr << "WARNING: SMF ignoring illegal MIDI event" << endl;
- *size = 0;
- return -1;
- }
-
- /* printf("SMF::read_event @ %u: ", *delta_t);
- for (size_t i = 0; i < *size; ++i) {
- printf("%X ", (*buf)[i]);
- } printf("\n") */
-
- return event_size;
- } else {
- return -1;
- }
-}
-
-void
-SMF::append_event_delta(uint32_t delta_t, uint32_t size, const uint8_t* buf, event_id_t note_id)
-{
- Glib::Threads::Mutex::Lock lm (_smf_lock);
-
- if (size == 0) {
- return;
- }
-
- /* printf("SMF::append_event_delta @ %u:", delta_t);
- for (size_t i = 0; i < size; ++i) {
- printf("%X ", buf[i]);
- } printf("\n"); */
-
- switch (buf[0]) {
- case 0xf1:
- case 0xf2:
- case 0xf3:
- case 0xf4:
- case 0xf5:
- case 0xf6:
- case 0xf8:
- case 0xf9:
- case 0xfa:
- case 0xfb:
- case 0xfc:
- case 0xfd:
- case 0xfe:
- case 0xff:
- /* System Real Time or System Common event: not valid in SMF
- */
- return;
- }
-
- if (!midi_event_is_valid(buf, size)) {
- cerr << "WARNING: SMF ignoring illegal MIDI event" << endl;
- return;
- }
-
- smf_event_t* event;
-
- /* XXX july 2010: currently only store event ID's for notes, program changes and bank changes
- */
-
- uint8_t const c = buf[0] & 0xf0;
- bool const store_id = (
- c == MIDI_CMD_NOTE_ON ||
- c == MIDI_CMD_NOTE_OFF ||
- c == MIDI_CMD_NOTE_PRESSURE ||
- c == MIDI_CMD_PGM_CHANGE ||
- (c == MIDI_CMD_CONTROL && (buf[1] == MIDI_CTL_MSB_BANK || buf[1] == MIDI_CTL_LSB_BANK))
- );
-
- if (store_id && note_id >= 0) {
- int idlen;
- int lenlen;
- uint8_t idbuf[16];
- uint8_t lenbuf[16];
-
- event = smf_event_new ();
- assert(event != NULL);
-
- /* generate VLQ representation of note ID */
- idlen = smf_format_vlq (idbuf, sizeof(idbuf), note_id);
-
- /* generate VLQ representation of meta event length,
- which is the idlen + 2 bytes (Evoral type ID plus Note ID type)
- */
-
- lenlen = smf_format_vlq (lenbuf, sizeof(lenbuf), idlen+2);
-
- event->midi_buffer_length = 2 + lenlen + 2 + idlen;
- /* this should be allocated by malloc(3) because libsmf will
- call free(3) on it
- */
- event->midi_buffer = (uint8_t*) malloc (sizeof(uint8_t) * event->midi_buffer_length);
-
- event->midi_buffer[0] = 0xff; // Meta-event
- event->midi_buffer[1] = 0x7f; // Sequencer-specific
- memcpy (&event->midi_buffer[2], lenbuf, lenlen);
- event->midi_buffer[2+lenlen] = 0x99; // Evoral type ID
- event->midi_buffer[3+lenlen] = 0x1; // Evoral type Note ID
- memcpy (&event->midi_buffer[4+lenlen], idbuf, idlen);
-
- assert(_smf_track);
- smf_track_add_event_delta_pulses(_smf_track, event, 0);
- }
-
- event = smf_event_new_from_pointer(buf, size);
- assert(event != NULL);
-
- assert(_smf_track);
- smf_track_add_event_delta_pulses(_smf_track, event, delta_t);
- _empty = false;
-}
-
-void
-SMF::begin_write()
-{
- Glib::Threads::Mutex::Lock lm (_smf_lock);
-
- assert(_smf_track);
- smf_track_delete(_smf_track);
-
- _smf_track = smf_track_new();
- assert(_smf_track);
-
- smf_add_track(_smf, _smf_track);
- assert(_smf->number_of_tracks == 1);
-}
-
-void
-SMF::end_write(string const & path)
-{
- Glib::Threads::Mutex::Lock lm (_smf_lock);
-
- if (!_smf) {
- return;
- }
-
- FILE* f = g_fopen (path.c_str(), "w+");
- if (f == 0) {
- throw FileError (path);
- }
-
- if (smf_save(_smf, f) != 0) {
- fclose(f);
- throw FileError (path);
- }
-
- fclose(f);
-}
-
-double
-SMF::round_to_file_precision (double val) const
-{
- double div = ppqn();
-
- return round (val * div) / div;
-}
-
-void
-SMF::track_names(vector<string>& names) const
-{
- if (!_smf) {
- return;
- }
-
- names.clear ();
-
- Glib::Threads::Mutex::Lock lm (_smf_lock);
-
- for (uint16_t n = 0; n < _smf->number_of_tracks; ++n) {
- smf_track_t* trk = smf_get_track_by_number (_smf, n+1);
- if (!trk) {
- names.push_back (string());
- } else {
- if (trk->name) {
- names.push_back (trk->name);
- } else {
- names.push_back (string());
- }
- }
- }
-}
-
-void
-SMF::instrument_names(vector<string>& names) const
-{
- if (!_smf) {
- return;
- }
-
- names.clear ();
-
- Glib::Threads::Mutex::Lock lm (_smf_lock);
-
- for (uint16_t n = 0; n < _smf->number_of_tracks; ++n) {
- smf_track_t* trk = smf_get_track_by_number (_smf, n+1);
- if (!trk) {
- names.push_back (string());
- } else {
- if (trk->instrument) {
- names.push_back (trk->instrument);
- } else {
- names.push_back (string());
- }
- }
- }
-}
-
-SMF::Tempo::Tempo (smf_tempo_t* smft)
- : time_pulses (smft->time_pulses)
- , time_seconds (smft->time_seconds)
- , microseconds_per_quarter_note (smft->microseconds_per_quarter_note)
- , numerator (smft->numerator)
- , denominator (smft->denominator)
- , clocks_per_click (smft->clocks_per_click)
- , notes_per_note (smft->notes_per_note)
-{
-}
-
-int
-SMF::num_tempos () const
-{
- assert (_smf);
- return smf_get_tempo_count (_smf);
-}
-
-SMF::Tempo*
-SMF::tempo_at_smf_pulse (size_t smf_pulse) const
-{
- smf_tempo_t* t = smf_get_tempo_by_seconds (_smf, smf_pulse);
- if (!t) {
- return 0;
- }
- return new Tempo (t);
-}
-
-SMF::Tempo*
-SMF::tempo_at_seconds (double seconds) const
-{
- smf_tempo_t* t = smf_get_tempo_by_seconds (_smf, seconds);
- if (!t) {
- return 0;
- }
- return new Tempo (t);
-}
-
-SMF::Tempo*
-SMF::nth_tempo (size_t n) const
-{
- assert (_smf);
-
- smf_tempo_t* t = smf_get_tempo_by_number (_smf, n);
- if (!t) {
- return 0;
- }
-
- return new Tempo (t);
-}
-
-} // namespace Evoral
--- /dev/null
+/*
+ * Copyright (C) 2008-2011 David Robillard <d@drobilla.net>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include <cstring>
+#include <cstdio>
+#include <cassert>
+#include <iostream>
+#include <glibmm/miscutils.h>
+
+#include "evoral/midi_util.h"
+#include "evoral/SMFReader.h"
+
+using namespace std;
+
+namespace Evoral {
+
+
+SMFReader::SMFReader(const string& filename)
+ : _fd(NULL)
+ , _ppqn(0)
+ , _track(0)
+ , _track_size(0)
+{
+ if (filename.length() > 0) {
+ open(filename);
+ }
+}
+
+
+SMFReader::~SMFReader()
+{
+ if (_fd)
+ close();
+}
+
+
+bool
+SMFReader::open(const string& filename) throw (logic_error, UnsupportedTime)
+{
+ if (_fd)
+ throw logic_error("Attempt to start new read while write in progress.");
+
+ cout << "Opening SMF file " << filename << " for reading." << endl;
+
+ _fd = fopen(filename.c_str(), "r+");
+
+ if (_fd) {
+ // Read type (bytes 8..9)
+ fseek(_fd, 0, SEEK_SET);
+ char mthd[5];
+ mthd[4] = '\0';
+ fread(mthd, 1, 4, _fd);
+ if (strcmp(mthd, "MThd")) {
+ cerr << filename << " is not an SMF file, aborting." << endl;
+ fclose(_fd);
+ _fd = NULL;
+ return false;
+ }
+
+ // Read type (bytes 8..9)
+ fseek(_fd, 8, SEEK_SET);
+ uint16_t type_be = 0;
+ fread(&type_be, 2, 1, _fd);
+ _type = GUINT16_FROM_BE(type_be);
+
+ // Read number of tracks (bytes 10..11)
+ uint16_t num_tracks_be = 0;
+ fread(&num_tracks_be, 2, 1, _fd);
+ _num_tracks = GUINT16_FROM_BE(num_tracks_be);
+
+ // Read PPQN (bytes 12..13)
+ uint16_t ppqn_be = 0;
+ fread(&ppqn_be, 2, 1, _fd);
+ _ppqn = GUINT16_FROM_BE(ppqn_be);
+
+ // TODO: Absolute (SMPTE seconds) time support
+ if ((_ppqn & 0x8000) != 0)
+ throw UnsupportedTime();
+
+ seek_to_track(1);
+
+ return true;
+ } else {
+ return false;
+ }
+}
+
+
+/** Seek to the start of a given track, starting from 1.
+ * Returns true if specified track was found.
+ */
+bool
+SMFReader::seek_to_track(unsigned track) throw (std::logic_error)
+{
+ if (track == 0)
+ throw logic_error("Seek to track 0 out of range (must be >= 1)");
+
+ if (!_fd)
+ throw logic_error("Attempt to seek to track on unopened SMF file.");
+
+ unsigned track_pos = 0;
+
+ fseek(_fd, 14, SEEK_SET);
+ char id[5];
+ id[4] = '\0';
+ uint32_t chunk_size = 0;
+
+ while (!feof(_fd)) {
+ fread(id, 1, 4, _fd);
+
+ if (!strcmp(id, "MTrk")) {
+ ++track_pos;
+ } else {
+ std::cerr << "Unknown chunk ID " << id << endl;
+ }
+
+ uint32_t chunk_size_be;
+ fread(&chunk_size_be, 4, 1, _fd);
+ chunk_size = GUINT32_FROM_BE(chunk_size_be);
+
+ if (track_pos == track)
+ break;
+
+ fseek(_fd, chunk_size, SEEK_CUR);
+ }
+
+ if (!feof(_fd) && track_pos == track) {
+ _track = track;
+ _track_size = chunk_size;
+ return true;
+ } else {
+ return false;
+ }
+}
+
+
+/** Read an event from the current position in file.
+ *
+ * File position MUST be at the beginning of a delta time, or this will die very messily.
+ * ev.buffer must be of size ev.size, and large enough for the event. The returned event
+ * will have it's time field set to it's delta time (so it's the caller's responsibility
+ * to keep track of delta time, even for ignored events).
+ *
+ * Returns event length (including status byte) on success, 0 if event was
+ * skipped (eg a meta event), or -1 on EOF (or end of track).
+ *
+ * If @a buf is not large enough to hold the event, 0 will be returned, but ev_size
+ * set to the actual size of the event.
+ */
+int
+SMFReader::read_event(size_t buf_len,
+ uint8_t* buf,
+ uint32_t* ev_size,
+ uint32_t* delta_time)
+ throw (std::logic_error, PrematureEOF, CorruptFile)
+{
+ if (_track == 0)
+ throw logic_error("Attempt to read from unopened SMF file");
+
+ if (!_fd || feof(_fd)) {
+ return -1;
+ }
+
+ assert(buf_len > 0);
+ assert(buf);
+ assert(ev_size);
+ assert(delta_time);
+
+ // Running status state
+ static uint8_t last_status = 0;
+ static uint32_t last_size = 0;
+
+ *delta_time = read_var_len(_fd);
+ int status = fgetc(_fd);
+ if (status == EOF)
+ throw PrematureEOF();
+ else if (status > 0xFF)
+ throw CorruptFile();
+
+ if (status < 0x80) {
+ if (last_status == 0)
+ throw CorruptFile();
+ status = last_status;
+ *ev_size = last_size;
+ fseek(_fd, -1, SEEK_CUR);
+ } else {
+ last_status = status;
+ *ev_size = midi_event_size(status);
+ last_size = *ev_size;
+ }
+
+ buf[0] = (uint8_t)status;
+
+ if (status == 0xFF) {
+ *ev_size = 0;
+ if (feof(_fd))
+ throw PrematureEOF();
+ uint8_t type = fgetc(_fd);
+ const uint32_t size = read_var_len(_fd);
+ /*cerr.flags(ios::hex);
+ cerr << "SMF - meta 0x" << (int)type << ", size = ";
+ cerr.flags(ios::dec);
+ cerr << size << endl;*/
+
+ if ((uint8_t)type == 0x2F) {
+ return -1; // we hit the logical EOF anyway...
+ } else {
+ fseek(_fd, size, SEEK_CUR);
+ return 0;
+ }
+ }
+
+ if (*ev_size > buf_len || *ev_size == 0 || feof(_fd)) {
+ //cerr << "SMF - Skipping event" << endl;
+ // Skip event, return 0
+ fseek(_fd, *ev_size - 1, SEEK_CUR);
+ return 0;
+ } else {
+ // Read event, return size
+ if (ferror(_fd))
+ throw CorruptFile();
+
+ fread(buf+1, 1, *ev_size - 1, _fd);
+
+ if ((buf[0] & 0xF0) == 0x90 && buf[2] == 0) {
+ buf[0] = (0x80 | (buf[0] & 0x0F));
+ buf[2] = 0x40;
+ }
+
+ return *ev_size;
+ }
+}
+
+
+void
+SMFReader::close()
+{
+ if (_fd)
+ fclose(_fd);
+
+ _fd = NULL;
+}
+
+
+uint32_t
+SMFReader::read_var_len(FILE* fd) throw (PrematureEOF)
+{
+ if (feof(fd))
+ throw PrematureEOF();
+
+ uint32_t value;
+ uint8_t c;
+
+ if ( (value = getc(fd)) & 0x80 ) {
+ value &= 0x7F;
+ do {
+ if (feof(fd))
+ throw PrematureEOF();
+ value = (value << 7) + ((c = getc(fd)) & 0x7F);
+ } while (c & 0x80);
+ }
+
+ return value;
+}
+
+
+} // namespace Evoral
+
+++ /dev/null
-/*
- * Copyright (C) 2008-2011 David Robillard <d@drobilla.net>
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-
-#include <cstring>
-#include <cstdio>
-#include <cassert>
-#include <iostream>
-#include <glibmm/miscutils.h>
-
-#include "evoral/midi_util.h"
-#include "evoral/SMFReader.hpp"
-
-using namespace std;
-
-namespace Evoral {
-
-
-SMFReader::SMFReader(const string& filename)
- : _fd(NULL)
- , _ppqn(0)
- , _track(0)
- , _track_size(0)
-{
- if (filename.length() > 0) {
- open(filename);
- }
-}
-
-
-SMFReader::~SMFReader()
-{
- if (_fd)
- close();
-}
-
-
-bool
-SMFReader::open(const string& filename) throw (logic_error, UnsupportedTime)
-{
- if (_fd)
- throw logic_error("Attempt to start new read while write in progress.");
-
- cout << "Opening SMF file " << filename << " for reading." << endl;
-
- _fd = fopen(filename.c_str(), "r+");
-
- if (_fd) {
- // Read type (bytes 8..9)
- fseek(_fd, 0, SEEK_SET);
- char mthd[5];
- mthd[4] = '\0';
- fread(mthd, 1, 4, _fd);
- if (strcmp(mthd, "MThd")) {
- cerr << filename << " is not an SMF file, aborting." << endl;
- fclose(_fd);
- _fd = NULL;
- return false;
- }
-
- // Read type (bytes 8..9)
- fseek(_fd, 8, SEEK_SET);
- uint16_t type_be = 0;
- fread(&type_be, 2, 1, _fd);
- _type = GUINT16_FROM_BE(type_be);
-
- // Read number of tracks (bytes 10..11)
- uint16_t num_tracks_be = 0;
- fread(&num_tracks_be, 2, 1, _fd);
- _num_tracks = GUINT16_FROM_BE(num_tracks_be);
-
- // Read PPQN (bytes 12..13)
- uint16_t ppqn_be = 0;
- fread(&ppqn_be, 2, 1, _fd);
- _ppqn = GUINT16_FROM_BE(ppqn_be);
-
- // TODO: Absolute (SMPTE seconds) time support
- if ((_ppqn & 0x8000) != 0)
- throw UnsupportedTime();
-
- seek_to_track(1);
-
- return true;
- } else {
- return false;
- }
-}
-
-
-/** Seek to the start of a given track, starting from 1.
- * Returns true if specified track was found.
- */
-bool
-SMFReader::seek_to_track(unsigned track) throw (std::logic_error)
-{
- if (track == 0)
- throw logic_error("Seek to track 0 out of range (must be >= 1)");
-
- if (!_fd)
- throw logic_error("Attempt to seek to track on unopened SMF file.");
-
- unsigned track_pos = 0;
-
- fseek(_fd, 14, SEEK_SET);
- char id[5];
- id[4] = '\0';
- uint32_t chunk_size = 0;
-
- while (!feof(_fd)) {
- fread(id, 1, 4, _fd);
-
- if (!strcmp(id, "MTrk")) {
- ++track_pos;
- } else {
- std::cerr << "Unknown chunk ID " << id << endl;
- }
-
- uint32_t chunk_size_be;
- fread(&chunk_size_be, 4, 1, _fd);
- chunk_size = GUINT32_FROM_BE(chunk_size_be);
-
- if (track_pos == track)
- break;
-
- fseek(_fd, chunk_size, SEEK_CUR);
- }
-
- if (!feof(_fd) && track_pos == track) {
- _track = track;
- _track_size = chunk_size;
- return true;
- } else {
- return false;
- }
-}
-
-
-/** Read an event from the current position in file.
- *
- * File position MUST be at the beginning of a delta time, or this will die very messily.
- * ev.buffer must be of size ev.size, and large enough for the event. The returned event
- * will have it's time field set to it's delta time (so it's the caller's responsibility
- * to keep track of delta time, even for ignored events).
- *
- * Returns event length (including status byte) on success, 0 if event was
- * skipped (eg a meta event), or -1 on EOF (or end of track).
- *
- * If @a buf is not large enough to hold the event, 0 will be returned, but ev_size
- * set to the actual size of the event.
- */
-int
-SMFReader::read_event(size_t buf_len,
- uint8_t* buf,
- uint32_t* ev_size,
- uint32_t* delta_time)
- throw (std::logic_error, PrematureEOF, CorruptFile)
-{
- if (_track == 0)
- throw logic_error("Attempt to read from unopened SMF file");
-
- if (!_fd || feof(_fd)) {
- return -1;
- }
-
- assert(buf_len > 0);
- assert(buf);
- assert(ev_size);
- assert(delta_time);
-
- // Running status state
- static uint8_t last_status = 0;
- static uint32_t last_size = 0;
-
- *delta_time = read_var_len(_fd);
- int status = fgetc(_fd);
- if (status == EOF)
- throw PrematureEOF();
- else if (status > 0xFF)
- throw CorruptFile();
-
- if (status < 0x80) {
- if (last_status == 0)
- throw CorruptFile();
- status = last_status;
- *ev_size = last_size;
- fseek(_fd, -1, SEEK_CUR);
- } else {
- last_status = status;
- *ev_size = midi_event_size(status);
- last_size = *ev_size;
- }
-
- buf[0] = (uint8_t)status;
-
- if (status == 0xFF) {
- *ev_size = 0;
- if (feof(_fd))
- throw PrematureEOF();
- uint8_t type = fgetc(_fd);
- const uint32_t size = read_var_len(_fd);
- /*cerr.flags(ios::hex);
- cerr << "SMF - meta 0x" << (int)type << ", size = ";
- cerr.flags(ios::dec);
- cerr << size << endl;*/
-
- if ((uint8_t)type == 0x2F) {
- return -1; // we hit the logical EOF anyway...
- } else {
- fseek(_fd, size, SEEK_CUR);
- return 0;
- }
- }
-
- if (*ev_size > buf_len || *ev_size == 0 || feof(_fd)) {
- //cerr << "SMF - Skipping event" << endl;
- // Skip event, return 0
- fseek(_fd, *ev_size - 1, SEEK_CUR);
- return 0;
- } else {
- // Read event, return size
- if (ferror(_fd))
- throw CorruptFile();
-
- fread(buf+1, 1, *ev_size - 1, _fd);
-
- if ((buf[0] & 0xF0) == 0x90 && buf[2] == 0) {
- buf[0] = (0x80 | (buf[0] & 0x0F));
- buf[2] = 0x40;
- }
-
- return *ev_size;
- }
-}
-
-
-void
-SMFReader::close()
-{
- if (_fd)
- fclose(_fd);
-
- _fd = NULL;
-}
-
-
-uint32_t
-SMFReader::read_var_len(FILE* fd) throw (PrematureEOF)
-{
- if (feof(fd))
- throw PrematureEOF();
-
- uint32_t value;
- uint8_t c;
-
- if ( (value = getc(fd)) & 0x80 ) {
- value &= 0x7F;
- do {
- if (feof(fd))
- throw PrematureEOF();
- value = (value << 7) + ((c = getc(fd)) & 0x7F);
- } while (c & 0x80);
- }
-
- return value;
-}
-
-
-} // namespace Evoral
-
--- /dev/null
+/*
+ * Copyright (C) 2008-2012 Hans Baier <hansfbaier@googlemail.com>
+ * Copyright (C) 2008-2016 David Robillard <d@drobilla.net>
+ * Copyright (C) 2008-2017 Paul Davis <paul@linuxaudiosystems.com>
+ * Copyright (C) 2009-2011 Carl Hetherington <carl@carlh.net>
+ * Copyright (C) 2014-2019 Robin Gareus <robin@gareus.org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include <algorithm>
+#include <cmath>
+#include <iostream>
+#include <limits>
+#include <stdexcept>
+#include <stdint.h>
+#include <cstdio>
+
+#if __clang__
+#include "evoral/Note.h"
+#endif
+
+#include "pbd/compose.h"
+#include "pbd/error.h"
+
+#include "temporal/beats.h"
+
+#include "evoral/Control.h"
+#include "evoral/ControlList.h"
+#include "evoral/ControlSet.h"
+#include "evoral/EventSink.h"
+#include "evoral/ParameterDescriptor.h"
+#include "evoral/Sequence.h"
+#include "evoral/TypeMap.h"
+#include "evoral/midi_util.h"
+
+#include "pbd/i18n.h"
+
+using namespace std;
+using namespace PBD;
+
+/** Minimum time between MIDI outputs from a single interpolated controller,
+ expressed in beats. This is to limit the rate at which MIDI messages
+ are generated. It is only applied to interpolated controllers.
+
+ XXX: This is a hack. The time should probably be expressed in
+ seconds rather than beats, and should be configurable etc. etc.
+*/
+static double const time_between_interpolated_controller_outputs = 1.0 / 256;
+
+namespace Evoral {
+
+// Read iterator (const_iterator)
+
+template<typename Time>
+Sequence<Time>::const_iterator::const_iterator()
+ : _seq(NULL)
+ , _event(boost::shared_ptr< Event<Time> >(new Event<Time>()))
+ , _active_patch_change_message (NO_EVENT)
+ , _type(NIL)
+ , _is_end(true)
+ , _control_iter(_control_iters.end())
+ , _force_discrete(false)
+{
+}
+
+/** @param force_discrete true to force ControlLists to use discrete evaluation, otherwise false to get them to use their configured mode */
+template<typename Time>
+Sequence<Time>::const_iterator::const_iterator(const Sequence<Time>& seq,
+ Time t,
+ bool force_discrete,
+ const std::set<Evoral::Parameter>& filtered,
+ const std::set<WeakNotePtr>* active_notes)
+ : _seq(&seq)
+ , _active_patch_change_message (0)
+ , _type(NIL)
+ , _is_end((t == DBL_MAX) || seq.empty())
+ , _note_iter(seq.notes().end())
+ , _sysex_iter(seq.sysexes().end())
+ , _patch_change_iter(seq.patch_changes().end())
+ , _control_iter(_control_iters.end())
+ , _force_discrete (force_discrete)
+{
+ DEBUG_TRACE (DEBUG::Sequence, string_compose ("Created Iterator @ %1 (is end: %2)\n)", t, _is_end));
+
+ if (_is_end) {
+ return;
+ }
+
+ _lock = seq.read_lock();
+
+ // Add currently active notes, if given
+ if (active_notes) {
+ for (typename std::set<WeakNotePtr>::const_iterator i = active_notes->begin();
+ i != active_notes->end(); ++i) {
+ NotePtr note = i->lock();
+ if (note && note->time() <= t && note->end_time() > t) {
+ _active_notes.push(note);
+ }
+ }
+ }
+
+ // Find first note which begins at or after t
+ _note_iter = seq.note_lower_bound(t);
+
+ // Find first sysex event at or after t
+ for (typename Sequence<Time>::SysExes::const_iterator i = seq.sysexes().begin();
+ i != seq.sysexes().end(); ++i) {
+ if ((*i)->time() >= t) {
+ _sysex_iter = i;
+ break;
+ }
+ }
+ assert(_sysex_iter == seq.sysexes().end() || (*_sysex_iter)->time() >= t);
+
+ // Find first patch event at or after t
+ for (typename Sequence<Time>::PatchChanges::const_iterator i = seq.patch_changes().begin(); i != seq.patch_changes().end(); ++i) {
+ if ((*i)->time() >= t) {
+ _patch_change_iter = i;
+ break;
+ }
+ }
+ assert (_patch_change_iter == seq.patch_changes().end() || (*_patch_change_iter)->time() >= t);
+
+ // Find first control event after t
+ _control_iters.reserve(seq._controls.size());
+ bool found = false;
+ size_t earliest_control_index = 0;
+ double earliest_control_x = DBL_MAX;
+ for (Controls::const_iterator i = seq._controls.begin(); i != seq._controls.end(); ++i) {
+
+ if (filtered.find (i->first) != filtered.end()) {
+ /* this parameter is filtered, so don't bother setting up an iterator for it */
+ continue;
+ }
+
+ DEBUG_TRACE (DEBUG::Sequence, string_compose ("Iterator: control: %1\n", seq._type_map.to_symbol(i->first)));
+ double x, y;
+ bool ret;
+ if (_force_discrete || i->second->list()->interpolation() == ControlList::Discrete) {
+ ret = i->second->list()->rt_safe_earliest_event_discrete_unlocked (t.to_double(), x, y, true);
+ } else {
+ ret = i->second->list()->rt_safe_earliest_event_unlocked(t.to_double(), x, y, true);
+ }
+ if (!ret) {
+ DEBUG_TRACE (DEBUG::Sequence, string_compose ("Iterator: CC %1 (size %2) has no events past %3\n",
+ i->first.id(), i->second->list()->size(), t));
+ continue;
+ }
+
+ assert(x >= 0);
+
+ const ParameterDescriptor& desc = seq.type_map().descriptor(i->first);
+ if (y < desc.lower || y > desc.upper) {
+ cerr << "ERROR: Controller value " << y
+ << " out of range [" << desc.lower << "," << desc.upper
+ << "], event ignored" << endl;
+ continue;
+ }
+
+ DEBUG_TRACE (DEBUG::Sequence, string_compose ("Iterator: CC %1 added (%2, %3)\n", i->first.id(), x, y));
+
+ const ControlIterator new_iter(i->second->list(), x, y);
+ _control_iters.push_back(new_iter);
+
+ // Found a new earliest_control
+ if (x < earliest_control_x) {
+ earliest_control_x = x;
+ earliest_control_index = _control_iters.size() - 1;
+ found = true;
+ }
+ }
+
+ if (found) {
+ _control_iter = _control_iters.begin() + earliest_control_index;
+ assert(_control_iter != _control_iters.end());
+ assert(_control_iter->list);
+ } else {
+ _control_iter = _control_iters.end();
+ }
+
+ // Choose the earliest event overall to point to
+ choose_next(t);
+
+ // Allocate a new event for storing the current event in MIDI format
+ _event = boost::shared_ptr< Event<Time> >(
+ new Event<Time>(NO_EVENT, Time(), 4, NULL, true));
+
+ // Set event from chosen sub-iterator
+ set_event();
+
+ if (_is_end) {
+ DEBUG_TRACE(DEBUG::Sequence,
+ string_compose("Starting at end @ %1\n", t));
+ } else {
+ DEBUG_TRACE(DEBUG::Sequence,
+ string_compose("Starting at type 0x%1 : 0x%2 @ %3\n",
+ (int)_event->event_type(),
+ (int)_event->buffer()[0],
+ _event->time()));
+ }
+}
+
+template<typename Time>
+void
+Sequence<Time>::const_iterator::invalidate(std::set< boost::weak_ptr< Note<Time> > >* notes)
+{
+ while (!_active_notes.empty()) {
+ if (notes) {
+ notes->insert(_active_notes.top());
+ }
+ _active_notes.pop();
+ }
+ _type = NIL;
+ _is_end = true;
+ if (_seq) {
+ _note_iter = _seq->notes().end();
+ _sysex_iter = _seq->sysexes().end();
+ _patch_change_iter = _seq->patch_changes().end();
+ _active_patch_change_message = 0;
+ }
+ _control_iters.clear();
+ _control_iter = _control_iters.end();
+ _lock.reset();
+}
+
+template<typename Time>
+Time
+Sequence<Time>::const_iterator::choose_next(Time earliest_t)
+{
+ _type = NIL;
+
+ // Next earliest note on, if any
+ if (_note_iter != _seq->notes().end()) {
+ _type = NOTE_ON;
+ earliest_t = (*_note_iter)->time();
+ }
+
+ /* Use the next earliest patch change iff it is earlier or coincident with the note-on.
+ * A patch-change with the same time-stamp applies to the concurrent note-on */
+ if (_patch_change_iter != _seq->patch_changes().end()) {
+ if (_type == NIL || (*_patch_change_iter)->time() <= earliest_t) {
+ _type = PATCH_CHANGE;
+ earliest_t = (*_patch_change_iter)->time();
+ }
+ }
+
+ /* Use the next earliest controller iff it's earlier or coincident with the note-on
+ * or patch-change. Bank-select (CC0, CC32) needs to be sent before the PGM. */
+ if (_control_iter != _control_iters.end() &&
+ _control_iter->list && _control_iter->x != DBL_MAX) {
+ if (_type == NIL || _control_iter->x <= earliest_t.to_double()) {
+ _type = CONTROL;
+ earliest_t = Time(_control_iter->x);
+ }
+ }
+
+ /* .. but prefer to send any Note-off first */
+ if ((!_active_notes.empty())) {
+ if (_type == NIL || _active_notes.top()->end_time().to_double() <= earliest_t.to_double()) {
+ _type = NOTE_OFF;
+ earliest_t = _active_notes.top()->end_time();
+ }
+ }
+
+ /* SysEx is last, always sent after any other concurrent 3 byte event */
+ if (_sysex_iter != _seq->sysexes().end()) {
+ if (_type == NIL || (*_sysex_iter)->time() < earliest_t) {
+ _type = SYSEX;
+ earliest_t = (*_sysex_iter)->time();
+ }
+ }
+
+ return earliest_t;
+}
+
+template<typename Time>
+void
+Sequence<Time>::const_iterator::set_event()
+{
+ switch (_type) {
+ case NOTE_ON:
+ DEBUG_TRACE(DEBUG::Sequence, "iterator = note on\n");
+ _event->assign ((*_note_iter)->on_event());
+ _active_notes.push(*_note_iter);
+ break;
+ case NOTE_OFF:
+ DEBUG_TRACE(DEBUG::Sequence, "iterator = note off\n");
+ assert(!_active_notes.empty());
+ _event->assign (_active_notes.top()->off_event());
+ // We don't pop the active note until we increment past it
+ break;
+ case SYSEX:
+ DEBUG_TRACE(DEBUG::Sequence, "iterator = sysex\n");
+ _event->assign (*(*_sysex_iter));
+ break;
+ case CONTROL:
+ DEBUG_TRACE(DEBUG::Sequence, "iterator = control\n");
+ _seq->control_to_midi_event(_event, *_control_iter);
+ break;
+ case PATCH_CHANGE:
+ DEBUG_TRACE(DEBUG::Sequence, "iterator = program change\n");
+ _event->assign ((*_patch_change_iter)->message (_active_patch_change_message));
+ break;
+ default:
+ _is_end = true;
+ break;
+ }
+
+ if (_type == NIL || !_event || _event->size() == 0) {
+ DEBUG_TRACE(DEBUG::Sequence, "iterator = end\n");
+ _type = NIL;
+ _is_end = true;
+ } else {
+ assert(midi_event_is_valid(_event->buffer(), _event->size()));
+ }
+}
+
+template<typename Time>
+const typename Sequence<Time>::const_iterator&
+Sequence<Time>::const_iterator::operator++()
+{
+ if (_is_end) {
+ throw std::logic_error("Attempt to iterate past end of Sequence");
+ }
+
+ assert(_event && _event->buffer() && _event->size() > 0);
+
+ const Event<Time>& ev = *_event.get();
+
+ if (!( ev.is_note()
+ || ev.is_cc()
+ || ev.is_pgm_change()
+ || ev.is_pitch_bender()
+ || ev.is_channel_pressure()
+ || ev.is_poly_pressure()
+ || ev.is_sysex()) ) {
+ cerr << "WARNING: Unknown event (type " << _type << "): " << hex
+ << int(ev.buffer()[0]) << int(ev.buffer()[1]) << int(ev.buffer()[2]) << endl;
+ }
+
+ double x = 0.0;
+ double y = 0.0;
+ bool ret = false;
+
+ // Increment past current event
+ switch (_type) {
+ case NOTE_ON:
+ ++_note_iter;
+ break;
+ case NOTE_OFF:
+ _active_notes.pop();
+ break;
+ case CONTROL:
+ // Increment current controller iterator
+ if (_force_discrete || _control_iter->list->interpolation() == ControlList::Discrete) {
+ ret = _control_iter->list->rt_safe_earliest_event_discrete_unlocked (
+ _control_iter->x, x, y, false);
+ } else {
+ ret = _control_iter->list->rt_safe_earliest_event_linear_unlocked (
+ _control_iter->x + time_between_interpolated_controller_outputs, x, y, false);
+ }
+ assert(!ret || x > _control_iter->x);
+ if (ret) {
+ _control_iter->x = x;
+ _control_iter->y = y;
+ } else {
+ _control_iter->list.reset();
+ _control_iter->x = DBL_MAX;
+ _control_iter->y = DBL_MAX;
+ }
+
+ // Find the controller with the next earliest event time
+ _control_iter = _control_iters.begin();
+ for (ControlIterators::iterator i = _control_iters.begin();
+ i != _control_iters.end(); ++i) {
+ if (i->x < _control_iter->x) {
+ _control_iter = i;
+ }
+ }
+ break;
+ case SYSEX:
+ ++_sysex_iter;
+ break;
+ case PATCH_CHANGE:
+ ++_active_patch_change_message;
+ if (_active_patch_change_message == (*_patch_change_iter)->messages()) {
+ ++_patch_change_iter;
+ _active_patch_change_message = 0;
+ }
+ break;
+ default:
+ assert(false);
+ }
+
+ // Choose the earliest event overall to point to
+ choose_next(std::numeric_limits<Time>::max());
+
+ // Set event from chosen sub-iterator
+ set_event();
+
+ assert(_is_end || (_event->size() > 0 && _event->buffer() && _event->buffer()[0] != '\0'));
+
+ return *this;
+}
+
+template<typename Time>
+bool
+Sequence<Time>::const_iterator::operator==(const const_iterator& other) const
+{
+ if (_seq != other._seq) {
+ return false;
+ } else if (_is_end || other._is_end) {
+ return (_is_end == other._is_end);
+ } else if (_type != other._type) {
+ return false;
+ } else {
+ return (_event == other._event);
+ }
+}
+
+template<typename Time>
+typename Sequence<Time>::const_iterator&
+Sequence<Time>::const_iterator::operator=(const const_iterator& other)
+{
+ _seq = other._seq;
+ _event = other._event;
+ _active_notes = other._active_notes;
+ _type = other._type;
+ _is_end = other._is_end;
+ _note_iter = other._note_iter;
+ _sysex_iter = other._sysex_iter;
+ _patch_change_iter = other._patch_change_iter;
+ _control_iters = other._control_iters;
+ _force_discrete = other._force_discrete;
+ _active_patch_change_message = other._active_patch_change_message;
+
+ if (other._lock) {
+ _lock = _seq->read_lock();
+ } else {
+ _lock.reset();
+ }
+
+ if (other._control_iter == other._control_iters.end()) {
+ _control_iter = _control_iters.end();
+ } else {
+ const size_t index = other._control_iter - other._control_iters.begin();
+ _control_iter = _control_iters.begin() + index;
+ }
+
+ return *this;
+}
+
+// Sequence
+
+template<typename Time>
+Sequence<Time>::Sequence(const TypeMap& type_map)
+ : _edited(false)
+ , _overlapping_pitches_accepted (true)
+ , _overlap_pitch_resolution (FirstOnFirstOff)
+ , _writing(false)
+ , _type_map(type_map)
+ , _end_iter(*this, std::numeric_limits<Time>::max(), false, std::set<Evoral::Parameter> ())
+ , _percussive(false)
+ , _lowest_note(127)
+ , _highest_note(0)
+{
+ DEBUG_TRACE (DEBUG::Sequence, string_compose ("Sequence constructed: %1\n", this));
+ assert(_end_iter._is_end);
+ assert( ! _end_iter._lock);
+
+ for (int i = 0; i < 16; ++i) {
+ _bank[i] = 0;
+ }
+}
+
+template<typename Time>
+Sequence<Time>::Sequence(const Sequence<Time>& other)
+ : ControlSet (other)
+ , _edited(false)
+ , _overlapping_pitches_accepted (other._overlapping_pitches_accepted)
+ , _overlap_pitch_resolution (other._overlap_pitch_resolution)
+ , _writing(false)
+ , _type_map(other._type_map)
+ , _end_iter(*this, std::numeric_limits<Time>::max(), false, std::set<Evoral::Parameter> ())
+ , _percussive(other._percussive)
+ , _lowest_note(other._lowest_note)
+ , _highest_note(other._highest_note)
+{
+ for (typename Notes::const_iterator i = other._notes.begin(); i != other._notes.end(); ++i) {
+ NotePtr n (new Note<Time> (**i));
+ _notes.insert (n);
+ }
+
+ for (typename SysExes::const_iterator i = other._sysexes.begin(); i != other._sysexes.end(); ++i) {
+ boost::shared_ptr<Event<Time> > n (new Event<Time> (**i, true));
+ _sysexes.insert (n);
+ }
+
+ for (typename PatchChanges::const_iterator i = other._patch_changes.begin(); i != other._patch_changes.end(); ++i) {
+ PatchChangePtr n (new PatchChange<Time> (**i));
+ _patch_changes.insert (n);
+ }
+
+ for (int i = 0; i < 16; ++i) {
+ _bank[i] = other._bank[i];
+ }
+
+ DEBUG_TRACE (DEBUG::Sequence, string_compose ("Sequence copied: %1\n", this));
+ assert(_end_iter._is_end);
+ assert(! _end_iter._lock);
+}
+
+/** Write the controller event pointed to by `iter` to `ev`.
+ * The buffer of `ev` will be allocated or resized as necessary.
+ * \return true on success
+ */
+template<typename Time>
+bool
+Sequence<Time>::control_to_midi_event(
+ boost::shared_ptr< Event<Time> >& ev,
+ const ControlIterator& iter) const
+{
+ assert(iter.list.get());
+
+ // initialize the event pointer with a new event, if necessary
+ if (!ev) {
+ ev = boost::shared_ptr< Event<Time> >(new Event<Time>(NO_EVENT, Time(), 3, NULL, true));
+ }
+
+ const uint8_t midi_type = _type_map.parameter_midi_type(iter.list->parameter());
+ ev->set_event_type(MIDI_EVENT);
+ ev->set_id(-1);
+ switch (midi_type) {
+ case MIDI_CMD_CONTROL:
+ assert(iter.list.get());
+ assert(iter.list->parameter().channel() < 16);
+ assert(iter.list->parameter().id() <= INT8_MAX);
+ assert(iter.y <= INT8_MAX);
+
+ ev->set_time(Time(iter.x));
+ ev->realloc(3);
+ ev->buffer()[0] = MIDI_CMD_CONTROL + iter.list->parameter().channel();
+ ev->buffer()[1] = (uint8_t)iter.list->parameter().id();
+ ev->buffer()[2] = (uint8_t)iter.y;
+ break;
+
+ case MIDI_CMD_PGM_CHANGE:
+ assert(iter.list.get());
+ assert(iter.list->parameter().channel() < 16);
+ assert(iter.y <= INT8_MAX);
+
+ ev->set_time(Time(iter.x));
+ ev->realloc(2);
+ ev->buffer()[0] = MIDI_CMD_PGM_CHANGE + iter.list->parameter().channel();
+ ev->buffer()[1] = (uint8_t)iter.y;
+ break;
+
+ case MIDI_CMD_BENDER:
+ assert(iter.list.get());
+ assert(iter.list->parameter().channel() < 16);
+ assert(iter.y < (1<<14));
+
+ ev->set_time(Time(iter.x));
+ ev->realloc(3);
+ ev->buffer()[0] = MIDI_CMD_BENDER + iter.list->parameter().channel();
+ ev->buffer()[1] = uint16_t(iter.y) & 0x7F; // LSB
+ ev->buffer()[2] = (uint16_t(iter.y) >> 7) & 0x7F; // MSB
+ break;
+
+ case MIDI_CMD_NOTE_PRESSURE:
+ assert(iter.list.get());
+ assert(iter.list->parameter().channel() < 16);
+ assert(iter.list->parameter().id() <= INT8_MAX);
+ assert(iter.y <= INT8_MAX);
+
+ ev->set_time(Time(iter.x));
+ ev->realloc(3);
+ ev->buffer()[0] = MIDI_CMD_NOTE_PRESSURE + iter.list->parameter().channel();
+ ev->buffer()[1] = (uint8_t)iter.list->parameter().id();
+ ev->buffer()[2] = (uint8_t)iter.y;
+ break;
+
+ case MIDI_CMD_CHANNEL_PRESSURE:
+ assert(iter.list.get());
+ assert(iter.list->parameter().channel() < 16);
+ assert(iter.y <= INT8_MAX);
+
+ ev->set_time(Time(iter.x));
+ ev->realloc(2);
+ ev->buffer()[0] = MIDI_CMD_CHANNEL_PRESSURE + iter.list->parameter().channel();
+ ev->buffer()[1] = (uint8_t)iter.y;
+ break;
+
+ default:
+ return false;
+ }
+
+ return true;
+}
+
+/** Clear all events from the model.
+ */
+template<typename Time>
+void
+Sequence<Time>::clear()
+{
+ WriteLock lock(write_lock());
+ _notes.clear();
+ for (Controls::iterator li = _controls.begin(); li != _controls.end(); ++li)
+ li->second->list()->clear();
+}
+
+/** Begin a write of events to the model.
+ *
+ * If \a mode is Sustained, complete notes with length are constructed as note
+ * on/off events are received. Otherwise (Percussive), only note on events are
+ * stored; note off events are discarded entirely and all contained notes will
+ * have length 0.
+ */
+template<typename Time>
+void
+Sequence<Time>::start_write()
+{
+ DEBUG_TRACE (DEBUG::Sequence, string_compose ("%1 : start_write (percussive = %2)\n", this, _percussive));
+ WriteLock lock(write_lock());
+ _writing = true;
+ for (int i = 0; i < 16; ++i) {
+ _write_notes[i].clear();
+ }
+}
+
+/** Finish a write of events to the model.
+ *
+ * If \a delete_stuck is true and the current mode is Sustained, note on events
+ * that were never resolved with a corresonding note off will be deleted.
+ * Otherwise they will remain as notes with length 0.
+ */
+template<typename Time>
+void
+Sequence<Time>::end_write (StuckNoteOption option, Time when)
+{
+ WriteLock lock(write_lock());
+
+ if (!_writing) {
+ return;
+ }
+
+ DEBUG_TRACE (DEBUG::Sequence, string_compose ("%1 : end_write (%2 notes) delete stuck option %3 @ %4\n", this, _notes.size(), option, when));
+
+ for (typename Notes::iterator n = _notes.begin(); n != _notes.end() ;) {
+ typename Notes::iterator next = n;
+ ++next;
+
+ if (!(*n)->length()) {
+ switch (option) {
+ case Relax:
+ break;
+ case DeleteStuckNotes:
+ cerr << "WARNING: Stuck note lost: " << (*n)->note() << endl;
+ _notes.erase(n);
+ break;
+ case ResolveStuckNotes:
+ if (when <= (*n)->time()) {
+ cerr << "WARNING: Stuck note resolution - end time @ "
+ << when << " is before note on: " << (**n) << endl;
+ _notes.erase (*n);
+ } else {
+ (*n)->set_length (when - (*n)->time());
+ cerr << "WARNING: resolved note-on with no note-off to generate " << (**n) << endl;
+ }
+ break;
+ }
+ }
+
+ n = next;
+ }
+
+ for (int i = 0; i < 16; ++i) {
+ _write_notes[i].clear();
+ }
+
+ _writing = false;
+}
+
+
+template<typename Time>
+bool
+Sequence<Time>::add_note_unlocked(const NotePtr note, void* arg)
+{
+ /* This is the core method to add notes to a Sequence
+ */
+
+ DEBUG_TRACE (DEBUG::Sequence, string_compose ("%1 add note %2 @ %3 dur %4\n", this, (int)note->note(), note->time(), note->length()));
+
+ if (resolve_overlaps_unlocked (note, arg)) {
+ DEBUG_TRACE (DEBUG::Sequence, string_compose ("%1 DISALLOWED: note %2 @ %3\n", this, (int)note->note(), note->time()));
+ return false;
+ }
+
+ if (note->id() < 0) {
+ note->set_id (Evoral::next_event_id());
+ }
+
+ if (note->note() < _lowest_note)
+ _lowest_note = note->note();
+ if (note->note() > _highest_note)
+ _highest_note = note->note();
+
+ _notes.insert (note);
+ _pitches[note->channel()].insert (note);
+
+ _edited = true;
+
+ return true;
+}
+
+template<typename Time>
+void
+Sequence<Time>::remove_note_unlocked(const constNotePtr note)
+{
+ bool erased = false;
+ bool id_matched = false;
+
+ DEBUG_TRACE (DEBUG::Sequence, string_compose ("%1 remove note #%2 %3 @ %4\n", this, note->id(), (int)note->note(), note->time()));
+
+ /* first try searching for the note using the time index, which is
+ * faster since the container is "indexed" by time. (technically, this
+ * means that lower_bound() can do a binary search rather than linear)
+ *
+ * this may not work, for reasons explained below.
+ */
+
+ typename Sequence<Time>::Notes::iterator i;
+
+ for (i = note_lower_bound(note->time()); i != _notes.end() && (*i)->time() == note->time(); ++i) {
+
+ if (*i == note) {
+
+ DEBUG_TRACE (DEBUG::Sequence, string_compose ("%1\terasing note #%2 %3 @ %4\n", this, (*i)->id(), (int)(*i)->note(), (*i)->time()));
+ _notes.erase (i);
+
+ if (note->note() == _lowest_note || note->note() == _highest_note) {
+
+ _lowest_note = 127;
+ _highest_note = 0;
+
+ for (typename Sequence<Time>::Notes::iterator ii = _notes.begin(); ii != _notes.end(); ++ii) {
+ if ((*ii)->note() < _lowest_note)
+ _lowest_note = (*ii)->note();
+ if ((*ii)->note() > _highest_note)
+ _highest_note = (*ii)->note();
+ }
+ }
+
+ erased = true;
+ break;
+ }
+ }
+
+ DEBUG_TRACE (DEBUG::Sequence, string_compose ("%1\ttime-based lookup did not find note #%2 %3 @ %4\n", this, note->id(), (int)note->note(), note->time()));
+
+ if (!erased) {
+
+ /* if the note's time property was changed in tandem with some
+ * other property as the next operation after it was added to
+ * the sequence, then at the point where we call this to undo
+ * the add, the note we are targetting currently has a
+ * different time property than the one we we passed via
+ * the argument.
+ *
+ * in this scenario, we have no choice other than to linear
+ * search the list of notes and find the note by ID.
+ */
+
+ for (i = _notes.begin(); i != _notes.end(); ++i) {
+
+ if ((*i)->id() == note->id()) {
+
+ DEBUG_TRACE (DEBUG::Sequence, string_compose ("%1\tID-based pass, erasing note #%2 %3 @ %4\n", this, (*i)->id(), (int)(*i)->note(), (*i)->time()));
+ _notes.erase (i);
+
+ if (note->note() == _lowest_note || note->note() == _highest_note) {
+
+ _lowest_note = 127;
+ _highest_note = 0;
+
+ for (typename Sequence<Time>::Notes::iterator ii = _notes.begin(); ii != _notes.end(); ++ii) {
+ if ((*ii)->note() < _lowest_note)
+ _lowest_note = (*ii)->note();
+ if ((*ii)->note() > _highest_note)
+ _highest_note = (*ii)->note();
+ }
+ }
+
+ erased = true;
+ id_matched = true;
+ break;
+ }
+ }
+ }
+
+ if (erased) {
+
+ Pitches& p (pitches (note->channel()));
+
+ typename Pitches::iterator j;
+
+ /* if we had to ID-match above, we can't expect to find it in
+ * pitches via note comparison either. so do another linear
+ * search to locate it. otherwise, we can use the note index
+ * to potentially speed things up.
+ */
+
+ if (id_matched) {
+
+ for (j = p.begin(); j != p.end(); ++j) {
+ if ((*j)->id() == note->id()) {
+ p.erase (j);
+ break;
+ }
+ }
+
+ } else {
+
+ /* Now find the same note in the "pitches" list (which indexes
+ * notes by channel+time. We care only about its note number
+ * so the search_note has all other properties unset.
+ */
+
+ NotePtr search_note (new Note<Time>(0, Time(), Time(), note->note(), 0));
+
+ for (j = p.lower_bound (search_note); j != p.end() && (*j)->note() == note->note(); ++j) {
+
+ if ((*j) == note) {
+ DEBUG_TRACE (DEBUG::Sequence, string_compose ("%1\terasing pitch %2 @ %3\n", this, (int)(*j)->note(), (*j)->time()));
+ p.erase (j);
+ break;
+ }
+ }
+ }
+
+ if (j == p.end()) {
+ warning << string_compose ("erased note %1 not found in pitches for channel %2", *note, (int) note->channel()) << endmsg;
+ }
+
+ _edited = true;
+
+ } else {
+ cerr << "Unable to find note to erase matching " << *note.get() << endmsg;
+ }
+}
+
+template<typename Time>
+void
+Sequence<Time>::remove_patch_change_unlocked (const constPatchChangePtr p)
+{
+ typename Sequence<Time>::PatchChanges::iterator i = patch_change_lower_bound (p->time ());
+
+ while (i != _patch_changes.end() && ((*i)->time() == p->time())) {
+
+ typename Sequence<Time>::PatchChanges::iterator tmp = i;
+ ++tmp;
+
+ if (**i == *p) {
+ _patch_changes.erase (i);
+ }
+
+ i = tmp;
+ }
+}
+
+template<typename Time>
+void
+Sequence<Time>::remove_sysex_unlocked (const SysExPtr sysex)
+{
+ typename Sequence<Time>::SysExes::iterator i = sysex_lower_bound (sysex->time ());
+ while (i != _sysexes.end() && (*i)->time() == sysex->time()) {
+
+ typename Sequence<Time>::SysExes::iterator tmp = i;
+ ++tmp;
+
+ if (*i == sysex) {
+ _sysexes.erase (i);
+ }
+
+ i = tmp;
+ }
+}
+
+/** Append \a ev to model. NOT realtime safe.
+ *
+ * The timestamp of event is expected to be relative to
+ * the start of this model (t=0) and MUST be monotonically increasing
+ * and MUST be >= the latest event currently in the model.
+ */
+template<typename Time>
+void
+Sequence<Time>::append(const Event<Time>& ev, event_id_t evid)
+{
+ WriteLock lock(write_lock());
+
+ assert(_notes.empty() || ev.time() >= (*_notes.rbegin())->time());
+ assert(_writing);
+
+ if (!midi_event_is_valid(ev.buffer(), ev.size())) {
+ cerr << "WARNING: Sequence ignoring illegal MIDI event" << endl;
+ return;
+ }
+
+ if (ev.is_note_on() && ev.velocity() > 0) {
+ append_note_on_unlocked (ev, evid);
+ } else if (ev.is_note_off() || (ev.is_note_on() && ev.velocity() == 0)) {
+ /* XXX note: event ID is discarded because we merge the on+off events into
+ a single note object
+ */
+ append_note_off_unlocked (ev);
+ } else if (ev.is_sysex()) {
+ append_sysex_unlocked(ev, evid);
+ } else if (ev.is_cc() && (ev.cc_number() == MIDI_CTL_MSB_BANK || ev.cc_number() == MIDI_CTL_LSB_BANK)) {
+ /* note bank numbers in our _bank[] array, so that we can write an event when the program change arrives */
+ if (ev.cc_number() == MIDI_CTL_MSB_BANK) {
+ _bank[ev.channel()] &= ~(0x7f << 7);
+ _bank[ev.channel()] |= ev.cc_value() << 7;
+ } else {
+ _bank[ev.channel()] &= ~0x7f;
+ _bank[ev.channel()] |= ev.cc_value();
+ }
+ } else if (ev.is_cc()) {
+ const ParameterType ptype = _type_map.midi_parameter_type(ev.buffer(), ev.size());
+ append_control_unlocked(
+ Parameter(ptype, ev.channel(), ev.cc_number()),
+ ev.time(), ev.cc_value(), evid);
+ } else if (ev.is_pgm_change()) {
+ /* write a patch change with this program change and any previously set-up bank number */
+ append_patch_change_unlocked (
+ PatchChange<Time> (ev.time(), ev.channel(),
+ ev.pgm_number(), _bank[ev.channel()]), evid);
+ } else if (ev.is_pitch_bender()) {
+ const ParameterType ptype = _type_map.midi_parameter_type(ev.buffer(), ev.size());
+ append_control_unlocked(
+ Parameter(ptype, ev.channel()),
+ ev.time(), double ((0x7F & ev.pitch_bender_msb()) << 7
+ | (0x7F & ev.pitch_bender_lsb())),
+ evid);
+ } else if (ev.is_poly_pressure()) {
+ const ParameterType ptype = _type_map.midi_parameter_type(ev.buffer(), ev.size());
+ append_control_unlocked (Parameter (ptype, ev.channel(), ev.poly_note()), ev.time(), ev.poly_pressure(), evid);
+ } else if (ev.is_channel_pressure()) {
+ const ParameterType ptype = _type_map.midi_parameter_type(ev.buffer(), ev.size());
+ append_control_unlocked(
+ Parameter(ptype, ev.channel()),
+ ev.time(), ev.channel_pressure(), evid);
+ } else if (!_type_map.type_is_midi(ev.event_type())) {
+ printf("WARNING: Sequence: Unknown event type %X: ", ev.event_type());
+ for (size_t i=0; i < ev.size(); ++i) {
+ printf("%X ", ev.buffer()[i]);
+ }
+ printf("\n");
+ } else {
+ printf("WARNING: Sequence: Unknown MIDI event type %X\n", ev.type());
+ }
+
+ _edited = true;
+}
+
+template<typename Time>
+void
+Sequence<Time>::append_note_on_unlocked (const Event<Time>& ev, event_id_t evid)
+{
+ DEBUG_TRACE (DEBUG::Sequence, string_compose ("%1 c=%2 note %3 on @ %4 v=%5\n", this,
+ (int)ev.channel(), (int)ev.note(),
+ ev.time(), (int)ev.velocity()));
+ assert(_writing);
+
+ if (ev.note() > 127) {
+ error << string_compose (_("invalid note on number (%1) ignored"), (int) ev.note()) << endmsg;
+ return;
+ } else if (ev.channel() >= 16) {
+ error << string_compose (_("invalid note on channel (%1) ignored"), (int) ev.channel()) << endmsg;
+ return;
+ } else if (ev.velocity() == 0) {
+ // Note on with velocity 0 handled as note off by caller
+ error << string_compose (_("invalid note on velocity (%1) ignored"), (int) ev.velocity()) << endmsg;
+ return;
+ }
+
+ NotePtr note(new Note<Time>(ev.channel(), ev.time(), Time(), ev.note(), ev.velocity()));
+ note->set_id (evid);
+
+ add_note_unlocked (note);
+
+ DEBUG_TRACE (DEBUG::Sequence, string_compose ("Appending active note on %1 channel %2\n",
+ (unsigned)(uint8_t)note->note(), note->channel()));
+ _write_notes[note->channel()].insert (note);
+
+}
+
+template<typename Time>
+void
+Sequence<Time>::append_note_off_unlocked (const Event<Time>& ev)
+{
+ DEBUG_TRACE (DEBUG::Sequence, string_compose ("%1 c=%2 note %3 OFF @ %4 v=%5\n",
+ this, (int)ev.channel(),
+ (int)ev.note(), ev.time(), (int)ev.velocity()));
+ assert(_writing);
+
+ if (ev.note() > 127) {
+ error << string_compose (_("invalid note off number (%1) ignored"), (int) ev.note()) << endmsg;
+ return;
+ } else if (ev.channel() >= 16) {
+ error << string_compose (_("invalid note off channel (%1) ignored"), (int) ev.channel()) << endmsg;
+ return;
+ }
+
+ _edited = true;
+
+ bool resolved = false;
+
+ /* _write_notes is sorted earliest-latest, so this will find the first matching note (FIFO) that
+ matches this note (by pitch & channel). the MIDI specification doesn't provide any guidance
+ whether to use FIFO or LIFO for this matching process, so SMF is fundamentally a lossy
+ format.
+ */
+
+ /* XXX use _overlap_pitch_resolution to determine FIFO/LIFO ... */
+
+ for (typename WriteNotes::iterator n = _write_notes[ev.channel()].begin(); n != _write_notes[ev.channel()].end(); ) {
+
+ typename WriteNotes::iterator tmp = n;
+ ++tmp;
+
+ NotePtr nn = *n;
+ if (ev.note() == nn->note() && nn->channel() == ev.channel()) {
+ assert(ev.time() >= nn->time());
+
+ nn->set_length (ev.time() - nn->time());
+ nn->set_off_velocity (ev.velocity());
+
+ _write_notes[ev.channel()].erase(n);
+ DEBUG_TRACE (DEBUG::Sequence, string_compose ("resolved note @ %2 length: %1\n", nn->length(), nn->time()));
+ resolved = true;
+ break;
+ }
+
+ n = tmp;
+ }
+
+ if (!resolved) {
+ cerr << this << " spurious note off chan " << (int)ev.channel()
+ << ", note " << (int)ev.note() << " @ " << ev.time() << endl;
+ }
+}
+
+template<typename Time>
+void
+Sequence<Time>::append_control_unlocked(const Parameter& param, Time time, double value, event_id_t /* evid */)
+{
+ DEBUG_TRACE (DEBUG::Sequence, string_compose ("%1 %2 @ %3 = %4 # controls: %5\n",
+ this, _type_map.to_symbol(param), time, value, _controls.size()));
+ boost::shared_ptr<Control> c = control(param, true);
+ c->list()->add (time.to_double(), value, true, false);
+ /* XXX control events should use IDs */
+}
+
+template<typename Time>
+void
+Sequence<Time>::append_sysex_unlocked(const Event<Time>& ev, event_id_t /* evid */)
+{
+#ifdef DEBUG_SEQUENCE
+ cerr << this << " SysEx @ " << ev.time() << " \t= \t [ " << hex;
+ for (size_t i=0; i < ev.size(); ++i) {
+ cerr << int(ev.buffer()[i]) << " ";
+ } cerr << "]" << endl;
+#endif
+
+ boost::shared_ptr< Event<Time> > event(new Event<Time>(ev, true));
+ /* XXX sysex events should use IDs */
+ _sysexes.insert(event);
+}
+
+template<typename Time>
+void
+Sequence<Time>::append_patch_change_unlocked (const PatchChange<Time>& ev, event_id_t id)
+{
+ PatchChangePtr p (new PatchChange<Time> (ev));
+
+ if (p->id() < 0) {
+ p->set_id (id);
+ }
+
+ _patch_changes.insert (p);
+}
+
+template<typename Time>
+void
+Sequence<Time>::add_patch_change_unlocked (PatchChangePtr p)
+{
+ if (p->id () < 0) {
+ p->set_id (Evoral::next_event_id ());
+ }
+
+ _patch_changes.insert (p);
+}
+
+template<typename Time>
+void
+Sequence<Time>::add_sysex_unlocked (SysExPtr s)
+{
+ if (s->id () < 0) {
+ s->set_id (Evoral::next_event_id ());
+ }
+
+ _sysexes.insert (s);
+}
+
+template<typename Time>
+bool
+Sequence<Time>::contains (const NotePtr& note) const
+{
+ ReadLock lock (read_lock());
+ return contains_unlocked (note);
+}
+
+template<typename Time>
+bool
+Sequence<Time>::contains_unlocked (const NotePtr& note) const
+{
+ const Pitches& p (pitches (note->channel()));
+ NotePtr search_note(new Note<Time>(0, Time(), Time(), note->note()));
+
+ for (typename Pitches::const_iterator i = p.lower_bound (search_note);
+ i != p.end() && (*i)->note() == note->note(); ++i) {
+
+ if (**i == *note) {
+ return true;
+ }
+ }
+
+ return false;
+}
+
+template<typename Time>
+bool
+Sequence<Time>::overlaps (const NotePtr& note, const NotePtr& without) const
+{
+ ReadLock lock (read_lock());
+ return overlaps_unlocked (note, without);
+}
+
+template<typename Time>
+bool
+Sequence<Time>::overlaps_unlocked (const NotePtr& note, const NotePtr& without) const
+{
+ Time sa = note->time();
+ Time ea = note->end_time();
+
+ const Pitches& p (pitches (note->channel()));
+ NotePtr search_note(new Note<Time>(0, Time(), Time(), note->note()));
+
+ for (typename Pitches::const_iterator i = p.lower_bound (search_note);
+ i != p.end() && (*i)->note() == note->note(); ++i) {
+
+ if (without && (**i) == *without) {
+ continue;
+ }
+
+ Time sb = (*i)->time();
+ Time eb = (*i)->end_time();
+
+ if (((sb > sa) && (eb <= ea)) ||
+ ((eb >= sa) && (eb <= ea)) ||
+ ((sb > sa) && (sb <= ea)) ||
+ ((sa >= sb) && (sa <= eb) && (ea <= eb))) {
+ return true;
+ }
+ }
+
+ return false;
+}
+
+template<typename Time>
+void
+Sequence<Time>::set_notes (const typename Sequence<Time>::Notes& n)
+{
+ _notes = n;
+}
+
+// CONST iterator implementations (x3)
+
+/** Return the earliest note with time >= t */
+template<typename Time>
+typename Sequence<Time>::Notes::const_iterator
+Sequence<Time>::note_lower_bound (Time t) const
+{
+ NotePtr search_note(new Note<Time>(0, t, Time(), 0, 0));
+ typename Sequence<Time>::Notes::const_iterator i = _notes.lower_bound(search_note);
+ assert(i == _notes.end() || (*i)->time() >= t);
+ return i;
+}
+
+/** Return the earliest patch change with time >= t */
+template<typename Time>
+typename Sequence<Time>::PatchChanges::const_iterator
+Sequence<Time>::patch_change_lower_bound (Time t) const
+{
+ PatchChangePtr search (new PatchChange<Time> (t, 0, 0, 0));
+ typename Sequence<Time>::PatchChanges::const_iterator i = _patch_changes.lower_bound (search);
+ assert (i == _patch_changes.end() || (*i)->time() >= t);
+ return i;
+}
+
+/** Return the earliest sysex with time >= t */
+template<typename Time>
+typename Sequence<Time>::SysExes::const_iterator
+Sequence<Time>::sysex_lower_bound (Time t) const
+{
+ SysExPtr search (new Event<Time> (NO_EVENT, t));
+ typename Sequence<Time>::SysExes::const_iterator i = _sysexes.lower_bound (search);
+ assert (i == _sysexes.end() || (*i)->time() >= t);
+ return i;
+}
+
+// NON-CONST iterator implementations (x3)
+
+/** Return the earliest note with time >= t */
+template<typename Time>
+typename Sequence<Time>::Notes::iterator
+Sequence<Time>::note_lower_bound (Time t)
+{
+ NotePtr search_note(new Note<Time>(0, t, Time(), 0, 0));
+ typename Sequence<Time>::Notes::iterator i = _notes.lower_bound(search_note);
+ assert(i == _notes.end() || (*i)->time() >= t);
+ return i;
+}
+
+/** Return the earliest patch change with time >= t */
+template<typename Time>
+typename Sequence<Time>::PatchChanges::iterator
+Sequence<Time>::patch_change_lower_bound (Time t)
+{
+ PatchChangePtr search (new PatchChange<Time> (t, 0, 0, 0));
+ typename Sequence<Time>::PatchChanges::iterator i = _patch_changes.lower_bound (search);
+ assert (i == _patch_changes.end() || (*i)->time() >= t);
+ return i;
+}
+
+/** Return the earliest sysex with time >= t */
+template<typename Time>
+typename Sequence<Time>::SysExes::iterator
+Sequence<Time>::sysex_lower_bound (Time t)
+{
+ SysExPtr search (new Event<Time> (NO_EVENT, t));
+ typename Sequence<Time>::SysExes::iterator i = _sysexes.lower_bound (search);
+ assert (i == _sysexes.end() || (*i)->time() >= t);
+ return i;
+}
+
+template<typename Time>
+void
+Sequence<Time>::get_notes (Notes& n, NoteOperator op, uint8_t val, int chan_mask) const
+{
+ switch (op) {
+ case PitchEqual:
+ case PitchLessThan:
+ case PitchLessThanOrEqual:
+ case PitchGreater:
+ case PitchGreaterThanOrEqual:
+ get_notes_by_pitch (n, op, val, chan_mask);
+ break;
+
+ case VelocityEqual:
+ case VelocityLessThan:
+ case VelocityLessThanOrEqual:
+ case VelocityGreater:
+ case VelocityGreaterThanOrEqual:
+ get_notes_by_velocity (n, op, val, chan_mask);
+ break;
+ }
+}
+
+template<typename Time>
+void
+Sequence<Time>::get_notes_by_pitch (Notes& n, NoteOperator op, uint8_t val, int chan_mask) const
+{
+ for (uint8_t c = 0; c < 16; ++c) {
+
+ if (chan_mask != 0 && !((1<<c) & chan_mask)) {
+ continue;
+ }
+
+ const Pitches& p (pitches (c));
+ NotePtr search_note(new Note<Time>(0, Time(), Time(), val, 0));
+ typename Pitches::const_iterator i;
+ switch (op) {
+ case PitchEqual:
+ i = p.lower_bound (search_note);
+ while (i != p.end() && (*i)->note() == val) {
+ n.insert (*i);
+ }
+ break;
+ case PitchLessThan:
+ i = p.upper_bound (search_note);
+ while (i != p.end() && (*i)->note() < val) {
+ n.insert (*i);
+ }
+ break;
+ case PitchLessThanOrEqual:
+ i = p.upper_bound (search_note);
+ while (i != p.end() && (*i)->note() <= val) {
+ n.insert (*i);
+ }
+ break;
+ case PitchGreater:
+ i = p.lower_bound (search_note);
+ while (i != p.end() && (*i)->note() > val) {
+ n.insert (*i);
+ }
+ break;
+ case PitchGreaterThanOrEqual:
+ i = p.lower_bound (search_note);
+ while (i != p.end() && (*i)->note() >= val) {
+ n.insert (*i);
+ }
+ break;
+
+ default:
+ //fatal << string_compose (_("programming error: %1 %2", X_("get_notes_by_pitch() called with illegal operator"), op)) << endmsg;
+ abort(); /* NOTREACHED*/
+ }
+ }
+}
+
+template<typename Time>
+void
+Sequence<Time>::get_notes_by_velocity (Notes& n, NoteOperator op, uint8_t val, int chan_mask) const
+{
+ ReadLock lock (read_lock());
+
+ for (typename Notes::const_iterator i = _notes.begin(); i != _notes.end(); ++i) {
+
+ if (chan_mask != 0 && !((1<<((*i)->channel())) & chan_mask)) {
+ continue;
+ }
+
+ switch (op) {
+ case VelocityEqual:
+ if ((*i)->velocity() == val) {
+ n.insert (*i);
+ }
+ break;
+ case VelocityLessThan:
+ if ((*i)->velocity() < val) {
+ n.insert (*i);
+ }
+ break;
+ case VelocityLessThanOrEqual:
+ if ((*i)->velocity() <= val) {
+ n.insert (*i);
+ }
+ break;
+ case VelocityGreater:
+ if ((*i)->velocity() > val) {
+ n.insert (*i);
+ }
+ break;
+ case VelocityGreaterThanOrEqual:
+ if ((*i)->velocity() >= val) {
+ n.insert (*i);
+ }
+ break;
+ default:
+ // fatal << string_compose (_("programming error: %1 %2", X_("get_notes_by_velocity() called with illegal operator"), op)) << endmsg;
+ abort(); /* NOTREACHED*/
+
+ }
+ }
+}
+
+template<typename Time>
+void
+Sequence<Time>::set_overlap_pitch_resolution (OverlapPitchResolution opr)
+{
+ _overlap_pitch_resolution = opr;
+
+ /* XXX todo: clean up existing overlaps in source data? */
+}
+
+template<typename Time>
+void
+Sequence<Time>::control_list_marked_dirty ()
+{
+ set_edited (true);
+}
+
+template<typename Time>
+void
+Sequence<Time>::dump (ostream& str) const
+{
+ typename Sequence<Time>::const_iterator i;
+ str << "+++ dump\n";
+ for (i = begin(); i != end(); ++i) {
+ str << *i << endl;
+ }
+ str << "--- dump\n";
+}
+
+template class Sequence<Temporal::Beats>;
+
+} // namespace Evoral
+++ /dev/null
-/*
- * Copyright (C) 2008-2012 Hans Baier <hansfbaier@googlemail.com>
- * Copyright (C) 2008-2016 David Robillard <d@drobilla.net>
- * Copyright (C) 2008-2017 Paul Davis <paul@linuxaudiosystems.com>
- * Copyright (C) 2009-2011 Carl Hetherington <carl@carlh.net>
- * Copyright (C) 2014-2019 Robin Gareus <robin@gareus.org>
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-
-#include <algorithm>
-#include <cmath>
-#include <iostream>
-#include <limits>
-#include <stdexcept>
-#include <stdint.h>
-#include <cstdio>
-
-#if __clang__
-#include "evoral/Note.hpp"
-#endif
-
-#include "pbd/compose.h"
-#include "pbd/error.h"
-
-#include "temporal/beats.h"
-
-#include "evoral/Control.hpp"
-#include "evoral/ControlList.hpp"
-#include "evoral/ControlSet.hpp"
-#include "evoral/EventSink.hpp"
-#include "evoral/ParameterDescriptor.hpp"
-#include "evoral/Sequence.hpp"
-#include "evoral/TypeMap.hpp"
-#include "evoral/midi_util.h"
-
-#include "pbd/i18n.h"
-
-using namespace std;
-using namespace PBD;
-
-/** Minimum time between MIDI outputs from a single interpolated controller,
- expressed in beats. This is to limit the rate at which MIDI messages
- are generated. It is only applied to interpolated controllers.
-
- XXX: This is a hack. The time should probably be expressed in
- seconds rather than beats, and should be configurable etc. etc.
-*/
-static double const time_between_interpolated_controller_outputs = 1.0 / 256;
-
-namespace Evoral {
-
-// Read iterator (const_iterator)
-
-template<typename Time>
-Sequence<Time>::const_iterator::const_iterator()
- : _seq(NULL)
- , _event(boost::shared_ptr< Event<Time> >(new Event<Time>()))
- , _active_patch_change_message (NO_EVENT)
- , _type(NIL)
- , _is_end(true)
- , _control_iter(_control_iters.end())
- , _force_discrete(false)
-{
-}
-
-/** @param force_discrete true to force ControlLists to use discrete evaluation, otherwise false to get them to use their configured mode */
-template<typename Time>
-Sequence<Time>::const_iterator::const_iterator(const Sequence<Time>& seq,
- Time t,
- bool force_discrete,
- const std::set<Evoral::Parameter>& filtered,
- const std::set<WeakNotePtr>* active_notes)
- : _seq(&seq)
- , _active_patch_change_message (0)
- , _type(NIL)
- , _is_end((t == DBL_MAX) || seq.empty())
- , _note_iter(seq.notes().end())
- , _sysex_iter(seq.sysexes().end())
- , _patch_change_iter(seq.patch_changes().end())
- , _control_iter(_control_iters.end())
- , _force_discrete (force_discrete)
-{
- DEBUG_TRACE (DEBUG::Sequence, string_compose ("Created Iterator @ %1 (is end: %2)\n)", t, _is_end));
-
- if (_is_end) {
- return;
- }
-
- _lock = seq.read_lock();
-
- // Add currently active notes, if given
- if (active_notes) {
- for (typename std::set<WeakNotePtr>::const_iterator i = active_notes->begin();
- i != active_notes->end(); ++i) {
- NotePtr note = i->lock();
- if (note && note->time() <= t && note->end_time() > t) {
- _active_notes.push(note);
- }
- }
- }
-
- // Find first note which begins at or after t
- _note_iter = seq.note_lower_bound(t);
-
- // Find first sysex event at or after t
- for (typename Sequence<Time>::SysExes::const_iterator i = seq.sysexes().begin();
- i != seq.sysexes().end(); ++i) {
- if ((*i)->time() >= t) {
- _sysex_iter = i;
- break;
- }
- }
- assert(_sysex_iter == seq.sysexes().end() || (*_sysex_iter)->time() >= t);
-
- // Find first patch event at or after t
- for (typename Sequence<Time>::PatchChanges::const_iterator i = seq.patch_changes().begin(); i != seq.patch_changes().end(); ++i) {
- if ((*i)->time() >= t) {
- _patch_change_iter = i;
- break;
- }
- }
- assert (_patch_change_iter == seq.patch_changes().end() || (*_patch_change_iter)->time() >= t);
-
- // Find first control event after t
- _control_iters.reserve(seq._controls.size());
- bool found = false;
- size_t earliest_control_index = 0;
- double earliest_control_x = DBL_MAX;
- for (Controls::const_iterator i = seq._controls.begin(); i != seq._controls.end(); ++i) {
-
- if (filtered.find (i->first) != filtered.end()) {
- /* this parameter is filtered, so don't bother setting up an iterator for it */
- continue;
- }
-
- DEBUG_TRACE (DEBUG::Sequence, string_compose ("Iterator: control: %1\n", seq._type_map.to_symbol(i->first)));
- double x, y;
- bool ret;
- if (_force_discrete || i->second->list()->interpolation() == ControlList::Discrete) {
- ret = i->second->list()->rt_safe_earliest_event_discrete_unlocked (t.to_double(), x, y, true);
- } else {
- ret = i->second->list()->rt_safe_earliest_event_unlocked(t.to_double(), x, y, true);
- }
- if (!ret) {
- DEBUG_TRACE (DEBUG::Sequence, string_compose ("Iterator: CC %1 (size %2) has no events past %3\n",
- i->first.id(), i->second->list()->size(), t));
- continue;
- }
-
- assert(x >= 0);
-
- const ParameterDescriptor& desc = seq.type_map().descriptor(i->first);
- if (y < desc.lower || y > desc.upper) {
- cerr << "ERROR: Controller value " << y
- << " out of range [" << desc.lower << "," << desc.upper
- << "], event ignored" << endl;
- continue;
- }
-
- DEBUG_TRACE (DEBUG::Sequence, string_compose ("Iterator: CC %1 added (%2, %3)\n", i->first.id(), x, y));
-
- const ControlIterator new_iter(i->second->list(), x, y);
- _control_iters.push_back(new_iter);
-
- // Found a new earliest_control
- if (x < earliest_control_x) {
- earliest_control_x = x;
- earliest_control_index = _control_iters.size() - 1;
- found = true;
- }
- }
-
- if (found) {
- _control_iter = _control_iters.begin() + earliest_control_index;
- assert(_control_iter != _control_iters.end());
- assert(_control_iter->list);
- } else {
- _control_iter = _control_iters.end();
- }
-
- // Choose the earliest event overall to point to
- choose_next(t);
-
- // Allocate a new event for storing the current event in MIDI format
- _event = boost::shared_ptr< Event<Time> >(
- new Event<Time>(NO_EVENT, Time(), 4, NULL, true));
-
- // Set event from chosen sub-iterator
- set_event();
-
- if (_is_end) {
- DEBUG_TRACE(DEBUG::Sequence,
- string_compose("Starting at end @ %1\n", t));
- } else {
- DEBUG_TRACE(DEBUG::Sequence,
- string_compose("Starting at type 0x%1 : 0x%2 @ %3\n",
- (int)_event->event_type(),
- (int)_event->buffer()[0],
- _event->time()));
- }
-}
-
-template<typename Time>
-void
-Sequence<Time>::const_iterator::invalidate(std::set< boost::weak_ptr< Note<Time> > >* notes)
-{
- while (!_active_notes.empty()) {
- if (notes) {
- notes->insert(_active_notes.top());
- }
- _active_notes.pop();
- }
- _type = NIL;
- _is_end = true;
- if (_seq) {
- _note_iter = _seq->notes().end();
- _sysex_iter = _seq->sysexes().end();
- _patch_change_iter = _seq->patch_changes().end();
- _active_patch_change_message = 0;
- }
- _control_iters.clear();
- _control_iter = _control_iters.end();
- _lock.reset();
-}
-
-template<typename Time>
-Time
-Sequence<Time>::const_iterator::choose_next(Time earliest_t)
-{
- _type = NIL;
-
- // Next earliest note on, if any
- if (_note_iter != _seq->notes().end()) {
- _type = NOTE_ON;
- earliest_t = (*_note_iter)->time();
- }
-
- /* Use the next earliest patch change iff it is earlier or coincident with the note-on.
- * A patch-change with the same time-stamp applies to the concurrent note-on */
- if (_patch_change_iter != _seq->patch_changes().end()) {
- if (_type == NIL || (*_patch_change_iter)->time() <= earliest_t) {
- _type = PATCH_CHANGE;
- earliest_t = (*_patch_change_iter)->time();
- }
- }
-
- /* Use the next earliest controller iff it's earlier or coincident with the note-on
- * or patch-change. Bank-select (CC0, CC32) needs to be sent before the PGM. */
- if (_control_iter != _control_iters.end() &&
- _control_iter->list && _control_iter->x != DBL_MAX) {
- if (_type == NIL || _control_iter->x <= earliest_t.to_double()) {
- _type = CONTROL;
- earliest_t = Time(_control_iter->x);
- }
- }
-
- /* .. but prefer to send any Note-off first */
- if ((!_active_notes.empty())) {
- if (_type == NIL || _active_notes.top()->end_time().to_double() <= earliest_t.to_double()) {
- _type = NOTE_OFF;
- earliest_t = _active_notes.top()->end_time();
- }
- }
-
- /* SysEx is last, always sent after any other concurrent 3 byte event */
- if (_sysex_iter != _seq->sysexes().end()) {
- if (_type == NIL || (*_sysex_iter)->time() < earliest_t) {
- _type = SYSEX;
- earliest_t = (*_sysex_iter)->time();
- }
- }
-
- return earliest_t;
-}
-
-template<typename Time>
-void
-Sequence<Time>::const_iterator::set_event()
-{
- switch (_type) {
- case NOTE_ON:
- DEBUG_TRACE(DEBUG::Sequence, "iterator = note on\n");
- _event->assign ((*_note_iter)->on_event());
- _active_notes.push(*_note_iter);
- break;
- case NOTE_OFF:
- DEBUG_TRACE(DEBUG::Sequence, "iterator = note off\n");
- assert(!_active_notes.empty());
- _event->assign (_active_notes.top()->off_event());
- // We don't pop the active note until we increment past it
- break;
- case SYSEX:
- DEBUG_TRACE(DEBUG::Sequence, "iterator = sysex\n");
- _event->assign (*(*_sysex_iter));
- break;
- case CONTROL:
- DEBUG_TRACE(DEBUG::Sequence, "iterator = control\n");
- _seq->control_to_midi_event(_event, *_control_iter);
- break;
- case PATCH_CHANGE:
- DEBUG_TRACE(DEBUG::Sequence, "iterator = program change\n");
- _event->assign ((*_patch_change_iter)->message (_active_patch_change_message));
- break;
- default:
- _is_end = true;
- break;
- }
-
- if (_type == NIL || !_event || _event->size() == 0) {
- DEBUG_TRACE(DEBUG::Sequence, "iterator = end\n");
- _type = NIL;
- _is_end = true;
- } else {
- assert(midi_event_is_valid(_event->buffer(), _event->size()));
- }
-}
-
-template<typename Time>
-const typename Sequence<Time>::const_iterator&
-Sequence<Time>::const_iterator::operator++()
-{
- if (_is_end) {
- throw std::logic_error("Attempt to iterate past end of Sequence");
- }
-
- assert(_event && _event->buffer() && _event->size() > 0);
-
- const Event<Time>& ev = *_event.get();
-
- if (!( ev.is_note()
- || ev.is_cc()
- || ev.is_pgm_change()
- || ev.is_pitch_bender()
- || ev.is_channel_pressure()
- || ev.is_poly_pressure()
- || ev.is_sysex()) ) {
- cerr << "WARNING: Unknown event (type " << _type << "): " << hex
- << int(ev.buffer()[0]) << int(ev.buffer()[1]) << int(ev.buffer()[2]) << endl;
- }
-
- double x = 0.0;
- double y = 0.0;
- bool ret = false;
-
- // Increment past current event
- switch (_type) {
- case NOTE_ON:
- ++_note_iter;
- break;
- case NOTE_OFF:
- _active_notes.pop();
- break;
- case CONTROL:
- // Increment current controller iterator
- if (_force_discrete || _control_iter->list->interpolation() == ControlList::Discrete) {
- ret = _control_iter->list->rt_safe_earliest_event_discrete_unlocked (
- _control_iter->x, x, y, false);
- } else {
- ret = _control_iter->list->rt_safe_earliest_event_linear_unlocked (
- _control_iter->x + time_between_interpolated_controller_outputs, x, y, false);
- }
- assert(!ret || x > _control_iter->x);
- if (ret) {
- _control_iter->x = x;
- _control_iter->y = y;
- } else {
- _control_iter->list.reset();
- _control_iter->x = DBL_MAX;
- _control_iter->y = DBL_MAX;
- }
-
- // Find the controller with the next earliest event time
- _control_iter = _control_iters.begin();
- for (ControlIterators::iterator i = _control_iters.begin();
- i != _control_iters.end(); ++i) {
- if (i->x < _control_iter->x) {
- _control_iter = i;
- }
- }
- break;
- case SYSEX:
- ++_sysex_iter;
- break;
- case PATCH_CHANGE:
- ++_active_patch_change_message;
- if (_active_patch_change_message == (*_patch_change_iter)->messages()) {
- ++_patch_change_iter;
- _active_patch_change_message = 0;
- }
- break;
- default:
- assert(false);
- }
-
- // Choose the earliest event overall to point to
- choose_next(std::numeric_limits<Time>::max());
-
- // Set event from chosen sub-iterator
- set_event();
-
- assert(_is_end || (_event->size() > 0 && _event->buffer() && _event->buffer()[0] != '\0'));
-
- return *this;
-}
-
-template<typename Time>
-bool
-Sequence<Time>::const_iterator::operator==(const const_iterator& other) const
-{
- if (_seq != other._seq) {
- return false;
- } else if (_is_end || other._is_end) {
- return (_is_end == other._is_end);
- } else if (_type != other._type) {
- return false;
- } else {
- return (_event == other._event);
- }
-}
-
-template<typename Time>
-typename Sequence<Time>::const_iterator&
-Sequence<Time>::const_iterator::operator=(const const_iterator& other)
-{
- _seq = other._seq;
- _event = other._event;
- _active_notes = other._active_notes;
- _type = other._type;
- _is_end = other._is_end;
- _note_iter = other._note_iter;
- _sysex_iter = other._sysex_iter;
- _patch_change_iter = other._patch_change_iter;
- _control_iters = other._control_iters;
- _force_discrete = other._force_discrete;
- _active_patch_change_message = other._active_patch_change_message;
-
- if (other._lock) {
- _lock = _seq->read_lock();
- } else {
- _lock.reset();
- }
-
- if (other._control_iter == other._control_iters.end()) {
- _control_iter = _control_iters.end();
- } else {
- const size_t index = other._control_iter - other._control_iters.begin();
- _control_iter = _control_iters.begin() + index;
- }
-
- return *this;
-}
-
-// Sequence
-
-template<typename Time>
-Sequence<Time>::Sequence(const TypeMap& type_map)
- : _edited(false)
- , _overlapping_pitches_accepted (true)
- , _overlap_pitch_resolution (FirstOnFirstOff)
- , _writing(false)
- , _type_map(type_map)
- , _end_iter(*this, std::numeric_limits<Time>::max(), false, std::set<Evoral::Parameter> ())
- , _percussive(false)
- , _lowest_note(127)
- , _highest_note(0)
-{
- DEBUG_TRACE (DEBUG::Sequence, string_compose ("Sequence constructed: %1\n", this));
- assert(_end_iter._is_end);
- assert( ! _end_iter._lock);
-
- for (int i = 0; i < 16; ++i) {
- _bank[i] = 0;
- }
-}
-
-template<typename Time>
-Sequence<Time>::Sequence(const Sequence<Time>& other)
- : ControlSet (other)
- , _edited(false)
- , _overlapping_pitches_accepted (other._overlapping_pitches_accepted)
- , _overlap_pitch_resolution (other._overlap_pitch_resolution)
- , _writing(false)
- , _type_map(other._type_map)
- , _end_iter(*this, std::numeric_limits<Time>::max(), false, std::set<Evoral::Parameter> ())
- , _percussive(other._percussive)
- , _lowest_note(other._lowest_note)
- , _highest_note(other._highest_note)
-{
- for (typename Notes::const_iterator i = other._notes.begin(); i != other._notes.end(); ++i) {
- NotePtr n (new Note<Time> (**i));
- _notes.insert (n);
- }
-
- for (typename SysExes::const_iterator i = other._sysexes.begin(); i != other._sysexes.end(); ++i) {
- boost::shared_ptr<Event<Time> > n (new Event<Time> (**i, true));
- _sysexes.insert (n);
- }
-
- for (typename PatchChanges::const_iterator i = other._patch_changes.begin(); i != other._patch_changes.end(); ++i) {
- PatchChangePtr n (new PatchChange<Time> (**i));
- _patch_changes.insert (n);
- }
-
- for (int i = 0; i < 16; ++i) {
- _bank[i] = other._bank[i];
- }
-
- DEBUG_TRACE (DEBUG::Sequence, string_compose ("Sequence copied: %1\n", this));
- assert(_end_iter._is_end);
- assert(! _end_iter._lock);
-}
-
-/** Write the controller event pointed to by `iter` to `ev`.
- * The buffer of `ev` will be allocated or resized as necessary.
- * \return true on success
- */
-template<typename Time>
-bool
-Sequence<Time>::control_to_midi_event(
- boost::shared_ptr< Event<Time> >& ev,
- const ControlIterator& iter) const
-{
- assert(iter.list.get());
-
- // initialize the event pointer with a new event, if necessary
- if (!ev) {
- ev = boost::shared_ptr< Event<Time> >(new Event<Time>(NO_EVENT, Time(), 3, NULL, true));
- }
-
- const uint8_t midi_type = _type_map.parameter_midi_type(iter.list->parameter());
- ev->set_event_type(MIDI_EVENT);
- ev->set_id(-1);
- switch (midi_type) {
- case MIDI_CMD_CONTROL:
- assert(iter.list.get());
- assert(iter.list->parameter().channel() < 16);
- assert(iter.list->parameter().id() <= INT8_MAX);
- assert(iter.y <= INT8_MAX);
-
- ev->set_time(Time(iter.x));
- ev->realloc(3);
- ev->buffer()[0] = MIDI_CMD_CONTROL + iter.list->parameter().channel();
- ev->buffer()[1] = (uint8_t)iter.list->parameter().id();
- ev->buffer()[2] = (uint8_t)iter.y;
- break;
-
- case MIDI_CMD_PGM_CHANGE:
- assert(iter.list.get());
- assert(iter.list->parameter().channel() < 16);
- assert(iter.y <= INT8_MAX);
-
- ev->set_time(Time(iter.x));
- ev->realloc(2);
- ev->buffer()[0] = MIDI_CMD_PGM_CHANGE + iter.list->parameter().channel();
- ev->buffer()[1] = (uint8_t)iter.y;
- break;
-
- case MIDI_CMD_BENDER:
- assert(iter.list.get());
- assert(iter.list->parameter().channel() < 16);
- assert(iter.y < (1<<14));
-
- ev->set_time(Time(iter.x));
- ev->realloc(3);
- ev->buffer()[0] = MIDI_CMD_BENDER + iter.list->parameter().channel();
- ev->buffer()[1] = uint16_t(iter.y) & 0x7F; // LSB
- ev->buffer()[2] = (uint16_t(iter.y) >> 7) & 0x7F; // MSB
- break;
-
- case MIDI_CMD_NOTE_PRESSURE:
- assert(iter.list.get());
- assert(iter.list->parameter().channel() < 16);
- assert(iter.list->parameter().id() <= INT8_MAX);
- assert(iter.y <= INT8_MAX);
-
- ev->set_time(Time(iter.x));
- ev->realloc(3);
- ev->buffer()[0] = MIDI_CMD_NOTE_PRESSURE + iter.list->parameter().channel();
- ev->buffer()[1] = (uint8_t)iter.list->parameter().id();
- ev->buffer()[2] = (uint8_t)iter.y;
- break;
-
- case MIDI_CMD_CHANNEL_PRESSURE:
- assert(iter.list.get());
- assert(iter.list->parameter().channel() < 16);
- assert(iter.y <= INT8_MAX);
-
- ev->set_time(Time(iter.x));
- ev->realloc(2);
- ev->buffer()[0] = MIDI_CMD_CHANNEL_PRESSURE + iter.list->parameter().channel();
- ev->buffer()[1] = (uint8_t)iter.y;
- break;
-
- default:
- return false;
- }
-
- return true;
-}
-
-/** Clear all events from the model.
- */
-template<typename Time>
-void
-Sequence<Time>::clear()
-{
- WriteLock lock(write_lock());
- _notes.clear();
- for (Controls::iterator li = _controls.begin(); li != _controls.end(); ++li)
- li->second->list()->clear();
-}
-
-/** Begin a write of events to the model.
- *
- * If \a mode is Sustained, complete notes with length are constructed as note
- * on/off events are received. Otherwise (Percussive), only note on events are
- * stored; note off events are discarded entirely and all contained notes will
- * have length 0.
- */
-template<typename Time>
-void
-Sequence<Time>::start_write()
-{
- DEBUG_TRACE (DEBUG::Sequence, string_compose ("%1 : start_write (percussive = %2)\n", this, _percussive));
- WriteLock lock(write_lock());
- _writing = true;
- for (int i = 0; i < 16; ++i) {
- _write_notes[i].clear();
- }
-}
-
-/** Finish a write of events to the model.
- *
- * If \a delete_stuck is true and the current mode is Sustained, note on events
- * that were never resolved with a corresonding note off will be deleted.
- * Otherwise they will remain as notes with length 0.
- */
-template<typename Time>
-void
-Sequence<Time>::end_write (StuckNoteOption option, Time when)
-{
- WriteLock lock(write_lock());
-
- if (!_writing) {
- return;
- }
-
- DEBUG_TRACE (DEBUG::Sequence, string_compose ("%1 : end_write (%2 notes) delete stuck option %3 @ %4\n", this, _notes.size(), option, when));
-
- for (typename Notes::iterator n = _notes.begin(); n != _notes.end() ;) {
- typename Notes::iterator next = n;
- ++next;
-
- if (!(*n)->length()) {
- switch (option) {
- case Relax:
- break;
- case DeleteStuckNotes:
- cerr << "WARNING: Stuck note lost: " << (*n)->note() << endl;
- _notes.erase(n);
- break;
- case ResolveStuckNotes:
- if (when <= (*n)->time()) {
- cerr << "WARNING: Stuck note resolution - end time @ "
- << when << " is before note on: " << (**n) << endl;
- _notes.erase (*n);
- } else {
- (*n)->set_length (when - (*n)->time());
- cerr << "WARNING: resolved note-on with no note-off to generate " << (**n) << endl;
- }
- break;
- }
- }
-
- n = next;
- }
-
- for (int i = 0; i < 16; ++i) {
- _write_notes[i].clear();
- }
-
- _writing = false;
-}
-
-
-template<typename Time>
-bool
-Sequence<Time>::add_note_unlocked(const NotePtr note, void* arg)
-{
- /* This is the core method to add notes to a Sequence
- */
-
- DEBUG_TRACE (DEBUG::Sequence, string_compose ("%1 add note %2 @ %3 dur %4\n", this, (int)note->note(), note->time(), note->length()));
-
- if (resolve_overlaps_unlocked (note, arg)) {
- DEBUG_TRACE (DEBUG::Sequence, string_compose ("%1 DISALLOWED: note %2 @ %3\n", this, (int)note->note(), note->time()));
- return false;
- }
-
- if (note->id() < 0) {
- note->set_id (Evoral::next_event_id());
- }
-
- if (note->note() < _lowest_note)
- _lowest_note = note->note();
- if (note->note() > _highest_note)
- _highest_note = note->note();
-
- _notes.insert (note);
- _pitches[note->channel()].insert (note);
-
- _edited = true;
-
- return true;
-}
-
-template<typename Time>
-void
-Sequence<Time>::remove_note_unlocked(const constNotePtr note)
-{
- bool erased = false;
- bool id_matched = false;
-
- DEBUG_TRACE (DEBUG::Sequence, string_compose ("%1 remove note #%2 %3 @ %4\n", this, note->id(), (int)note->note(), note->time()));
-
- /* first try searching for the note using the time index, which is
- * faster since the container is "indexed" by time. (technically, this
- * means that lower_bound() can do a binary search rather than linear)
- *
- * this may not work, for reasons explained below.
- */
-
- typename Sequence<Time>::Notes::iterator i;
-
- for (i = note_lower_bound(note->time()); i != _notes.end() && (*i)->time() == note->time(); ++i) {
-
- if (*i == note) {
-
- DEBUG_TRACE (DEBUG::Sequence, string_compose ("%1\terasing note #%2 %3 @ %4\n", this, (*i)->id(), (int)(*i)->note(), (*i)->time()));
- _notes.erase (i);
-
- if (note->note() == _lowest_note || note->note() == _highest_note) {
-
- _lowest_note = 127;
- _highest_note = 0;
-
- for (typename Sequence<Time>::Notes::iterator ii = _notes.begin(); ii != _notes.end(); ++ii) {
- if ((*ii)->note() < _lowest_note)
- _lowest_note = (*ii)->note();
- if ((*ii)->note() > _highest_note)
- _highest_note = (*ii)->note();
- }
- }
-
- erased = true;
- break;
- }
- }
-
- DEBUG_TRACE (DEBUG::Sequence, string_compose ("%1\ttime-based lookup did not find note #%2 %3 @ %4\n", this, note->id(), (int)note->note(), note->time()));
-
- if (!erased) {
-
- /* if the note's time property was changed in tandem with some
- * other property as the next operation after it was added to
- * the sequence, then at the point where we call this to undo
- * the add, the note we are targetting currently has a
- * different time property than the one we we passed via
- * the argument.
- *
- * in this scenario, we have no choice other than to linear
- * search the list of notes and find the note by ID.
- */
-
- for (i = _notes.begin(); i != _notes.end(); ++i) {
-
- if ((*i)->id() == note->id()) {
-
- DEBUG_TRACE (DEBUG::Sequence, string_compose ("%1\tID-based pass, erasing note #%2 %3 @ %4\n", this, (*i)->id(), (int)(*i)->note(), (*i)->time()));
- _notes.erase (i);
-
- if (note->note() == _lowest_note || note->note() == _highest_note) {
-
- _lowest_note = 127;
- _highest_note = 0;
-
- for (typename Sequence<Time>::Notes::iterator ii = _notes.begin(); ii != _notes.end(); ++ii) {
- if ((*ii)->note() < _lowest_note)
- _lowest_note = (*ii)->note();
- if ((*ii)->note() > _highest_note)
- _highest_note = (*ii)->note();
- }
- }
-
- erased = true;
- id_matched = true;
- break;
- }
- }
- }
-
- if (erased) {
-
- Pitches& p (pitches (note->channel()));
-
- typename Pitches::iterator j;
-
- /* if we had to ID-match above, we can't expect to find it in
- * pitches via note comparison either. so do another linear
- * search to locate it. otherwise, we can use the note index
- * to potentially speed things up.
- */
-
- if (id_matched) {
-
- for (j = p.begin(); j != p.end(); ++j) {
- if ((*j)->id() == note->id()) {
- p.erase (j);
- break;
- }
- }
-
- } else {
-
- /* Now find the same note in the "pitches" list (which indexes
- * notes by channel+time. We care only about its note number
- * so the search_note has all other properties unset.
- */
-
- NotePtr search_note (new Note<Time>(0, Time(), Time(), note->note(), 0));
-
- for (j = p.lower_bound (search_note); j != p.end() && (*j)->note() == note->note(); ++j) {
-
- if ((*j) == note) {
- DEBUG_TRACE (DEBUG::Sequence, string_compose ("%1\terasing pitch %2 @ %3\n", this, (int)(*j)->note(), (*j)->time()));
- p.erase (j);
- break;
- }
- }
- }
-
- if (j == p.end()) {
- warning << string_compose ("erased note %1 not found in pitches for channel %2", *note, (int) note->channel()) << endmsg;
- }
-
- _edited = true;
-
- } else {
- cerr << "Unable to find note to erase matching " << *note.get() << endmsg;
- }
-}
-
-template<typename Time>
-void
-Sequence<Time>::remove_patch_change_unlocked (const constPatchChangePtr p)
-{
- typename Sequence<Time>::PatchChanges::iterator i = patch_change_lower_bound (p->time ());
-
- while (i != _patch_changes.end() && ((*i)->time() == p->time())) {
-
- typename Sequence<Time>::PatchChanges::iterator tmp = i;
- ++tmp;
-
- if (**i == *p) {
- _patch_changes.erase (i);
- }
-
- i = tmp;
- }
-}
-
-template<typename Time>
-void
-Sequence<Time>::remove_sysex_unlocked (const SysExPtr sysex)
-{
- typename Sequence<Time>::SysExes::iterator i = sysex_lower_bound (sysex->time ());
- while (i != _sysexes.end() && (*i)->time() == sysex->time()) {
-
- typename Sequence<Time>::SysExes::iterator tmp = i;
- ++tmp;
-
- if (*i == sysex) {
- _sysexes.erase (i);
- }
-
- i = tmp;
- }
-}
-
-/** Append \a ev to model. NOT realtime safe.
- *
- * The timestamp of event is expected to be relative to
- * the start of this model (t=0) and MUST be monotonically increasing
- * and MUST be >= the latest event currently in the model.
- */
-template<typename Time>
-void
-Sequence<Time>::append(const Event<Time>& ev, event_id_t evid)
-{
- WriteLock lock(write_lock());
-
- assert(_notes.empty() || ev.time() >= (*_notes.rbegin())->time());
- assert(_writing);
-
- if (!midi_event_is_valid(ev.buffer(), ev.size())) {
- cerr << "WARNING: Sequence ignoring illegal MIDI event" << endl;
- return;
- }
-
- if (ev.is_note_on() && ev.velocity() > 0) {
- append_note_on_unlocked (ev, evid);
- } else if (ev.is_note_off() || (ev.is_note_on() && ev.velocity() == 0)) {
- /* XXX note: event ID is discarded because we merge the on+off events into
- a single note object
- */
- append_note_off_unlocked (ev);
- } else if (ev.is_sysex()) {
- append_sysex_unlocked(ev, evid);
- } else if (ev.is_cc() && (ev.cc_number() == MIDI_CTL_MSB_BANK || ev.cc_number() == MIDI_CTL_LSB_BANK)) {
- /* note bank numbers in our _bank[] array, so that we can write an event when the program change arrives */
- if (ev.cc_number() == MIDI_CTL_MSB_BANK) {
- _bank[ev.channel()] &= ~(0x7f << 7);
- _bank[ev.channel()] |= ev.cc_value() << 7;
- } else {
- _bank[ev.channel()] &= ~0x7f;
- _bank[ev.channel()] |= ev.cc_value();
- }
- } else if (ev.is_cc()) {
- const ParameterType ptype = _type_map.midi_parameter_type(ev.buffer(), ev.size());
- append_control_unlocked(
- Parameter(ptype, ev.channel(), ev.cc_number()),
- ev.time(), ev.cc_value(), evid);
- } else if (ev.is_pgm_change()) {
- /* write a patch change with this program change and any previously set-up bank number */
- append_patch_change_unlocked (
- PatchChange<Time> (ev.time(), ev.channel(),
- ev.pgm_number(), _bank[ev.channel()]), evid);
- } else if (ev.is_pitch_bender()) {
- const ParameterType ptype = _type_map.midi_parameter_type(ev.buffer(), ev.size());
- append_control_unlocked(
- Parameter(ptype, ev.channel()),
- ev.time(), double ((0x7F & ev.pitch_bender_msb()) << 7
- | (0x7F & ev.pitch_bender_lsb())),
- evid);
- } else if (ev.is_poly_pressure()) {
- const ParameterType ptype = _type_map.midi_parameter_type(ev.buffer(), ev.size());
- append_control_unlocked (Parameter (ptype, ev.channel(), ev.poly_note()), ev.time(), ev.poly_pressure(), evid);
- } else if (ev.is_channel_pressure()) {
- const ParameterType ptype = _type_map.midi_parameter_type(ev.buffer(), ev.size());
- append_control_unlocked(
- Parameter(ptype, ev.channel()),
- ev.time(), ev.channel_pressure(), evid);
- } else if (!_type_map.type_is_midi(ev.event_type())) {
- printf("WARNING: Sequence: Unknown event type %X: ", ev.event_type());
- for (size_t i=0; i < ev.size(); ++i) {
- printf("%X ", ev.buffer()[i]);
- }
- printf("\n");
- } else {
- printf("WARNING: Sequence: Unknown MIDI event type %X\n", ev.type());
- }
-
- _edited = true;
-}
-
-template<typename Time>
-void
-Sequence<Time>::append_note_on_unlocked (const Event<Time>& ev, event_id_t evid)
-{
- DEBUG_TRACE (DEBUG::Sequence, string_compose ("%1 c=%2 note %3 on @ %4 v=%5\n", this,
- (int)ev.channel(), (int)ev.note(),
- ev.time(), (int)ev.velocity()));
- assert(_writing);
-
- if (ev.note() > 127) {
- error << string_compose (_("invalid note on number (%1) ignored"), (int) ev.note()) << endmsg;
- return;
- } else if (ev.channel() >= 16) {
- error << string_compose (_("invalid note on channel (%1) ignored"), (int) ev.channel()) << endmsg;
- return;
- } else if (ev.velocity() == 0) {
- // Note on with velocity 0 handled as note off by caller
- error << string_compose (_("invalid note on velocity (%1) ignored"), (int) ev.velocity()) << endmsg;
- return;
- }
-
- NotePtr note(new Note<Time>(ev.channel(), ev.time(), Time(), ev.note(), ev.velocity()));
- note->set_id (evid);
-
- add_note_unlocked (note);
-
- DEBUG_TRACE (DEBUG::Sequence, string_compose ("Appending active note on %1 channel %2\n",
- (unsigned)(uint8_t)note->note(), note->channel()));
- _write_notes[note->channel()].insert (note);
-
-}
-
-template<typename Time>
-void
-Sequence<Time>::append_note_off_unlocked (const Event<Time>& ev)
-{
- DEBUG_TRACE (DEBUG::Sequence, string_compose ("%1 c=%2 note %3 OFF @ %4 v=%5\n",
- this, (int)ev.channel(),
- (int)ev.note(), ev.time(), (int)ev.velocity()));
- assert(_writing);
-
- if (ev.note() > 127) {
- error << string_compose (_("invalid note off number (%1) ignored"), (int) ev.note()) << endmsg;
- return;
- } else if (ev.channel() >= 16) {
- error << string_compose (_("invalid note off channel (%1) ignored"), (int) ev.channel()) << endmsg;
- return;
- }
-
- _edited = true;
-
- bool resolved = false;
-
- /* _write_notes is sorted earliest-latest, so this will find the first matching note (FIFO) that
- matches this note (by pitch & channel). the MIDI specification doesn't provide any guidance
- whether to use FIFO or LIFO for this matching process, so SMF is fundamentally a lossy
- format.
- */
-
- /* XXX use _overlap_pitch_resolution to determine FIFO/LIFO ... */
-
- for (typename WriteNotes::iterator n = _write_notes[ev.channel()].begin(); n != _write_notes[ev.channel()].end(); ) {
-
- typename WriteNotes::iterator tmp = n;
- ++tmp;
-
- NotePtr nn = *n;
- if (ev.note() == nn->note() && nn->channel() == ev.channel()) {
- assert(ev.time() >= nn->time());
-
- nn->set_length (ev.time() - nn->time());
- nn->set_off_velocity (ev.velocity());
-
- _write_notes[ev.channel()].erase(n);
- DEBUG_TRACE (DEBUG::Sequence, string_compose ("resolved note @ %2 length: %1\n", nn->length(), nn->time()));
- resolved = true;
- break;
- }
-
- n = tmp;
- }
-
- if (!resolved) {
- cerr << this << " spurious note off chan " << (int)ev.channel()
- << ", note " << (int)ev.note() << " @ " << ev.time() << endl;
- }
-}
-
-template<typename Time>
-void
-Sequence<Time>::append_control_unlocked(const Parameter& param, Time time, double value, event_id_t /* evid */)
-{
- DEBUG_TRACE (DEBUG::Sequence, string_compose ("%1 %2 @ %3 = %4 # controls: %5\n",
- this, _type_map.to_symbol(param), time, value, _controls.size()));
- boost::shared_ptr<Control> c = control(param, true);
- c->list()->add (time.to_double(), value, true, false);
- /* XXX control events should use IDs */
-}
-
-template<typename Time>
-void
-Sequence<Time>::append_sysex_unlocked(const Event<Time>& ev, event_id_t /* evid */)
-{
-#ifdef DEBUG_SEQUENCE
- cerr << this << " SysEx @ " << ev.time() << " \t= \t [ " << hex;
- for (size_t i=0; i < ev.size(); ++i) {
- cerr << int(ev.buffer()[i]) << " ";
- } cerr << "]" << endl;
-#endif
-
- boost::shared_ptr< Event<Time> > event(new Event<Time>(ev, true));
- /* XXX sysex events should use IDs */
- _sysexes.insert(event);
-}
-
-template<typename Time>
-void
-Sequence<Time>::append_patch_change_unlocked (const PatchChange<Time>& ev, event_id_t id)
-{
- PatchChangePtr p (new PatchChange<Time> (ev));
-
- if (p->id() < 0) {
- p->set_id (id);
- }
-
- _patch_changes.insert (p);
-}
-
-template<typename Time>
-void
-Sequence<Time>::add_patch_change_unlocked (PatchChangePtr p)
-{
- if (p->id () < 0) {
- p->set_id (Evoral::next_event_id ());
- }
-
- _patch_changes.insert (p);
-}
-
-template<typename Time>
-void
-Sequence<Time>::add_sysex_unlocked (SysExPtr s)
-{
- if (s->id () < 0) {
- s->set_id (Evoral::next_event_id ());
- }
-
- _sysexes.insert (s);
-}
-
-template<typename Time>
-bool
-Sequence<Time>::contains (const NotePtr& note) const
-{
- ReadLock lock (read_lock());
- return contains_unlocked (note);
-}
-
-template<typename Time>
-bool
-Sequence<Time>::contains_unlocked (const NotePtr& note) const
-{
- const Pitches& p (pitches (note->channel()));
- NotePtr search_note(new Note<Time>(0, Time(), Time(), note->note()));
-
- for (typename Pitches::const_iterator i = p.lower_bound (search_note);
- i != p.end() && (*i)->note() == note->note(); ++i) {
-
- if (**i == *note) {
- return true;
- }
- }
-
- return false;
-}
-
-template<typename Time>
-bool
-Sequence<Time>::overlaps (const NotePtr& note, const NotePtr& without) const
-{
- ReadLock lock (read_lock());
- return overlaps_unlocked (note, without);
-}
-
-template<typename Time>
-bool
-Sequence<Time>::overlaps_unlocked (const NotePtr& note, const NotePtr& without) const
-{
- Time sa = note->time();
- Time ea = note->end_time();
-
- const Pitches& p (pitches (note->channel()));
- NotePtr search_note(new Note<Time>(0, Time(), Time(), note->note()));
-
- for (typename Pitches::const_iterator i = p.lower_bound (search_note);
- i != p.end() && (*i)->note() == note->note(); ++i) {
-
- if (without && (**i) == *without) {
- continue;
- }
-
- Time sb = (*i)->time();
- Time eb = (*i)->end_time();
-
- if (((sb > sa) && (eb <= ea)) ||
- ((eb >= sa) && (eb <= ea)) ||
- ((sb > sa) && (sb <= ea)) ||
- ((sa >= sb) && (sa <= eb) && (ea <= eb))) {
- return true;
- }
- }
-
- return false;
-}
-
-template<typename Time>
-void
-Sequence<Time>::set_notes (const typename Sequence<Time>::Notes& n)
-{
- _notes = n;
-}
-
-// CONST iterator implementations (x3)
-
-/** Return the earliest note with time >= t */
-template<typename Time>
-typename Sequence<Time>::Notes::const_iterator
-Sequence<Time>::note_lower_bound (Time t) const
-{
- NotePtr search_note(new Note<Time>(0, t, Time(), 0, 0));
- typename Sequence<Time>::Notes::const_iterator i = _notes.lower_bound(search_note);
- assert(i == _notes.end() || (*i)->time() >= t);
- return i;
-}
-
-/** Return the earliest patch change with time >= t */
-template<typename Time>
-typename Sequence<Time>::PatchChanges::const_iterator
-Sequence<Time>::patch_change_lower_bound (Time t) const
-{
- PatchChangePtr search (new PatchChange<Time> (t, 0, 0, 0));
- typename Sequence<Time>::PatchChanges::const_iterator i = _patch_changes.lower_bound (search);
- assert (i == _patch_changes.end() || (*i)->time() >= t);
- return i;
-}
-
-/** Return the earliest sysex with time >= t */
-template<typename Time>
-typename Sequence<Time>::SysExes::const_iterator
-Sequence<Time>::sysex_lower_bound (Time t) const
-{
- SysExPtr search (new Event<Time> (NO_EVENT, t));
- typename Sequence<Time>::SysExes::const_iterator i = _sysexes.lower_bound (search);
- assert (i == _sysexes.end() || (*i)->time() >= t);
- return i;
-}
-
-// NON-CONST iterator implementations (x3)
-
-/** Return the earliest note with time >= t */
-template<typename Time>
-typename Sequence<Time>::Notes::iterator
-Sequence<Time>::note_lower_bound (Time t)
-{
- NotePtr search_note(new Note<Time>(0, t, Time(), 0, 0));
- typename Sequence<Time>::Notes::iterator i = _notes.lower_bound(search_note);
- assert(i == _notes.end() || (*i)->time() >= t);
- return i;
-}
-
-/** Return the earliest patch change with time >= t */
-template<typename Time>
-typename Sequence<Time>::PatchChanges::iterator
-Sequence<Time>::patch_change_lower_bound (Time t)
-{
- PatchChangePtr search (new PatchChange<Time> (t, 0, 0, 0));
- typename Sequence<Time>::PatchChanges::iterator i = _patch_changes.lower_bound (search);
- assert (i == _patch_changes.end() || (*i)->time() >= t);
- return i;
-}
-
-/** Return the earliest sysex with time >= t */
-template<typename Time>
-typename Sequence<Time>::SysExes::iterator
-Sequence<Time>::sysex_lower_bound (Time t)
-{
- SysExPtr search (new Event<Time> (NO_EVENT, t));
- typename Sequence<Time>::SysExes::iterator i = _sysexes.lower_bound (search);
- assert (i == _sysexes.end() || (*i)->time() >= t);
- return i;
-}
-
-template<typename Time>
-void
-Sequence<Time>::get_notes (Notes& n, NoteOperator op, uint8_t val, int chan_mask) const
-{
- switch (op) {
- case PitchEqual:
- case PitchLessThan:
- case PitchLessThanOrEqual:
- case PitchGreater:
- case PitchGreaterThanOrEqual:
- get_notes_by_pitch (n, op, val, chan_mask);
- break;
-
- case VelocityEqual:
- case VelocityLessThan:
- case VelocityLessThanOrEqual:
- case VelocityGreater:
- case VelocityGreaterThanOrEqual:
- get_notes_by_velocity (n, op, val, chan_mask);
- break;
- }
-}
-
-template<typename Time>
-void
-Sequence<Time>::get_notes_by_pitch (Notes& n, NoteOperator op, uint8_t val, int chan_mask) const
-{
- for (uint8_t c = 0; c < 16; ++c) {
-
- if (chan_mask != 0 && !((1<<c) & chan_mask)) {
- continue;
- }
-
- const Pitches& p (pitches (c));
- NotePtr search_note(new Note<Time>(0, Time(), Time(), val, 0));
- typename Pitches::const_iterator i;
- switch (op) {
- case PitchEqual:
- i = p.lower_bound (search_note);
- while (i != p.end() && (*i)->note() == val) {
- n.insert (*i);
- }
- break;
- case PitchLessThan:
- i = p.upper_bound (search_note);
- while (i != p.end() && (*i)->note() < val) {
- n.insert (*i);
- }
- break;
- case PitchLessThanOrEqual:
- i = p.upper_bound (search_note);
- while (i != p.end() && (*i)->note() <= val) {
- n.insert (*i);
- }
- break;
- case PitchGreater:
- i = p.lower_bound (search_note);
- while (i != p.end() && (*i)->note() > val) {
- n.insert (*i);
- }
- break;
- case PitchGreaterThanOrEqual:
- i = p.lower_bound (search_note);
- while (i != p.end() && (*i)->note() >= val) {
- n.insert (*i);
- }
- break;
-
- default:
- //fatal << string_compose (_("programming error: %1 %2", X_("get_notes_by_pitch() called with illegal operator"), op)) << endmsg;
- abort(); /* NOTREACHED*/
- }
- }
-}
-
-template<typename Time>
-void
-Sequence<Time>::get_notes_by_velocity (Notes& n, NoteOperator op, uint8_t val, int chan_mask) const
-{
- ReadLock lock (read_lock());
-
- for (typename Notes::const_iterator i = _notes.begin(); i != _notes.end(); ++i) {
-
- if (chan_mask != 0 && !((1<<((*i)->channel())) & chan_mask)) {
- continue;
- }
-
- switch (op) {
- case VelocityEqual:
- if ((*i)->velocity() == val) {
- n.insert (*i);
- }
- break;
- case VelocityLessThan:
- if ((*i)->velocity() < val) {
- n.insert (*i);
- }
- break;
- case VelocityLessThanOrEqual:
- if ((*i)->velocity() <= val) {
- n.insert (*i);
- }
- break;
- case VelocityGreater:
- if ((*i)->velocity() > val) {
- n.insert (*i);
- }
- break;
- case VelocityGreaterThanOrEqual:
- if ((*i)->velocity() >= val) {
- n.insert (*i);
- }
- break;
- default:
- // fatal << string_compose (_("programming error: %1 %2", X_("get_notes_by_velocity() called with illegal operator"), op)) << endmsg;
- abort(); /* NOTREACHED*/
-
- }
- }
-}
-
-template<typename Time>
-void
-Sequence<Time>::set_overlap_pitch_resolution (OverlapPitchResolution opr)
-{
- _overlap_pitch_resolution = opr;
-
- /* XXX todo: clean up existing overlaps in source data? */
-}
-
-template<typename Time>
-void
-Sequence<Time>::control_list_marked_dirty ()
-{
- set_edited (true);
-}
-
-template<typename Time>
-void
-Sequence<Time>::dump (ostream& str) const
-{
- typename Sequence<Time>::const_iterator i;
- str << "+++ dump\n";
- for (i = begin(); i != end(); ++i) {
- str << *i << endl;
- }
- str << "--- dump\n";
-}
-
-template class Sequence<Temporal::Beats>;
-
-} // namespace Evoral
--- /dev/null
+/*
+ * Copyright (C) 2014-2015 David Robillard <d@drobilla.net>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include <stdint.h>
+
+#include "temporal/beats.h"
+#include "temporal/types.h"
+
+#include "evoral/TimeConverter.h"
+#include "evoral/types.h"
+
+namespace Evoral {
+
+template<typename A, typename B>
+TimeConverter<A,B>::~TimeConverter()
+{}
+
+template<typename A, typename B>
+B
+IdentityConverter<A,B>::to(A a) const
+{
+ return static_cast<B>(a);
+}
+
+template<typename A, typename B>
+A
+IdentityConverter<A,B>::from(B b) const
+{
+ return static_cast<A>(b);
+}
+
+template class IdentityConverter<double, Temporal::samplepos_t>;
+template class TimeConverter<double, Temporal::samplepos_t>;
+template class TimeConverter<Temporal::Beats, Temporal::samplepos_t>;
+
+} // namespace Evoral
+++ /dev/null
-/*
- * Copyright (C) 2014-2015 David Robillard <d@drobilla.net>
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-
-#include <stdint.h>
-
-#include "temporal/beats.h"
-#include "temporal/types.h"
-
-#include "evoral/TimeConverter.hpp"
-#include "evoral/types.hpp"
-
-namespace Evoral {
-
-template<typename A, typename B>
-TimeConverter<A,B>::~TimeConverter()
-{}
-
-template<typename A, typename B>
-B
-IdentityConverter<A,B>::to(A a) const
-{
- return static_cast<B>(a);
-}
-
-template<typename A, typename B>
-A
-IdentityConverter<A,B>::from(B b) const
-{
- return static_cast<A>(b);
-}
-
-template class IdentityConverter<double, Temporal::samplepos_t>;
-template class TimeConverter<double, Temporal::samplepos_t>;
-template class TimeConverter<Temporal::Beats, Temporal::samplepos_t>;
-
-} // namespace Evoral
--- /dev/null
+#include "evoral/types.h"
+
+PBD::DebugBits PBD::DEBUG::Sequence = PBD::new_debug_bit ("sequence");
+PBD::DebugBits PBD::DEBUG::Note = PBD::new_debug_bit ("note");
+PBD::DebugBits PBD::DEBUG::ControlList = PBD::new_debug_bit ("controllist");
+
+++ /dev/null
-#include "evoral/types.hpp"
-
-PBD::DebugBits PBD::DEBUG::Sequence = PBD::new_debug_bit ("sequence");
-PBD::DebugBits PBD::DEBUG::Note = PBD::new_debug_bit ("note");
-PBD::DebugBits PBD::DEBUG::ControlList = PBD::new_debug_bit ("controllist");
-
--- /dev/null
+#include <stdlib.h>
+
+#include "BeatsTest.hpp"
+#include "temporal/beats.h"
+
+CPPUNIT_TEST_SUITE_REGISTRATION(BeatsTest);
+
+using namespace Evoral;
+using namespace Temporal;
+
+static const double delta = 1.5 / (double)Beats::PPQN;
+
+void
+BeatsTest::createTest()
+{
+ const Beats a(1, 2);
+ CPPUNIT_ASSERT_EQUAL(1, a.get_beats());
+ CPPUNIT_ASSERT_EQUAL(2, a.get_ticks());
+ CPPUNIT_ASSERT_DOUBLES_EQUAL(1 + 2 / (double)Beats::PPQN, a.to_double(), delta);
+
+ const Beats b(1.5);
+ CPPUNIT_ASSERT_EQUAL(1, b.get_beats());
+ CPPUNIT_ASSERT_EQUAL(Beats::PPQN / 2, b.get_ticks());
+ CPPUNIT_ASSERT_DOUBLES_EQUAL(1.5, b.to_double(), delta);
+
+ const Beats c = Beats::beats(6);
+ CPPUNIT_ASSERT_EQUAL(6, c.get_beats());
+ CPPUNIT_ASSERT_EQUAL(0, c.get_ticks());
+
+ const Beats d = Beats::ticks(7);
+ CPPUNIT_ASSERT_EQUAL(0, d.get_beats());
+ CPPUNIT_ASSERT_EQUAL(7, d.get_ticks());
+
+ Beats e(8, 9);
+ e = d;
+ CPPUNIT_ASSERT_EQUAL(d, e);
+
+
+ // const Beats diff = n2 - n1;
+ // CPPUNIT_ASSERT_EQUAL(-44, diff.get_beats());
+ // CPPUNIT_ASSERT_EQUAL(44 / Beats::PPQN, diff.get_ticks());
+ // CPPUNIT_ASSERT_DOUBLES_EQUAL(diff.to_double(), -44.44, delta);
+}
+
+void
+BeatsTest::addTest()
+{
+ const Beats a(1, 2);
+ const Beats b(3, 4);
+
+ // Positive + positive
+ const Beats c = a + b;
+ CPPUNIT_ASSERT_EQUAL(4, c.get_beats());
+ CPPUNIT_ASSERT_EQUAL(6, c.get_ticks());
+
+ const Beats n1(-12.34);
+ CPPUNIT_ASSERT_DOUBLES_EQUAL(-12.34, n1.to_double(), delta);
+
+ const Beats n2(-56.78);
+ CPPUNIT_ASSERT_DOUBLES_EQUAL(-56.78, n2.to_double(), delta);
+
+ // Positive + negative
+ const Beats p1(1.0);
+ const Beats p_n = p1 + n1;
+ CPPUNIT_ASSERT_EQUAL(-11, p_n.get_beats());
+ CPPUNIT_ASSERT_EQUAL((int32_t)(Beats::PPQN * -0.34), p_n.get_ticks());
+ CPPUNIT_ASSERT_DOUBLES_EQUAL(-11.34, p_n.to_double(), delta);
+
+ // Negative + positive
+ const Beats n_p = n1 + p1;
+ CPPUNIT_ASSERT_EQUAL(-11, n_p.get_beats());
+ CPPUNIT_ASSERT_EQUAL((int32_t)(Beats::PPQN * -0.34), n_p.get_ticks());
+ CPPUNIT_ASSERT_DOUBLES_EQUAL(-11.34, n_p.to_double(), delta);
+
+ // Negative + negative
+ const Beats sum = n1 + n2;
+ CPPUNIT_ASSERT_EQUAL(-69, sum.get_beats());
+ //CPPUNIT_ASSERT_EQUAL((int32_t)(Beats::PPQN * -0.12), n_p.get_ticks());
+ CPPUNIT_ASSERT_DOUBLES_EQUAL(-69.12, sum.to_double(), delta);
+}
+
+void
+BeatsTest::subtractTest()
+{
+ const Beats a(1, 2);
+ const Beats b(3, 4);
+
+ // Positive - positive
+ const Beats c = b - a;
+ CPPUNIT_ASSERT_EQUAL(2, c.get_beats());
+ CPPUNIT_ASSERT_EQUAL(2, c.get_ticks());
+
+ const Beats n1(-12.34);
+ CPPUNIT_ASSERT_DOUBLES_EQUAL(-12.34, n1.to_double(), delta);
+
+ const Beats n2(-56.78);
+ CPPUNIT_ASSERT_DOUBLES_EQUAL(-56.78, n2.to_double(), delta);
+
+ // Positive - negative
+ const Beats p1(1.0);
+ const Beats p_n = p1 - n1;
+ CPPUNIT_ASSERT_EQUAL(13, p_n.get_beats());
+ CPPUNIT_ASSERT_EQUAL((int32_t)(Beats::PPQN * 0.34), p_n.get_ticks());
+ CPPUNIT_ASSERT_DOUBLES_EQUAL(13.34, p_n.to_double(), delta);
+
+ // Negative - positive
+ const Beats n_p = n1 - p1;
+ CPPUNIT_ASSERT_EQUAL(-13, n_p.get_beats());
+ CPPUNIT_ASSERT_EQUAL((int32_t)(Beats::PPQN * -0.34), n_p.get_ticks());
+ CPPUNIT_ASSERT_DOUBLES_EQUAL(-13.34, n_p.to_double(), delta);
+
+ // Negative - negative
+ const Beats diff = n1 - n2;
+ CPPUNIT_ASSERT_EQUAL(44, diff.get_beats());
+ CPPUNIT_ASSERT_EQUAL((int32_t)lrint(Beats::PPQN * 0.44), diff.get_ticks());
+ CPPUNIT_ASSERT_DOUBLES_EQUAL(44.44, diff.to_double(), delta);
+}
+
+void
+BeatsTest::multiplyTest()
+{
+ CPPUNIT_ASSERT_DOUBLES_EQUAL(3.0, (Beats(1.5) * 2.0).to_double(), delta);
+ CPPUNIT_ASSERT_DOUBLES_EQUAL(-10.0, (Beats(5.0) * -2.0).to_double(), delta);
+ CPPUNIT_ASSERT_DOUBLES_EQUAL(-10.0, (Beats(-5.0) * 2.0).to_double(), delta);
+}
+
+void
+BeatsTest::roundTest()
+{
+ Beats a(1, 1);
+
+ // Round a up
+ const Beats au = a.round_up_to_beat();
+ CPPUNIT_ASSERT_EQUAL(au.get_beats(), 2);
+ CPPUNIT_ASSERT_EQUAL(au.get_ticks(), 0);
+
+ // Round a down
+ const Beats ad = a.round_down_to_beat();
+ CPPUNIT_ASSERT_EQUAL(ad.get_beats(), 1);
+ CPPUNIT_ASSERT_EQUAL(ad.get_ticks(), 0);
+
+ // Round result down again
+ const Beats add = ad.round_down_to_beat();
+ CPPUNIT_ASSERT_EQUAL(ad, add);
+
+ // Round result up
+ const Beats adu = ad.round_up_to_beat();
+ CPPUNIT_ASSERT_EQUAL(ad, adu);
+
+ // Snap to 1.5
+ const Beats snapped = a.snap_to(Beats(1.5));
+ CPPUNIT_ASSERT_EQUAL(snapped.get_beats(), 1);
+ CPPUNIT_ASSERT_EQUAL(snapped.get_ticks(), Beats::PPQN / 2);
+}
+
+void
+BeatsTest::convertTest()
+{
+ const Beats a = Beats::ticks_at_rate(72000, 48000);
+ CPPUNIT_ASSERT_DOUBLES_EQUAL(1, a.get_beats(), delta);
+ CPPUNIT_ASSERT_DOUBLES_EQUAL(Beats::PPQN / 2, a.get_ticks(), delta);
+ CPPUNIT_ASSERT_DOUBLES_EQUAL(1.5, a.to_double(), delta);
+
+ const Beats b = Beats::ticks_at_rate(8, 48000);
+ CPPUNIT_ASSERT_DOUBLES_EQUAL(0, b.get_beats(), delta);
+ CPPUNIT_ASSERT_DOUBLES_EQUAL(Beats::PPQN * 8 / 48000, b.get_ticks(), delta);
+ CPPUNIT_ASSERT_DOUBLES_EQUAL((8 / 48000.0), b.to_double(), delta);
+
+ CPPUNIT_ASSERT_EQUAL(int64_t(1.5 * Beats::PPQN), a.to_ticks());
+ CPPUNIT_ASSERT_EQUAL(int64_t(1.5 * 192), a.to_ticks(192));
+}
+++ /dev/null
-#include <stdlib.h>
-
-#include "BeatsTest.hpp"
-#include "temporal/beats.h"
-
-CPPUNIT_TEST_SUITE_REGISTRATION(BeatsTest);
-
-using namespace Evoral;
-using namespace Temporal;
-
-static const double delta = 1.5 / (double)Beats::PPQN;
-
-void
-BeatsTest::createTest()
-{
- const Beats a(1, 2);
- CPPUNIT_ASSERT_EQUAL(1, a.get_beats());
- CPPUNIT_ASSERT_EQUAL(2, a.get_ticks());
- CPPUNIT_ASSERT_DOUBLES_EQUAL(1 + 2 / (double)Beats::PPQN, a.to_double(), delta);
-
- const Beats b(1.5);
- CPPUNIT_ASSERT_EQUAL(1, b.get_beats());
- CPPUNIT_ASSERT_EQUAL(Beats::PPQN / 2, b.get_ticks());
- CPPUNIT_ASSERT_DOUBLES_EQUAL(1.5, b.to_double(), delta);
-
- const Beats c = Beats::beats(6);
- CPPUNIT_ASSERT_EQUAL(6, c.get_beats());
- CPPUNIT_ASSERT_EQUAL(0, c.get_ticks());
-
- const Beats d = Beats::ticks(7);
- CPPUNIT_ASSERT_EQUAL(0, d.get_beats());
- CPPUNIT_ASSERT_EQUAL(7, d.get_ticks());
-
- Beats e(8, 9);
- e = d;
- CPPUNIT_ASSERT_EQUAL(d, e);
-
-
- // const Beats diff = n2 - n1;
- // CPPUNIT_ASSERT_EQUAL(-44, diff.get_beats());
- // CPPUNIT_ASSERT_EQUAL(44 / Beats::PPQN, diff.get_ticks());
- // CPPUNIT_ASSERT_DOUBLES_EQUAL(diff.to_double(), -44.44, delta);
-}
-
-void
-BeatsTest::addTest()
-{
- const Beats a(1, 2);
- const Beats b(3, 4);
-
- // Positive + positive
- const Beats c = a + b;
- CPPUNIT_ASSERT_EQUAL(4, c.get_beats());
- CPPUNIT_ASSERT_EQUAL(6, c.get_ticks());
-
- const Beats n1(-12.34);
- CPPUNIT_ASSERT_DOUBLES_EQUAL(-12.34, n1.to_double(), delta);
-
- const Beats n2(-56.78);
- CPPUNIT_ASSERT_DOUBLES_EQUAL(-56.78, n2.to_double(), delta);
-
- // Positive + negative
- const Beats p1(1.0);
- const Beats p_n = p1 + n1;
- CPPUNIT_ASSERT_EQUAL(-11, p_n.get_beats());
- CPPUNIT_ASSERT_EQUAL((int32_t)(Beats::PPQN * -0.34), p_n.get_ticks());
- CPPUNIT_ASSERT_DOUBLES_EQUAL(-11.34, p_n.to_double(), delta);
-
- // Negative + positive
- const Beats n_p = n1 + p1;
- CPPUNIT_ASSERT_EQUAL(-11, n_p.get_beats());
- CPPUNIT_ASSERT_EQUAL((int32_t)(Beats::PPQN * -0.34), n_p.get_ticks());
- CPPUNIT_ASSERT_DOUBLES_EQUAL(-11.34, n_p.to_double(), delta);
-
- // Negative + negative
- const Beats sum = n1 + n2;
- CPPUNIT_ASSERT_EQUAL(-69, sum.get_beats());
- //CPPUNIT_ASSERT_EQUAL((int32_t)(Beats::PPQN * -0.12), n_p.get_ticks());
- CPPUNIT_ASSERT_DOUBLES_EQUAL(-69.12, sum.to_double(), delta);
-}
-
-void
-BeatsTest::subtractTest()
-{
- const Beats a(1, 2);
- const Beats b(3, 4);
-
- // Positive - positive
- const Beats c = b - a;
- CPPUNIT_ASSERT_EQUAL(2, c.get_beats());
- CPPUNIT_ASSERT_EQUAL(2, c.get_ticks());
-
- const Beats n1(-12.34);
- CPPUNIT_ASSERT_DOUBLES_EQUAL(-12.34, n1.to_double(), delta);
-
- const Beats n2(-56.78);
- CPPUNIT_ASSERT_DOUBLES_EQUAL(-56.78, n2.to_double(), delta);
-
- // Positive - negative
- const Beats p1(1.0);
- const Beats p_n = p1 - n1;
- CPPUNIT_ASSERT_EQUAL(13, p_n.get_beats());
- CPPUNIT_ASSERT_EQUAL((int32_t)(Beats::PPQN * 0.34), p_n.get_ticks());
- CPPUNIT_ASSERT_DOUBLES_EQUAL(13.34, p_n.to_double(), delta);
-
- // Negative - positive
- const Beats n_p = n1 - p1;
- CPPUNIT_ASSERT_EQUAL(-13, n_p.get_beats());
- CPPUNIT_ASSERT_EQUAL((int32_t)(Beats::PPQN * -0.34), n_p.get_ticks());
- CPPUNIT_ASSERT_DOUBLES_EQUAL(-13.34, n_p.to_double(), delta);
-
- // Negative - negative
- const Beats diff = n1 - n2;
- CPPUNIT_ASSERT_EQUAL(44, diff.get_beats());
- CPPUNIT_ASSERT_EQUAL((int32_t)lrint(Beats::PPQN * 0.44), diff.get_ticks());
- CPPUNIT_ASSERT_DOUBLES_EQUAL(44.44, diff.to_double(), delta);
-}
-
-void
-BeatsTest::multiplyTest()
-{
- CPPUNIT_ASSERT_DOUBLES_EQUAL(3.0, (Beats(1.5) * 2.0).to_double(), delta);
- CPPUNIT_ASSERT_DOUBLES_EQUAL(-10.0, (Beats(5.0) * -2.0).to_double(), delta);
- CPPUNIT_ASSERT_DOUBLES_EQUAL(-10.0, (Beats(-5.0) * 2.0).to_double(), delta);
-}
-
-void
-BeatsTest::roundTest()
-{
- Beats a(1, 1);
-
- // Round a up
- const Beats au = a.round_up_to_beat();
- CPPUNIT_ASSERT_EQUAL(au.get_beats(), 2);
- CPPUNIT_ASSERT_EQUAL(au.get_ticks(), 0);
-
- // Round a down
- const Beats ad = a.round_down_to_beat();
- CPPUNIT_ASSERT_EQUAL(ad.get_beats(), 1);
- CPPUNIT_ASSERT_EQUAL(ad.get_ticks(), 0);
-
- // Round result down again
- const Beats add = ad.round_down_to_beat();
- CPPUNIT_ASSERT_EQUAL(ad, add);
-
- // Round result up
- const Beats adu = ad.round_up_to_beat();
- CPPUNIT_ASSERT_EQUAL(ad, adu);
-
- // Snap to 1.5
- const Beats snapped = a.snap_to(Beats(1.5));
- CPPUNIT_ASSERT_EQUAL(snapped.get_beats(), 1);
- CPPUNIT_ASSERT_EQUAL(snapped.get_ticks(), Beats::PPQN / 2);
-}
-
-void
-BeatsTest::convertTest()
-{
- const Beats a = Beats::ticks_at_rate(72000, 48000);
- CPPUNIT_ASSERT_DOUBLES_EQUAL(1, a.get_beats(), delta);
- CPPUNIT_ASSERT_DOUBLES_EQUAL(Beats::PPQN / 2, a.get_ticks(), delta);
- CPPUNIT_ASSERT_DOUBLES_EQUAL(1.5, a.to_double(), delta);
-
- const Beats b = Beats::ticks_at_rate(8, 48000);
- CPPUNIT_ASSERT_DOUBLES_EQUAL(0, b.get_beats(), delta);
- CPPUNIT_ASSERT_DOUBLES_EQUAL(Beats::PPQN * 8 / 48000, b.get_ticks(), delta);
- CPPUNIT_ASSERT_DOUBLES_EQUAL((8 / 48000.0), b.to_double(), delta);
-
- CPPUNIT_ASSERT_EQUAL(int64_t(1.5 * Beats::PPQN), a.to_ticks());
- CPPUNIT_ASSERT_EQUAL(int64_t(1.5 * 192), a.to_ticks(192));
-}
--- /dev/null
+#include <cppunit/TestFixture.h>
+#include <cppunit/extensions/HelperMacros.h>
+
+class BeatsTest : public CppUnit::TestFixture
+{
+ CPPUNIT_TEST_SUITE(BeatsTest);
+ CPPUNIT_TEST(createTest);
+ CPPUNIT_TEST(addTest);
+ CPPUNIT_TEST(subtractTest);
+ CPPUNIT_TEST(multiplyTest);
+ CPPUNIT_TEST(convertTest);
+ CPPUNIT_TEST(roundTest);
+ CPPUNIT_TEST_SUITE_END();
+
+public:
+ void createTest();
+ void addTest();
+ void subtractTest();
+ void multiplyTest();
+ void convertTest();
+ void roundTest();
+};
+++ /dev/null
-#include <cppunit/TestFixture.h>
-#include <cppunit/extensions/HelperMacros.h>
-
-class BeatsTest : public CppUnit::TestFixture
-{
- CPPUNIT_TEST_SUITE(BeatsTest);
- CPPUNIT_TEST(createTest);
- CPPUNIT_TEST(addTest);
- CPPUNIT_TEST(subtractTest);
- CPPUNIT_TEST(multiplyTest);
- CPPUNIT_TEST(convertTest);
- CPPUNIT_TEST(roundTest);
- CPPUNIT_TEST_SUITE_END();
-
-public:
- void createTest();
- void addTest();
- void subtractTest();
- void multiplyTest();
- void convertTest();
- void roundTest();
-};
--- /dev/null
+#include "CurveTest.hpp"
+#include "evoral/ControlList.h"
+#include "evoral/Curve.h"
+#include <stdlib.h>
+
+CPPUNIT_TEST_SUITE_REGISTRATION (CurveTest);
+
+#if defined(PLATFORM_WINDOWS) && defined(COMPILER_MINGW)
+/* cppunit-1.13.2 uses assertion_traits<double>
+ * sprintf( , "%.*g", precision, x)
+ * to format a double. The actual comparison is performed on a string.
+ * This is problematic with mingw/windows|wine, "%.*g" formatting fails.
+ *
+ * This quick hack compares float, however float compatisons are at most Y.MMMM+eXX,
+ * the max precision needs to be limited. to the last mantissa digit.
+ *
+ * Anyway, actual maths is verified with Linux and OSX unit-tests,
+ * and this needs to go to https://sourceforge.net/p/cppunit/bugs/
+ */
+#define MAXPREC(P) ((P) < .0005 ? .0005 : (P))
+#define CPPUNIT_ASSERT_DOUBLES_EQUAL_MESSAGE(M,A,B,P) CPPUNIT_ASSERT_EQUAL_MESSAGE(M, (float)rint ((A) / MAXPREC(P)),(float)rint ((B) / MAXPREC(P)))
+#define CPPUNIT_ASSERT_DOUBLES_EQUAL(A,B,P) CPPUNIT_ASSERT_EQUAL((float)rint ((A) / MAXPREC(P)),(float)rint ((B) / MAXPREC(P)))
+#endif
+
+using namespace Evoral;
+
+// linear y = Y0 + YS * x ; with x = i * (X1 - X0) + X0; and i = [0..1023]
+#define VEC1024LINCMP(X0, X1, Y0, YS) \
+ cl->curve ().get_vector ((X0), (X1), vec, 1024); \
+ for (int i = 0; i < 1024; ++i) { \
+ char msg[64]; \
+ snprintf (msg, 64, "at i=%d (x0=%.1f, x1=%.1f, y0=%.1f, ys=%.3f)", \
+ i, X0, X1, Y0, YS); \
+ CPPUNIT_ASSERT_DOUBLES_EQUAL_MESSAGE ( \
+ msg, \
+ (Y0) + i * (YS), vec[i], \
+ 1e-24 \
+ ); \
+ }
+
+void
+CurveTest::trivial ()
+{
+ float vec[1024];
+
+ boost::shared_ptr<Evoral::ControlList> cl = TestCtrlList();
+
+ cl->create_curve ();
+
+ // Empty curve
+ cl->curve().get_vector (1024.0, 2047.0, vec, 1024);
+ for (int i = 0; i < 1024; ++i) {
+ CPPUNIT_ASSERT_EQUAL (0.0f, vec[i]);
+ }
+
+ // Single point curve
+ cl->fast_simple_add(0.0, 42.0);
+ cl->curve().get_vector (1024.0, 2047.0, vec, 1024);
+ for (int i = 0; i < 1024; ++i) {
+ CPPUNIT_ASSERT_EQUAL (42.0f, vec[i]);
+ }
+}
+
+void
+CurveTest::rtGet ()
+{
+ float vec[1024];
+
+ // Create simple control list
+ boost::shared_ptr<Evoral::ControlList> cl = TestCtrlList();
+ cl->create_curve ();
+ cl->fast_simple_add(0.0, 42.0);
+
+ {
+ // Write-lock list
+ Glib::Threads::RWLock::WriterLock lm(cl->lock());
+
+ // Attempt to get vector in RT (expect failure)
+ CPPUNIT_ASSERT (!cl->curve().rt_safe_get_vector (1024.0, 2047.0, vec, 1024));
+ }
+
+ // Attempt to get vector in RT (expect success)
+ CPPUNIT_ASSERT (cl->curve().rt_safe_get_vector (1024.0, 2047.0, vec, 1024));
+ for (int i = 0; i < 1024; ++i) {
+ CPPUNIT_ASSERT_EQUAL (42.0f, vec[i]);
+ }
+}
+
+void
+CurveTest::twoPointLinear ()
+{
+ float vec[1024];
+
+ boost::shared_ptr<Evoral::ControlList> cl = TestCtrlList();
+
+ cl->create_curve ();
+ cl->set_interpolation (ControlList::Linear);
+
+ // add two points to curve
+ cl->fast_simple_add ( 0.0 , 2048.0);
+ cl->fast_simple_add (8192.0 , 4096.0);
+
+ cl->curve ().get_vector (1024.0, 2047.0, vec, 1024);
+
+ VEC1024LINCMP (1024.0, 2047.0, 2304.f, .25f);
+ VEC1024LINCMP (2048.0, 2559.5, 2560.f, .125f);
+ VEC1024LINCMP ( 0.0, 4092.0, 2048.f, 1.f);
+
+ // greetings to tartina
+ cl->curve ().get_vector (2048.0, 2048.0, vec, 1);
+ CPPUNIT_ASSERT_EQUAL_MESSAGE ("veclen=1 @ 2048..2048", 2560.f, vec[0]);
+
+ /* XXX WHAT DO WE EXPECT WITH veclen=1 AND x1 > x0 ? */
+#if 0
+ /* .. interpolated value at (x1+x0)/2 */
+ cl->curve ().get_vector (2048.0, 2049.0, vec, 1);
+ CPPUNIT_ASSERT_EQUAL_MESSAGE ("veclen=1 @ 2048-2049", 2560.125f, vec[0]);
+
+ cl->curve ().get_vector (2048.0, 2056.0, vec, 1);
+ CPPUNIT_ASSERT_EQUAL_MESSAGE ("veclen=1 @ 2048-2049", 2561.f, vec[0]);
+#else
+ /* .. value at x0 */
+ cl->curve ().get_vector (2048.0, 2049.0, vec, 1);
+ CPPUNIT_ASSERT_EQUAL_MESSAGE ("veclen=1 , 2048..2049", 2560.f, vec[0]);
+
+ cl->curve ().get_vector (2048.0, 2056.0, vec, 1);
+ CPPUNIT_ASSERT_EQUAL_MESSAGE ("veclen=1 , 2048..2049", 2560.f, vec[0]);
+#endif
+
+ cl->curve ().get_vector (2048.0, 2048.0, vec, 2);
+ CPPUNIT_ASSERT_EQUAL_MESSAGE ("veclen=2 , 2048..2048 @ 0", 2560.f, vec[0]);
+ CPPUNIT_ASSERT_EQUAL_MESSAGE ("veclen=2 , 2048..2048 @ 1", 2560.f, vec[1]);
+
+ cl->curve ().get_vector (2048.0, 2056.0, vec, 2);
+ CPPUNIT_ASSERT_EQUAL_MESSAGE ("veclen=2 , 2048..2056 @ 0", 2560.f, vec[0]);
+ CPPUNIT_ASSERT_EQUAL_MESSAGE ("veclen=2 , 2048..2056 @ 0", 2562.f, vec[1]);
+
+ cl->curve ().get_vector (2048.0, 2056.0, vec, 3);
+ CPPUNIT_ASSERT_EQUAL_MESSAGE ("veclen=3 , 2048..2056 @ 0", 2560.f, vec[0]);
+ CPPUNIT_ASSERT_EQUAL_MESSAGE ("veclen=3 , 2048..2056 @ 1", 2561.f, vec[1]);
+ CPPUNIT_ASSERT_EQUAL_MESSAGE ("veclen=3 , 2048..2056 @ 2", 2562.f, vec[2]);
+
+ /* check out-of range..
+ * we expect the first and last value - no interpolation
+ */
+ cl->curve ().get_vector (-1, -1, vec, 1);
+ CPPUNIT_ASSERT_EQUAL_MESSAGE ("veclen=1 @ -1", 2048.f, vec[0]);
+
+ cl->curve ().get_vector (9999.0, 9999.0, vec, 1);
+ CPPUNIT_ASSERT_EQUAL_MESSAGE ("veclen=1 @ 9999", 4096.f, vec[0]);
+
+ cl->curve ().get_vector (-999.0, 0, vec, 13);
+ for (int i = 0; i < 13; ++i) {
+ CPPUNIT_ASSERT_EQUAL_MESSAGE ("veclen=13 @ -999..0", 2048.f, vec[i]);
+ }
+
+ cl->curve ().get_vector (9998.0, 9999.0, vec, 8);
+ for (int i = 0; i < 8; ++i) {
+ CPPUNIT_ASSERT_EQUAL_MESSAGE ("veclen=8 @ 9998..9999", 4096.f, vec[i]);
+ }
+}
+
+void
+CurveTest::threePointLinear ()
+{
+ float vec[4];
+
+ boost::shared_ptr<Evoral::ControlList> cl = TestCtrlList();
+
+ cl->create_curve ();
+ cl->set_interpolation (ControlList::Linear);
+
+ // add 3 points to curve
+ cl->fast_simple_add ( 0.0 , 2.0);
+ cl->fast_simple_add ( 100.0 , 4.0);
+ cl->fast_simple_add ( 200.0 , 0.0);
+
+ cl->curve ().get_vector (50.0, 60.0, vec, 1);
+ CPPUNIT_ASSERT_EQUAL_MESSAGE ("veclen=1 @ 50", 3.f, vec[0]);
+
+ cl->curve ().get_vector (100.0, 100.0, vec, 1);
+ CPPUNIT_ASSERT_EQUAL_MESSAGE ("veclen=1 @ 100", 4.f, vec[0]);
+
+ cl->curve ().get_vector (150.0, 150.0, vec, 1);
+ CPPUNIT_ASSERT_EQUAL_MESSAGE ("veclen=1 @ 150", 2.f, vec[0]);
+
+ cl->curve ().get_vector (130.0, 150.0, vec, 3);
+ CPPUNIT_ASSERT_EQUAL_MESSAGE ("veclen=3 130..150 @ 0", 2.8f, vec[0]);
+ CPPUNIT_ASSERT_EQUAL_MESSAGE ("veclen=3 130..150 @ 2", 2.4f, vec[1]);
+ CPPUNIT_ASSERT_EQUAL_MESSAGE ("veclen=3 130..150 @ 3", 2.0f, vec[2]);
+
+ cl->curve ().get_vector (80.0, 160.0, vec, 3);
+ CPPUNIT_ASSERT_EQUAL_MESSAGE ("veclen=3 80..160 @ 0", 3.6f, vec[0]);
+ CPPUNIT_ASSERT_EQUAL_MESSAGE ("veclen=3 80..160 @ 2", 3.2f, vec[1]);
+ CPPUNIT_ASSERT_EQUAL_MESSAGE ("veclen=3 80..160 @ 3", 1.6f, vec[2]);
+}
+
+void
+CurveTest::threePointDiscete ()
+{
+ boost::shared_ptr<Evoral::ControlList> cl = TestCtrlList();
+ cl->set_interpolation (ControlList::Discrete);
+
+ // add 3 points to curve
+ cl->fast_simple_add ( 0.0 , 2.0);
+ cl->fast_simple_add ( 100.0 , 4.0);
+ cl->fast_simple_add ( 200.0 , 0.0);
+
+ CPPUNIT_ASSERT_EQUAL(2.0, cl->unlocked_eval(80.));
+ CPPUNIT_ASSERT_EQUAL(4.0, cl->unlocked_eval(120.));
+ CPPUNIT_ASSERT_EQUAL(4.0, cl->unlocked_eval(160.));
+
+ cl->set_interpolation (ControlList::Linear);
+
+ CPPUNIT_ASSERT_EQUAL(3.6, cl->unlocked_eval(80.));
+ CPPUNIT_ASSERT_EQUAL(3.2, cl->unlocked_eval(120.));
+ CPPUNIT_ASSERT_EQUAL(1.6, cl->unlocked_eval(160.));
+}
+
+void
+CurveTest::ctrlListEval ()
+{
+ boost::shared_ptr<Evoral::ControlList> cl = TestCtrlList();
+
+ cl->fast_simple_add ( 0.0 , 2.0);
+
+ cl->set_interpolation (ControlList::Discrete);
+ CPPUNIT_ASSERT_EQUAL(2.0, cl->unlocked_eval(80.));
+ CPPUNIT_ASSERT_EQUAL(2.0, cl->unlocked_eval(120.));
+ CPPUNIT_ASSERT_EQUAL(2.0, cl->unlocked_eval(160.));
+
+ cl->set_interpolation (ControlList::Linear);
+ CPPUNIT_ASSERT_EQUAL(2.0, cl->unlocked_eval(80.));
+ CPPUNIT_ASSERT_EQUAL(2.0, cl->unlocked_eval(120.));
+ CPPUNIT_ASSERT_EQUAL(2.0, cl->unlocked_eval(160.));
+
+ cl->fast_simple_add ( 100.0 , 4.0);
+
+ cl->set_interpolation (ControlList::Discrete);
+ CPPUNIT_ASSERT_EQUAL(2.0, cl->unlocked_eval(80.));
+ CPPUNIT_ASSERT_EQUAL(4.0, cl->unlocked_eval(120.));
+ CPPUNIT_ASSERT_EQUAL(4.0, cl->unlocked_eval(160.));
+
+ cl->set_interpolation (ControlList::Linear);
+ CPPUNIT_ASSERT_EQUAL(3.6, cl->unlocked_eval(80.));
+ CPPUNIT_ASSERT_EQUAL(4.0, cl->unlocked_eval(120.));
+ CPPUNIT_ASSERT_EQUAL(4.0, cl->unlocked_eval(160.));
+
+ cl->fast_simple_add ( 200.0 , 0.0);
+
+ cl->set_interpolation (ControlList::Discrete);
+ CPPUNIT_ASSERT_EQUAL(2.0, cl->unlocked_eval(80.));
+ CPPUNIT_ASSERT_EQUAL(4.0, cl->unlocked_eval(120.));
+ CPPUNIT_ASSERT_EQUAL(4.0, cl->unlocked_eval(160.));
+
+ cl->set_interpolation (ControlList::Linear);
+ CPPUNIT_ASSERT_EQUAL(3.6, cl->unlocked_eval(80.));
+ CPPUNIT_ASSERT_EQUAL(3.2, cl->unlocked_eval(120.));
+ CPPUNIT_ASSERT_EQUAL(1.6, cl->unlocked_eval(160.));
+
+ cl->fast_simple_add ( 300.0 , 8.0);
+
+ cl->set_interpolation (ControlList::Discrete);
+ CPPUNIT_ASSERT_EQUAL(2.0, cl->unlocked_eval(80.));
+ CPPUNIT_ASSERT_EQUAL(4.0, cl->unlocked_eval(120.));
+ CPPUNIT_ASSERT_EQUAL(4.0, cl->unlocked_eval(160.));
+ CPPUNIT_ASSERT_EQUAL(0.0, cl->unlocked_eval(250.));
+ CPPUNIT_ASSERT_EQUAL(8.0, cl->unlocked_eval(999.));
+
+ cl->set_interpolation (ControlList::Linear);
+ CPPUNIT_ASSERT_EQUAL(3.6, cl->unlocked_eval(80.));
+ CPPUNIT_ASSERT_EQUAL(3.2, cl->unlocked_eval(120.));
+ CPPUNIT_ASSERT_EQUAL(1.6, cl->unlocked_eval(160.));
+ CPPUNIT_ASSERT_EQUAL(4.0, cl->unlocked_eval(250.));
+ CPPUNIT_ASSERT_EQUAL(8.0, cl->unlocked_eval(999.));
+
+ cl->fast_simple_add ( 400.0 , 9.0);
+
+ cl->set_interpolation (ControlList::Discrete);
+ CPPUNIT_ASSERT_EQUAL(2.0, cl->unlocked_eval(80.));
+ CPPUNIT_ASSERT_EQUAL(4.0, cl->unlocked_eval(120.));
+ CPPUNIT_ASSERT_EQUAL(4.0, cl->unlocked_eval(160.));
+ CPPUNIT_ASSERT_EQUAL(0.0, cl->unlocked_eval(250.));
+ CPPUNIT_ASSERT_EQUAL(8.0, cl->unlocked_eval(350.));
+ CPPUNIT_ASSERT_EQUAL(9.0, cl->unlocked_eval(999.));
+
+ cl->set_interpolation (ControlList::Linear);
+ CPPUNIT_ASSERT_EQUAL(3.6, cl->unlocked_eval(80.));
+ CPPUNIT_ASSERT_EQUAL(3.2, cl->unlocked_eval(120.));
+ CPPUNIT_ASSERT_EQUAL(1.6, cl->unlocked_eval(160.));
+ CPPUNIT_ASSERT_EQUAL(4.0, cl->unlocked_eval(250.));
+ CPPUNIT_ASSERT_EQUAL(8.5, cl->unlocked_eval(350.));
+ CPPUNIT_ASSERT_EQUAL(9.0, cl->unlocked_eval(999.));
+}
+
+void
+CurveTest::constrainedCubic ()
+{
+
+ struct point {
+ int x, y;
+ };
+
+ static const struct point data[] = {
+ /* values from worked example in www.korf.co.uk/spline.pdf */
+ { 0, 30 },
+ { 10, 130 },
+ { 30, 150 },
+ { 50, 150 },
+ { 70, 170 },
+ { 90, 220 },
+ { 100, 320 },
+ };
+
+ int32_t type = 0;
+ Evoral::Parameter p(type);
+ Evoral::ParameterDescriptor pd;
+ pd.lower = 5;
+ pd.upper = 325;
+ Evoral::ControlList l(p,pd);
+
+ size_t i;
+ l.set_interpolation(Evoral::ControlList::Curved);
+
+ for (i=0; i<sizeof(data)/sizeof(data[0]); i++) {
+ l.add (data[i].x, data[i].y);
+ }
+
+ Evoral::Curve curve(l);
+
+ float f[121];
+ curve.get_vector(-10, 110, f, 121);
+
+ const float *g = &f[10]; /* so g starts at x==0 */
+
+ /* given points - should be exactly equal */
+ CPPUNIT_ASSERT_EQUAL( 30.0f, g[-10]);
+ CPPUNIT_ASSERT_EQUAL( 30.0f, g[ 0]);
+ CPPUNIT_ASSERT_EQUAL(130.0f, g[ 10]);
+ CPPUNIT_ASSERT_EQUAL(150.0f, g[ 30]);
+ CPPUNIT_ASSERT_EQUAL(150.0f, g[ 40]);
+ CPPUNIT_ASSERT_EQUAL(150.0f, g[ 50]);
+ CPPUNIT_ASSERT_EQUAL(320.0f, g[100]);
+ CPPUNIT_ASSERT_EQUAL(320.0f, g[110]);
+
+ /*
+ First segment, i=1, for 0 <= x <= 10
+ f'1(x1) = 2/((x2 – x1)/(y2 – y1) + (x1 – x0)/(y1 – y0))
+ = 2/((30 – 10)/(150 – 130) + (10 – 0)/(130 – 30))
+ = 1.8181
+ f'1(x0) = 3/2*(y1 – y0)/(x1 – x0) - f'1(x1)/2
+ = 3/2*(130 – 30)/(10 – 0) – 1.818/2
+ = 14.0909
+ f"1(x0) = -2*(f'1(x1) + 2* f'1(x0))/(x1 – x0) + 6*(y1 – y0)/ (x1 – x0)^2
+ = -2*(1.8181 + 2*14.0909)/(10 – 0) + 6*(130 – 30)/(10 – 0)^2
+ = 0
+ f"1(x1) = 2*(2*f'1(x1) + f'1(x0))/(x1 – x0) - 6*(y1 – y0)/ (x1 – x0)^2
+ = 2*(2*1.818 + 14.0909)/(10 – 0) – 6*(130 – 30)/(10 – 0)^2
+ = -2.4545
+ d1 = 1/6 * (f"1(x1) - f"1(x0))/(x1 – x0)
+ = 1/6 * (-2.4545 – 0)/(10 – 0)
+ = -0.0409
+ c1 = 1/2 * (x1*f"1(x0) – x0*f"1(x1))/(x1 – x0)
+ = 1/2 * (10*0 – 0*1.8181)/(10 – 0)
+ = 0
+ b1 = ((y1 – y0) – c1*(x21 – x20) – d1*( x31 – x30))/(x1 – x0)
+ = ((130 – 30) – 0*(102 – 02) + 0.0409*(103 – 03))/(10 – 0)
+ = 14.09
+ a1 = y0 – b1*x0 – c1*x20 – d1*x30
+ = 30
+ y1 = 30 + 14.09x - 0.0409x3 for 0 <= x <= 10
+ */
+ /*
+ Second segment, i=2, for 10 <= x <= 30
+ f'2(x2) = 2/((x3 – x2)/(y3 – y2) + (x2 – x1)/(y2 – y1))
+ = 2/((50 – 30)/(150 – 150) + (30 – 10)/(150 – 130))
+ = 0
+ f'2(x1) = 2/((x2 – x1)/(y2 – y1) + (x1 – x0)/(y1 – y0))
+ = 1.8181
+
+ f"2(x1) = -2*(f'2(x2) + 2* f'2(x1))/(x2 – x1) + 6*(y2 – y1)/ (x2 – x1)^2
+ = -2*(0 + 2*1.8181)/(30 – 10) + 6*(150 – 130)/(30 – 10)2
+ = -0.063636
+ f"2(x2) = 2*(2*f'2(x2) + f'2(x1))/(x2 – x1) - 6*(y2 – y1)/ (x2 – x1)^2
+ = 2*(2*0 + 1.8181)/(30 – 10) – 6*(150 – 130)/(30 – 10)^2
+ = -0.11818
+
+ d2 = 1/6 * (f"2(x2) - f"2(x1))/(x2 – x1)
+ = 1/6 * (-0.11818 + 0.063636)/(30 – 10)
+ = -0.0004545
+ c2 = 1/2 * (x2*f"2(x1) – x1*f"2(x2))/(x2 – x1)
+ = 1/2 * (-30*0.063636 + 10*0.11818)/(30 – 10)
+ = -0.01818
+ b2 = ((y2 – y1) – c2*(x2^2 – x1^2) – d2*( x2^3 – x1^3))/(x2 – x1)
+ = ((150 – 130) + 0.01818*(302 – 102) + 0.0004545*(303 – 103))/(30 – 10)
+ = 2.31818
+ a2 = y1 – b2*x1 – c2*x1^2 – d2*x1^3
+ = 130 – 2.31818*10 + 0.01818*102 + 0.0004545*103
+ = 109.09
+ y2 = 109.09 + 2.31818x - 0.01818x^2 - 0.0004545x^3 for 10 <= x <= 30
+ */
+
+
+ int x;
+ long double a1, b1, c1, d1, a2, b2, c2, d2, fdx0, fddx0, fdx1, fdx2, fddx1, fddx2;
+ double x0 = data[0].x;
+ double y0 = data[0].y;
+ double x1 = data[1].x;
+ double y1 = data[1].y;
+ double x2 = data[2].x;
+ double y2 = data[2].y;
+ double x3 = data[3].x;
+ double y3 = data[3].y;
+
+ double dx0 = x1 - x0;
+ double dy0 = y1 - y0;
+ double dx1 = x2 - x1;
+ double dy1 = y2 - y1;
+ double dx2 = x3 - x2;
+ double dy2 = y3 - y2;
+
+ // First (leftmost) segment
+ fdx1 = 2.0 / ( dx1 / dy1 + dx0 / dy0 );
+ fdx0 = 3.0 / 2.0 * dy0 / dx0 - fdx1 / 2.0;
+
+ fddx0 = -2.0 * (fdx1 + 2.0 * fdx0) / dx0 + 6.0 * dy0 / (dx0*dx0);
+ fddx1 = 2.0 * (2.0 * fdx1 + fdx0) / dx0 - 6.0 * dy0 / (dx0*dx0);
+ d1 = 1.0 / 6.0 * (fddx1 - fddx0) / dx0;
+ c1 = 1.0 / 2.0 * (x1 * fddx0 - x0 * fddx1) / dx0;
+ b1 = (dy0 - c1 * (x1* x1 - x0*x0) - d1 * (x1*x1*x1 - x0*x0*x0)) / dx0;
+ a1 = y0 - b1*x0 - c1*x0*x0 - d1*x0*x0*x0;
+
+ // printf("dx0=%f, dy0=%f, dx1=%f, dy1=%f\n", dx0, dy0, dx1, dy1);
+ // printf("fdx0=%Lf, fdx1=%Lf, fddx0=%Lf, fddx1=%Lf\n", fdx0, fdx1, fddx0, fddx1);
+ // printf("a1=%Lf, b1=%Lf, c1=%Lf, d1=%Lf\n", a1, b1, c1, d1);
+
+ // values from worked example: deltas rather arbitrary, I'm afraid
+ CPPUNIT_ASSERT_DOUBLES_EQUAL(30.0, a1, 0.1);
+ CPPUNIT_ASSERT_DOUBLES_EQUAL(14.09, b1, 0.01);
+ CPPUNIT_ASSERT_DOUBLES_EQUAL(0.0, c1, 0.1);
+ CPPUNIT_ASSERT_DOUBLES_EQUAL(-0.0409, d1, 0.0001);
+
+ for (x = 0; x <= 10; x++) {
+ double v = a1 + b1*x + c1*x*x + d1*x*x*x;
+ char msg[64];
+ snprintf(msg, 64, "interpolating %d: v=%f, x=%f...\n", x, v, g[x]);
+ CPPUNIT_ASSERT_DOUBLES_EQUAL(v, g[x], 0.000004);
+ }
+
+ // Second segment
+ fdx2 = 2.0 / ( dx2 / dy2 + dx1 / dy1 );
+
+ fddx1 = -2.0 * (fdx2 + 2.0 * fdx1) / dx1 + 6.0 * dy1 / (dx1*dx1);
+ fddx2 = 2.0 * (2.0 * fdx2 + fdx1) / dx1 - 6.0 * dy1 / (dx1*dx1);
+ d2 = 1.0 / 6.0 * (fddx2 - fddx1) / dx1;
+ c2 = 1.0 / 2.0 * (x2 * fddx1 - x1 * fddx2) / dx1;
+ b2 = (dy1 - c2 * (x2*x2 - x1*x1) - d2 * (x2*x2*x2 - x1*x1*x1)) / dx1;
+ a2 = y1 - b2*x1 - c2*x1*x1 - d2*x1*x1*x1;
+
+ // printf("dx0=%f, dy0=%f, dx1=%f, dy1=%f dx2=%f, dy2=%f\n", dx0, dy0, dx1, dy1, dx2, dy2);
+ // printf("fdx1=%Lf, fdx2=%Lf, fddx1=%Lf, fddx2=%Lf\n", fdx1, fdx2, fddx1, fddx2);
+ // printf("a2=%Lf, b2=%Lf, c2=%Lf, d2=%Lf\n", a2, b2, c2, d2);
+
+ // values from worked example: deltas rather arbitrary, I'm afraid
+ CPPUNIT_ASSERT_DOUBLES_EQUAL(109.09, a2, 0.01);
+ CPPUNIT_ASSERT_DOUBLES_EQUAL(2.31818, b2, 0.00001);
+ CPPUNIT_ASSERT_DOUBLES_EQUAL(-0.01818, c2, 0.00001);
+ CPPUNIT_ASSERT_DOUBLES_EQUAL(-0.0004545, d2, 0.0000001);
+
+ for (x = 10; x <= 30; x++) {
+ double v = a2 + b2*x + c2*x*x + d2*x*x*x;
+ char msg[64];
+ snprintf(msg, 64, "interpolating %d: v=%f, x=%f...\n", x, v, g[x]);
+ CPPUNIT_ASSERT_DOUBLES_EQUAL(v, g[x], 0.000008);
+ }
+}
+++ /dev/null
-#include "CurveTest.hpp"
-#include "evoral/ControlList.hpp"
-#include "evoral/Curve.hpp"
-#include <stdlib.h>
-
-CPPUNIT_TEST_SUITE_REGISTRATION (CurveTest);
-
-#if defined(PLATFORM_WINDOWS) && defined(COMPILER_MINGW)
-/* cppunit-1.13.2 uses assertion_traits<double>
- * sprintf( , "%.*g", precision, x)
- * to format a double. The actual comparison is performed on a string.
- * This is problematic with mingw/windows|wine, "%.*g" formatting fails.
- *
- * This quick hack compares float, however float compatisons are at most Y.MMMM+eXX,
- * the max precision needs to be limited. to the last mantissa digit.
- *
- * Anyway, actual maths is verified with Linux and OSX unit-tests,
- * and this needs to go to https://sourceforge.net/p/cppunit/bugs/
- */
-#define MAXPREC(P) ((P) < .0005 ? .0005 : (P))
-#define CPPUNIT_ASSERT_DOUBLES_EQUAL_MESSAGE(M,A,B,P) CPPUNIT_ASSERT_EQUAL_MESSAGE(M, (float)rint ((A) / MAXPREC(P)),(float)rint ((B) / MAXPREC(P)))
-#define CPPUNIT_ASSERT_DOUBLES_EQUAL(A,B,P) CPPUNIT_ASSERT_EQUAL((float)rint ((A) / MAXPREC(P)),(float)rint ((B) / MAXPREC(P)))
-#endif
-
-using namespace Evoral;
-
-// linear y = Y0 + YS * x ; with x = i * (X1 - X0) + X0; and i = [0..1023]
-#define VEC1024LINCMP(X0, X1, Y0, YS) \
- cl->curve ().get_vector ((X0), (X1), vec, 1024); \
- for (int i = 0; i < 1024; ++i) { \
- char msg[64]; \
- snprintf (msg, 64, "at i=%d (x0=%.1f, x1=%.1f, y0=%.1f, ys=%.3f)", \
- i, X0, X1, Y0, YS); \
- CPPUNIT_ASSERT_DOUBLES_EQUAL_MESSAGE ( \
- msg, \
- (Y0) + i * (YS), vec[i], \
- 1e-24 \
- ); \
- }
-
-void
-CurveTest::trivial ()
-{
- float vec[1024];
-
- boost::shared_ptr<Evoral::ControlList> cl = TestCtrlList();
-
- cl->create_curve ();
-
- // Empty curve
- cl->curve().get_vector (1024.0, 2047.0, vec, 1024);
- for (int i = 0; i < 1024; ++i) {
- CPPUNIT_ASSERT_EQUAL (0.0f, vec[i]);
- }
-
- // Single point curve
- cl->fast_simple_add(0.0, 42.0);
- cl->curve().get_vector (1024.0, 2047.0, vec, 1024);
- for (int i = 0; i < 1024; ++i) {
- CPPUNIT_ASSERT_EQUAL (42.0f, vec[i]);
- }
-}
-
-void
-CurveTest::rtGet ()
-{
- float vec[1024];
-
- // Create simple control list
- boost::shared_ptr<Evoral::ControlList> cl = TestCtrlList();
- cl->create_curve ();
- cl->fast_simple_add(0.0, 42.0);
-
- {
- // Write-lock list
- Glib::Threads::RWLock::WriterLock lm(cl->lock());
-
- // Attempt to get vector in RT (expect failure)
- CPPUNIT_ASSERT (!cl->curve().rt_safe_get_vector (1024.0, 2047.0, vec, 1024));
- }
-
- // Attempt to get vector in RT (expect success)
- CPPUNIT_ASSERT (cl->curve().rt_safe_get_vector (1024.0, 2047.0, vec, 1024));
- for (int i = 0; i < 1024; ++i) {
- CPPUNIT_ASSERT_EQUAL (42.0f, vec[i]);
- }
-}
-
-void
-CurveTest::twoPointLinear ()
-{
- float vec[1024];
-
- boost::shared_ptr<Evoral::ControlList> cl = TestCtrlList();
-
- cl->create_curve ();
- cl->set_interpolation (ControlList::Linear);
-
- // add two points to curve
- cl->fast_simple_add ( 0.0 , 2048.0);
- cl->fast_simple_add (8192.0 , 4096.0);
-
- cl->curve ().get_vector (1024.0, 2047.0, vec, 1024);
-
- VEC1024LINCMP (1024.0, 2047.0, 2304.f, .25f);
- VEC1024LINCMP (2048.0, 2559.5, 2560.f, .125f);
- VEC1024LINCMP ( 0.0, 4092.0, 2048.f, 1.f);
-
- // greetings to tartina
- cl->curve ().get_vector (2048.0, 2048.0, vec, 1);
- CPPUNIT_ASSERT_EQUAL_MESSAGE ("veclen=1 @ 2048..2048", 2560.f, vec[0]);
-
- /* XXX WHAT DO WE EXPECT WITH veclen=1 AND x1 > x0 ? */
-#if 0
- /* .. interpolated value at (x1+x0)/2 */
- cl->curve ().get_vector (2048.0, 2049.0, vec, 1);
- CPPUNIT_ASSERT_EQUAL_MESSAGE ("veclen=1 @ 2048-2049", 2560.125f, vec[0]);
-
- cl->curve ().get_vector (2048.0, 2056.0, vec, 1);
- CPPUNIT_ASSERT_EQUAL_MESSAGE ("veclen=1 @ 2048-2049", 2561.f, vec[0]);
-#else
- /* .. value at x0 */
- cl->curve ().get_vector (2048.0, 2049.0, vec, 1);
- CPPUNIT_ASSERT_EQUAL_MESSAGE ("veclen=1 , 2048..2049", 2560.f, vec[0]);
-
- cl->curve ().get_vector (2048.0, 2056.0, vec, 1);
- CPPUNIT_ASSERT_EQUAL_MESSAGE ("veclen=1 , 2048..2049", 2560.f, vec[0]);
-#endif
-
- cl->curve ().get_vector (2048.0, 2048.0, vec, 2);
- CPPUNIT_ASSERT_EQUAL_MESSAGE ("veclen=2 , 2048..2048 @ 0", 2560.f, vec[0]);
- CPPUNIT_ASSERT_EQUAL_MESSAGE ("veclen=2 , 2048..2048 @ 1", 2560.f, vec[1]);
-
- cl->curve ().get_vector (2048.0, 2056.0, vec, 2);
- CPPUNIT_ASSERT_EQUAL_MESSAGE ("veclen=2 , 2048..2056 @ 0", 2560.f, vec[0]);
- CPPUNIT_ASSERT_EQUAL_MESSAGE ("veclen=2 , 2048..2056 @ 0", 2562.f, vec[1]);
-
- cl->curve ().get_vector (2048.0, 2056.0, vec, 3);
- CPPUNIT_ASSERT_EQUAL_MESSAGE ("veclen=3 , 2048..2056 @ 0", 2560.f, vec[0]);
- CPPUNIT_ASSERT_EQUAL_MESSAGE ("veclen=3 , 2048..2056 @ 1", 2561.f, vec[1]);
- CPPUNIT_ASSERT_EQUAL_MESSAGE ("veclen=3 , 2048..2056 @ 2", 2562.f, vec[2]);
-
- /* check out-of range..
- * we expect the first and last value - no interpolation
- */
- cl->curve ().get_vector (-1, -1, vec, 1);
- CPPUNIT_ASSERT_EQUAL_MESSAGE ("veclen=1 @ -1", 2048.f, vec[0]);
-
- cl->curve ().get_vector (9999.0, 9999.0, vec, 1);
- CPPUNIT_ASSERT_EQUAL_MESSAGE ("veclen=1 @ 9999", 4096.f, vec[0]);
-
- cl->curve ().get_vector (-999.0, 0, vec, 13);
- for (int i = 0; i < 13; ++i) {
- CPPUNIT_ASSERT_EQUAL_MESSAGE ("veclen=13 @ -999..0", 2048.f, vec[i]);
- }
-
- cl->curve ().get_vector (9998.0, 9999.0, vec, 8);
- for (int i = 0; i < 8; ++i) {
- CPPUNIT_ASSERT_EQUAL_MESSAGE ("veclen=8 @ 9998..9999", 4096.f, vec[i]);
- }
-}
-
-void
-CurveTest::threePointLinear ()
-{
- float vec[4];
-
- boost::shared_ptr<Evoral::ControlList> cl = TestCtrlList();
-
- cl->create_curve ();
- cl->set_interpolation (ControlList::Linear);
-
- // add 3 points to curve
- cl->fast_simple_add ( 0.0 , 2.0);
- cl->fast_simple_add ( 100.0 , 4.0);
- cl->fast_simple_add ( 200.0 , 0.0);
-
- cl->curve ().get_vector (50.0, 60.0, vec, 1);
- CPPUNIT_ASSERT_EQUAL_MESSAGE ("veclen=1 @ 50", 3.f, vec[0]);
-
- cl->curve ().get_vector (100.0, 100.0, vec, 1);
- CPPUNIT_ASSERT_EQUAL_MESSAGE ("veclen=1 @ 100", 4.f, vec[0]);
-
- cl->curve ().get_vector (150.0, 150.0, vec, 1);
- CPPUNIT_ASSERT_EQUAL_MESSAGE ("veclen=1 @ 150", 2.f, vec[0]);
-
- cl->curve ().get_vector (130.0, 150.0, vec, 3);
- CPPUNIT_ASSERT_EQUAL_MESSAGE ("veclen=3 130..150 @ 0", 2.8f, vec[0]);
- CPPUNIT_ASSERT_EQUAL_MESSAGE ("veclen=3 130..150 @ 2", 2.4f, vec[1]);
- CPPUNIT_ASSERT_EQUAL_MESSAGE ("veclen=3 130..150 @ 3", 2.0f, vec[2]);
-
- cl->curve ().get_vector (80.0, 160.0, vec, 3);
- CPPUNIT_ASSERT_EQUAL_MESSAGE ("veclen=3 80..160 @ 0", 3.6f, vec[0]);
- CPPUNIT_ASSERT_EQUAL_MESSAGE ("veclen=3 80..160 @ 2", 3.2f, vec[1]);
- CPPUNIT_ASSERT_EQUAL_MESSAGE ("veclen=3 80..160 @ 3", 1.6f, vec[2]);
-}
-
-void
-CurveTest::threePointDiscete ()
-{
- boost::shared_ptr<Evoral::ControlList> cl = TestCtrlList();
- cl->set_interpolation (ControlList::Discrete);
-
- // add 3 points to curve
- cl->fast_simple_add ( 0.0 , 2.0);
- cl->fast_simple_add ( 100.0 , 4.0);
- cl->fast_simple_add ( 200.0 , 0.0);
-
- CPPUNIT_ASSERT_EQUAL(2.0, cl->unlocked_eval(80.));
- CPPUNIT_ASSERT_EQUAL(4.0, cl->unlocked_eval(120.));
- CPPUNIT_ASSERT_EQUAL(4.0, cl->unlocked_eval(160.));
-
- cl->set_interpolation (ControlList::Linear);
-
- CPPUNIT_ASSERT_EQUAL(3.6, cl->unlocked_eval(80.));
- CPPUNIT_ASSERT_EQUAL(3.2, cl->unlocked_eval(120.));
- CPPUNIT_ASSERT_EQUAL(1.6, cl->unlocked_eval(160.));
-}
-
-void
-CurveTest::ctrlListEval ()
-{
- boost::shared_ptr<Evoral::ControlList> cl = TestCtrlList();
-
- cl->fast_simple_add ( 0.0 , 2.0);
-
- cl->set_interpolation (ControlList::Discrete);
- CPPUNIT_ASSERT_EQUAL(2.0, cl->unlocked_eval(80.));
- CPPUNIT_ASSERT_EQUAL(2.0, cl->unlocked_eval(120.));
- CPPUNIT_ASSERT_EQUAL(2.0, cl->unlocked_eval(160.));
-
- cl->set_interpolation (ControlList::Linear);
- CPPUNIT_ASSERT_EQUAL(2.0, cl->unlocked_eval(80.));
- CPPUNIT_ASSERT_EQUAL(2.0, cl->unlocked_eval(120.));
- CPPUNIT_ASSERT_EQUAL(2.0, cl->unlocked_eval(160.));
-
- cl->fast_simple_add ( 100.0 , 4.0);
-
- cl->set_interpolation (ControlList::Discrete);
- CPPUNIT_ASSERT_EQUAL(2.0, cl->unlocked_eval(80.));
- CPPUNIT_ASSERT_EQUAL(4.0, cl->unlocked_eval(120.));
- CPPUNIT_ASSERT_EQUAL(4.0, cl->unlocked_eval(160.));
-
- cl->set_interpolation (ControlList::Linear);
- CPPUNIT_ASSERT_EQUAL(3.6, cl->unlocked_eval(80.));
- CPPUNIT_ASSERT_EQUAL(4.0, cl->unlocked_eval(120.));
- CPPUNIT_ASSERT_EQUAL(4.0, cl->unlocked_eval(160.));
-
- cl->fast_simple_add ( 200.0 , 0.0);
-
- cl->set_interpolation (ControlList::Discrete);
- CPPUNIT_ASSERT_EQUAL(2.0, cl->unlocked_eval(80.));
- CPPUNIT_ASSERT_EQUAL(4.0, cl->unlocked_eval(120.));
- CPPUNIT_ASSERT_EQUAL(4.0, cl->unlocked_eval(160.));
-
- cl->set_interpolation (ControlList::Linear);
- CPPUNIT_ASSERT_EQUAL(3.6, cl->unlocked_eval(80.));
- CPPUNIT_ASSERT_EQUAL(3.2, cl->unlocked_eval(120.));
- CPPUNIT_ASSERT_EQUAL(1.6, cl->unlocked_eval(160.));
-
- cl->fast_simple_add ( 300.0 , 8.0);
-
- cl->set_interpolation (ControlList::Discrete);
- CPPUNIT_ASSERT_EQUAL(2.0, cl->unlocked_eval(80.));
- CPPUNIT_ASSERT_EQUAL(4.0, cl->unlocked_eval(120.));
- CPPUNIT_ASSERT_EQUAL(4.0, cl->unlocked_eval(160.));
- CPPUNIT_ASSERT_EQUAL(0.0, cl->unlocked_eval(250.));
- CPPUNIT_ASSERT_EQUAL(8.0, cl->unlocked_eval(999.));
-
- cl->set_interpolation (ControlList::Linear);
- CPPUNIT_ASSERT_EQUAL(3.6, cl->unlocked_eval(80.));
- CPPUNIT_ASSERT_EQUAL(3.2, cl->unlocked_eval(120.));
- CPPUNIT_ASSERT_EQUAL(1.6, cl->unlocked_eval(160.));
- CPPUNIT_ASSERT_EQUAL(4.0, cl->unlocked_eval(250.));
- CPPUNIT_ASSERT_EQUAL(8.0, cl->unlocked_eval(999.));
-
- cl->fast_simple_add ( 400.0 , 9.0);
-
- cl->set_interpolation (ControlList::Discrete);
- CPPUNIT_ASSERT_EQUAL(2.0, cl->unlocked_eval(80.));
- CPPUNIT_ASSERT_EQUAL(4.0, cl->unlocked_eval(120.));
- CPPUNIT_ASSERT_EQUAL(4.0, cl->unlocked_eval(160.));
- CPPUNIT_ASSERT_EQUAL(0.0, cl->unlocked_eval(250.));
- CPPUNIT_ASSERT_EQUAL(8.0, cl->unlocked_eval(350.));
- CPPUNIT_ASSERT_EQUAL(9.0, cl->unlocked_eval(999.));
-
- cl->set_interpolation (ControlList::Linear);
- CPPUNIT_ASSERT_EQUAL(3.6, cl->unlocked_eval(80.));
- CPPUNIT_ASSERT_EQUAL(3.2, cl->unlocked_eval(120.));
- CPPUNIT_ASSERT_EQUAL(1.6, cl->unlocked_eval(160.));
- CPPUNIT_ASSERT_EQUAL(4.0, cl->unlocked_eval(250.));
- CPPUNIT_ASSERT_EQUAL(8.5, cl->unlocked_eval(350.));
- CPPUNIT_ASSERT_EQUAL(9.0, cl->unlocked_eval(999.));
-}
-
-void
-CurveTest::constrainedCubic ()
-{
-
- struct point {
- int x, y;
- };
-
- static const struct point data[] = {
- /* values from worked example in www.korf.co.uk/spline.pdf */
- { 0, 30 },
- { 10, 130 },
- { 30, 150 },
- { 50, 150 },
- { 70, 170 },
- { 90, 220 },
- { 100, 320 },
- };
-
- int32_t type = 0;
- Evoral::Parameter p(type);
- Evoral::ParameterDescriptor pd;
- pd.lower = 5;
- pd.upper = 325;
- Evoral::ControlList l(p,pd);
-
- size_t i;
- l.set_interpolation(Evoral::ControlList::Curved);
-
- for (i=0; i<sizeof(data)/sizeof(data[0]); i++) {
- l.add (data[i].x, data[i].y);
- }
-
- Evoral::Curve curve(l);
-
- float f[121];
- curve.get_vector(-10, 110, f, 121);
-
- const float *g = &f[10]; /* so g starts at x==0 */
-
- /* given points - should be exactly equal */
- CPPUNIT_ASSERT_EQUAL( 30.0f, g[-10]);
- CPPUNIT_ASSERT_EQUAL( 30.0f, g[ 0]);
- CPPUNIT_ASSERT_EQUAL(130.0f, g[ 10]);
- CPPUNIT_ASSERT_EQUAL(150.0f, g[ 30]);
- CPPUNIT_ASSERT_EQUAL(150.0f, g[ 40]);
- CPPUNIT_ASSERT_EQUAL(150.0f, g[ 50]);
- CPPUNIT_ASSERT_EQUAL(320.0f, g[100]);
- CPPUNIT_ASSERT_EQUAL(320.0f, g[110]);
-
- /*
- First segment, i=1, for 0 <= x <= 10
- f'1(x1) = 2/((x2 – x1)/(y2 – y1) + (x1 – x0)/(y1 – y0))
- = 2/((30 – 10)/(150 – 130) + (10 – 0)/(130 – 30))
- = 1.8181
- f'1(x0) = 3/2*(y1 – y0)/(x1 – x0) - f'1(x1)/2
- = 3/2*(130 – 30)/(10 – 0) – 1.818/2
- = 14.0909
- f"1(x0) = -2*(f'1(x1) + 2* f'1(x0))/(x1 – x0) + 6*(y1 – y0)/ (x1 – x0)^2
- = -2*(1.8181 + 2*14.0909)/(10 – 0) + 6*(130 – 30)/(10 – 0)^2
- = 0
- f"1(x1) = 2*(2*f'1(x1) + f'1(x0))/(x1 – x0) - 6*(y1 – y0)/ (x1 – x0)^2
- = 2*(2*1.818 + 14.0909)/(10 – 0) – 6*(130 – 30)/(10 – 0)^2
- = -2.4545
- d1 = 1/6 * (f"1(x1) - f"1(x0))/(x1 – x0)
- = 1/6 * (-2.4545 – 0)/(10 – 0)
- = -0.0409
- c1 = 1/2 * (x1*f"1(x0) – x0*f"1(x1))/(x1 – x0)
- = 1/2 * (10*0 – 0*1.8181)/(10 – 0)
- = 0
- b1 = ((y1 – y0) – c1*(x21 – x20) – d1*( x31 – x30))/(x1 – x0)
- = ((130 – 30) – 0*(102 – 02) + 0.0409*(103 – 03))/(10 – 0)
- = 14.09
- a1 = y0 – b1*x0 – c1*x20 – d1*x30
- = 30
- y1 = 30 + 14.09x - 0.0409x3 for 0 <= x <= 10
- */
- /*
- Second segment, i=2, for 10 <= x <= 30
- f'2(x2) = 2/((x3 – x2)/(y3 – y2) + (x2 – x1)/(y2 – y1))
- = 2/((50 – 30)/(150 – 150) + (30 – 10)/(150 – 130))
- = 0
- f'2(x1) = 2/((x2 – x1)/(y2 – y1) + (x1 – x0)/(y1 – y0))
- = 1.8181
-
- f"2(x1) = -2*(f'2(x2) + 2* f'2(x1))/(x2 – x1) + 6*(y2 – y1)/ (x2 – x1)^2
- = -2*(0 + 2*1.8181)/(30 – 10) + 6*(150 – 130)/(30 – 10)2
- = -0.063636
- f"2(x2) = 2*(2*f'2(x2) + f'2(x1))/(x2 – x1) - 6*(y2 – y1)/ (x2 – x1)^2
- = 2*(2*0 + 1.8181)/(30 – 10) – 6*(150 – 130)/(30 – 10)^2
- = -0.11818
-
- d2 = 1/6 * (f"2(x2) - f"2(x1))/(x2 – x1)
- = 1/6 * (-0.11818 + 0.063636)/(30 – 10)
- = -0.0004545
- c2 = 1/2 * (x2*f"2(x1) – x1*f"2(x2))/(x2 – x1)
- = 1/2 * (-30*0.063636 + 10*0.11818)/(30 – 10)
- = -0.01818
- b2 = ((y2 – y1) – c2*(x2^2 – x1^2) – d2*( x2^3 – x1^3))/(x2 – x1)
- = ((150 – 130) + 0.01818*(302 – 102) + 0.0004545*(303 – 103))/(30 – 10)
- = 2.31818
- a2 = y1 – b2*x1 – c2*x1^2 – d2*x1^3
- = 130 – 2.31818*10 + 0.01818*102 + 0.0004545*103
- = 109.09
- y2 = 109.09 + 2.31818x - 0.01818x^2 - 0.0004545x^3 for 10 <= x <= 30
- */
-
-
- int x;
- long double a1, b1, c1, d1, a2, b2, c2, d2, fdx0, fddx0, fdx1, fdx2, fddx1, fddx2;
- double x0 = data[0].x;
- double y0 = data[0].y;
- double x1 = data[1].x;
- double y1 = data[1].y;
- double x2 = data[2].x;
- double y2 = data[2].y;
- double x3 = data[3].x;
- double y3 = data[3].y;
-
- double dx0 = x1 - x0;
- double dy0 = y1 - y0;
- double dx1 = x2 - x1;
- double dy1 = y2 - y1;
- double dx2 = x3 - x2;
- double dy2 = y3 - y2;
-
- // First (leftmost) segment
- fdx1 = 2.0 / ( dx1 / dy1 + dx0 / dy0 );
- fdx0 = 3.0 / 2.0 * dy0 / dx0 - fdx1 / 2.0;
-
- fddx0 = -2.0 * (fdx1 + 2.0 * fdx0) / dx0 + 6.0 * dy0 / (dx0*dx0);
- fddx1 = 2.0 * (2.0 * fdx1 + fdx0) / dx0 - 6.0 * dy0 / (dx0*dx0);
- d1 = 1.0 / 6.0 * (fddx1 - fddx0) / dx0;
- c1 = 1.0 / 2.0 * (x1 * fddx0 - x0 * fddx1) / dx0;
- b1 = (dy0 - c1 * (x1* x1 - x0*x0) - d1 * (x1*x1*x1 - x0*x0*x0)) / dx0;
- a1 = y0 - b1*x0 - c1*x0*x0 - d1*x0*x0*x0;
-
- // printf("dx0=%f, dy0=%f, dx1=%f, dy1=%f\n", dx0, dy0, dx1, dy1);
- // printf("fdx0=%Lf, fdx1=%Lf, fddx0=%Lf, fddx1=%Lf\n", fdx0, fdx1, fddx0, fddx1);
- // printf("a1=%Lf, b1=%Lf, c1=%Lf, d1=%Lf\n", a1, b1, c1, d1);
-
- // values from worked example: deltas rather arbitrary, I'm afraid
- CPPUNIT_ASSERT_DOUBLES_EQUAL(30.0, a1, 0.1);
- CPPUNIT_ASSERT_DOUBLES_EQUAL(14.09, b1, 0.01);
- CPPUNIT_ASSERT_DOUBLES_EQUAL(0.0, c1, 0.1);
- CPPUNIT_ASSERT_DOUBLES_EQUAL(-0.0409, d1, 0.0001);
-
- for (x = 0; x <= 10; x++) {
- double v = a1 + b1*x + c1*x*x + d1*x*x*x;
- char msg[64];
- snprintf(msg, 64, "interpolating %d: v=%f, x=%f...\n", x, v, g[x]);
- CPPUNIT_ASSERT_DOUBLES_EQUAL(v, g[x], 0.000004);
- }
-
- // Second segment
- fdx2 = 2.0 / ( dx2 / dy2 + dx1 / dy1 );
-
- fddx1 = -2.0 * (fdx2 + 2.0 * fdx1) / dx1 + 6.0 * dy1 / (dx1*dx1);
- fddx2 = 2.0 * (2.0 * fdx2 + fdx1) / dx1 - 6.0 * dy1 / (dx1*dx1);
- d2 = 1.0 / 6.0 * (fddx2 - fddx1) / dx1;
- c2 = 1.0 / 2.0 * (x2 * fddx1 - x1 * fddx2) / dx1;
- b2 = (dy1 - c2 * (x2*x2 - x1*x1) - d2 * (x2*x2*x2 - x1*x1*x1)) / dx1;
- a2 = y1 - b2*x1 - c2*x1*x1 - d2*x1*x1*x1;
-
- // printf("dx0=%f, dy0=%f, dx1=%f, dy1=%f dx2=%f, dy2=%f\n", dx0, dy0, dx1, dy1, dx2, dy2);
- // printf("fdx1=%Lf, fdx2=%Lf, fddx1=%Lf, fddx2=%Lf\n", fdx1, fdx2, fddx1, fddx2);
- // printf("a2=%Lf, b2=%Lf, c2=%Lf, d2=%Lf\n", a2, b2, c2, d2);
-
- // values from worked example: deltas rather arbitrary, I'm afraid
- CPPUNIT_ASSERT_DOUBLES_EQUAL(109.09, a2, 0.01);
- CPPUNIT_ASSERT_DOUBLES_EQUAL(2.31818, b2, 0.00001);
- CPPUNIT_ASSERT_DOUBLES_EQUAL(-0.01818, c2, 0.00001);
- CPPUNIT_ASSERT_DOUBLES_EQUAL(-0.0004545, d2, 0.0000001);
-
- for (x = 10; x <= 30; x++) {
- double v = a2 + b2*x + c2*x*x + d2*x*x*x;
- char msg[64];
- snprintf(msg, 64, "interpolating %d: v=%f, x=%f...\n", x, v, g[x]);
- CPPUNIT_ASSERT_DOUBLES_EQUAL(v, g[x], 0.000008);
- }
-}
--- /dev/null
+#include <cppunit/TestFixture.h>
+#include <cppunit/extensions/HelperMacros.h>
+#include <boost/shared_ptr.hpp>
+#include "evoral/ControlList.h"
+
+class CurveTest : public CppUnit::TestFixture
+{
+ CPPUNIT_TEST_SUITE (CurveTest);
+ CPPUNIT_TEST (trivial);
+ CPPUNIT_TEST (rtGet);
+ CPPUNIT_TEST (twoPointLinear);
+ CPPUNIT_TEST (threePointLinear);
+ CPPUNIT_TEST (threePointDiscete);
+ CPPUNIT_TEST (constrainedCubic);
+ CPPUNIT_TEST (ctrlListEval);
+ CPPUNIT_TEST_SUITE_END ();
+
+public:
+ void trivial ();
+ void rtGet ();
+ void twoPointLinear ();
+ void threePointLinear ();
+ void threePointDiscete ();
+ void constrainedCubic ();
+ void ctrlListEval ();
+
+private:
+ boost::shared_ptr<Evoral::ControlList> TestCtrlList() {
+ Evoral::Parameter param (Evoral::Parameter(0));
+ const Evoral::ParameterDescriptor desc;
+ return boost::shared_ptr<Evoral::ControlList> (new Evoral::ControlList(param, desc));
+ }
+};
+++ /dev/null
-#include <cppunit/TestFixture.h>
-#include <cppunit/extensions/HelperMacros.h>
-#include <boost/shared_ptr.hpp>
-#include "evoral/ControlList.hpp"
-
-class CurveTest : public CppUnit::TestFixture
-{
- CPPUNIT_TEST_SUITE (CurveTest);
- CPPUNIT_TEST (trivial);
- CPPUNIT_TEST (rtGet);
- CPPUNIT_TEST (twoPointLinear);
- CPPUNIT_TEST (threePointLinear);
- CPPUNIT_TEST (threePointDiscete);
- CPPUNIT_TEST (constrainedCubic);
- CPPUNIT_TEST (ctrlListEval);
- CPPUNIT_TEST_SUITE_END ();
-
-public:
- void trivial ();
- void rtGet ();
- void twoPointLinear ();
- void threePointLinear ();
- void threePointDiscete ();
- void constrainedCubic ();
- void ctrlListEval ();
-
-private:
- boost::shared_ptr<Evoral::ControlList> TestCtrlList() {
- Evoral::Parameter param (Evoral::Parameter(0));
- const Evoral::ParameterDescriptor desc;
- return boost::shared_ptr<Evoral::ControlList> (new Evoral::ControlList(param, desc));
- }
-};
--- /dev/null
+#include "NoteTest.hpp"
+#include "temporal/beats.h"
+#include "evoral/Note.h"
+#include <stdlib.h>
+
+CPPUNIT_TEST_SUITE_REGISTRATION (NoteTest);
+
+using namespace Evoral;
+
+typedef Temporal::Beats Time;
+
+void
+NoteTest::copyTest ()
+{
+ Note<Time> a(0, Time(1.0), Time(2.0), 60, 0x40);
+ Note<Time> b(a);
+ CPPUNIT_ASSERT (a == b);
+
+ // Broken due to event double free!
+ // Note<Time> c(1, Beats(3.0), Beats(4.0), 61, 0x41);
+ // c = a;
+ // CPPUNIT_ASSERT (a == c);
+}
+
+void
+NoteTest::idTest ()
+{
+ Note<Time> a(0, Time(1.0), Time(2.0), 60, 0x40);
+ CPPUNIT_ASSERT_EQUAL (-1, a.id());
+
+ a.set_id(1234);
+ CPPUNIT_ASSERT_EQUAL (1234, a.id());
+}
+++ /dev/null
-#include "NoteTest.hpp"
-#include "temporal/beats.h"
-#include "evoral/Note.hpp"
-#include <stdlib.h>
-
-CPPUNIT_TEST_SUITE_REGISTRATION (NoteTest);
-
-using namespace Evoral;
-
-typedef Temporal::Beats Time;
-
-void
-NoteTest::copyTest ()
-{
- Note<Time> a(0, Time(1.0), Time(2.0), 60, 0x40);
- Note<Time> b(a);
- CPPUNIT_ASSERT (a == b);
-
- // Broken due to event double free!
- // Note<Time> c(1, Beats(3.0), Beats(4.0), 61, 0x41);
- // c = a;
- // CPPUNIT_ASSERT (a == c);
-}
-
-void
-NoteTest::idTest ()
-{
- Note<Time> a(0, Time(1.0), Time(2.0), 60, 0x40);
- CPPUNIT_ASSERT_EQUAL (-1, a.id());
-
- a.set_id(1234);
- CPPUNIT_ASSERT_EQUAL (1234, a.id());
-}
--- /dev/null
+#include <cppunit/TestFixture.h>
+#include <cppunit/extensions/HelperMacros.h>
+
+class NoteTest : public CppUnit::TestFixture
+{
+ CPPUNIT_TEST_SUITE (NoteTest);
+ CPPUNIT_TEST (copyTest);
+ CPPUNIT_TEST (idTest);
+ CPPUNIT_TEST_SUITE_END ();
+
+public:
+ void copyTest ();
+ void idTest ();
+};
+
+
+++ /dev/null
-#include <cppunit/TestFixture.h>
-#include <cppunit/extensions/HelperMacros.h>
-
-class NoteTest : public CppUnit::TestFixture
-{
- CPPUNIT_TEST_SUITE (NoteTest);
- CPPUNIT_TEST (copyTest);
- CPPUNIT_TEST (idTest);
- CPPUNIT_TEST_SUITE_END ();
-
-public:
- void copyTest ();
- void idTest ();
-};
-
-
--- /dev/null
+#include "RangeTest.hpp"
+#include "evoral/Range.h"
+#include <stdlib.h>
+
+CPPUNIT_TEST_SUITE_REGISTRATION (RangeTest);
+
+using namespace Evoral;
+
+void
+RangeTest::coalesceTest ()
+{
+ RangeList<int> fred;
+ fred.add (Range<int> (2, 4));
+ fred.add (Range<int> (5, 6));
+ fred.add (Range<int> (6, 8));
+
+ RangeList<int>::List jim = fred.get ();
+
+ RangeList<int>::List::iterator i = jim.begin ();
+ CPPUNIT_ASSERT_EQUAL (2, i->from);
+ CPPUNIT_ASSERT_EQUAL (4, i->to);
+
+ ++i;
+ CPPUNIT_ASSERT_EQUAL (5, i->from);
+ CPPUNIT_ASSERT_EQUAL (8, i->to);
+}
+
+/* Basic subtraction of a few smaller ranges from a larger one */
+void
+RangeTest::subtractTest1 ()
+{
+
+/* 01234567890
+ * fred: |---------|
+ * jim: |-| ||
+ * sheila: || || ||
+ */
+
+ Range<int> fred (0, 10);
+
+ RangeList<int> jim;
+ jim.add (Range<int> (2, 4));
+ jim.add (Range<int> (7, 8));
+
+ RangeList<int> sheila = subtract (fred, jim);
+
+ RangeList<int>::List s = sheila.get ();
+ CPPUNIT_ASSERT_EQUAL (size_t (3), s.size ());
+
+ RangeList<int>::List::iterator i = s.begin ();
+ CPPUNIT_ASSERT_EQUAL (0, i->from);
+ CPPUNIT_ASSERT_EQUAL (1, i->to);
+
+ ++i;
+ CPPUNIT_ASSERT_EQUAL (5, i->from);
+ CPPUNIT_ASSERT_EQUAL (6, i->to);
+
+ ++i;
+ CPPUNIT_ASSERT_EQUAL (9, i->from);
+ CPPUNIT_ASSERT_EQUAL (10, i->to);
+}
+
+/* Test subtraction of a range B from a range A, where A and B do not overlap */
+void
+RangeTest::subtractTest2 ()
+{
+ Range<int> fred (0, 10);
+
+ RangeList<int> jim;
+ jim.add (Range<int> (12, 19));
+
+ RangeList<int> sheila = subtract (fred, jim);
+
+ RangeList<int>::List s = sheila.get ();
+ CPPUNIT_ASSERT_EQUAL (size_t (1), s.size ());
+
+ RangeList<int>::List::iterator i = s.begin ();
+ CPPUNIT_ASSERT_EQUAL (0, i->from);
+ CPPUNIT_ASSERT_EQUAL (10, i->to);
+}
+
+/* Test subtraction of B from A, where B entirely overlaps A */
+void
+RangeTest::subtractTest3 ()
+{
+ Range<int> fred (0, 10);
+
+ RangeList<int> jim;
+ jim.add (Range<int> (0, 12));
+
+ RangeList<int> sheila = subtract (fred, jim);
+
+ RangeList<int>::List s = sheila.get ();
+ CPPUNIT_ASSERT_EQUAL (size_t (0), s.size ());
+}
+
+/* A bit like subtractTest1, except some of the ranges
+ we are subtracting overlap.
+*/
+void
+RangeTest::subtractTest4 ()
+{
+/* 01234567890
+ * fred: |---------|
+ * jim: |-| ||
+ * ||
+ * sheila: || || |
+ */
+
+ Range<int> fred (0, 10);
+
+ RangeList<int> jim;
+ jim.add (Range<int> (2, 4));
+ jim.add (Range<int> (7, 8));
+ jim.add (Range<int> (8, 9));
+
+ RangeList<int> sheila = subtract (fred, jim);
+
+ RangeList<int>::List s = sheila.get ();
+ CPPUNIT_ASSERT_EQUAL (size_t (3), s.size ());
+
+ RangeList<int>::List::iterator i = s.begin ();
+ CPPUNIT_ASSERT_EQUAL (0, i->from);
+ CPPUNIT_ASSERT_EQUAL (1, i->to);
+
+ ++i;
+ CPPUNIT_ASSERT_EQUAL (5, i->from);
+ CPPUNIT_ASSERT_EQUAL (6, i->to);
+
+ ++i;
+ CPPUNIT_ASSERT_EQUAL (10, i->from);
+ CPPUNIT_ASSERT_EQUAL (10, i->to);
+}
+
+/* A bit like subtractTest1, except some of the ranges
+ we are subtracting overlap the start / end of the
+ initial range.
+*/
+void
+RangeTest::subtractTest5 ()
+{
+/* 01234567890123
+ * fred: |----------|
+ * jim: |---| || |------...
+ * sheila:i | |
+ */
+
+ Range<int> fred (1, 12);
+
+ RangeList<int> jim;
+ jim.add (Range<int> (0, 4));
+ jim.add (Range<int> (6, 7));
+ jim.add (Range<int> (9, 42));
+
+ RangeList<int> sheila = subtract (fred, jim);
+
+ RangeList<int>::List s = sheila.get ();
+ CPPUNIT_ASSERT_EQUAL (size_t (2), s.size ());
+
+ RangeList<int>::List::iterator i = s.begin ();
+ CPPUNIT_ASSERT_EQUAL (5, i->from);
+ CPPUNIT_ASSERT_EQUAL (5, i->to);
+
+ ++i;
+ CPPUNIT_ASSERT_EQUAL (8, i->from);
+ CPPUNIT_ASSERT_EQUAL (8, i->to);
+}
+
+/* Test coverage() with all possible types of overlap.
+ */
+
+void
+RangeTest::coverageTest ()
+{
+
+ // b starts before a
+ CPPUNIT_ASSERT_EQUAL (coverage(3, 7, 1, 1), Evoral::OverlapNone);
+ CPPUNIT_ASSERT_EQUAL (coverage(3, 7, 1, 2), Evoral::OverlapNone);
+ CPPUNIT_ASSERT_EQUAL (coverage(3, 7, 1, 3), Evoral::OverlapStart);
+ CPPUNIT_ASSERT_EQUAL (coverage(3, 7, 1, 5), Evoral::OverlapStart);
+ CPPUNIT_ASSERT_EQUAL (coverage(3, 7, 1, 7), Evoral::OverlapExternal);
+ CPPUNIT_ASSERT_EQUAL (coverage(3, 7, 1, 9), Evoral::OverlapExternal);
+
+ // b starts at a
+ CPPUNIT_ASSERT_EQUAL (coverage(3, 7, 3, 3), Evoral::OverlapStart);
+ CPPUNIT_ASSERT_EQUAL (coverage(3, 7, 3, 5), Evoral::OverlapStart);
+ CPPUNIT_ASSERT_EQUAL (coverage(3, 7, 3, 7), Evoral::OverlapExternal);
+ CPPUNIT_ASSERT_EQUAL (coverage(3, 7, 3, 9), Evoral::OverlapExternal);
+
+ // b starts inside a
+ CPPUNIT_ASSERT_EQUAL (coverage(3, 7, 4, 4), Evoral::OverlapInternal);
+ CPPUNIT_ASSERT_EQUAL (coverage(3, 7, 4, 6), Evoral::OverlapInternal);
+ CPPUNIT_ASSERT_EQUAL (coverage(3, 7, 4, 7), Evoral::OverlapEnd);
+ CPPUNIT_ASSERT_EQUAL (coverage(3, 7, 4, 8), Evoral::OverlapEnd);
+
+ // b starts at end of a
+ CPPUNIT_ASSERT_EQUAL (coverage(3, 7, 7, 7), Evoral::OverlapEnd);
+ CPPUNIT_ASSERT_EQUAL (coverage(3, 7, 7, 9), Evoral::OverlapEnd);
+
+ // b starts after end of a
+ CPPUNIT_ASSERT_EQUAL (coverage(3, 7, 8, 8), Evoral::OverlapNone);
+ CPPUNIT_ASSERT_EQUAL (coverage(3, 7, 8, 9), Evoral::OverlapNone);
+
+ // zero-length range a
+ CPPUNIT_ASSERT_EQUAL (coverage(3, 3, 2, 4), Evoral::OverlapExternal);
+ CPPUNIT_ASSERT_EQUAL (coverage(3, 3, 1, 2), Evoral::OverlapNone);
+ CPPUNIT_ASSERT_EQUAL (coverage(3, 3, 3, 3), Evoral::OverlapExternal);
+ CPPUNIT_ASSERT_EQUAL (coverage(3, 3, 8, 9), Evoral::OverlapNone);
+
+ // negative length range a
+ // XXX these are debatable - should we just consider start & end to be
+ // swapped if end < start?
+ CPPUNIT_ASSERT_EQUAL (coverage(4, 3, 1, 2), Evoral::OverlapNone);
+ CPPUNIT_ASSERT_EQUAL (coverage(4, 3, 2, 3), Evoral::OverlapNone);
+ CPPUNIT_ASSERT_EQUAL (coverage(4, 3, 2, 4), Evoral::OverlapNone);
+ CPPUNIT_ASSERT_EQUAL (coverage(4, 3, 3, 3), Evoral::OverlapNone);
+ CPPUNIT_ASSERT_EQUAL (coverage(4, 3, 8, 9), Evoral::OverlapNone);
+
+ // negative length range b
+ // b starts before a
+ CPPUNIT_ASSERT_EQUAL (coverage(3, 7, 1, 0), Evoral::OverlapNone);
+ // b starts at a
+ CPPUNIT_ASSERT_EQUAL (coverage(3, 7, 3, 2), Evoral::OverlapNone);
+ // b starts inside a
+ CPPUNIT_ASSERT_EQUAL (coverage(3, 7, 4, 3), Evoral::OverlapNone);
+ // b starts at end of a
+ CPPUNIT_ASSERT_EQUAL (coverage(3, 7, 7, 5), Evoral::OverlapNone);
+ // b starts after end of a
+ CPPUNIT_ASSERT_EQUAL (coverage(3, 7, 8, 7), Evoral::OverlapNone);
+
+}
+++ /dev/null
-#include "RangeTest.hpp"
-#include "evoral/Range.hpp"
-#include <stdlib.h>
-
-CPPUNIT_TEST_SUITE_REGISTRATION (RangeTest);
-
-using namespace Evoral;
-
-void
-RangeTest::coalesceTest ()
-{
- RangeList<int> fred;
- fred.add (Range<int> (2, 4));
- fred.add (Range<int> (5, 6));
- fred.add (Range<int> (6, 8));
-
- RangeList<int>::List jim = fred.get ();
-
- RangeList<int>::List::iterator i = jim.begin ();
- CPPUNIT_ASSERT_EQUAL (2, i->from);
- CPPUNIT_ASSERT_EQUAL (4, i->to);
-
- ++i;
- CPPUNIT_ASSERT_EQUAL (5, i->from);
- CPPUNIT_ASSERT_EQUAL (8, i->to);
-}
-
-/* Basic subtraction of a few smaller ranges from a larger one */
-void
-RangeTest::subtractTest1 ()
-{
-
-/* 01234567890
- * fred: |---------|
- * jim: |-| ||
- * sheila: || || ||
- */
-
- Range<int> fred (0, 10);
-
- RangeList<int> jim;
- jim.add (Range<int> (2, 4));
- jim.add (Range<int> (7, 8));
-
- RangeList<int> sheila = subtract (fred, jim);
-
- RangeList<int>::List s = sheila.get ();
- CPPUNIT_ASSERT_EQUAL (size_t (3), s.size ());
-
- RangeList<int>::List::iterator i = s.begin ();
- CPPUNIT_ASSERT_EQUAL (0, i->from);
- CPPUNIT_ASSERT_EQUAL (1, i->to);
-
- ++i;
- CPPUNIT_ASSERT_EQUAL (5, i->from);
- CPPUNIT_ASSERT_EQUAL (6, i->to);
-
- ++i;
- CPPUNIT_ASSERT_EQUAL (9, i->from);
- CPPUNIT_ASSERT_EQUAL (10, i->to);
-}
-
-/* Test subtraction of a range B from a range A, where A and B do not overlap */
-void
-RangeTest::subtractTest2 ()
-{
- Range<int> fred (0, 10);
-
- RangeList<int> jim;
- jim.add (Range<int> (12, 19));
-
- RangeList<int> sheila = subtract (fred, jim);
-
- RangeList<int>::List s = sheila.get ();
- CPPUNIT_ASSERT_EQUAL (size_t (1), s.size ());
-
- RangeList<int>::List::iterator i = s.begin ();
- CPPUNIT_ASSERT_EQUAL (0, i->from);
- CPPUNIT_ASSERT_EQUAL (10, i->to);
-}
-
-/* Test subtraction of B from A, where B entirely overlaps A */
-void
-RangeTest::subtractTest3 ()
-{
- Range<int> fred (0, 10);
-
- RangeList<int> jim;
- jim.add (Range<int> (0, 12));
-
- RangeList<int> sheila = subtract (fred, jim);
-
- RangeList<int>::List s = sheila.get ();
- CPPUNIT_ASSERT_EQUAL (size_t (0), s.size ());
-}
-
-/* A bit like subtractTest1, except some of the ranges
- we are subtracting overlap.
-*/
-void
-RangeTest::subtractTest4 ()
-{
-/* 01234567890
- * fred: |---------|
- * jim: |-| ||
- * ||
- * sheila: || || |
- */
-
- Range<int> fred (0, 10);
-
- RangeList<int> jim;
- jim.add (Range<int> (2, 4));
- jim.add (Range<int> (7, 8));
- jim.add (Range<int> (8, 9));
-
- RangeList<int> sheila = subtract (fred, jim);
-
- RangeList<int>::List s = sheila.get ();
- CPPUNIT_ASSERT_EQUAL (size_t (3), s.size ());
-
- RangeList<int>::List::iterator i = s.begin ();
- CPPUNIT_ASSERT_EQUAL (0, i->from);
- CPPUNIT_ASSERT_EQUAL (1, i->to);
-
- ++i;
- CPPUNIT_ASSERT_EQUAL (5, i->from);
- CPPUNIT_ASSERT_EQUAL (6, i->to);
-
- ++i;
- CPPUNIT_ASSERT_EQUAL (10, i->from);
- CPPUNIT_ASSERT_EQUAL (10, i->to);
-}
-
-/* A bit like subtractTest1, except some of the ranges
- we are subtracting overlap the start / end of the
- initial range.
-*/
-void
-RangeTest::subtractTest5 ()
-{
-/* 01234567890123
- * fred: |----------|
- * jim: |---| || |------...
- * sheila:i | |
- */
-
- Range<int> fred (1, 12);
-
- RangeList<int> jim;
- jim.add (Range<int> (0, 4));
- jim.add (Range<int> (6, 7));
- jim.add (Range<int> (9, 42));
-
- RangeList<int> sheila = subtract (fred, jim);
-
- RangeList<int>::List s = sheila.get ();
- CPPUNIT_ASSERT_EQUAL (size_t (2), s.size ());
-
- RangeList<int>::List::iterator i = s.begin ();
- CPPUNIT_ASSERT_EQUAL (5, i->from);
- CPPUNIT_ASSERT_EQUAL (5, i->to);
-
- ++i;
- CPPUNIT_ASSERT_EQUAL (8, i->from);
- CPPUNIT_ASSERT_EQUAL (8, i->to);
-}
-
-/* Test coverage() with all possible types of overlap.
- */
-
-void
-RangeTest::coverageTest ()
-{
-
- // b starts before a
- CPPUNIT_ASSERT_EQUAL (coverage(3, 7, 1, 1), Evoral::OverlapNone);
- CPPUNIT_ASSERT_EQUAL (coverage(3, 7, 1, 2), Evoral::OverlapNone);
- CPPUNIT_ASSERT_EQUAL (coverage(3, 7, 1, 3), Evoral::OverlapStart);
- CPPUNIT_ASSERT_EQUAL (coverage(3, 7, 1, 5), Evoral::OverlapStart);
- CPPUNIT_ASSERT_EQUAL (coverage(3, 7, 1, 7), Evoral::OverlapExternal);
- CPPUNIT_ASSERT_EQUAL (coverage(3, 7, 1, 9), Evoral::OverlapExternal);
-
- // b starts at a
- CPPUNIT_ASSERT_EQUAL (coverage(3, 7, 3, 3), Evoral::OverlapStart);
- CPPUNIT_ASSERT_EQUAL (coverage(3, 7, 3, 5), Evoral::OverlapStart);
- CPPUNIT_ASSERT_EQUAL (coverage(3, 7, 3, 7), Evoral::OverlapExternal);
- CPPUNIT_ASSERT_EQUAL (coverage(3, 7, 3, 9), Evoral::OverlapExternal);
-
- // b starts inside a
- CPPUNIT_ASSERT_EQUAL (coverage(3, 7, 4, 4), Evoral::OverlapInternal);
- CPPUNIT_ASSERT_EQUAL (coverage(3, 7, 4, 6), Evoral::OverlapInternal);
- CPPUNIT_ASSERT_EQUAL (coverage(3, 7, 4, 7), Evoral::OverlapEnd);
- CPPUNIT_ASSERT_EQUAL (coverage(3, 7, 4, 8), Evoral::OverlapEnd);
-
- // b starts at end of a
- CPPUNIT_ASSERT_EQUAL (coverage(3, 7, 7, 7), Evoral::OverlapEnd);
- CPPUNIT_ASSERT_EQUAL (coverage(3, 7, 7, 9), Evoral::OverlapEnd);
-
- // b starts after end of a
- CPPUNIT_ASSERT_EQUAL (coverage(3, 7, 8, 8), Evoral::OverlapNone);
- CPPUNIT_ASSERT_EQUAL (coverage(3, 7, 8, 9), Evoral::OverlapNone);
-
- // zero-length range a
- CPPUNIT_ASSERT_EQUAL (coverage(3, 3, 2, 4), Evoral::OverlapExternal);
- CPPUNIT_ASSERT_EQUAL (coverage(3, 3, 1, 2), Evoral::OverlapNone);
- CPPUNIT_ASSERT_EQUAL (coverage(3, 3, 3, 3), Evoral::OverlapExternal);
- CPPUNIT_ASSERT_EQUAL (coverage(3, 3, 8, 9), Evoral::OverlapNone);
-
- // negative length range a
- // XXX these are debatable - should we just consider start & end to be
- // swapped if end < start?
- CPPUNIT_ASSERT_EQUAL (coverage(4, 3, 1, 2), Evoral::OverlapNone);
- CPPUNIT_ASSERT_EQUAL (coverage(4, 3, 2, 3), Evoral::OverlapNone);
- CPPUNIT_ASSERT_EQUAL (coverage(4, 3, 2, 4), Evoral::OverlapNone);
- CPPUNIT_ASSERT_EQUAL (coverage(4, 3, 3, 3), Evoral::OverlapNone);
- CPPUNIT_ASSERT_EQUAL (coverage(4, 3, 8, 9), Evoral::OverlapNone);
-
- // negative length range b
- // b starts before a
- CPPUNIT_ASSERT_EQUAL (coverage(3, 7, 1, 0), Evoral::OverlapNone);
- // b starts at a
- CPPUNIT_ASSERT_EQUAL (coverage(3, 7, 3, 2), Evoral::OverlapNone);
- // b starts inside a
- CPPUNIT_ASSERT_EQUAL (coverage(3, 7, 4, 3), Evoral::OverlapNone);
- // b starts at end of a
- CPPUNIT_ASSERT_EQUAL (coverage(3, 7, 7, 5), Evoral::OverlapNone);
- // b starts after end of a
- CPPUNIT_ASSERT_EQUAL (coverage(3, 7, 8, 7), Evoral::OverlapNone);
-
-}
--- /dev/null
+#include <cppunit/TestFixture.h>
+#include <cppunit/extensions/HelperMacros.h>
+
+class RangeTest : public CppUnit::TestFixture
+{
+ CPPUNIT_TEST_SUITE (RangeTest);
+ CPPUNIT_TEST (coalesceTest);
+ CPPUNIT_TEST (subtractTest1);
+ CPPUNIT_TEST (subtractTest2);
+ CPPUNIT_TEST (subtractTest3);
+ CPPUNIT_TEST (subtractTest4);
+ CPPUNIT_TEST (subtractTest5);
+ CPPUNIT_TEST (coverageTest);
+ CPPUNIT_TEST_SUITE_END ();
+
+public:
+ void coalesceTest ();
+ void subtractTest1 ();
+ void subtractTest2 ();
+ void subtractTest3 ();
+ void subtractTest4 ();
+ void subtractTest5 ();
+ void coverageTest ();
+};
+
+
+++ /dev/null
-#include <cppunit/TestFixture.h>
-#include <cppunit/extensions/HelperMacros.h>
-
-class RangeTest : public CppUnit::TestFixture
-{
- CPPUNIT_TEST_SUITE (RangeTest);
- CPPUNIT_TEST (coalesceTest);
- CPPUNIT_TEST (subtractTest1);
- CPPUNIT_TEST (subtractTest2);
- CPPUNIT_TEST (subtractTest3);
- CPPUNIT_TEST (subtractTest4);
- CPPUNIT_TEST (subtractTest5);
- CPPUNIT_TEST (coverageTest);
- CPPUNIT_TEST_SUITE_END ();
-
-public:
- void coalesceTest ();
- void subtractTest1 ();
- void subtractTest2 ();
- void subtractTest3 ();
- void subtractTest4 ();
- void subtractTest5 ();
- void coverageTest ();
-};
-
-
--- /dev/null
+#include "SMFTest.hpp"
+
+#include <glibmm/fileutils.h>
+#include <glibmm/miscutils.h>
+
+#include "pbd/file_utils.h"
+
+using namespace std;
+
+CPPUNIT_TEST_SUITE_REGISTRATION( SMFTest );
+
+void
+SMFTest::createNewFileTest ()
+{
+ TestSMF smf;
+
+ string output_dir_path = PBD::tmp_writable_directory (PACKAGE, "createNewFileTest");
+ string new_file_path = Glib::build_filename (output_dir_path, "NewFile.mid");
+ smf.create(new_file_path);
+ smf.close();
+ CPPUNIT_ASSERT(Glib::file_test (new_file_path, Glib::FILE_TEST_IS_REGULAR));
+}
+
+PBD::Searchpath
+test_search_path ()
+{
+#ifdef PLATFORM_WINDOWS
+ if (!getenv("EVORAL_TEST_PATH")) {
+ string wsp(g_win32_get_package_installation_directory_of_module(NULL));
+ return Glib::build_filename (wsp, "evoral_testdata");
+ }
+#endif
+ return Glib::getenv("EVORAL_TEST_PATH");
+}
+
+void
+SMFTest::takeFiveTest ()
+{
+ TestSMF smf;
+ string testdata_path;
+ CPPUNIT_ASSERT (find_file (test_search_path (), "TakeFive.mid", testdata_path));
+ CPPUNIT_ASSERT (SMF::test(testdata_path));
+
+ smf.open(testdata_path);
+ CPPUNIT_ASSERT(!smf.is_empty());
+
+ CPPUNIT_ASSERT_EQUAL((uint16_t)1, smf.num_tracks());
+ CPPUNIT_ASSERT_EQUAL(0, smf.seek_to_track(1));
+
+ seq->start_write();
+ smf.seek_to_start();
+
+ uint64_t time = 0; /* in SMF ticks */
+ Evoral::Event<Time> ev;
+
+ uint32_t delta_t = 0;
+ uint32_t size = 0;
+ uint8_t* buf = NULL;
+ int ret;
+ while ((ret = smf.read_event(&delta_t, &size, &buf)) >= 0) {
+ ev.set(buf, size, Time());
+ time += delta_t;
+
+ if (ret > 0) { // didn't skip (meta) event
+ //cerr << "read smf event type " << hex << int(buf[0]) << endl;
+ ev.set_time(Temporal::Beats::ticks_at_rate(time, smf.ppqn()));
+ ev.set_event_type(Evoral::MIDI_EVENT);
+ seq->append(ev, next_event_id ());
+ }
+ }
+
+ seq->end_write (Sequence<Time>::Relax,
+ Temporal::Beats::ticks_at_rate(time, smf.ppqn()));
+ CPPUNIT_ASSERT(!seq->empty());
+
+ // Iterate over all notes
+ bool on = true;
+ size_t num_notes = 0;
+ size_t num_sysexes = 0;
+ for (Sequence<Time>::const_iterator i = seq->begin(Time()); i != seq->end(); ++i) {
+ if (i->is_note_on()) {
+ ++num_notes;
+ } else if (i->is_sysex()) {
+ ++num_sysexes;
+ }
+ }
+ CPPUNIT_ASSERT_EQUAL(size_t(3833), seq->notes().size());
+ CPPUNIT_ASSERT_EQUAL(size_t(3833), num_notes);
+ CPPUNIT_ASSERT_EQUAL(size_t(232), seq->sysexes().size());
+ CPPUNIT_ASSERT_EQUAL(size_t(232), num_sysexes);
+}
+
+void
+SMFTest::writeTest ()
+{
+ TestSMF smf;
+ string testdata_path;
+ CPPUNIT_ASSERT (find_file (test_search_path (), "TakeFive.mid", testdata_path));
+
+ smf.open(testdata_path);
+ CPPUNIT_ASSERT(!smf.is_empty());
+
+ TestSMF out;
+ const string output_dir_path = PBD::tmp_writable_directory (PACKAGE, "writeTest");
+ const string new_file_path = Glib::build_filename (output_dir_path, "TakeFiveCopy.mid");
+ CPPUNIT_ASSERT_EQUAL (0, out.create(new_file_path, 1, 1920));
+ out.begin_write();
+
+ uint32_t delta_t = 0;
+ uint32_t size = 0;
+ uint8_t* buf = NULL;
+ while (smf.read_event(&delta_t, &size, &buf) >= 0) {
+ out.append_event_delta(delta_t, size, buf, 0);
+ }
+
+ out.end_write(new_file_path);
+
+ // TODO: Check files are actually equivalent
+}
+++ /dev/null
-#include "SMFTest.hpp"
-
-#include <glibmm/fileutils.h>
-#include <glibmm/miscutils.h>
-
-#include "pbd/file_utils.h"
-
-using namespace std;
-
-CPPUNIT_TEST_SUITE_REGISTRATION( SMFTest );
-
-void
-SMFTest::createNewFileTest ()
-{
- TestSMF smf;
-
- string output_dir_path = PBD::tmp_writable_directory (PACKAGE, "createNewFileTest");
- string new_file_path = Glib::build_filename (output_dir_path, "NewFile.mid");
- smf.create(new_file_path);
- smf.close();
- CPPUNIT_ASSERT(Glib::file_test (new_file_path, Glib::FILE_TEST_IS_REGULAR));
-}
-
-PBD::Searchpath
-test_search_path ()
-{
-#ifdef PLATFORM_WINDOWS
- if (!getenv("EVORAL_TEST_PATH")) {
- string wsp(g_win32_get_package_installation_directory_of_module(NULL));
- return Glib::build_filename (wsp, "evoral_testdata");
- }
-#endif
- return Glib::getenv("EVORAL_TEST_PATH");
-}
-
-void
-SMFTest::takeFiveTest ()
-{
- TestSMF smf;
- string testdata_path;
- CPPUNIT_ASSERT (find_file (test_search_path (), "TakeFive.mid", testdata_path));
- CPPUNIT_ASSERT (SMF::test(testdata_path));
-
- smf.open(testdata_path);
- CPPUNIT_ASSERT(!smf.is_empty());
-
- CPPUNIT_ASSERT_EQUAL((uint16_t)1, smf.num_tracks());
- CPPUNIT_ASSERT_EQUAL(0, smf.seek_to_track(1));
-
- seq->start_write();
- smf.seek_to_start();
-
- uint64_t time = 0; /* in SMF ticks */
- Evoral::Event<Time> ev;
-
- uint32_t delta_t = 0;
- uint32_t size = 0;
- uint8_t* buf = NULL;
- int ret;
- while ((ret = smf.read_event(&delta_t, &size, &buf)) >= 0) {
- ev.set(buf, size, Time());
- time += delta_t;
-
- if (ret > 0) { // didn't skip (meta) event
- //cerr << "read smf event type " << hex << int(buf[0]) << endl;
- ev.set_time(Temporal::Beats::ticks_at_rate(time, smf.ppqn()));
- ev.set_event_type(Evoral::MIDI_EVENT);
- seq->append(ev, next_event_id ());
- }
- }
-
- seq->end_write (Sequence<Time>::Relax,
- Temporal::Beats::ticks_at_rate(time, smf.ppqn()));
- CPPUNIT_ASSERT(!seq->empty());
-
- // Iterate over all notes
- bool on = true;
- size_t num_notes = 0;
- size_t num_sysexes = 0;
- for (Sequence<Time>::const_iterator i = seq->begin(Time()); i != seq->end(); ++i) {
- if (i->is_note_on()) {
- ++num_notes;
- } else if (i->is_sysex()) {
- ++num_sysexes;
- }
- }
- CPPUNIT_ASSERT_EQUAL(size_t(3833), seq->notes().size());
- CPPUNIT_ASSERT_EQUAL(size_t(3833), num_notes);
- CPPUNIT_ASSERT_EQUAL(size_t(232), seq->sysexes().size());
- CPPUNIT_ASSERT_EQUAL(size_t(232), num_sysexes);
-}
-
-void
-SMFTest::writeTest ()
-{
- TestSMF smf;
- string testdata_path;
- CPPUNIT_ASSERT (find_file (test_search_path (), "TakeFive.mid", testdata_path));
-
- smf.open(testdata_path);
- CPPUNIT_ASSERT(!smf.is_empty());
-
- TestSMF out;
- const string output_dir_path = PBD::tmp_writable_directory (PACKAGE, "writeTest");
- const string new_file_path = Glib::build_filename (output_dir_path, "TakeFiveCopy.mid");
- CPPUNIT_ASSERT_EQUAL (0, out.create(new_file_path, 1, 1920));
- out.begin_write();
-
- uint32_t delta_t = 0;
- uint32_t size = 0;
- uint8_t* buf = NULL;
- while (smf.read_event(&delta_t, &size, &buf) >= 0) {
- out.append_event_delta(delta_t, size, buf, 0);
- }
-
- out.end_write(new_file_path);
-
- // TODO: Check files are actually equivalent
-}
--- /dev/null
+/*
+ * Copyright (C) 2009-2010 Carl Hetherington <carl@carlh.net>
+ * Copyright (C) 2009-2016 David Robillard <d@drobilla.net>
+ * Copyright (C) 2009 Hans Baier <hansfbaier@googlemail.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include <cassert>
+#include <stdint.h>
+#include <sigc++/sigc++.h>
+#include <cppunit/TestFixture.h>
+#include <cppunit/extensions/HelperMacros.h>
+#include "temporal/beats.h"
+#include "evoral/SMF.h"
+#include "SequenceTest.hpp"
+
+using namespace Evoral;
+
+class TestSMF : public SMF {
+public:
+ std::string path() const { return _path; }
+
+ int open(const std::string& path) {
+ _path = path;
+ return SMF::open(path);
+ }
+
+ void close() {
+ return SMF::close();
+ }
+
+ int read_event(uint32_t* delta_t, uint32_t* size, uint8_t** buf) const {
+ event_id_t id;
+ return SMF::read_event(delta_t, size, buf, &id);
+ }
+
+private:
+ std::string _path;
+};
+
+class SMFTest : public CppUnit::TestFixture
+{
+ CPPUNIT_TEST_SUITE(SMFTest);
+ CPPUNIT_TEST(createNewFileTest);
+ CPPUNIT_TEST(takeFiveTest);
+ CPPUNIT_TEST(writeTest);
+ CPPUNIT_TEST_SUITE_END();
+
+public:
+ typedef Temporal::Beats Time;
+
+ void setUp() {
+ type_map = new DummyTypeMap();
+ assert(type_map);
+ seq = new MySequence<Time>(*type_map);
+ assert(seq);
+ }
+
+ void tearDown() {
+ delete seq;
+ delete type_map;
+ }
+
+ void createNewFileTest();
+ void takeFiveTest();
+ void writeTest();
+
+private:
+ DummyTypeMap* type_map;
+ MySequence<Time>* seq;
+};
+
+++ /dev/null
-/*
- * Copyright (C) 2009-2010 Carl Hetherington <carl@carlh.net>
- * Copyright (C) 2009-2016 David Robillard <d@drobilla.net>
- * Copyright (C) 2009 Hans Baier <hansfbaier@googlemail.com>
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-
-#include <cassert>
-#include <stdint.h>
-#include <sigc++/sigc++.h>
-#include <cppunit/TestFixture.h>
-#include <cppunit/extensions/HelperMacros.h>
-#include "temporal/beats.h"
-#include "evoral/SMF.hpp"
-#include "SequenceTest.hpp"
-
-using namespace Evoral;
-
-class TestSMF : public SMF {
-public:
- std::string path() const { return _path; }
-
- int open(const std::string& path) {
- _path = path;
- return SMF::open(path);
- }
-
- void close() {
- return SMF::close();
- }
-
- int read_event(uint32_t* delta_t, uint32_t* size, uint8_t** buf) const {
- event_id_t id;
- return SMF::read_event(delta_t, size, buf, &id);
- }
-
-private:
- std::string _path;
-};
-
-class SMFTest : public CppUnit::TestFixture
-{
- CPPUNIT_TEST_SUITE(SMFTest);
- CPPUNIT_TEST(createNewFileTest);
- CPPUNIT_TEST(takeFiveTest);
- CPPUNIT_TEST(writeTest);
- CPPUNIT_TEST_SUITE_END();
-
-public:
- typedef Temporal::Beats Time;
-
- void setUp() {
- type_map = new DummyTypeMap();
- assert(type_map);
- seq = new MySequence<Time>(*type_map);
- assert(seq);
- }
-
- void tearDown() {
- delete seq;
- delete type_map;
- }
-
- void createNewFileTest();
- void takeFiveTest();
- void writeTest();
-
-private:
- DummyTypeMap* type_map;
- MySequence<Time>* seq;
-};
-
--- /dev/null
+#include "SequenceTest.hpp"
+#include <cassert>
+
+CPPUNIT_TEST_SUITE_REGISTRATION(SequenceTest);
+
+using namespace std;
+using namespace Evoral;
+
+void
+SequenceTest::createTest ()
+{
+ CPPUNIT_ASSERT_EQUAL(size_t(0), seq->sysexes().size());
+ CPPUNIT_ASSERT_EQUAL(size_t(0), seq->notes().size());
+ CPPUNIT_ASSERT(seq->notes().begin() == seq->notes().end());
+}
+
+void
+SequenceTest::copyTest ()
+{
+ DummyTypeMap map;
+ MySequence<Time> a(map);
+ for (Notes::const_iterator i = test_notes.begin(); i != test_notes.end(); ++i) {
+ a.notes().insert(*i);
+ }
+
+ MySequence<Time> b(a);
+ CPPUNIT_ASSERT_EQUAL(b.notes().size(), a.notes().size());
+}
+
+void
+SequenceTest::preserveEventOrderingTest ()
+{
+ vector< boost::shared_ptr< Event<Time> > > inserted_events;
+
+ seq->start_write();
+
+ for (Notes::const_iterator i = test_notes.begin(); i != test_notes.end(); ++i) {
+ uint8_t buffer[3];
+ Event<Time>* event = new Event<Time>(
+ (Evoral::EventType)DummyTypeMap::CONTROL, (*i)->on_event().time(), 3, buffer, true
+ );
+
+ event->buffer()[0] = MIDI_CMD_CONTROL;
+ event->buffer()[1] = event->time().to_double() / 1000;
+ event->buffer()[2] = event->time().to_double() / 1000;
+
+ boost::shared_ptr<Event<Time> > event_ptr(event);
+
+ seq->append((*i)->on_event(), next_event_id ());
+ inserted_events.push_back(
+ boost::shared_ptr<Event<Time> >(
+ new Event<Time>((*i)->on_event(), true)
+ ));
+
+ seq->append(*event_ptr, next_event_id ());
+ inserted_events.push_back(event_ptr);
+
+ seq->append((*i)->off_event(), next_event_id ());
+ inserted_events.push_back(
+ boost::shared_ptr<Event<Time> >(
+ new Event<Time>((*i)->off_event(), true)
+ ));
+ }
+
+ seq->end_write (Sequence<Time>::Relax);
+
+ TestSink<Time> sink;
+ sink.writing.connect(sigc::mem_fun(&sink, &TestSink<Time>::assertLastEventTimeEarlier));
+
+
+ for (MySequence<Time>::const_iterator i = seq->begin(); i != seq->end(); ++i) {
+ sink.write(i->time(), i->event_type(), i->size(), i->buffer());
+ }
+
+ CPPUNIT_ASSERT_EQUAL(size_t(12), test_notes.size());
+}
+
+void
+SequenceTest::iteratorSeekTest ()
+{
+ size_t num_notes = 0;
+
+ seq->clear();
+
+ for (Notes::const_iterator i = test_notes.begin(); i != test_notes.end(); ++i) {
+ seq->notes().insert(*i);
+ }
+
+ // Iterate over all notes
+ bool on = true;
+ for (Sequence<Time>::const_iterator i = seq->begin(Time(600)); i != seq->end(); ++i) {
+ if (on) {
+ CPPUNIT_ASSERT(i->is_note_on());
+ CPPUNIT_ASSERT_EQUAL(i->time(), Time((num_notes + 6) * 100));
+ ++num_notes;
+ on = false;
+ } else {
+ CPPUNIT_ASSERT(i->is_note_off());
+ on = true;
+ }
+ }
+
+ CPPUNIT_ASSERT_EQUAL(size_t(6), num_notes);
+
+ // Test invalidation
+ Sequence<Time>::const_iterator i = seq->begin(Time(600));
+ std::set< boost::weak_ptr< Note<Time> > > active_notes;
+ i.invalidate(&active_notes);
+ CPPUNIT_ASSERT_EQUAL((size_t)1, active_notes.size());
+
+ // Test resuming after invalidation
+ i = seq->begin(Time(601), false, std::set<Evoral::Parameter>(), &active_notes);
+ CPPUNIT_ASSERT(i->is_note_off());
+ on = false;
+ num_notes = 1;
+ for (; i != seq->end(); ++i) {
+ if (on) {
+ CPPUNIT_ASSERT(i->is_note_on());
+ CPPUNIT_ASSERT_EQUAL(Time((num_notes + 6) * 100), i->time());
+ ++num_notes;
+ on = false;
+ } else {
+ CPPUNIT_ASSERT(i->is_note_off());
+ on = true;
+ }
+ }
+
+ CPPUNIT_ASSERT_EQUAL(size_t(6), num_notes);
+
+ // Test equality of copied iterators
+ i = seq->begin();
+ ++i;
+ Sequence<Time>::const_iterator j = i;
+ CPPUNIT_ASSERT(i == j);
+}
+
+void
+SequenceTest::controlInterpolationTest ()
+{
+ seq->clear();
+
+ static const uint64_t delay = 1000;
+ static const uint32_t cc_type = 1;
+
+ boost::shared_ptr<Control> c = seq->control(Parameter(cc_type, 1, 1), true);
+ CPPUNIT_ASSERT(c);
+
+ double min = 0.0;
+ double max = 127.0;
+
+ // Make a ramp like /\ from min to max and back to min
+ c->set_double(min, 0, true);
+ c->set_double(max, delay, true);
+ c->set_double(min, 2*delay, true);
+
+ CCTestSink<Time> sink(cc_type);
+
+ // Test discrete (lack of) interpolation
+ c->list()->set_interpolation(ControlList::Discrete);
+ for (MySequence<Time>::const_iterator i = seq->begin(); i != seq->end(); ++i) {
+ sink.write(i->time(), i->event_type(), i->size(), i->buffer());
+ }
+ CPPUNIT_ASSERT_EQUAL((size_t)3, sink.events.size());
+ CPPUNIT_ASSERT_EQUAL(Time(0), sink.events[0].first);
+ CPPUNIT_ASSERT_EQUAL((uint8_t)0, sink.events[0].second);
+ CPPUNIT_ASSERT_EQUAL(Time(1000), sink.events[1].first);
+ CPPUNIT_ASSERT_EQUAL((uint8_t)127, sink.events[1].second);
+ CPPUNIT_ASSERT_EQUAL(Time(2000), sink.events[2].first);
+ CPPUNIT_ASSERT_EQUAL((uint8_t)0, sink.events[2].second);
+ sink.events.clear();
+ CPPUNIT_ASSERT_EQUAL((size_t)0, sink.events.size());
+
+ // Test linear interpolation
+ c->list()->set_interpolation(ControlList::Linear);
+ for (MySequence<Time>::const_iterator i = seq->begin(); i != seq->end(); ++i) {
+ sink.write(i->time(), i->event_type(), i->size(), i->buffer());
+ }
+ CPPUNIT_ASSERT_EQUAL((size_t)(128 * 2 - 1), sink.events.size());
+ Time last_time(0);
+ int16_t last_value = -1;
+ bool ascending = true;
+ for (CCTestSink<Time>::Events::const_iterator i = sink.events.begin();
+ i != sink.events.end(); ++i) {
+ CPPUNIT_ASSERT(last_time == 0 || i->first > last_time);
+ if (last_value == 127) {
+ ascending = false;
+ }
+ if (ascending) {
+ CPPUNIT_ASSERT_EQUAL((int)i->second, last_value + 1);
+ } else {
+ CPPUNIT_ASSERT_EQUAL((int)i->second, last_value - 1);
+ }
+ last_time = i->first;
+ last_value = i->second;
+ }
+}
+++ /dev/null
-#include "SequenceTest.hpp"
-#include <cassert>
-
-CPPUNIT_TEST_SUITE_REGISTRATION(SequenceTest);
-
-using namespace std;
-using namespace Evoral;
-
-void
-SequenceTest::createTest ()
-{
- CPPUNIT_ASSERT_EQUAL(size_t(0), seq->sysexes().size());
- CPPUNIT_ASSERT_EQUAL(size_t(0), seq->notes().size());
- CPPUNIT_ASSERT(seq->notes().begin() == seq->notes().end());
-}
-
-void
-SequenceTest::copyTest ()
-{
- DummyTypeMap map;
- MySequence<Time> a(map);
- for (Notes::const_iterator i = test_notes.begin(); i != test_notes.end(); ++i) {
- a.notes().insert(*i);
- }
-
- MySequence<Time> b(a);
- CPPUNIT_ASSERT_EQUAL(b.notes().size(), a.notes().size());
-}
-
-void
-SequenceTest::preserveEventOrderingTest ()
-{
- vector< boost::shared_ptr< Event<Time> > > inserted_events;
-
- seq->start_write();
-
- for (Notes::const_iterator i = test_notes.begin(); i != test_notes.end(); ++i) {
- uint8_t buffer[3];
- Event<Time>* event = new Event<Time>(
- (Evoral::EventType)DummyTypeMap::CONTROL, (*i)->on_event().time(), 3, buffer, true
- );
-
- event->buffer()[0] = MIDI_CMD_CONTROL;
- event->buffer()[1] = event->time().to_double() / 1000;
- event->buffer()[2] = event->time().to_double() / 1000;
-
- boost::shared_ptr<Event<Time> > event_ptr(event);
-
- seq->append((*i)->on_event(), next_event_id ());
- inserted_events.push_back(
- boost::shared_ptr<Event<Time> >(
- new Event<Time>((*i)->on_event(), true)
- ));
-
- seq->append(*event_ptr, next_event_id ());
- inserted_events.push_back(event_ptr);
-
- seq->append((*i)->off_event(), next_event_id ());
- inserted_events.push_back(
- boost::shared_ptr<Event<Time> >(
- new Event<Time>((*i)->off_event(), true)
- ));
- }
-
- seq->end_write (Sequence<Time>::Relax);
-
- TestSink<Time> sink;
- sink.writing.connect(sigc::mem_fun(&sink, &TestSink<Time>::assertLastEventTimeEarlier));
-
-
- for (MySequence<Time>::const_iterator i = seq->begin(); i != seq->end(); ++i) {
- sink.write(i->time(), i->event_type(), i->size(), i->buffer());
- }
-
- CPPUNIT_ASSERT_EQUAL(size_t(12), test_notes.size());
-}
-
-void
-SequenceTest::iteratorSeekTest ()
-{
- size_t num_notes = 0;
-
- seq->clear();
-
- for (Notes::const_iterator i = test_notes.begin(); i != test_notes.end(); ++i) {
- seq->notes().insert(*i);
- }
-
- // Iterate over all notes
- bool on = true;
- for (Sequence<Time>::const_iterator i = seq->begin(Time(600)); i != seq->end(); ++i) {
- if (on) {
- CPPUNIT_ASSERT(i->is_note_on());
- CPPUNIT_ASSERT_EQUAL(i->time(), Time((num_notes + 6) * 100));
- ++num_notes;
- on = false;
- } else {
- CPPUNIT_ASSERT(i->is_note_off());
- on = true;
- }
- }
-
- CPPUNIT_ASSERT_EQUAL(size_t(6), num_notes);
-
- // Test invalidation
- Sequence<Time>::const_iterator i = seq->begin(Time(600));
- std::set< boost::weak_ptr< Note<Time> > > active_notes;
- i.invalidate(&active_notes);
- CPPUNIT_ASSERT_EQUAL((size_t)1, active_notes.size());
-
- // Test resuming after invalidation
- i = seq->begin(Time(601), false, std::set<Evoral::Parameter>(), &active_notes);
- CPPUNIT_ASSERT(i->is_note_off());
- on = false;
- num_notes = 1;
- for (; i != seq->end(); ++i) {
- if (on) {
- CPPUNIT_ASSERT(i->is_note_on());
- CPPUNIT_ASSERT_EQUAL(Time((num_notes + 6) * 100), i->time());
- ++num_notes;
- on = false;
- } else {
- CPPUNIT_ASSERT(i->is_note_off());
- on = true;
- }
- }
-
- CPPUNIT_ASSERT_EQUAL(size_t(6), num_notes);
-
- // Test equality of copied iterators
- i = seq->begin();
- ++i;
- Sequence<Time>::const_iterator j = i;
- CPPUNIT_ASSERT(i == j);
-}
-
-void
-SequenceTest::controlInterpolationTest ()
-{
- seq->clear();
-
- static const uint64_t delay = 1000;
- static const uint32_t cc_type = 1;
-
- boost::shared_ptr<Control> c = seq->control(Parameter(cc_type, 1, 1), true);
- CPPUNIT_ASSERT(c);
-
- double min = 0.0;
- double max = 127.0;
-
- // Make a ramp like /\ from min to max and back to min
- c->set_double(min, 0, true);
- c->set_double(max, delay, true);
- c->set_double(min, 2*delay, true);
-
- CCTestSink<Time> sink(cc_type);
-
- // Test discrete (lack of) interpolation
- c->list()->set_interpolation(ControlList::Discrete);
- for (MySequence<Time>::const_iterator i = seq->begin(); i != seq->end(); ++i) {
- sink.write(i->time(), i->event_type(), i->size(), i->buffer());
- }
- CPPUNIT_ASSERT_EQUAL((size_t)3, sink.events.size());
- CPPUNIT_ASSERT_EQUAL(Time(0), sink.events[0].first);
- CPPUNIT_ASSERT_EQUAL((uint8_t)0, sink.events[0].second);
- CPPUNIT_ASSERT_EQUAL(Time(1000), sink.events[1].first);
- CPPUNIT_ASSERT_EQUAL((uint8_t)127, sink.events[1].second);
- CPPUNIT_ASSERT_EQUAL(Time(2000), sink.events[2].first);
- CPPUNIT_ASSERT_EQUAL((uint8_t)0, sink.events[2].second);
- sink.events.clear();
- CPPUNIT_ASSERT_EQUAL((size_t)0, sink.events.size());
-
- // Test linear interpolation
- c->list()->set_interpolation(ControlList::Linear);
- for (MySequence<Time>::const_iterator i = seq->begin(); i != seq->end(); ++i) {
- sink.write(i->time(), i->event_type(), i->size(), i->buffer());
- }
- CPPUNIT_ASSERT_EQUAL((size_t)(128 * 2 - 1), sink.events.size());
- Time last_time(0);
- int16_t last_value = -1;
- bool ascending = true;
- for (CCTestSink<Time>::Events::const_iterator i = sink.events.begin();
- i != sink.events.end(); ++i) {
- CPPUNIT_ASSERT(last_time == 0 || i->first > last_time);
- if (last_value == 127) {
- ascending = false;
- }
- if (ascending) {
- CPPUNIT_ASSERT_EQUAL((int)i->second, last_value + 1);
- } else {
- CPPUNIT_ASSERT_EQUAL((int)i->second, last_value - 1);
- }
- last_time = i->first;
- last_value = i->second;
- }
-}
--- /dev/null
+#include <cassert>
+#include <sigc++/sigc++.h>
+#include <cppunit/TestFixture.h>
+#include <cppunit/extensions/HelperMacros.h>
+#include "temporal/beats.h"
+#include "evoral/Sequence.h"
+#include "evoral/TypeMap.h"
+#include "evoral/EventSink.h"
+#include "evoral/midi_events.h"
+#include "evoral/Control.h"
+
+using namespace Evoral;
+
+class DummyTypeMap : public TypeMap {
+public:
+ enum DummyEventType {
+ NOTE,
+ CONTROL,
+ SYSEX
+ };
+
+ ~DummyTypeMap() {}
+
+ bool type_is_midi (uint32_t /*type*/) const { return true; }
+
+ uint8_t parameter_midi_type(const Parameter& param) const {
+ switch (param.type()) {
+ case CONTROL: return MIDI_CMD_CONTROL;
+ case SYSEX: return MIDI_CMD_COMMON_SYSEX;
+ default: return 0;
+ };
+ }
+
+ virtual ParameterType midi_parameter_type(const uint8_t* buf, uint32_t len) const {
+ switch (buf[0] & 0xF0) {
+ case MIDI_CMD_CONTROL: return CONTROL;
+ case MIDI_CMD_COMMON_SYSEX: return SYSEX;
+ case MIDI_CMD_NOTE_ON: return NOTE;
+ case MIDI_CMD_NOTE_OFF: return NOTE;
+ default: return 0;
+ }
+ }
+
+ ParameterDescriptor descriptor(const Parameter& param) const {
+ Evoral::ParameterDescriptor desc;
+ desc.upper = 127;
+ desc.rangesteps = 128;
+ return desc;
+ }
+
+ std::string to_symbol(const Parameter& /*param*/) const { return "control"; }
+};
+
+template<typename Time>
+class MySequence : public Sequence<Time> {
+public:
+ MySequence(DummyTypeMap&map) : Sequence<Time>(map) {}
+ MySequence(const MySequence& copy) : ControlSet(copy), Sequence<Time>(copy) {}
+
+ virtual bool find_next_event(double start, double end, ControlEvent& ev, bool only_active) const { return false; }
+
+ boost::shared_ptr<Control> control_factory(const Parameter& param) {
+ Evoral::ParameterDescriptor desc;
+ desc.upper = 127;
+ desc.rangesteps = 128;
+ boost::shared_ptr<ControlList> list(new ControlList(param, desc));
+ return boost::shared_ptr<Control>(new Control(param, desc, list));
+ }
+};
+
+template<typename Time>
+class TestSink : public EventSink<Time> {
+public:
+ TestSink() : _last_event_time(-1) {}
+
+ /// return value, time, type, size, buffer
+ sigc::signal<uint32_t, Time, EventType, uint32_t, const uint8_t*> writing;
+
+ virtual uint32_t write(Time time, EventType type, uint32_t size, const uint8_t* buf) {
+ //std::cerr << "last event time: " << _last_event_time << " time: " << time << std::endl;
+ uint32_t result = writing(time, type, size, buf);
+ _last_event_time = time;
+ return result;
+ }
+
+ uint32_t assertLastEventTimeEarlier(
+ Time time, EventType /*type*/, uint32_t /*size*/, const uint8_t* /*buf*/) {
+ CPPUNIT_ASSERT(_last_event_time <= time);
+ return 0;
+ }
+
+ Time last_event_time() const { return _last_event_time; }
+
+private:
+ Time _last_event_time;
+};
+
+template<typename Time>
+class CCTestSink : public EventSink<Time> {
+public:
+ CCTestSink(uint32_t t) : cc_type(t) {}
+
+ virtual uint32_t write(Time time, EventType type, uint32_t size, const uint8_t* buf) {
+ if (type == cc_type) {
+ CPPUNIT_ASSERT_EQUAL((uint32_t)3, size);
+ events.push_back(std::make_pair(time, buf[2]));
+ }
+ return size;
+ }
+
+ typedef std::vector< std::pair<Time, uint8_t> > Events;
+ Events events;
+ uint32_t cc_type;
+};
+
+class SequenceTest : public CppUnit::TestFixture
+{
+ CPPUNIT_TEST_SUITE (SequenceTest);
+ CPPUNIT_TEST (createTest);
+ CPPUNIT_TEST (copyTest);
+ CPPUNIT_TEST (preserveEventOrderingTest);
+ CPPUNIT_TEST (iteratorSeekTest);
+ CPPUNIT_TEST (controlInterpolationTest);
+ CPPUNIT_TEST_SUITE_END ();
+
+public:
+ typedef Temporal::Beats Time;
+ typedef std::vector< boost::shared_ptr< Note<Time> > > Notes;
+
+ void setUp () {
+ type_map = new DummyTypeMap();
+ assert(type_map);
+ seq = new MySequence<Time>(*type_map);
+ assert(seq);
+
+ for (int i = 0; i < 12; i++) {
+ test_notes.push_back(
+ boost::shared_ptr<Note<Time> >(
+ new Note<Time>(0, Time(i * 100), Time(100), 64 + i, 64)));
+ }
+ }
+
+ void tearDown () {
+ test_notes.clear();
+ delete seq;
+ delete type_map;
+ }
+
+ void createTest ();
+ void copyTest ();
+ void preserveEventOrderingTest ();
+ void iteratorSeekTest ();
+ void controlInterpolationTest ();
+
+private:
+ DummyTypeMap* type_map;
+ MySequence<Time>* seq;
+
+ Notes test_notes;
+};
+++ /dev/null
-#include <cassert>
-#include <sigc++/sigc++.h>
-#include <cppunit/TestFixture.h>
-#include <cppunit/extensions/HelperMacros.h>
-#include "temporal/beats.h"
-#include "evoral/Sequence.hpp"
-#include "evoral/TypeMap.hpp"
-#include "evoral/EventSink.hpp"
-#include "evoral/midi_events.h"
-#include "evoral/Control.hpp"
-
-using namespace Evoral;
-
-class DummyTypeMap : public TypeMap {
-public:
- enum DummyEventType {
- NOTE,
- CONTROL,
- SYSEX
- };
-
- ~DummyTypeMap() {}
-
- bool type_is_midi (uint32_t /*type*/) const { return true; }
-
- uint8_t parameter_midi_type(const Parameter& param) const {
- switch (param.type()) {
- case CONTROL: return MIDI_CMD_CONTROL;
- case SYSEX: return MIDI_CMD_COMMON_SYSEX;
- default: return 0;
- };
- }
-
- virtual ParameterType midi_parameter_type(const uint8_t* buf, uint32_t len) const {
- switch (buf[0] & 0xF0) {
- case MIDI_CMD_CONTROL: return CONTROL;
- case MIDI_CMD_COMMON_SYSEX: return SYSEX;
- case MIDI_CMD_NOTE_ON: return NOTE;
- case MIDI_CMD_NOTE_OFF: return NOTE;
- default: return 0;
- }
- }
-
- ParameterDescriptor descriptor(const Parameter& param) const {
- Evoral::ParameterDescriptor desc;
- desc.upper = 127;
- desc.rangesteps = 128;
- return desc;
- }
-
- std::string to_symbol(const Parameter& /*param*/) const { return "control"; }
-};
-
-template<typename Time>
-class MySequence : public Sequence<Time> {
-public:
- MySequence(DummyTypeMap&map) : Sequence<Time>(map) {}
- MySequence(const MySequence& copy) : ControlSet(copy), Sequence<Time>(copy) {}
-
- virtual bool find_next_event(double start, double end, ControlEvent& ev, bool only_active) const { return false; }
-
- boost::shared_ptr<Control> control_factory(const Parameter& param) {
- Evoral::ParameterDescriptor desc;
- desc.upper = 127;
- desc.rangesteps = 128;
- boost::shared_ptr<ControlList> list(new ControlList(param, desc));
- return boost::shared_ptr<Control>(new Control(param, desc, list));
- }
-};
-
-template<typename Time>
-class TestSink : public EventSink<Time> {
-public:
- TestSink() : _last_event_time(-1) {}
-
- /// return value, time, type, size, buffer
- sigc::signal<uint32_t, Time, EventType, uint32_t, const uint8_t*> writing;
-
- virtual uint32_t write(Time time, EventType type, uint32_t size, const uint8_t* buf) {
- //std::cerr << "last event time: " << _last_event_time << " time: " << time << std::endl;
- uint32_t result = writing(time, type, size, buf);
- _last_event_time = time;
- return result;
- }
-
- uint32_t assertLastEventTimeEarlier(
- Time time, EventType /*type*/, uint32_t /*size*/, const uint8_t* /*buf*/) {
- CPPUNIT_ASSERT(_last_event_time <= time);
- return 0;
- }
-
- Time last_event_time() const { return _last_event_time; }
-
-private:
- Time _last_event_time;
-};
-
-template<typename Time>
-class CCTestSink : public EventSink<Time> {
-public:
- CCTestSink(uint32_t t) : cc_type(t) {}
-
- virtual uint32_t write(Time time, EventType type, uint32_t size, const uint8_t* buf) {
- if (type == cc_type) {
- CPPUNIT_ASSERT_EQUAL((uint32_t)3, size);
- events.push_back(std::make_pair(time, buf[2]));
- }
- return size;
- }
-
- typedef std::vector< std::pair<Time, uint8_t> > Events;
- Events events;
- uint32_t cc_type;
-};
-
-class SequenceTest : public CppUnit::TestFixture
-{
- CPPUNIT_TEST_SUITE (SequenceTest);
- CPPUNIT_TEST (createTest);
- CPPUNIT_TEST (copyTest);
- CPPUNIT_TEST (preserveEventOrderingTest);
- CPPUNIT_TEST (iteratorSeekTest);
- CPPUNIT_TEST (controlInterpolationTest);
- CPPUNIT_TEST_SUITE_END ();
-
-public:
- typedef Temporal::Beats Time;
- typedef std::vector< boost::shared_ptr< Note<Time> > > Notes;
-
- void setUp () {
- type_map = new DummyTypeMap();
- assert(type_map);
- seq = new MySequence<Time>(*type_map);
- assert(seq);
-
- for (int i = 0; i < 12; i++) {
- test_notes.push_back(
- boost::shared_ptr<Note<Time> >(
- new Note<Time>(0, Time(i * 100), Time(100), 64 + i, 64)));
- }
- }
-
- void tearDown () {
- test_notes.clear();
- delete seq;
- delete type_map;
- }
-
- void createTest ();
- void copyTest ();
- void preserveEventOrderingTest ();
- void iteratorSeekTest ();
- void controlInterpolationTest ();
-
-private:
- DummyTypeMap* type_map;
- MySequence<Time>* seq;
-
- Notes test_notes;
-};
--- /dev/null
+#include <cppunit/CompilerOutputter.h>
+#include <cppunit/extensions/TestFactoryRegistry.h>
+#include <cppunit/TestResult.h>
+#include <cppunit/TestResultCollector.h>
+#include <cppunit/TestRunner.h>
+#include <cppunit/BriefTestProgressListener.h>
+
+#include "pbd/pbd.h"
+
+int
+main()
+{
+ if (!PBD::init ()) return 1;
+
+ CppUnit::TestResult testresult;
+
+ CppUnit::TestResultCollector collectedresults;
+ testresult.addListener (&collectedresults);
+
+ CppUnit::BriefTestProgressListener progress;
+ testresult.addListener (&progress);
+
+ CppUnit::TestRunner testrunner;
+ testrunner.addTest (CppUnit::TestFactoryRegistry::getRegistry ().makeTest ());
+ testrunner.run (testresult);
+
+ CppUnit::CompilerOutputter compileroutputter (&collectedresults, std::cerr);
+ compileroutputter.write ();
+
+ return collectedresults.wasSuccessful () ? 0 : 1;
+}
+++ /dev/null
-#include <cppunit/CompilerOutputter.h>
-#include <cppunit/extensions/TestFactoryRegistry.h>
-#include <cppunit/TestResult.h>
-#include <cppunit/TestResultCollector.h>
-#include <cppunit/TestRunner.h>
-#include <cppunit/BriefTestProgressListener.h>
-
-#include "pbd/pbd.h"
-
-int
-main()
-{
- if (!PBD::init ()) return 1;
-
- CppUnit::TestResult testresult;
-
- CppUnit::TestResultCollector collectedresults;
- testresult.addListener (&collectedresults);
-
- CppUnit::BriefTestProgressListener progress;
- testresult.addListener (&progress);
-
- CppUnit::TestRunner testrunner;
- testrunner.addTest (CppUnit::TestFactoryRegistry::getRegistry ().makeTest ());
- testrunner.run (testresult);
-
- CppUnit::CompilerOutputter compileroutputter (&collectedresults, std::cerr);
- compileroutputter.write ();
-
- return collectedresults.wasSuccessful () ? 0 : 1;
-}
def build(bld):
# Headers
#bld.install_files('${INCLUDEDIR}/evoral', 'evoral/*.h')
- #bld.install_files('${INCLUDEDIR}/evoral', 'evoral/*.hpp')
+ #bld.install_files('${INCLUDEDIR}/evoral', 'evoral/*.h')
# Pkgconfig file
#autowaf.build_pc(bld, 'EVORAL', EVORAL_VERSION, 'GLIBMM GTHREAD')
libsmf.cflags = [ bld.env['compiler_flags_dict']['pic'] ]
lib_source = '''
- src/Control.cpp
- src/ControlList.cpp
- src/ControlSet.cpp
- src/Curve.cpp
- src/Event.cpp
- src/Note.cpp
- src/SMF.cpp
- src/Sequence.cpp
- src/TimeConverter.cpp
- src/debug.cpp
+ src/Control.cc
+ src/ControlList.cc
+ src/ControlSet.cc
+ src/Curve.cc
+ src/Event.cc
+ src/Note.cc
+ src/SMF.cc
+ src/Sequence.cc
+ src/TimeConverter.cc
+ src/debug.cc
'''
# Library
# Unit tests
obj = bld(features = 'cxx cxxprogram')
obj.source = '''
- test/SequenceTest.cpp
- test/SMFTest.cpp
- test/RangeTest.cpp
- test/NoteTest.cpp
- test/CurveTest.cpp
- test/testrunner.cpp
+ test/SequenceTest.cc
+ test/SMFTest.cc
+ test/RangeTest.cc
+ test/NoteTest.cc
+ test/CurveTest.cc
+ test/testrunner.cc
'''
obj.includes = ['.', './src']
obj.use = 'libevoral_static'
*/
#define EVORAL_EVENT_ALLOC 1
-#include "evoral/Event.hpp"
+#include "evoral/Event.h"
#endif /* __libmidipp_midi_event_h__ */
#include "pbd/xml++.h"
#include "pbd/enumwriter.h"
-#include "evoral/Curve.hpp"
+#include "evoral/Curve.h"
#include "ardour/session.h"
#include "ardour/panner.h"
#include "pbd/xml++.h"
#include "pbd/enumwriter.h"
-#include "evoral/Curve.hpp"
+#include "evoral/Curve.h"
#include "ardour/audio_buffer.h"
#include "ardour/audio_buffer.h"
#include "pbd/xml++.h"
#include "pbd/enumwriter.h"
-#include "evoral/Curve.hpp"
+#include "evoral/Curve.h"
#include "ardour/audio_buffer.h"
#include "ardour/audio_buffer.h"
#include <string>
#include <iostream>
-#include "evoral/Parameter.hpp"
+#include "evoral/Parameter.h"
#include "pbd/property_basics.h"
#include "pbd/ringbuffer.h"
#include <string>
#include <iostream>
-#include "evoral/Parameter.hpp"
+#include "evoral/Parameter.h"
#include "pbd/property_basics.h"
#include "pbd/ringbuffer.h"
#include "ardour/source_factory.h"
#include "ardour/tempo.h"
-#include "evoral/Note.hpp"
-#include "evoral/Sequence.hpp"
+#include "evoral/Note.h"
+#include "evoral/Sequence.h"
#include "common.h"