diff --git a/gtk2_ardour/editor_ops.cc b/gtk2_ardour/editor_ops.cc index 0155015b1b..0076aaee88 100644 --- a/gtk2_ardour/editor_ops.cc +++ b/gtk2_ardour/editor_ops.cc @@ -4305,7 +4305,7 @@ Editor::cut_copy_points (Editing::CutCopyOp op, Evoral::Beats earliest, bool mid /* Snap start time backwards, so copy/paste is snap aligned. */ if (midi) { - if (earliest == Evoral::Beats::max()) { + if (earliest == std::numeric_limits::max()) { earliest = Evoral::Beats(); // Weird... don't offset } earliest.round_down_to_beat(); @@ -4373,7 +4373,7 @@ Editor::cut_copy_points (Editing::CutCopyOp op, Evoral::Beats earliest, bool mid void Editor::cut_copy_midi (CutCopyOp op) { - Evoral::Beats earliest = Evoral::Beats::max(); + Evoral::Beats earliest = std::numeric_limits::max(); for (MidiRegionSelection::iterator i = selection->midi_regions.begin(); i != selection->midi_regions.end(); ++i) { MidiRegionView* mrv = dynamic_cast(*i); if (mrv) { diff --git a/gtk2_ardour/midi_region_view.cc b/gtk2_ardour/midi_region_view.cc index 47d1843407..d67a4b6b65 100644 --- a/gtk2_ardour/midi_region_view.cc +++ b/gtk2_ardour/midi_region_view.cc @@ -2395,7 +2395,7 @@ MidiRegionView::note_selected (NoteBase* ev, bool add, bool extend) } else { /* find end of latest note selected, select all between that and the start of "ev" */ - Evoral::Beats earliest = Evoral::MaxBeats; + Evoral::Beats earliest = std::numeric_limits::max(); Evoral::Beats latest = Evoral::Beats(); for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) { @@ -2551,7 +2551,7 @@ MidiRegionView::add_to_selection (NoteBase* ev) Evoral::Beats MidiRegionView::earliest_in_selection () { - Evoral::Beats earliest = Evoral::MaxBeats; + Evoral::Beats earliest = std::numeric_limits::max(); for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) { if ((*i)->note()->time() < earliest) { diff --git a/libs/ardour/ardour/midi_model.h b/libs/ardour/ardour/midi_model.h index fb71f69085..8f8fd5c2cf 100644 --- a/libs/ardour/ardour/midi_model.h +++ b/libs/ardour/ardour/midi_model.h @@ -265,8 +265,8 @@ public: bool write_section_to(boost::shared_ptr source, const Glib::Threads::Mutex::Lock& source_lock, - Evoral::Beats begin = Evoral::MinBeats, - Evoral::Beats end = Evoral::MaxBeats, + Evoral::Beats begin = Evoral::Beats(), + Evoral::Beats end = std::numeric_limits::max(), bool offset_events = false); // MidiModel doesn't use the normal AutomationList serialisation code diff --git a/libs/ardour/ardour/midi_source.h b/libs/ardour/ardour/midi_source.h index f8f1e429ac..f1d9473a13 100644 --- a/libs/ardour/ardour/midi_source.h +++ b/libs/ardour/ardour/midi_source.h @@ -62,8 +62,8 @@ class LIBARDOUR_API MidiSource : virtual public Source, public boost::enable_sha */ int write_to (const Lock& lock, boost::shared_ptr newsrc, - Evoral::Beats begin = Evoral::MinBeats, - Evoral::Beats end = Evoral::MaxBeats); + Evoral::Beats begin = Evoral::Beats(), + Evoral::Beats end = std::numeric_limits::max()); /** Export the midi data in the given time range to another MidiSource * \param newsrc MidiSource to which data will be written. Should be a diff --git a/libs/ardour/midi_source.cc b/libs/ardour/midi_source.cc index ea0bd15b0c..7033ff84ad 100644 --- a/libs/ardour/midi_source.cc +++ b/libs/ardour/midi_source.cc @@ -408,7 +408,7 @@ MidiSource::write_to (const Lock& lock, boost::shared_ptr newsrc, Ev newsrc->copy_automation_state_from (this); if (_model) { - if (begin == Evoral::MinBeats && end == Evoral::MaxBeats) { + if (!begin && end == std::numeric_limits::max()) { _model->write_to (newsrc, newsrc_lock); } else { _model->write_section_to (newsrc, newsrc_lock, begin, end); @@ -422,7 +422,7 @@ MidiSource::write_to (const Lock& lock, boost::shared_ptr newsrc, Ev /* force a reload of the model if the range is partial */ - if (begin != Evoral::MinBeats || end != Evoral::MaxBeats) { + if (!!begin || end != std::numeric_limits::max()) { newsrc->load_model (newsrc_lock, true); } else { newsrc->set_model (newsrc_lock, _model); diff --git a/libs/ardour/session_state.cc b/libs/ardour/session_state.cc index a99a014af5..34b792dacc 100644 --- a/libs/ardour/session_state.cc +++ b/libs/ardour/session_state.cc @@ -29,6 +29,7 @@ #include #include /* snprintf(3) ... grrr */ #include +#include #include #include @@ -1251,7 +1252,7 @@ Session::state (bool full_state, snapshot_t snapshot_type) if (!ms->model()) { ms->load_model (lm); } - if (ms->write_to (lm, newsrc, Evoral::MinBeats, Evoral::MaxBeats)) { + if (ms->write_to (lm, newsrc, std::numeric_limits::lowest(), std::numeric_limits::max())) { error << string_compose (_("Session-Save: Failed to copy MIDI Source '%1' for snapshot"), ancestor_name) << endmsg; } else { if (snapshot_type == SnapshotKeep) { diff --git a/libs/evoral/evoral/Beats.hpp b/libs/evoral/evoral/Beats.hpp index e0277c4b3d..902008d7ec 100644 --- a/libs/evoral/evoral/Beats.hpp +++ b/libs/evoral/evoral/Beats.hpp @@ -22,6 +22,7 @@ #include #include #include +#include #include #include @@ -33,62 +34,101 @@ namespace Evoral { /** Musical time in beats. */ class /*LIBEVORAL_API*/ Beats { public: - LIBEVORAL_API static const double PPQN; + LIBEVORAL_API static const int32_t PPQN; - Beats() : _time(0.0) {} + Beats() : _beats(0), _ticks(0) {} + + /** Normalize so ticks is within PPQN. */ + void normalize() { + // First, fix negative ticks with positive beats + if (_beats >= 0) { + while (_ticks < 0) { + --_beats; + _ticks += PPQN; + } + } + + // Work with positive beats and ticks to normalize + const int32_t sign = _beats < 0 ? -1 : 1; + int32_t beats = abs(_beats); + int32_t ticks = abs(_ticks); + + // Fix ticks greater than 1 beat + while (ticks >= PPQN) { + ++beats; + ticks -= PPQN; + } + + // Set fields with appropriate sign + _beats = sign * beats; + _ticks = sign * ticks; + } + + /** Create from a precise BT time. */ + explicit Beats(int32_t b, int32_t t) : _beats(b), _ticks(t) { + normalize(); + } /** Create from a real number of beats. */ - explicit Beats(double time) : _time(time) {} + explicit Beats(double time) { + double whole; + const double frac = modf(time, &whole); + + _beats = whole; + _ticks = frac * PPQN; + } /** Create from an integer number of beats. */ static Beats beats(int32_t beats) { - return Beats((double)beats); + return Beats(beats, 0); } /** Create from ticks at the standard PPQN. */ - static Beats ticks(uint32_t ticks) { - return Beats(ticks / PPQN); + static Beats ticks(int32_t ticks) { + return Beats(0, ticks); } /** 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. + * number of samples per beat. Note the resulting Beats will, like all + * others, have the default PPQN, so this is a potentially lossy + * conversion. */ - static Beats ticks_at_rate(uint64_t ticks, uint32_t ppqn) { - return Beats((double)ticks / (double)ppqn); + static Beats ticks_at_rate(int64_t ticks, uint32_t ppqn) { + return Beats(ticks / ppqn, (ticks % ppqn) * PPQN / ppqn); } Beats& operator=(const Beats& other) { - _time = other._time; + _beats = other._beats; + _ticks = other._ticks; return *this; } Beats round_up_to_beat() const { - return Evoral::Beats(ceil(_time)); + return (_ticks == 0) ? *this : Beats(_beats + 1, 0); } Beats round_down_to_beat() const { - return Evoral::Beats(floor(_time)); + return Beats(_beats, 0); } Beats snap_to(const Evoral::Beats& snap) const { - return Beats(ceil(_time / snap._time) * snap._time); + const double snap_time = snap.to_double(); + return Beats(ceil(to_double() / snap_time) * snap_time); } inline bool operator==(const Beats& b) const { - /* Acceptable tolerance is 1 tick. */ - return fabs(_time - b._time) <= (1.0 / PPQN); + return _beats == b._beats && _ticks == b._ticks; } inline bool operator==(double t) const { /* Acceptable tolerance is 1 tick. */ - return fabs(_time - t) <= (1.0 / PPQN); + return fabs(to_double() - t) <= (1.0 / PPQN); } inline bool operator==(int beats) const { - /* Acceptable tolerance is 1 tick. */ - return fabs(_time - beats) <= (1.0 / PPQN); + return _beats == beats; } inline bool operator!=(const Beats& b) const { @@ -96,37 +136,28 @@ public: } 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; - } + return _beats < b._beats || (_beats == b._beats && _ticks < b._ticks); } inline bool operator<=(const Beats& b) const { - return operator==(b) || operator<(b); + return _beats < b._beats || (_beats == b._beats && _ticks <= b._ticks); } 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; - } + return _beats > b._beats || (_beats == b._beats && _ticks > b._ticks); } inline bool operator>=(const Beats& b) const { - return operator==(b) || operator>(b); + return _beats > b._beats || (_beats == b._beats && _ticks >= b._ticks); } inline bool operator<(double b) const { /* Acceptable tolerance is 1 tick. */ - if (fabs(_time - b) <= (1.0 / PPQN)) { + const double time = to_double(); + if (fabs(time - b) <= (1.0 / PPQN)) { return false; /* Effectively identical. */ } else { - return _time < b; + return time < b; } } @@ -136,10 +167,11 @@ public: inline bool operator>(double b) const { /* Acceptable tolerance is 1 tick. */ - if (fabs(_time - b) <= (1.0 / PPQN)) { + const double time = to_double(); + if (fabs(time - b) <= (1.0 / PPQN)) { return false; /* Effectively identical. */ } else { - return _time > b; + return time > b; } } @@ -148,60 +180,60 @@ public: } Beats operator+(const Beats& b) const { - return Beats(_time + b._time); + return Beats(_beats + b._beats, _ticks + b._ticks); } Beats operator-(const Beats& b) const { - return Beats(_time - b._time); + return Beats(_beats - b._beats, _ticks - b._ticks); } Beats operator+(double d) const { - return Beats(_time + d); + return Beats(to_double() + d); } Beats operator-(double d) const { - return Beats(_time - d); + return Beats(to_double() - d); } Beats operator-() const { - return Beats(-_time); + return Beats(-_beats, -_ticks); } template Beats operator*(Number factor) const { - return Beats(_time * factor); + return Beats(_beats * factor, _ticks * factor); } Beats& operator+=(const Beats& b) { - _time += b._time; + _beats += b._beats; + _ticks += b._ticks; + normalize(); return *this; } Beats& operator-=(const Beats& b) { - _time -= b._time; + _beats -= b._beats; + _ticks -= b._ticks; + normalize(); 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); } + double to_double() const { return (double)_beats + (_ticks / (double)PPQN); } + int64_t to_ticks() const { return (int64_t)_beats * PPQN + _ticks; } + int64_t to_ticks(uint32_t ppqn) const { return (int64_t)_beats * ppqn + (_ticks * ppqn / PPQN); } - uint32_t get_beats() const { return floor(_time); } - uint32_t get_ticks() const { return (uint32_t)lrint(fmod(_time, 1.0) * PPQN); } + int32_t get_beats() const { return _beats; } + int32_t get_ticks() const { return _ticks; } - bool operator!() const { return _time == 0; } + bool operator!() const { return _beats == 0 && _ticks == 0; } - static Beats min() { return Beats(DBL_MIN); } - static Beats max() { return Beats(DBL_MAX); } - static Beats tick() { return Beats(1.0 / PPQN); } + static Beats tick() { return Beats(0, 1); } private: - double _time; + int32_t _beats; + int32_t _ticks; }; -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 @@ -239,8 +271,19 @@ namespace PBD { namespace std { template<> struct numeric_limits { - static Evoral::Beats min() { return Evoral::Beats::min(); } - static Evoral::Beats max() { return Evoral::Beats::max(); } + static Evoral::Beats lowest() { + return Evoral::Beats(std::numeric_limits::min(), + std::numeric_limits::min()); + } + + /* We don't define min() since this has different behaviour for integral and floating point types, + but Beats is used as both. Better to avoid providing a min at all + than a confusing one. */ + + static Evoral::Beats max() { + return Evoral::Beats(std::numeric_limits::max(), + std::numeric_limits::max()); + } }; } diff --git a/libs/evoral/src/types.cpp b/libs/evoral/src/types.cpp index db061fd8e9..9c055c2ad6 100644 --- a/libs/evoral/src/types.cpp +++ b/libs/evoral/src/types.cpp @@ -16,15 +16,11 @@ * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ -#include - #include "evoral/Beats.hpp" #include "evoral/types.hpp" namespace Evoral { -const double Beats::PPQN = 1920.0; -const Beats MaxBeats = Evoral::Beats(DBL_MAX); -const Beats MinBeats = Evoral::Beats(DBL_MIN); +const int32_t Beats::PPQN = 1920; } diff --git a/libs/evoral/wscript b/libs/evoral/wscript index 9ceab182a5..852af087c8 100644 --- a/libs/evoral/wscript +++ b/libs/evoral/wscript @@ -141,6 +141,7 @@ def build(bld): # Unit tests obj = bld(features = 'cxx cxxprogram') obj.source = ''' + test/BeatsTest.cpp test/SequenceTest.cpp test/SMFTest.cpp test/RangeTest.cpp