add _locked() variants to new tempo experiment

This commit is contained in:
Paul Davis 2017-09-13 19:31:42 -04:00
parent e37558502e
commit cba53a2023
14 changed files with 21269 additions and 0 deletions

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,11 @@
--- gtk2_ardour/midi_region_view.cc
+++ gtk2_ardour/midi_region_view.cc
@@ -2516,7 +2516,7 @@ MidiRegionView::move_selection(double dx, double dy, double cumulative_dy)
{
typedef vector<boost::shared_ptr<NoteType> > PossibleChord;
PossibleChord to_play;
- Evoral::Beats earliest = Evoral::MaxBeats;
+ Evoral::Beats earliest = std::numeric_limits<Evoral::Beats>::max();
for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
if ((*i)->note()->time() < earliest) {

5182
gtk2_ardour/out Normal file

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,334 @@
/*
Copyright (C) 2007 Paul Davis
Author: David Robillard
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*/
#ifndef __ardour_midi_model_h__
#define __ardour_midi_model_h__
#include <deque>
#include <queue>
#include <utility>
#include <boost/utility.hpp>
#include <glibmm/threads.h>
#include "pbd/command.h"
#include "ardour/automatable_sequence.h"
#include "ardour/libardour_visibility.h"
#include "ardour/libardour_visibility.h"
#include "ardour/types.h"
#include "ardour/types.h"
#include "ardour/variant.h"
#include "evoral/Note.hpp"
#include "evoral/Sequence.hpp"
namespace ARDOUR {
class Session;
class MidiSource;
/** This is a higher level (than MidiBuffer) model of MIDI data, with separate
* representations for notes (instead of just unassociated note on/off events)
* and controller data. Controller data is represented as part of the
* Automatable base (i.e. in a map of AutomationList, keyed by Parameter).
* Because of this MIDI controllers and automatable controllers/widgets/etc
* are easily interchangeable.
*/
class LIBARDOUR_API MidiModel : public AutomatableSequence<Evoral::Beats> {
public:
typedef Evoral::Beats TimeType;
MidiModel (boost::shared_ptr<MidiSource>);
NoteMode note_mode() const { return (percussive() ? Percussive : Sustained); }
void set_note_mode(NoteMode mode) { set_percussive(mode == Percussive); };
class LIBARDOUR_API DiffCommand : public Command {
public:
DiffCommand (boost::shared_ptr<MidiModel> m, const std::string& name);
const std::string& name () const { return _name; }
virtual void operator() () = 0;
virtual void undo () = 0;
virtual int set_state (const XMLNode&, int version) = 0;
virtual XMLNode & get_state () = 0;
boost::shared_ptr<MidiModel> model() const { return _model; }
protected:
boost::shared_ptr<MidiModel> _model;
const std::string _name;
};
class LIBARDOUR_API NoteDiffCommand : public DiffCommand {
public:
NoteDiffCommand (boost::shared_ptr<MidiModel> m, const std::string& name) : DiffCommand (m, name) {}
NoteDiffCommand (boost::shared_ptr<MidiModel> m, const XMLNode& node);
enum Property {
NoteNumber,
Velocity,
StartTime,
Length,
Channel
};
void operator() ();
void undo ();
int set_state (const XMLNode&, int version);
XMLNode & get_state ();
void add (const NotePtr note);
void remove (const NotePtr note);
void side_effect_remove (const NotePtr note);
void change (const NotePtr note, Property prop, uint8_t new_value) {
change(note, prop, Variant(new_value));
}
void change (const NotePtr note, Property prop, TimeType new_time) {
change(note, prop, Variant(new_time));
}
void change (const NotePtr note, Property prop, const Variant& new_value);
bool adds_or_removes() const {
return !_added_notes.empty() || !_removed_notes.empty();
}
NoteDiffCommand& operator+= (const NoteDiffCommand& other);
static Variant get_value (const NotePtr note, Property prop);
static Variant::Type value_type (Property prop);
struct NoteChange {
NoteDiffCommand::Property property;
NotePtr note;
uint32_t note_id;
Variant old_value;
Variant new_value;
};
typedef std::list<NoteChange> ChangeList;
typedef std::list< boost::shared_ptr< Evoral::Note<TimeType> > > NoteList;
const ChangeList& changes() const { return _changes; }
const NoteList& added_notes() const { return _added_notes; }
const NoteList& removed_notes() const { return _removed_notes; }
private:
ChangeList _changes;
NoteList _added_notes;
NoteList _removed_notes;
std::set<NotePtr> side_effect_removals;
XMLNode &marshal_change(const NoteChange&);
NoteChange unmarshal_change(XMLNode *xml_note);
XMLNode &marshal_note(const NotePtr note);
NotePtr unmarshal_note(XMLNode *xml_note);
};
/* Currently this class only supports changes of sys-ex time, but could be expanded */
class LIBARDOUR_API SysExDiffCommand : public DiffCommand {
public:
SysExDiffCommand (boost::shared_ptr<MidiModel> m, const XMLNode& node);
enum Property {
Time,
};
int set_state (const XMLNode&, int version);
XMLNode & get_state ();
void remove (SysExPtr sysex);
void operator() ();
void undo ();
void change (boost::shared_ptr<Evoral::Event<TimeType> >, TimeType);
private:
struct Change {
Change () : sysex_id (0) {}
boost::shared_ptr<Evoral::Event<TimeType> > sysex;
gint sysex_id;
SysExDiffCommand::Property property;
TimeType old_time;
TimeType new_time;
};
typedef std::list<Change> ChangeList;
ChangeList _changes;
std::list<SysExPtr> _removed;
XMLNode & marshal_change (const Change &);
Change unmarshal_change (XMLNode *);
};
class LIBARDOUR_API PatchChangeDiffCommand : public DiffCommand {
public:
PatchChangeDiffCommand (boost::shared_ptr<MidiModel>, const std::string &);
PatchChangeDiffCommand (boost::shared_ptr<MidiModel>, const XMLNode &);
int set_state (const XMLNode &, int version);
XMLNode & get_state ();
void operator() ();
void undo ();
void add (PatchChangePtr);
void remove (PatchChangePtr);
void change_time (PatchChangePtr, TimeType);
void change_channel (PatchChangePtr, uint8_t);
void change_program (PatchChangePtr, uint8_t);
void change_bank (PatchChangePtr, int);
enum Property {
Time,
Channel,
Program,
Bank
};
private:
struct Change {
PatchChangePtr patch;
Property property;
gint patch_id;
TimeType old_time;
union {
uint8_t old_channel;
int old_bank;
uint8_t old_program;
};
TimeType new_time;
union {
uint8_t new_channel;
uint8_t new_program;
int new_bank;
};
Change() : patch_id (-1) {}
};
typedef std::list<Change> ChangeList;
ChangeList _changes;
std::list<PatchChangePtr> _added;
std::list<PatchChangePtr> _removed;
XMLNode & marshal_change (const Change &);
Change unmarshal_change (XMLNode *);
XMLNode & marshal_patch_change (constPatchChangePtr);
PatchChangePtr unmarshal_patch_change (XMLNode *);
};
MidiModel::NoteDiffCommand* new_note_diff_command (const std::string& name = "midi edit");
MidiModel::SysExDiffCommand* new_sysex_diff_command (const std::string& name = "midi edit");
MidiModel::PatchChangeDiffCommand* new_patch_change_diff_command (const std::string& name = "midi edit");
void apply_command (Session& session, Command* cmd);
void apply_command (Session* session, Command* cmd) { if (session) { apply_command (*session, cmd); } }
void apply_command_as_subcommand (Session& session, Command* cmd);
bool sync_to_source (const Glib::Threads::Mutex::Lock& source_lock);
bool write_to(boost::shared_ptr<MidiSource> source,
const Glib::Threads::Mutex::Lock& source_lock);
bool write_section_to(boost::shared_ptr<MidiSource> source,
const Glib::Threads::Mutex::Lock& source_lock,
Evoral::Beats begin = Evoral::MinBeats,
Evoral::Beats end = Evoral::MaxBeats,
bool offset_events = false);
// MidiModel doesn't use the normal AutomationList serialisation code
// since controller data is stored in the .mid
XMLNode& get_state();
int set_state(const XMLNode&) { return 0; }
PBD::Signal0<void> ContentsChanged;
PBD::Signal1<void, double> ContentsShifted;
boost::shared_ptr<const MidiSource> midi_source ();
void set_midi_source (boost::shared_ptr<MidiSource>);
boost::shared_ptr<Evoral::Note<TimeType> > find_note (NotePtr);
PatchChangePtr find_patch_change (Evoral::event_id_t);
boost::shared_ptr<Evoral::Note<TimeType> > find_note (gint note_id);
boost::shared_ptr<Evoral::Event<TimeType> > find_sysex (gint);
InsertMergePolicy insert_merge_policy () const;
void set_insert_merge_policy (InsertMergePolicy);
boost::shared_ptr<Evoral::Control> control_factory(const Evoral::Parameter& id);
void insert_silence_at_start (TimeType);
void transpose (NoteDiffCommand *, const NotePtr, int);
protected:
int resolve_overlaps_unlocked (const NotePtr, void* arg = 0);
private:
struct WriteLockImpl : public AutomatableSequence<TimeType>::WriteLockImpl {
WriteLockImpl(Glib::Threads::Mutex::Lock* slock, Glib::Threads::RWLock& s, Glib::Threads::Mutex& c)
: AutomatableSequence<TimeType>::WriteLockImpl(s, c)
, source_lock (slock)
{}
~WriteLockImpl() {
delete source_lock;
}
Glib::Threads::Mutex::Lock* source_lock;
};
public:
WriteLock edit_lock();
private:
friend class DeltaCommand;
void source_interpolation_changed (Evoral::Parameter, Evoral::ControlList::InterpolationStyle);
void source_automation_state_changed (Evoral::Parameter, AutoState);
void control_list_interpolation_changed (Evoral::Parameter, Evoral::ControlList::InterpolationStyle);
void automation_list_automation_state_changed (Evoral::Parameter, AutoState);
void control_list_marked_dirty ();
PBD::ScopedConnectionList _midi_source_connections;
// We cannot use a boost::shared_ptr here to avoid a retain cycle
boost::weak_ptr<MidiSource> _midi_source;
InsertMergePolicy _insert_merge_policy;
};
} /* namespace ARDOUR */
#endif /* __ardour_midi_model_h__ */

View File

@ -0,0 +1,258 @@
/*
Copyright (C) 2006 Paul Davis
Author: David Robillard
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*/
#ifndef __ardour_midi_source_h__
#define __ardour_midi_source_h__
#include <string>
#include <time.h>
#include <glibmm/threads.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 "ardour/ardour.h"
#include "ardour/buffer.h"
#include "ardour/midi_cursor.h"
#include "ardour/source.h"
#include "ardour/beats_frames_converter.h"
namespace ARDOUR {
class MidiChannelFilter;
class MidiModel;
class MidiStateTracker;
template<typename T> class MidiRingBuffer;
/** Source for MIDI data */
class LIBARDOUR_API MidiSource : virtual public Source, public boost::enable_shared_from_this<MidiSource>
{
public:
typedef Evoral::Beats TimeType;
MidiSource (Session& session, std::string name, Source::Flag flags = Source::Flag(0));
MidiSource (Session& session, const XMLNode&);
virtual ~MidiSource ();
/** Write the data in the given time range to another MidiSource
* \param newsrc MidiSource to which data will be written. Should be a
* new, empty source. If it already has contents, the results are
* undefined. Source must be writable.
* \param begin time of earliest event that can be written.
* \param end time of latest event that can be written.
* \return zero on success, non-zero if the write failed for any reason.
*/
int write_to (const Lock& lock,
boost::shared_ptr<MidiSource> newsrc,
Evoral::Beats begin = Evoral::MinBeats,
Evoral::Beats end = Evoral::MaxBeats);
/** Export the midi data in the given time range to another MidiSource
* \param newsrc MidiSource to which data will be written. Should be a
* new, empty source. If it already has contents, the results are
* undefined. Source must be writable.
* \param begin time of earliest event that can be written.
* \param end time of latest event that can be written.
* \return zero on success, non-zero if the write failed for any reason.
*/
int export_write_to (const Lock& lock,
boost::shared_ptr<MidiSource> newsrc,
Evoral::Beats begin,
Evoral::Beats end);
/** Read the data in a given time range from the MIDI source.
* All time stamps in parameters are in audio frames (even if the source has tempo time).
* \param dst Ring buffer where read events are written.
* \param source_start Start position of the SOURCE in this read context.
* \param start Start of range to be read.
* \param cnt Length of range to be read (in audio frames).
* \param loop_range If non-null, all event times will be mapped into this loop range.
* \param tracker an optional pointer to MidiStateTracker object, for note on/off tracking.
* \param filtered Parameters whose MIDI messages will not be returned.
*/
virtual framecnt_t midi_read (const Lock& lock,
Evoral::EventSink<framepos_t>& dst,
framepos_t source_start,
framepos_t start,
framecnt_t cnt,
Evoral::Range<framepos_t>* loop_range,
MidiCursor& cursor,
MidiStateTracker* tracker,
MidiChannelFilter* filter,
const std::set<Evoral::Parameter>& filtered,
const double pulse,
const double start_beats) const;
/** Write data from a MidiRingBuffer to this source.
* @param source Source to read from.
* @param source_start This source's start position in session frames.
* @param cnt The length of time to write.
*/
virtual framecnt_t midi_write (const Lock& lock,
MidiRingBuffer<framepos_t>& src,
framepos_t source_start,
framecnt_t cnt);
/** Append a single event with a timestamp in beats.
*
* Caller must ensure that the event is later than the last written event.
*/
virtual void append_event_beats(const Lock& lock,
const Evoral::Event<Evoral::Beats>& ev) = 0;
/** Append a single event with a timestamp in frames.
*
* Caller must ensure that the event is later than the last written event.
*/
virtual void append_event_frames(const Lock& lock,
const Evoral::Event<framepos_t>& ev,
framepos_t source_start) = 0;
virtual bool empty () const;
virtual framecnt_t length (framepos_t pos) const;
virtual void update_length (framecnt_t);
virtual void mark_streaming_midi_write_started (const Lock& lock, NoteMode mode);
virtual void mark_streaming_write_started (const Lock& lock);
virtual void mark_streaming_write_completed (const Lock& lock);
/** Mark write starting with the given time parameters.
*
* This is called by MidiDiskStream::process before writing to the capture
* buffer which will be later read by midi_read().
*
* @param position The timeline position the source now starts at.
* @param capture_length The current length of the capture, which may not
* be zero if record is armed while rolling.
* @param loop_length The loop length if looping, otherwise zero.
*/
void mark_write_starting_now (framecnt_t position,
framecnt_t capture_length,
framecnt_t loop_length);
/* like ::mark_streaming_write_completed() but with more arguments to
* allow control over MIDI-specific behaviour. Expected to be used only
* when recording actual MIDI input, rather then when importing files
* etc.
*/
virtual void mark_midi_streaming_write_completed (
const Lock& lock,
Evoral::Sequence<Evoral::Beats>::StuckNoteOption stuck_option,
Evoral::Beats when = Evoral::Beats());
virtual void session_saved();
std::string captured_for() const { return _captured_for; }
void set_captured_for (std::string str) { _captured_for = str; }
XMLNode& get_state ();
int set_state (const XMLNode&, int version);
bool length_mutable() const { return true; }
void set_length_beats(TimeType l) { _length_beats = l; }
TimeType length_beats() const { return _length_beats; }
virtual void load_model(const Glib::Threads::Mutex::Lock& lock, bool force_reload=false) = 0;
virtual void destroy_model(const Glib::Threads::Mutex::Lock& lock) = 0;
/** Reset cached information (like iterators) when things have changed.
* @param lock Source lock, which must be held by caller.
*/
void invalidate(const Glib::Threads::Mutex::Lock& lock);
/** Thou shalt not emit this directly, use invalidate() instead. */
mutable PBD::Signal1<void, bool> Invalidated;
void set_note_mode(const Glib::Threads::Mutex::Lock& lock, NoteMode mode);
boost::shared_ptr<MidiModel> model() { return _model; }
void set_model(const Glib::Threads::Mutex::Lock& lock, boost::shared_ptr<MidiModel>);
void drop_model(const Glib::Threads::Mutex::Lock& lock);
Evoral::ControlList::InterpolationStyle interpolation_of (Evoral::Parameter) const;
void set_interpolation_of (Evoral::Parameter, Evoral::ControlList::InterpolationStyle);
void copy_interpolation_from (boost::shared_ptr<MidiSource>);
void copy_interpolation_from (MidiSource *);
AutoState automation_state_of (Evoral::Parameter) const;
void set_automation_state_of (Evoral::Parameter, AutoState);
void copy_automation_state_from (boost::shared_ptr<MidiSource>);
void copy_automation_state_from (MidiSource *);
/** Emitted when a different MidiModel is set */
PBD::Signal0<void> ModelChanged;
/** Emitted when a parameter's interpolation style is changed */
PBD::Signal2<void, Evoral::Parameter, Evoral::ControlList::InterpolationStyle> InterpolationChanged;
/** Emitted when a parameter's automation state is changed */
PBD::Signal2<void, Evoral::Parameter, AutoState> AutomationStateChanged;
protected:
virtual void flush_midi(const Lock& lock) = 0;
virtual framecnt_t read_unlocked (const Lock& lock,
Evoral::EventSink<framepos_t>& dst,
framepos_t position,
framepos_t start,
framecnt_t cnt,
Evoral::Range<framepos_t>* loop_range,
MidiStateTracker* tracker,
MidiChannelFilter* filter) const = 0;
/** Write data to this source from a MidiRingBuffer.
* @param source Buffer to read from.
* @param position This source's start position in session frames.
* @param cnt The duration of this block to write for.
*/
virtual framecnt_t write_unlocked (const Lock& lock,
MidiRingBuffer<framepos_t>& source,
framepos_t position,
framecnt_t cnt) = 0;
std::string _captured_for;
boost::shared_ptr<MidiModel> _model;
bool _writing;
Evoral::Beats _length_beats;
/** The total duration of the current capture. */
framepos_t _capture_length;
/** Length of transport loop during current capture, or zero. */
framepos_t _capture_loop_length;
/** Map of interpolation styles to use for Parameters; if they are not in this map,
* the correct interpolation style can be obtained from EventTypeMap::interpolation_of ()
*/
typedef std::map<Evoral::Parameter, Evoral::ControlList::InterpolationStyle> InterpolationStyleMap;
InterpolationStyleMap _interpolation_style;
/** Map of automation states to use for Parameters; if they are not in this map,
* the correct automation state is Off.
*/
typedef std::map<Evoral::Parameter, AutoState> AutomationStateMap;
AutomationStateMap _automation_state;
};
}
#endif /* __ardour_midi_source_h__ */

View File

@ -0,0 +1,580 @@
/*
Copyright (C) 2006 Paul Davis
Author: David Robillard
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*/
#include <sys/stat.h>
#include <unistd.h>
#include <fcntl.h>
#include <float.h>
#include <cerrno>
#include <ctime>
#include <cmath>
#include <iomanip>
#include <algorithm>
#include <glibmm/fileutils.h>
#include <glibmm/miscutils.h>
#include "pbd/xml++.h"
#include "pbd/pthread_utils.h"
#include "pbd/basename.h"
#include "evoral/Control.hpp"
#include "evoral/EventSink.hpp"
#include "ardour/debug.h"
#include "ardour/file_source.h"
#include "ardour/midi_channel_filter.h"
#include "ardour/midi_cursor.h"
#include "ardour/midi_model.h"
#include "ardour/midi_source.h"
#include "ardour/midi_state_tracker.h"
#include "ardour/session.h"
#include "ardour/session_directory.h"
#include "ardour/source_factory.h"
#include "ardour/tempo.h"
#include "pbd/i18n.h"
namespace ARDOUR { template <typename T> class MidiRingBuffer; }
using namespace std;
using namespace ARDOUR;
using namespace PBD;
MidiSource::MidiSource (Session& s, string name, Source::Flag flags)
: Source(s, DataType::MIDI, name, flags)
, _writing(false)
, _length_beats(0.0)
, _capture_length(0)
, _capture_loop_length(0)
{
}
MidiSource::MidiSource (Session& s, const XMLNode& node)
: Source(s, node)
, _writing(false)
, _length_beats(0.0)
, _capture_length(0)
, _capture_loop_length(0)
{
if (set_state (node, Stateful::loading_state_version)) {
throw failed_constructor();
}
}
MidiSource::~MidiSource ()
{
/* invalidate any existing iterators */
Invalidated (false);
}
XMLNode&
MidiSource::get_state ()
{
XMLNode& node (Source::get_state());
if (_captured_for.length()) {
node.set_property ("captured-for", _captured_for);
}
for (InterpolationStyleMap::const_iterator i = _interpolation_style.begin(); i != _interpolation_style.end(); ++i) {
XMLNode* child = node.add_child (X_("InterpolationStyle"));
child->set_property (X_("parameter"), EventTypeMap::instance().to_symbol (i->first));
child->set_property (X_("style"), enum_2_string (i->second));
}
for (AutomationStateMap::const_iterator i = _automation_state.begin(); i != _automation_state.end(); ++i) {
XMLNode* child = node.add_child (X_("AutomationState"));
child->set_property (X_("parameter"), EventTypeMap::instance().to_symbol (i->first));
child->set_property (X_("state"), enum_2_string (i->second));
}
return node;
}
int
MidiSource::set_state (const XMLNode& node, int /*version*/)
{
node.get_property ("captured-for", _captured_for);
std::string str;
XMLNodeList children = node.children ();
for (XMLNodeConstIterator i = children.begin(); i != children.end(); ++i) {
if ((*i)->name() == X_("InterpolationStyle")) {
if (!(*i)->get_property (X_("parameter"), str)) {
error << _("Missing parameter property on InterpolationStyle") << endmsg;
return -1;
}
Evoral::Parameter p = EventTypeMap::instance().from_symbol (str);
if (!(*i)->get_property (X_("style"), str)) {
error << _("Missing style property on InterpolationStyle") << endmsg;
return -1;
}
Evoral::ControlList::InterpolationStyle s =
static_cast<Evoral::ControlList::InterpolationStyle>(string_2_enum (str, s));
set_interpolation_of (p, s);
} else if ((*i)->name() == X_("AutomationState")) {
if (!(*i)->get_property (X_("parameter"), str)) {
error << _("Missing parameter property on AutomationState") << endmsg;
return -1;
}
Evoral::Parameter p = EventTypeMap::instance().from_symbol (str);
if (!(*i)->get_property (X_("state"), str)) {
error << _("Missing state property on AutomationState") << endmsg;
return -1;
}
AutoState s = static_cast<AutoState>(string_2_enum (str, s));
set_automation_state_of (p, s);
}
}
return 0;
}
bool
MidiSource::empty () const
{
return !_length_beats;
}
framecnt_t
MidiSource::length (framepos_t pos) const
{
if (!_length_beats) {
return 0;
}
BeatsFramesConverter converter(_session.tempo_map(), pos);
return converter.to(_length_beats);
}
void
MidiSource::update_length (framecnt_t)
{
// You're not the boss of me!
}
void
MidiSource::invalidate (const Lock& lock)
{
Invalidated(_session.transport_rolling());
}
framecnt_t
MidiSource::midi_read (const Lock& lm,
Evoral::EventSink<framepos_t>& dst,
framepos_t source_start,
framepos_t start,
framecnt_t cnt,
Evoral::Range<framepos_t>* loop_range,
MidiCursor& cursor,
MidiStateTracker* tracker,
MidiChannelFilter* filter,
const std::set<Evoral::Parameter>& filtered,
const double pos_beats,
const double start_beats) const
{
BeatsFramesConverter converter(_session.tempo_map(), source_start);
const double start_qn = pos_beats - start_beats;
DEBUG_TRACE (DEBUG::MidiSourceIO,
string_compose ("MidiSource::midi_read() %5 sstart %1 start %2 cnt %3 tracker %4\n",
source_start, start, cnt, tracker, name()));
if (!_model) {
return read_unlocked (lm, dst, source_start, start, cnt, loop_range, tracker, filter);
}
// Find appropriate model iterator
Evoral::Sequence<Evoral::Beats>::const_iterator& i = cursor.iter;
const bool linear_read = cursor.last_read_end != 0 && start == cursor.last_read_end;
if (!linear_read || !i.valid()) {
/* Cached iterator is invalid, search for the first event past start.
Note that multiple tracks can use a MidiSource simultaneously, so
all playback state must be in parameters (the cursor) and must not
be cached in the source of model itself.
See http://tracker.ardour.org/view.php?id=6541
*/
cursor.connect(Invalidated);
cursor.iter = _model->begin(converter.from(start), false, filtered, &cursor.active_notes);
cursor.active_notes.clear();
}
cursor.last_read_end = start + cnt;
// Copy events in [start, start + cnt) into dst
for (; i != _model->end(); ++i) {
// Offset by source start to convert event time to session time
framepos_t time_frames = _session.tempo_map().frame_at_quarter_note (i->time().to_double() + start_qn);
if (time_frames < start + source_start) {
/* event too early */
continue;
} else if (time_frames >= start + cnt + source_start) {
DEBUG_TRACE (DEBUG::MidiSourceIO,
string_compose ("%1: reached end with event @ %2 vs. %3\n",
_name, time_frames, start+cnt));
break;
} else {
/* in range */
if (loop_range) {
time_frames = loop_range->squish (time_frames);
}
const uint8_t status = i->buffer()[0];
const bool is_channel_event = (0x80 <= (status & 0xF0)) && (status <= 0xE0);
if (filter && is_channel_event) {
/* Copy event so the filter can modify the channel. I'm not
sure if this is necessary here (channels are mapped later in
buffers anyway), but it preserves existing behaviour without
destroying events in the model during read. */
Evoral::Event<Evoral::Beats> ev(*i, true);
if (!filter->filter(ev.buffer(), ev.size())) {
dst.write(time_frames, ev.event_type(), ev.size(), ev.buffer());
} else {
DEBUG_TRACE (DEBUG::MidiSourceIO,
string_compose ("%1: filter event @ %2 type %3 size %4\n",
_name, time_frames, i->event_type(), i->size()));
}
} else {
dst.write (time_frames, i->event_type(), i->size(), i->buffer());
}
#ifndef NDEBUG
if (DEBUG_ENABLED(DEBUG::MidiSourceIO)) {
DEBUG_STR_DECL(a);
DEBUG_STR_APPEND(a, string_compose ("%1 added event @ %2 sz %3 within %4 .. %5 ",
_name, time_frames, i->size(),
start + source_start, start + cnt + source_start));
for (size_t n=0; n < i->size(); ++n) {
DEBUG_STR_APPEND(a,hex);
DEBUG_STR_APPEND(a,"0x");
DEBUG_STR_APPEND(a,(int)i->buffer()[n]);
DEBUG_STR_APPEND(a,' ');
}
DEBUG_STR_APPEND(a,'\n');
DEBUG_TRACE (DEBUG::MidiSourceIO, DEBUG_STR(a).str());
}
#endif
if (tracker) {
tracker->track (*i);
}
}
}
return cnt;
}
framecnt_t
MidiSource::midi_write (const Lock& lm,
MidiRingBuffer<framepos_t>& source,
framepos_t source_start,
framecnt_t cnt)
{
const framecnt_t ret = write_unlocked (lm, source, source_start, cnt);
if (cnt == max_framecnt) {
invalidate(lm);
} else {
_capture_length += cnt;
}
return ret;
}
void
MidiSource::mark_streaming_midi_write_started (const Lock& lock, NoteMode mode)
{
if (_model) {
_model->set_note_mode (mode);
_model->start_write ();
}
_writing = true;
}
void
MidiSource::mark_write_starting_now (framecnt_t position,
framecnt_t capture_length,
framecnt_t loop_length)
{
/* I'm not sure if this is the best way to approach this, but
_capture_length needs to be set up with the transport frame
when a record actually starts, as it is used by
SMFSource::write_unlocked to decide whether incoming notes
are within the correct time range.
mark_streaming_midi_write_started (perhaps a more logical
place to do this) is not called at exactly the time when
record starts, and I don't think it necessarily can be
because it is not RT-safe.
*/
set_timeline_position(position);
_capture_length = capture_length;
_capture_loop_length = loop_length;
TempoMap& map (_session.tempo_map());
BeatsFramesConverter converter(map, position);
_length_beats = converter.from(capture_length);
}
void
MidiSource::mark_streaming_write_started (const Lock& lock)
{
NoteMode note_mode = _model ? _model->note_mode() : Sustained;
mark_streaming_midi_write_started (lock, note_mode);
}
void
MidiSource::mark_midi_streaming_write_completed (const Lock& lock,
Evoral::Sequence<Evoral::Beats>::StuckNoteOption option,
Evoral::Beats end)
{
if (_model) {
_model->end_write (option, end);
/* Make captured controls discrete to play back user input exactly. */
for (MidiModel::Controls::iterator i = _model->controls().begin(); i != _model->controls().end(); ++i) {
if (i->second->list()) {
i->second->list()->set_interpolation(Evoral::ControlList::Discrete);
_interpolation_style.insert(std::make_pair(i->second->parameter(), Evoral::ControlList::Discrete));
}
}
}
invalidate(lock);
_writing = false;
}
void
MidiSource::mark_streaming_write_completed (const Lock& lock)
{
mark_midi_streaming_write_completed (lock, Evoral::Sequence<Evoral::Beats>::DeleteStuckNotes);
}
int
MidiSource::export_write_to (const Lock& lock, boost::shared_ptr<MidiSource> newsrc, Evoral::Beats begin, Evoral::Beats end)
{
Lock newsrc_lock (newsrc->mutex ());
if (!_model) {
error << string_compose (_("programming error: %1"), X_("no model for MidiSource during export"));
return -1;
}
_model->write_section_to (newsrc, newsrc_lock, begin, end, true);
newsrc->flush_midi(newsrc_lock);
return 0;
}
int
MidiSource::write_to (const Lock& lock, boost::shared_ptr<MidiSource> newsrc, Evoral::Beats begin, Evoral::Beats end)
{
Lock newsrc_lock (newsrc->mutex ());
newsrc->set_timeline_position (_timeline_position);
newsrc->copy_interpolation_from (this);
newsrc->copy_automation_state_from (this);
if (_model) {
if (begin == Evoral::MinBeats && end == Evoral::MaxBeats) {
_model->write_to (newsrc, newsrc_lock);
} else {
_model->write_section_to (newsrc, newsrc_lock, begin, end);
}
} else {
error << string_compose (_("programming error: %1"), X_("no model for MidiSource during ::clone()"));
return -1;
}
newsrc->flush_midi(newsrc_lock);
/* force a reload of the model if the range is partial */
if (begin != Evoral::MinBeats || end != Evoral::MaxBeats) {
newsrc->load_model (newsrc_lock, true);
} else {
newsrc->set_model (newsrc_lock, _model);
}
/* this file is not removable (but since it is MIDI, it is mutable) */
boost::dynamic_pointer_cast<FileSource> (newsrc)->prevent_deletion ();
return 0;
}
void
MidiSource::session_saved()
{
Lock lm (_lock);
/* this writes a copy of the data to disk.
XXX do we need to do this every time?
*/
if (_model && _model->edited()) {
/* The model is edited, write its contents into the current source
file (overwiting previous contents). */
/* Temporarily drop our reference to the model so that as the model
pushes its current state to us, we don't try to update it. */
boost::shared_ptr<MidiModel> mm = _model;
_model.reset ();
/* Flush model contents to disk. */
mm->sync_to_source (lm);
/* Reacquire model. */
_model = mm;
} else {
flush_midi(lm);
}
}
void
MidiSource::set_note_mode(const Lock& lock, NoteMode mode)
{
if (_model) {
_model->set_note_mode(mode);
}
}
void
MidiSource::drop_model (const Lock& lock)
{
_model.reset();
invalidate(lock);
ModelChanged (); /* EMIT SIGNAL */
}
void
MidiSource::set_model (const Lock& lock, boost::shared_ptr<MidiModel> m)
{
_model = m;
invalidate(lock);
ModelChanged (); /* EMIT SIGNAL */
}
Evoral::ControlList::InterpolationStyle
MidiSource::interpolation_of (Evoral::Parameter p) const
{
InterpolationStyleMap::const_iterator i = _interpolation_style.find (p);
if (i == _interpolation_style.end()) {
return EventTypeMap::instance().interpolation_of (p);
}
return i->second;
}
AutoState
MidiSource::automation_state_of (Evoral::Parameter p) const
{
AutomationStateMap::const_iterator i = _automation_state.find (p);
if (i == _automation_state.end()) {
/* default to `play', otherwise if MIDI is recorded /
imported with controllers etc. they are by default
not played back, which is a little surprising.
*/
return Play;
}
return i->second;
}
/** Set interpolation style to be used for a given parameter. This change will be
* propagated to anyone who needs to know.
*/
void
MidiSource::set_interpolation_of (Evoral::Parameter p, Evoral::ControlList::InterpolationStyle s)
{
if (interpolation_of (p) == s) {
return;
}
if (EventTypeMap::instance().interpolation_of (p) == s) {
/* interpolation type is being set to the default, so we don't need a note in our map */
_interpolation_style.erase (p);
} else {
_interpolation_style[p] = s;
}
InterpolationChanged (p, s); /* EMIT SIGNAL */
}
void
MidiSource::set_automation_state_of (Evoral::Parameter p, AutoState s)
{
if (automation_state_of (p) == s) {
return;
}
if (s == Play) {
/* automation state is being set to the default, so we don't need a note in our map */
_automation_state.erase (p);
} else {
_automation_state[p] = s;
}
AutomationStateChanged (p, s); /* EMIT SIGNAL */
}
void
MidiSource::copy_interpolation_from (boost::shared_ptr<MidiSource> s)
{
copy_interpolation_from (s.get ());
}
void
MidiSource::copy_automation_state_from (boost::shared_ptr<MidiSource> s)
{
copy_automation_state_from (s.get ());
}
void
MidiSource::copy_interpolation_from (MidiSource* s)
{
_interpolation_style = s->_interpolation_style;
/* XXX: should probably emit signals here */
}
void
MidiSource::copy_automation_state_from (MidiSource* s)
{
_automation_state = s->_automation_state;
/* XXX: should probably emit signals here */
}

View File

@ -0,0 +1,247 @@
/* This file is part of Evoral.
* Copyright (C) 2008-2015 David Robillard <http://drobilla.net>
* Copyright (C) 2000-2008 Paul Davis
*
* Evoral is free software; you can redistribute it and/or modify it under the
* terms of the GNU General Public License as published by the Free Software
* Foundation; either version 2 of the License, or (at your option) any later
* version.
*
* Evoral is distributed in the hope that it will be useful, but WITHOUT ANY
* WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
* FOR A PARTICULAR PURPOSE. See the GNU General Public License for details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*/
#ifndef EVORAL_BEATS_HPP
#define EVORAL_BEATS_HPP
#include <float.h>
#include <math.h>
#include <stdint.h>
#include <iostream>
#include <limits>
#include "evoral/visibility.h"
namespace Evoral {
/** Musical time in beats. */
class /*LIBEVORAL_API*/ Beats {
public:
LIBEVORAL_API static const double PPQN;
Beats() : _time(0.0) {}
/** Create from a real number of beats. */
explicit Beats(double time) : _time(time) {}
/** Create from an integer number of beats. */
static Beats beats(int32_t beats) {
return Beats((double)beats);
}
/** Create from ticks at the standard PPQN. */
static Beats ticks(uint32_t ticks) {
return Beats(ticks / PPQN);
}
/** Create from ticks at a given rate.
*
* Note this can also be used to create from frames by setting ppqn to the
* number of samples per beat.
*/
static Beats ticks_at_rate(uint64_t ticks, uint32_t ppqn) {
return Beats((double)ticks / (double)ppqn);
}
Beats& operator=(const Beats& other) {
_time = other._time;
return *this;
}
Beats round_up_to_beat() const {
return Evoral::Beats(ceil(_time));
}
Beats round_down_to_beat() const {
return Evoral::Beats(floor(_time));
}
Beats snap_to(const Evoral::Beats& snap) const {
return Beats(ceil(_time / snap._time) * snap._time);
}
inline bool operator==(const Beats& b) const {
/* Acceptable tolerance is 1 tick. */
return fabs(_time - b._time) <= (1.0 / PPQN);
}
inline bool operator==(double t) const {
/* Acceptable tolerance is 1 tick. */
return fabs(_time - t) <= (1.0 / PPQN);
}
inline bool operator==(int beats) const {
/* Acceptable tolerance is 1 tick. */
return fabs(_time - beats) <= (1.0 / PPQN);
}
inline bool operator!=(const Beats& b) const {
return !operator==(b);
}
inline bool operator<(const Beats& b) const {
/* Acceptable tolerance is 1 tick. */
if (fabs(_time - b._time) <= (1.0 / PPQN)) {
return false; /* Effectively identical. */
} else {
return _time < b._time;
}
}
inline bool operator<=(const Beats& b) const {
return operator==(b) || operator<(b);
}
inline bool operator>(const Beats& b) const {
/* Acceptable tolerance is 1 tick. */
if (fabs(_time - b._time) <= (1.0 / PPQN)) {
return false; /* Effectively identical. */
} else {
return _time > b._time;
}
}
inline bool operator>=(const Beats& b) const {
return operator==(b) || operator>(b);
}
inline bool operator<(double b) const {
/* Acceptable tolerance is 1 tick. */
if (fabs(_time - b) <= (1.0 / PPQN)) {
return false; /* Effectively identical. */
} else {
return _time < b;
}
}
inline bool operator<=(double b) const {
return operator==(b) || operator<(b);
}
inline bool operator>(double b) const {
/* Acceptable tolerance is 1 tick. */
if (fabs(_time - b) <= (1.0 / PPQN)) {
return false; /* Effectively identical. */
} else {
return _time > b;
}
}
inline bool operator>=(double b) const {
return operator==(b) || operator>(b);
}
Beats operator+(const Beats& b) const {
return Beats(_time + b._time);
}
Beats operator-(const Beats& b) const {
return Beats(_time - b._time);
}
Beats operator+(double d) const {
return Beats(_time + d);
}
Beats operator-(double d) const {
return Beats(_time - d);
}
Beats operator-() const {
return Beats(-_time);
}
template<typename Number>
Beats operator*(Number factor) const {
return Beats(_time * factor);
}
Beats& operator+=(const Beats& b) {
_time += b._time;
return *this;
}
Beats& operator-=(const Beats& b) {
_time -= b._time;
return *this;
}
double to_double() const { return _time; }
uint64_t to_ticks() const { return lrint(_time * PPQN); }
uint64_t to_ticks(uint32_t ppqn) const { return lrint(_time * ppqn); }
uint32_t get_beats() const { return floor(_time); }
uint32_t get_ticks() const { return (uint32_t)lrint(fmod(_time, 1.0) * PPQN); }
bool operator!() const { return _time == 0; }
static Beats min() { return Beats(DBL_MIN); }
static Beats max() { return Beats(DBL_MAX); }
static Beats tick() { return Beats(1.0 / PPQN); }
private:
double _time;
};
extern LIBEVORAL_API const Beats MaxBeats;
extern LIBEVORAL_API const Beats MinBeats;
/*
TIL, several horrible hours later, that sometimes the compiler looks in the
namespace of a type (Evoral::Beats in this case) for an operator, and
does *NOT* look in the global namespace.
C++ is proof that hell exists and we are living in it. In any case, move
these to the global namespace and PBD::Property's loopy
virtual-method-in-a-template will bite you.
*/
inline std::ostream&
operator<<(std::ostream& os, const Beats& t)
{
os << t.to_double();
return os;
}
inline std::istream&
operator>>(std::istream& is, Beats& t)
{
double beats;
is >> beats;
t = Beats(beats);
return is;
}
} // namespace Evoral
namespace PBD {
namespace DEBUG {
LIBEVORAL_API extern uint64_t Beats;
}
}
namespace std {
template<>
struct numeric_limits<Evoral::Beats> {
static Evoral::Beats min() { return Evoral::Beats::min(); }
static Evoral::Beats max() { return Evoral::Beats::max(); }
};
}
#endif // EVORAL_BEATS_HPP

View File

@ -0,0 +1,170 @@
#include <stdlib.h>
#include "BeatsTest.hpp"
#include "evoral/Beats.hpp"
CPPUNIT_TEST_SUITE_REGISTRATION(BeatsTest);
using namespace Evoral;
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));
}

View File

@ -0,0 +1,22 @@
#include <cppunit/TestFixture.h>
#include <cppunit/extensions/HelperMacros.h>
class BeatsTest : public CppUnit::TestFixture
{
CPPUNIT_TEST_SUITE(BeatsTest);
CPPUNIT_TEST(createTest);
CPPUNIT_TEST(addTest);
CPPUNIT_TEST(subtractTest);
CPPUNIT_TEST(multiplyTest);
CPPUNIT_TEST(convertTest);
CPPUNIT_TEST(roundTest);
CPPUNIT_TEST_SUITE_END();
public:
void createTest();
void addTest();
void subtractTest();
void multiplyTest();
void convertTest();
void roundTest();
};

168
libs/evoral/wscript.orig Normal file
View File

@ -0,0 +1,168 @@
#!/usr/bin/env python
from waflib.extras import autowaf as autowaf
from waflib import Options
import os
# Version of this package (even if built as a child)
EVORAL_VERSION = '0.0.0'
# Library version (UNIX style major, minor, micro)
# major increment <=> incompatible changes
# minor increment <=> compatible changes (additions)
# micro increment <=> no interface changes
# Version history:
# 0.0.0 = 0,0,0
EVORAL_LIB_VERSION = '0.0.0'
# Variables for 'waf dist'
APPNAME = 'evoral'
VERSION = EVORAL_VERSION
# Mandatory variables
top = '.'
out = 'build'
def options(opt):
opt.load('compiler_c')
opt.load('compiler_cxx')
autowaf.set_options(opt)
opt.add_option('--test', action='store_true', default=False, dest='build_tests',
help="Build unit tests")
opt.add_option('--test-coverage', action='store_true', default=False, dest='test_coverage',
help="Use gcov to test for code coverage")
opt.add_option('--internal-shared-libs', action='store_true', default=True, dest='internal_shared_libs',
help='Build internal libs as shared libraries')
def configure(conf):
conf.load('compiler_c')
conf.load('compiler_cxx')
autowaf.configure(conf)
#autowaf.display_header('Evoral Configuration')
autowaf.check_pkg(conf, 'cppunit', uselib_store='CPPUNIT', atleast_version='1.12.0', mandatory=False)
autowaf.check_pkg(conf, 'glib-2.0', uselib_store='GLIB', atleast_version='2.2')
autowaf.check_pkg(conf, 'glibmm-2.4', uselib_store='GLIBMM', atleast_version='2.14.0')
autowaf.check_pkg(conf, 'gthread-2.0', uselib_store='GTHREAD', atleast_version='2.14.0')
if not autowaf.is_child():
autowaf.check_pkg(conf, 'libpbd-4', uselib_store='LIBPBD', atleast_version='4.0.0', mandatory=True)
# Boost headers
autowaf.check_header(conf, 'cxx', 'boost/shared_ptr.hpp')
autowaf.check_header(conf, 'cxx', 'boost/weak_ptr.hpp')
conf.env['BUILD_TESTS'] = Options.options.build_tests
conf.env['TEST_COVERAGE'] = Options.options.test_coverage
if Options.options.internal_shared_libs:
conf.define('INTERNAL_SHARED_LIBS', 1)
#autowaf.display_msg(conf, "Unit tests", str(conf.env['BUILD_TESTS']))
#print
def build(bld):
# Headers
#bld.install_files('${INCLUDEDIR}/evoral', 'evoral/*.h')
#bld.install_files('${INCLUDEDIR}/evoral', 'evoral/*.hpp')
# Pkgconfig file
#autowaf.build_pc(bld, 'EVORAL', EVORAL_VERSION, 'GLIBMM GTHREAD')
libsmf = bld(features = 'c cstlib')
libsmf.source = '''
src/libsmf/smf.c
src/libsmf/smf_decode.c
src/libsmf/smf_load.c
src/libsmf/smf_save.c
src/libsmf/smf_tempo.c
'''
libsmf.export_includes = ['./src/libsmf']
libsmf.defines = ['SMF_VERSION="1.2"', 'LIBSMF_DLL_EXPORTS']
libsmf.includes = ['./src']
libsmf.name = 'libsmf'
libsmf.target = 'smf'
libsmf.uselib = 'GLIB'
libsmf.install_path = None
if bld.env['build_target'] != 'mingw':
libsmf.cxxflags = [ '-fPIC' ]
libsmf.cflags = [ '-fPIC' ]
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/types.cpp
'''
# Library
if bld.is_defined ('INTERNAL_SHARED_LIBS'):
obj = bld.shlib(features = 'c cxx cshlib cxxshlib', source=lib_source)
# DLL exports for this library
obj.defines = [ 'LIBEVORAL_DLL_EXPORTS' ]
else:
obj = bld.stlib(features = 'c cxx cstlib cxxstlib', source=lib_source)
obj.cxxflags = [ '-fPIC' ]
obj.cflags = [ '-fPIC' ]
obj.defines = [ ]
obj.export_includes = ['.']
obj.includes = ['.', './src']
obj.name = 'libevoral'
obj.target = 'evoral'
obj.uselib = 'GLIBMM GTHREAD SMF XML LIBPBD'
obj.use = 'libsmf libpbd'
obj.vnum = EVORAL_LIB_VERSION
obj.install_path = bld.env['LIBDIR']
obj.defines += [ 'PACKAGE="libevoral"' ]
if bld.env['BUILD_TESTS'] and bld.is_defined('HAVE_CPPUNIT'):
# Static library (for unit test code coverage)
obj = bld(features = 'cxx cstlib')
obj.source = lib_source
obj.export_includes = ['.']
obj.includes = ['.', './src']
obj.name = 'libevoral_static'
obj.target = 'evoral_static'
obj.uselib = 'GLIBMM GTHREAD SMF XML LIBPBD'
obj.use = 'libsmf libpbd'
obj.vnum = EVORAL_LIB_VERSION
obj.install_path = ''
if bld.env['TEST_COVERAGE']:
obj.linkflags = ['--coverage']
obj.cflags = ['--coverage']
obj.cxxflags = ['--coverage']
obj.defines = ['PACKAGE="libevoral"']
# 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
'''
obj.includes = ['.', './src']
obj.use = 'libevoral_static'
obj.uselib = 'CPPUNIT SNDFILE LIBPBD'
obj.target = 'run-tests'
obj.name = 'libevoral-tests'
obj.install_path = ''
obj.defines = ['PACKAGE="libevoraltest"']
if bld.env['TEST_COVERAGE']:
obj.linkflags = ['--coverage']
obj.cflags = ['--coverage']
obj.cxxflags = ['--coverage']
def test(ctx):
autowaf.pre_test(ctx, APPNAME)
print(os.getcwd())
os.environ['EVORAL_TEST_PATH'] = os.path.abspath('../test/testdata/')
autowaf.run_tests(ctx, APPNAME, ['./run-tests'])
autowaf.post_test(ctx, APPNAME)

0
nutemp/t Normal file
View File

1243
nutemp/t.cc Normal file

File diff suppressed because it is too large Load Diff

402
nutemp/t.h Normal file
View File

@ -0,0 +1,402 @@
#ifndef __ardour_tempo_h__
#define __ardour_tempo_h__
#include <list>
#include <string>
#include <vector>
#include <cmath>
#include <exception>
#include <glibmm/threads.h>
#include "evoral/Beats.hpp"
#include "ardour/ardour.h"
#include "ardour/superclock.h"
#include "timecode/bbt_time.h"
namespace ARDOUR {
class Meter;
class TempoMap;
/** Tempo, the speed at which musical time progresses (BPM).
*/
class LIBARDOUR_API Tempo {
public:
/**
* @param npm Note Types per minute
* @param type Note Type (default `4': quarter note)
*/
Tempo (double npm, int type = 4) : _superclocks_per_note_type (double_npm_to_sc (npm)), _note_type (type) {}
/* these two methods should only be used to show and collect information to the user (for whom
* bpm as a floating point number is the obvious representation)
*/
double note_types_per_minute () const { return (superclock_ticks_per_second * 60.0) / _superclocks_per_note_type; }
void set_note_types_per_minute (double npm) { _superclocks_per_note_type = double_npm_to_sc (npm); }
int note_type () const { return _note_type; }
superclock_t superclocks_per_note_type () const {
return _superclocks_per_note_type;
}
superclock_t superclocks_per_note_type (int note_type) const {
return (_superclocks_per_note_type * _note_type) / note_type;
}
superclock_t superclocks_per_quarter_note () const {
return superclocks_per_note_type (4);
}
Tempo& operator=(Tempo const& other) {
if (&other != this) {
_superclocks_per_note_type = other._superclocks_per_note_type;
_note_type = other._note_type;
}
return *this;
}
protected:
superclock_t _superclocks_per_note_type;
int8_t _note_type;
static inline double sc_to_double_npm (superclock_t sc) { return (superclock_ticks_per_second * 60.0) / sc; }
static inline superclock_t double_npm_to_sc (double npm) { return llrint ((superclock_ticks_per_second / npm) * 60.0); }
};
/** Meter, or time signature (subdivisions per bar, and which note type is a single subdivision). */
class LIBARDOUR_API Meter {
public:
Meter (int8_t dpb, int8_t nv) : _note_value (nv), _divisions_per_bar (dpb) {}
int divisions_per_bar () const { return _divisions_per_bar; }
int note_value() const { return _note_value; }
inline bool operator==(const Meter& other) { return _divisions_per_bar == other.divisions_per_bar() && _note_value == other.note_value(); }
inline bool operator!=(const Meter& other) { return _divisions_per_bar != other.divisions_per_bar() || _note_value != other.note_value(); }
Meter& operator=(Meter const & other) {
if (&other != this) {
_divisions_per_bar = other._divisions_per_bar;
_note_value = other._note_value;
}
return *this;
}
Timecode::BBT_Time bbt_add (Timecode::BBT_Time const & bbt, Timecode::BBT_Offset const & add) const;
Timecode::BBT_Time bbt_subtract (Timecode::BBT_Time const & bbt, Timecode::BBT_Offset const & sub) const;
Timecode::BBT_Offset bbt_delta (Timecode::BBT_Time const & bbt, Timecode::BBT_Time const & sub) const;
Timecode::BBT_Time round_up_to_bar (Timecode::BBT_Time const &) const;
Timecode::BBT_Time round_down_to_bar (Timecode::BBT_Time const &) const;
Timecode::BBT_Time round_to_bar (Timecode::BBT_Time const &) const;
Evoral::Beats to_quarters (Timecode::BBT_Offset const &) const;
protected:
/** The type of "note" that a division represents. For example, 4 is
a quarter (crotchet) note, 8 is an eighth (quaver) note, etc.
*/
int8_t _note_value;
/* how many of '_note_value' make up a bar or measure */
int8_t _divisions_per_bar;
};
/** Helper class to keep track of the Meter *AND* Tempo in effect
at a given point in time.
*/
class LIBARDOUR_API TempoMetric : public Tempo, public Meter {
public:
TempoMetric (Tempo const & t, Meter const & m, bool ramp) : Tempo (t), Meter (m), _c_per_quarter (0.0), _c_per_superclock (0.0), _ramped (ramp) {}
~TempoMetric () {}
double c_per_superclock () const { return _c_per_superclock; }
double c_per_quarter () const { return _c_per_quarter; }
void compute_c_superclock (framecnt_t sr, superclock_t end_superclocks_per_note_type, superclock_t duration);
void compute_c_quarters (framecnt_t sr, superclock_t end_superclocks_per_note_type, Evoral::Beats const & duration);
superclock_t superclocks_per_bar (framecnt_t sr) const;
superclock_t superclocks_per_grid (framecnt_t sr) const;
superclock_t superclock_at_qn (Evoral::Beats const & qn) const;
superclock_t superclock_per_note_type_at_superclock (superclock_t) const;
bool ramped () const { return _ramped; }
void set_ramped (bool yn) { _ramped = yn; } /* caller must mark something dirty to force recompute */
private:
double _c_per_quarter;
double _c_per_superclock;
bool _ramped;
};
/** Tempo Map - mapping of timecode to musical time.
* convert audio-samples, sample-rate to Bar/Beat/Tick, Meter/Tempo
*/
/* TempoMap concepts
we have several different ways of talking about time:
* PULSE : whole notes, just because. These are linearly related to any other
note type, so if you know a number of pulses (whole notes), you
know the corresponding number of any other note type (e.g. quarter
notes).
* QUARTER NOTES : just what the name says. A lot of MIDI software and
concepts assume that a "beat" is a quarter-note.
* BEAT : a fraction of a PULSE. Defined by the meter in effect, so requires
meter (time signature) information to convert to/from PULSE or QUARTER NOTES.
In a 5/8 time, a BEAT is 1/8th note. In a 4/4 time, a beat is quarter note.
This means that measuring time in BEATS is potentially non-linear (if
the time signature changes, there will be a different number of BEATS
corresponding to a given time in any other unit).
* SUPERCLOCK : a very high resolution clock whose frequency
has as factors all common sample rates and all common note
type divisors. Related to MINUTES or SAMPLES only when a
sample rate is known. Related to PULSE or QUARTER NOTES only
when a tempo is known.
* MINUTES : wallclock time measurement. related to SAMPLES or SUPERCLOCK
only when a sample rate is known.
* SAMPLES : audio time measurement. Related to MINUTES or SUPERCLOCK only
when a sample rate is known
* BBT : bars|beats|ticks ... linearly related to BEATS but with the added
semantics of bars ("measures") added, in which beats are broken up
into groups of bars ("measures"). Requires meter (time signature)
information to compute to/from a given BEATS value. Contains no
additional time information compared to BEATS, but does have
additional semantic information.
Nick sez: not every note onset is on a tick
Paul wonders: if it's 8 samples off, does it matter?
Nick sez: it should not phase with existing audio
*/
class LIBARDOUR_API TempoMapPoint
{
public:
enum Flag {
ExplicitTempo = 0x1,
ExplicitMeter = 0x2,
};
TempoMapPoint (Flag f, Tempo const& t, Meter const& m, superclock_t sc, Evoral::Beats const & q, Timecode::BBT_Time const & bbt, PositionLockStyle psl, bool ramp = false)
: _flags (f), _explicit (t, m, psl, ramp), _sclock (sc), _quarters (q), _bbt (bbt), _dirty (true) {}
TempoMapPoint (TempoMapPoint const & tmp, superclock_t sc, Evoral::Beats const & q, Timecode::BBT_Time const & bbt)
: _flags (Flag (0)), _reference (&tmp), _sclock (sc), _quarters (q), _bbt (bbt), _dirty (true) {}
~TempoMapPoint () {}
bool is_explicit() const { return _flags != Flag (0); }
bool is_implicit() const { return _flags == Flag (0); }
superclock_t superclocks_per_note_type (int8_t note_type) const {
if (is_explicit()) {
return _explicit.metric.superclocks_per_note_type (note_type);
}
return _reference->superclocks_per_note_type (note_type);
}
struct BadTempoMetricLookup : public std::exception {
virtual const char* what() const throw() { return "cannot obtain non-const Metric from implicit map point"; }
};
bool dirty() const { return _dirty; }
superclock_t sclock() const { return _sclock; }
Evoral::Beats const & quarters() const { return _quarters; }
Timecode::BBT_Time const & bbt() const { return _bbt; }
bool ramped() const { return metric().ramped(); }
TempoMetric const & metric() const { return is_explicit() ? _explicit.metric : _reference->metric(); }
/* Implicit points are not allowed to return non-const references to their reference metric */
TempoMetric & metric() { if (is_explicit()) { return _explicit.metric; } throw BadTempoMetricLookup(); }
PositionLockStyle lock_style() const { return is_explicit() ? _explicit.lock_style : _reference->lock_style(); }
/* None of these properties can be set for an Implicit point, because
* they are determined by the TempoMapPoint pointed to by _reference.
*/
void set_sclock (superclock_t sc) { if (is_explicit()) { _sclock = sc; _dirty = true; } }
void set_quarters (Evoral::Beats const & q) { if (is_explicit()) { _quarters = q; _dirty = true; } }
void set_bbt (Timecode::BBT_Time const & bbt) { if (is_explicit()) { _bbt = bbt; _dirty = true; } }
void set_dirty (bool yn) { if (is_explicit()) { _dirty = yn; } }
void set_lock_style (PositionLockStyle psl) { if (is_explicit()) { _explicit.lock_style = psl; _dirty = true; } }
void make_explicit (Flag f) {
_flags = Flag (_flags|f);
/* since _metric and _reference are part of an anonymous union,
avoid possible compiler glitches by copying to a stack
variable first, then assign.
*/
TempoMetric tm (_explicit.metric);
_explicit.metric = tm;
_dirty = true;
}
void make_implicit (TempoMapPoint & tmp) { _flags = Flag (0); _reference = &tmp; }
Evoral::Beats quarters_at (superclock_t sc) const;
Evoral::Beats quarters_at (Timecode::BBT_Time const &) const;
Timecode::BBT_Time bbt_at (Evoral::Beats const &) const;
#if 0
XMLNode& get_state() const;
int set_state (XMLNode const&, int version);
#endif
struct SuperClockComparator {
bool operator() (TempoMapPoint const & a, TempoMapPoint const & b) const { return a.sclock() < b.sclock(); }
};
struct QuarterComparator {
bool operator() (TempoMapPoint const & a, TempoMapPoint const & b) const { return a.quarters() < b.quarters(); }
};
struct BBTComparator {
bool operator() (TempoMapPoint const & a, TempoMapPoint const & b) const { return a.bbt() < b.bbt(); }
};
protected:
friend class TempoMap;
void map_reset_set_sclock_for_sr_change (superclock_t sc) { _sclock = sc; }
private:
struct ExplicitInfo {
ExplicitInfo (Tempo const & t, Meter const & m, PositionLockStyle psl, bool ramp) : metric (t, m, ramp), lock_style (psl) {}
TempoMetric metric;
PositionLockStyle lock_style;
};
Flag _flags;
union {
TempoMapPoint const * _reference;
ExplicitInfo _explicit;
};
superclock_t _sclock;
Evoral::Beats _quarters;
Timecode::BBT_Time _bbt;
bool _dirty;
};
typedef std::list<TempoMapPoint> TempoMapPoints;
class LIBARDOUR_API TempoMap
{
public:
TempoMap (Tempo const & initial_tempo, Meter const & initial_meter, framecnt_t sr);
void set_sample_rate (framecnt_t sr);
framecnt_t sample_rate() const { return _sample_rate; }
void remove_explicit_point (superclock_t);
void move_implicit (superclock_t current, superclock_t destination);
void move_explicit (superclock_t current, superclock_t destination);
//bool set_tempo_at (Tempo const &, Evoral::Beats const &, PositionLockStyle psl, bool ramp = false);
bool set_tempo (Tempo const &, Timecode::BBT_Time const &, bool ramp = false);
bool set_tempo (Tempo const &, superclock_t, bool ramp = false);
//bool set_meter_at (Meter const &, Evoral::Beats const &);
bool set_meter (Meter const &, Timecode::BBT_Time const &);
bool set_meter (Meter const &, superclock_t);
Meter const & meter_at (superclock_t sc) const;
Meter const & meter_at (Evoral::Beats const & b) const;
Meter const & meter_at (Timecode::BBT_Time const & bbt) const;
Tempo const & tempo_at (superclock_t sc) const;
Tempo const & tempo_at (Evoral::Beats const &b) const;
Tempo const & tempo_at (Timecode::BBT_Time const & bbt) const;
Timecode::BBT_Time bbt_at (superclock_t sc) const;
Timecode::BBT_Time bbt_at (Evoral::Beats const &) const;
Evoral::Beats quarter_note_at (superclock_t sc) const;
Evoral::Beats quarter_note_at (Timecode::BBT_Time const &) const;
superclock_t superclock_at (Evoral::Beats const &) const;
superclock_t superclock_at (Timecode::BBT_Time const &) const;
struct EmptyTempoMapException : public std::exception {
virtual const char* what() const throw() { return "TempoMap is empty"; }
};
void dump (std::ostream&);
void rebuild (superclock_t limit);
private:
TempoMapPoints _points;
framecnt_t _sample_rate;
mutable Glib::Threads::RWLock _lock;
/* these return an iterator that refers to the TempoMapPoint at or most immediately preceding the given position.
*
* Conceptually, these could be const methods, but C++ prevents them returning a non-const iterator in that case.
*
* Note that they cannot return an invalid iterator (e.g. _points.end()) because:
*
* - if the map is empty, an exception is thrown
* - if the given time is before the first map entry, _points.begin() is returned
* - if the given time is after the last map entry, the equivalent of _points.rbegin() is returned
* - if the given time is within the map entries, a valid iterator will be returned
*/
TempoMapPoints::iterator iterator_at (superclock_t sc);
TempoMapPoints::iterator iterator_at (Evoral::Beats const &);
TempoMapPoints::iterator iterator_at (Timecode::BBT_Time const &);
TempoMapPoints::const_iterator const_iterator_at (superclock_t sc) const { return const_cast<TempoMap*>(this)->iterator_at (sc); }
TempoMapPoints::const_iterator const_iterator_at (Evoral::Beats const & b) const { return const_cast<TempoMap*>(this)->iterator_at (b); }
TempoMapPoints::const_iterator const_iterator_at (Timecode::BBT_Time const & bbt) const { return const_cast<TempoMap*>(this)->iterator_at (bbt); }
/* Returns the TempoMapPoint at or most immediately preceding the given time. If the given time is
* before the first map entry, then the first map entry will be returned, which underlies the semantics
* that the first map entry's values propagate backwards in time if not at absolute zero.
*
* As for iterator_at(), define both const+const and non-const variants, because C++ won't let us return a non-const iterator
from a const method (which is a bit silly, but presumably aids compiler reasoning).
*/
TempoMapPoint & point_at (superclock_t sc) { return *iterator_at (sc); }
TempoMapPoint & point_at (Evoral::Beats const & b) { return *iterator_at (b); }
TempoMapPoint & point_at (Timecode::BBT_Time const & bbt) { return *iterator_at (bbt); }
TempoMapPoint const & const_point_at (superclock_t sc) const { return *const_iterator_at (sc); }
TempoMapPoint const & const_point_at (Evoral::Beats const & b) const { return *const_iterator_at (b); }
TempoMapPoint const & const_point_at (Timecode::BBT_Time const & bbt) const { return *const_iterator_at (bbt); }
Meter const & meter_at_locked (superclock_t sc) const { return const_point_at (sc).metric(); }
Meter const & meter_at_locked (Evoral::Beats const & b) const { return const_point_at (b).metric(); }
Meter const & meter_at_locked (Timecode::BBT_Time const & bbt) const { return const_point_at (bbt).metric(); }
Tempo const & tempo_at_locked (superclock_t sc) const { return const_point_at (sc).metric(); }
Tempo const & tempo_at_locked (Evoral::Beats const &b) const { return const_point_at (b).metric(); }
Tempo const & tempo_at_locked (Timecode::BBT_Time const & bbt) const { return const_point_at (bbt).metric(); }
Timecode::BBT_Time bbt_at_locked (superclock_t sc) const;
Timecode::BBT_Time bbt_at_locked (Evoral::Beats const &) const;
Evoral::Beats quarter_note_at_locked (superclock_t sc) const;
Evoral::Beats quarter_note_at_locked (Timecode::BBT_Time const &) const;
superclock_t superclock_at_locked (Evoral::Beats const &) const;
superclock_t superclock_at_locked (Timecode::BBT_Time const &) const;
void move_explicit_to (TempoMapPoints::iterator, superclock_t destination);
void rebuild_locked (superclock_t limit);
};
}
std::ostream& operator<<(std::ostream&, ARDOUR::TempoMapPoint const &);
std::ostream& operator<<(std::ostream&, ARDOUR::Tempo const &);
std::ostream& operator<<(std::ostream&, ARDOUR::Meter const &);
#endif /* __ardour_tempo_h__ */