summaryrefslogtreecommitdiff
path: root/nutemp
diff options
context:
space:
mode:
authorPaul Davis <paul@linuxaudiosystems.com>2017-09-13 19:31:42 -0400
committerPaul Davis <paul@linuxaudiosystems.com>2017-09-18 11:40:54 -0400
commitcba53a20233531ef3e6c3692993eac8f74e991a1 (patch)
treedc5655c4bfe367842804200bfef0b2dcc57d770b /nutemp
parente37558502eabc54768b0858fb611aeba40d1fc9e (diff)
add _locked() variants to new tempo experiment
Diffstat (limited to 'nutemp')
-rw-r--r--nutemp/t0
-rw-r--r--nutemp/t.cc1243
-rw-r--r--nutemp/t.h402
3 files changed, 1645 insertions, 0 deletions
diff --git a/nutemp/t b/nutemp/t
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/nutemp/t
diff --git a/nutemp/t.cc b/nutemp/t.cc
new file mode 100644
index 0000000000..8741377d7b
--- /dev/null
+++ b/nutemp/t.cc
@@ -0,0 +1,1243 @@
+#include "t.h"
+
+using namespace ARDOUR;
+using std::cerr;
+using std::cout;
+using std::endl;
+
+/* overloaded operator* that avoids floating point math when multiplying a superclock position by a number of quarter notes */
+superclock_t operator*(superclock_t sc, Evoral::Beats const & b) { return (sc * ((b.get_beats() * Evoral::Beats::PPQN) + b.get_ticks())) / Evoral::Beats::PPQN; }
+
+Timecode::BBT_Time
+Meter::bbt_add (Timecode::BBT_Time const & bbt, Timecode::BBT_Offset const & add) const
+{
+ int32_t bars = bbt.bars;
+ int32_t beats = bbt.beats;
+ int32_t ticks = bbt.ticks;
+
+ if ((bars ^ add.bars) < 0) {
+ /* signed-ness varies */
+ if (abs(add.bars) >= abs(bars)) {
+ /* addition will change which side of "zero" the answer is on;
+ adjust bbt.bars towards zero to deal with "unusual" BBT math
+ */
+ if (bars < 0) {
+ bars++;
+ } else {
+ bars--;
+ }
+ }
+ }
+
+ if ((beats ^ add.beats) < 0) {
+ /* signed-ness varies */
+ if (abs (add.beats) >= abs (beats)) {
+ /* adjust bbt.beats towards zero to deal with "unusual" BBT math */
+ if (beats < 0) {
+ beats++;
+ } else {
+ beats--;
+ }
+ }
+ }
+
+ Timecode::BBT_Offset r (bars + add.bars, beats + add.beats, ticks + add.ticks);
+
+ if (r.ticks >= Evoral::Beats::PPQN) {
+ r.beats += r.ticks / Evoral::Beats::PPQN;
+ r.ticks %= Evoral::Beats::PPQN;
+ }
+
+ if (r.beats > _divisions_per_bar) {
+ r.bars += r.beats / _divisions_per_bar;
+ r.beats %= _divisions_per_bar;
+ }
+
+ if (r.beats == 0) {
+ r.beats = 1;
+ }
+
+ if (r.bars == 0) {
+ r.bars = 1;
+ }
+
+ return Timecode::BBT_Time (r.bars, r.beats, r.ticks);
+}
+
+Timecode::BBT_Time
+Meter::bbt_subtract (Timecode::BBT_Time const & bbt, Timecode::BBT_Offset const & sub) const
+{
+ int32_t bars = bbt.bars;
+ int32_t beats = bbt.beats;
+ int32_t ticks = bbt.ticks;
+
+ if ((bars ^ sub.bars) < 0) {
+ /* signed-ness varies */
+ if (abs (sub.bars) >= abs (bars)) {
+ /* adjust bbt.bars towards zero to deal with "unusual" BBT math */
+ if (bars < 0) {
+ bars++;
+ } else {
+ bars--;
+ }
+ }
+ }
+
+ if ((beats ^ sub.beats) < 0) {
+ /* signed-ness varies */
+ if (abs (sub.beats) >= abs (beats)) {
+ /* adjust bbt.beats towards zero to deal with "unusual" BBT math */
+ if (beats < 0) {
+ beats++;
+ } else {
+ beats--;
+ }
+ }
+ }
+
+ Timecode::BBT_Offset r (bars - sub.bars, beats - sub.beats, ticks - sub.ticks);
+
+ if (r.ticks < 0) {
+ r.beats -= 1 - (r.ticks / Evoral::Beats::PPQN);
+ r.ticks = Evoral::Beats::PPQN + (r.ticks % Evoral::Beats::PPQN);
+ }
+
+ if (r.beats <= 0) {
+ r.bars -= 1 - (r.beats / _divisions_per_bar);
+ r.beats = _divisions_per_bar + (r.beats % _divisions_per_bar);
+ }
+
+ if (r.beats == 0) {
+ r.beats = 1;
+ }
+
+ if (r.bars <= 0) {
+ r.bars -= 1;
+ }
+
+ return Timecode::BBT_Time (r.bars, r.beats, r.ticks);
+}
+
+Timecode::BBT_Offset
+Meter::bbt_delta (Timecode::BBT_Time const & a, Timecode::BBT_Time const & b) const
+{
+ return Timecode::BBT_Offset (a.bars - b.bars, a.beats - b.beats, a.ticks - b.ticks);
+}
+
+Timecode::BBT_Time
+Meter::round_up_to_bar (Timecode::BBT_Time const & bbt) const
+{
+ Timecode::BBT_Time b = bbt.round_up_to_beat ();
+ if (b.beats > 1) {
+ b.bars++;
+ b.beats = 1;
+ }
+ return b;
+}
+
+Evoral::Beats
+Meter::to_quarters (Timecode::BBT_Offset const & offset) const
+{
+ Evoral::Beats b;
+
+ b += (offset.bars * _divisions_per_bar * 4) / _note_value;
+ cerr << offset.bars << " bars as quarters : " << b << " nv = " << (int) _note_value << endl;
+ b += (offset.beats * 4) / _note_value;
+ cerr << offset.beats << " beats as quarters : " << (offset.beats * 4) / _note_value << " nv = " << (int) _note_value << endl;
+ b += Evoral::Beats::ticks (offset.ticks);
+
+ return b;
+}
+
+superclock_t
+TempoMetric::superclock_per_note_type_at_superclock (superclock_t sc) const
+{
+ return superclocks_per_note_type () * expm1 (_c_per_superclock * sc);
+}
+
+superclock_t
+TempoMetric::superclocks_per_grid (framecnt_t sr) const
+{
+ return (superclock_ticks_per_second * Meter::note_value()) / (note_types_per_minute() / Tempo::note_type());
+}
+
+superclock_t
+TempoMetric::superclocks_per_bar (framecnt_t sr) const
+{
+ return superclocks_per_grid (sr) * _divisions_per_bar;
+}
+
+/*
+Ramp Overview
+
+ | *
+Tempo | *
+Tt----|-----------------*|
+Ta----|--------------|* |
+ | * | |
+ | * | |
+ | * | |
+T0----|* | |
+ * | | |
+ _______________|___|____
+ time a t (next tempo)
+ [ c ] defines c
+
+Duration in beats at time a is the integral of some Tempo function.
+In our case, the Tempo function (Tempo at time t) is
+T(t) = T0(e^(ct))
+
+>>1/S(t) = (1/S0)(e^ct) => (1/S)(t) = (e^(ct))/S0 => S(t) = S0/(e^(ct))
+
+with function constant
+c = log(Ta/T0)/a
+
+>>c = log ((1/Sa)/(1/S0)) / a => c = log (S0/Sa) / a
+
+so
+a = log(Ta/T0)/c
+
+>>a = log ((1/Ta)/(1/S0) / c => a = log (S0/Sa) / c
+
+The integral over t of our Tempo function (the beat function, which is the duration in beats at some time t) is:
+b(t) = T0(e^(ct) - 1) / c
+
+>>b(t) = 1/S0(e^(ct) - 1) / c => b(t) = (e^(ct) - 1) / (c * S0)
+
+To find the time t at beat duration b, we use the inverse function of the beat function (the time function) which can be shown to be:
+t(b) = log((c.b / T0) + 1) / c
+
+>>t(b) = log((c*b / (1/S0)) + 1) / c => t(b) = log ((c*b * S0) + 1) / c
+
+The time t at which Tempo T occurs is a as above:
+t(T) = log(T / T0) / c
+
+>> t(1/S) = log ((1/S) / (1/S0) /c => t(1/S) = log (S0/S) / c
+
+The beat at which a Tempo T occurs is:
+b(T) = (T - T0) / c
+
+>> b(1/S) = (1/S - 1/S0) / c
+
+The Tempo at which beat b occurs is:
+T(b) = b.c + T0
+
+>> T(b) = b.c + (1/S0)
+
+We define c for this tempo ramp by placing a new tempo section at some time t after this one.
+Our problem is that we usually don't know t.
+We almost always know the duration in beats between this and the new section, so we need to find c in terms of the beat function.
+Where a = t (i.e. when a is equal to the time of the next tempo section), the beat function reveals:
+t = b log (Ta / T0) / (T0 (e^(log (Ta / T0)) - 1))
+
+By substituting our expanded t as a in the c function above, our problem is reduced to:
+c = T0 (e^(log (Ta / T0)) - 1) / b
+
+>> c = (1/S0) (e^(log ((1/Sa) / (1/S0))) - 1) / b => c = (1/S0) (e^(log (S0/Sa)) - 1) / b => c (e^(log (S0/Sa)) - 1) / (b * S0)
+
+Of course the word 'beat' has been left loosely defined above.
+In music, a beat is defined by the musical pulse (which comes from the tempo)
+and the meter in use at a particular time (how many pulse divisions there are in one bar).
+It would be more accurate to substitute the work 'pulse' for 'beat' above.
+
+ */
+
+/* equation to compute c is:
+ *
+ * c = log (Ta / T0) / a
+ *
+ * where
+ *
+ * a : time into section (from section start
+ * T0 : tempo at start of section
+ * Ta : tempo at time a into section
+ *
+ * THE UNITS QUESTION
+ *
+ * log (Ta / T0) / (time-units) => C is in per-time-units (1/time-units)
+ *
+ * so the question is what are the units of a, and thus c?
+ *
+ * we could use ANY time units (because we can measure a in any time units)
+ * but whichever one we pick dictates how we can use c in the future since
+ * all subsequent computations will need to use the same time units.
+ *
+ * options:
+ *
+ * pulses ... whole notes, possibly useful, since we can use it for any other note_type
+ * quarter notes ... linearly related to pulses
+ * beats ... not a fixed unit of time
+ * minutes ... linearly related to superclocks
+ * samples ... needs sample rate
+ * superclocks ... frequently correct
+ *
+ * so one answer might be to compute c in two different units so that we have both available.
+ *
+ * hence, compute_c_superclocks() and compute_c_pulses()
+ */
+
+void
+TempoMetric::compute_c_superclock (framecnt_t sr, superclock_t end_scpqn, superclock_t superclock_duration)
+{
+ if ((superclocks_per_quarter_note() == end_scpqn) || !ramped()) {
+ _c_per_superclock = 0.0;
+ return;
+ }
+
+ _c_per_superclock = log ((double) superclocks_per_quarter_note () / end_scpqn) / superclock_duration;
+}
+void
+TempoMetric::compute_c_quarters (framecnt_t sr, superclock_t end_scpqn, Evoral::Beats const & quarter_duration)
+{
+ if ((superclocks_per_quarter_note () == end_scpqn) || !ramped()) {
+ _c_per_quarter = 0.0;
+ return;
+ }
+
+ _c_per_quarter = log (superclocks_per_quarter_note () / (double) end_scpqn) / quarter_duration.to_double();
+}
+
+superclock_t
+TempoMetric::superclock_at_qn (Evoral::Beats const & qn) const
+{
+ if (_c_per_quarter == 0.0) {
+ /* not ramped, use linear */
+ return llrint (superclocks_per_quarter_note () * qn.to_double());
+ }
+
+ return llrint (superclocks_per_quarter_note() * (log1p (_c_per_quarter * qn.to_double()) / _c_per_quarter));
+}
+
+Evoral::Beats
+TempoMapPoint::quarters_at (superclock_t sc) const
+{
+ /* This TempoMapPoint must already have a fully computed metric and position */
+
+ if (!ramped()) {
+ return _quarters + Evoral::Beats ((sc - _sclock) / (double) (metric().superclocks_per_quarter_note ()));
+ }
+
+ return _quarters + Evoral::Beats (expm1 (metric().c_per_superclock() * (sc - _sclock)) / (metric().c_per_superclock() * metric().superclocks_per_quarter_note ()));
+}
+
+Evoral::Beats
+TempoMapPoint::quarters_at (Timecode::BBT_Time const & bbt) const
+{
+ /* This TempoMapPoint must already have a fully computed metric and position */
+
+ Timecode::BBT_Offset offset = metric().bbt_delta (bbt, _bbt);
+ cerr << "QA BBT DELTA between " << bbt << " and " << _bbt << " = " << offset << " as quarters for " << static_cast<Meter> (metric()) << " = " << metric().to_quarters (offset) << endl;
+ return _quarters + metric().to_quarters (offset);
+}
+
+Timecode::BBT_Time
+TempoMapPoint::bbt_at (Evoral::Beats const & qn) const
+{
+ /* This TempoMapPoint must already have a fully computed metric and position */
+
+ Evoral::Beats quarters_delta = qn - _quarters;
+ int32_t ticks_delta = quarters_delta.to_ticks (Evoral::Beats::PPQN);
+ return metric().bbt_add (_bbt, Timecode::BBT_Offset (0, 0, ticks_delta));
+}
+
+TempoMap::TempoMap (Tempo const & initial_tempo, Meter const & initial_meter, framecnt_t sr)
+ : _sample_rate (sr)
+{
+ TempoMapPoint tmp (TempoMapPoint::Flag (TempoMapPoint::ExplicitMeter|TempoMapPoint::ExplicitTempo), initial_tempo, initial_meter, 0, Evoral::Beats(), Timecode::BBT_Time(), AudioTime);
+ _points.push_back (tmp);
+}
+
+Meter const &
+TempoMap::meter_at (superclock_t sc) const
+{
+ Glib::Threads::RWLock::ReaderLock lm (_lock);
+ return meter_at_locked (sc);
+}
+
+Meter const &
+TempoMap::meter_at (Evoral::Beats const & b) const
+{
+ Glib::Threads::RWLock::ReaderLock lm (_lock);
+ return meter_at_locked (b);
+}
+
+Meter const &
+TempoMap::meter_at (Timecode::BBT_Time const & bbt) const
+{
+ Glib::Threads::RWLock::ReaderLock lm (_lock);
+ return meter_at_locked (bbt);
+}
+
+Tempo const &
+TempoMap::tempo_at (superclock_t sc) const
+{
+ Glib::Threads::RWLock::ReaderLock lm (_lock);
+ return tempo_at_locked (sc);
+}
+
+Tempo const &
+TempoMap::tempo_at (Evoral::Beats const &b) const
+{
+ Glib::Threads::RWLock::ReaderLock lm (_lock);
+ return tempo_at_locked (b);
+}
+
+Tempo const &
+TempoMap::tempo_at (Timecode::BBT_Time const & bbt) const
+{
+ Glib::Threads::RWLock::ReaderLock lm (_lock);
+ return tempo_at_locked (bbt);
+}
+
+void
+TempoMap::rebuild (superclock_t limit)
+{
+ Glib::Threads::RWLock::WriterLock lm (_lock);
+ rebuild_locked (limit);
+}
+
+void
+TempoMap::rebuild_locked (superclock_t limit)
+{
+ /* step one: remove all implicit points after a dirty explicit point */
+
+ restart:
+ TempoMapPoints::iterator tmp = _points.begin();
+ TempoMapPoints::iterator first_explicit_dirty = _points.end();
+
+ while ((tmp != _points.end()) && (tmp->is_implicit() || (tmp->is_explicit() && !tmp->dirty ()))) {
+ ++tmp;
+ }
+
+ first_explicit_dirty = tmp;
+
+ /* remove all implicit points, because we're going to recalculate them all */
+
+ while (tmp != _points.end()) {
+ TempoMapPoints::iterator next = tmp;
+ ++next;
+
+ if (tmp->is_implicit()) {
+ _points.erase (tmp);
+ }
+
+ tmp = next;
+ }
+
+ /* compute C for all ramped sections */
+
+ for (tmp = first_explicit_dirty; tmp != _points.end(); ) {
+ TempoMapPoints::iterator nxt = tmp;
+ ++nxt;
+
+ if (tmp->ramped() && (nxt != _points.end())) {
+ tmp->metric().compute_c_quarters (_sample_rate, nxt->metric().superclocks_per_quarter_note (), nxt->quarters() - tmp->quarters());
+ }
+
+ tmp = nxt;
+ }
+
+ TempoMapPoints::iterator prev = _points.end();
+
+ /* Compute correct quarter-note and superclock times for all music-time locked explicit points */
+
+ for (tmp = first_explicit_dirty; tmp != _points.end(); ) {
+
+ TempoMapPoints::iterator next = tmp;
+ ++next;
+
+ if (prev != _points.end()) {
+ if ((tmp->lock_style() == MusicTime)) {
+ /* determine superclock and quarter note time for this (music-time) locked point */
+
+ cerr << "MT-lock, prev = " << *prev << endl;
+
+ Evoral::Beats qn = prev->quarters_at (tmp->bbt());
+ cerr << "MT-lock @ " << tmp->bbt() << " => " << qn << endl;
+ superclock_t sc = prev->sclock() + prev->metric().superclock_at_qn (qn - prev->quarters());
+ cerr << "MT-lock sc is " << prev->metric().superclock_at_qn (qn - prev->quarters()) << " after " << prev->sclock() << " = " << sc
+ << " secs = " << prev->metric().superclock_at_qn (qn - prev->quarters()) / (double) superclock_ticks_per_second
+ << endl;
+
+ if (qn != tmp->quarters() || tmp->sclock() != sc) {
+ cerr << "Ned to move " << *tmp << endl;
+ tmp->set_quarters (qn);
+ tmp->set_sclock (sc);
+ cerr << "using " << *prev << " moved music-time-locked @ " << tmp->bbt() << " to " << sc << " aka " << qn << endl;
+ _points.sort (TempoMapPoint::SuperClockComparator());
+ cerr << "Restart\n";
+ goto restart;
+ }
+ }
+ }
+
+ prev = tmp;
+ tmp = next;
+ }
+
+ /* _points is guaranteed sorted in superclock and quarter note order. It may not be sorted BBT order because of re-ordering
+ * of music-time locked points.
+ */
+
+ cerr << "POST-SORT\n";
+ dump (cerr);
+
+ prev = _points.end();
+
+ /* step two: add new implicit points between each pair of explicit
+ * points, after the dirty explicit point
+ */
+
+ for (tmp = _points.begin(); tmp != _points.end(); ) {
+
+ if (!tmp->dirty()) {
+ ++tmp;
+ continue;
+ }
+
+ TempoMapPoints::iterator next = tmp;
+ ++next;
+
+ if (prev != _points.end()) {
+ if ((tmp->lock_style() == AudioTime)) {
+ cerr << "AT: check " << *tmp << endl
+ << "\t\tusing " << *prev << endl;
+ /* audio-locked explicit point: recompute it's BBT and quarter-note position since this may have changed */
+ tmp->set_quarters (prev->quarters_at (tmp->sclock()));
+ cerr << "AT - recompute quarters at " << tmp->quarters () << endl;
+ if (static_cast<Meter>(tmp->metric()) != static_cast<Meter>(prev->metric())) {
+ /* new meter, must be on bar/measure start */
+ tmp->set_bbt (prev->metric().round_up_to_bar (prev->bbt_at (tmp->quarters())));
+ } else {
+ /* no meter change, tempo change required to be on beat */
+ tmp->set_bbt (prev->bbt_at (tmp->quarters()).round_up_to_beat());
+ }
+ cerr << "AT - recompute bbt at " << tmp->bbt () << endl;
+ }
+ }
+
+ superclock_t sc = tmp->sclock();
+ Evoral::Beats qn (tmp->quarters ());
+ Timecode::BBT_Time bbt (tmp->bbt());
+ const bool ramped = tmp->ramped () && next != _points.end();
+
+ /* Evoral::Beats are really quarter notes. This counts how many quarter notes
+ there are between grid points in this section of the tempo map.
+ */
+ const Evoral::Beats qn_step = (Evoral::Beats (1) * 4) / tmp->metric().note_value();
+
+ /* compute implicit points as far as the next explicit point, or limit,
+ whichever comes first.
+ */
+
+ const superclock_t sc_limit = (next == _points.end() ? limit : (*next).sclock());
+
+ while (1) {
+
+ /* define next beat in superclocks, beats and bbt */
+
+ qn += qn_step;
+ bbt = tmp->metric().bbt_add (bbt, Timecode::BBT_Offset (0, 1, 0));
+
+ if (!ramped) {
+ sc += tmp->metric().superclocks_per_note_type();
+ } else {
+ sc = tmp->sclock() + tmp->metric().superclock_at_qn (qn - tmp->quarters());
+ }
+
+ if (sc >= sc_limit) {
+ break;
+ }
+
+ _points.insert (next, TempoMapPoint (*tmp, sc, qn, bbt));
+ }
+
+ (*tmp).set_dirty (false);
+ prev = tmp;
+ tmp = next;
+ }
+}
+
+bool
+TempoMap::set_tempo (Tempo const & t, superclock_t sc, bool ramp)
+{
+ Glib::Threads::RWLock::WriterLock lm (_lock);
+
+ assert (!_points.empty());
+
+ /* special case: first map entry is later than the new point */
+
+ if (_points.front().sclock() > sc) {
+ /* first point is later than sc. There's no iterator to reference a point at or before sc */
+
+ /* determine beats and BBT time for this new tempo point. Note that tempo changes (points) must be deemed to be on beat,
+ even if the user moves them later. Even after moving, the TempoMapPoint that was beat N is still beat N, and is not
+ fractional.
+ */
+
+ Evoral::Beats b = _points.front().quarters_at (sc).round_to_beat();
+ Timecode::BBT_Time bbt = _points.front().bbt_at (b).round_to_beat ();
+
+ _points.insert (_points.begin(), TempoMapPoint (TempoMapPoint::ExplicitTempo, t, _points.front().metric(), sc, b, bbt, AudioTime, ramp));
+ return true;
+ }
+
+ /* special case #3: only one map entry, at the same time as the new point.
+ This is the common case when editing tempo/meter in a session with a single tempo/meter
+ */
+
+ if (_points.size() == 1 && _points.front().sclock() == sc) {
+ /* change tempo */
+ *((Tempo*) &_points.front().metric()) = t;
+ _points.front().make_explicit (TempoMapPoint::ExplicitTempo);
+ return true;
+ }
+
+ /* Remember: iterator_at() returns an iterator that references the TempoMapPoint at or BEFORE sc */
+
+ TempoMapPoints::iterator i = iterator_at (sc);
+ TempoMapPoints::iterator nxt = i;
+ ++nxt;
+
+ if (i->sclock() == sc) {
+ /* change tempo */
+ *((Tempo*) &i->metric()) = t;
+ i->make_explicit (TempoMapPoint::ExplicitTempo);
+ /* done */
+ return true;
+ }
+
+ if (sc - i->sclock() < i->metric().superclocks_per_note_type()) {
+ cerr << "new tempo too close to previous ...\n";
+ return false;
+ }
+
+ Meter const & meter (i->metric());
+
+ if (i->metric().ramped()) {
+ /* need to adjust ramp constants for preceding explict point, since the new point will be positioned right after it
+ and thus defines the new ramp distance.
+ */
+ i->metric().compute_c_superclock (_sample_rate, t.superclocks_per_quarter_note (), sc);
+ }
+
+ /* determine beats and BBT time for this new tempo point. Note that tempo changes (points) must be deemed to be on beat,
+ even if the user moves them later. Even after moving, the TempoMapPoint that was beat N is still beat N, and is not
+ fractional.
+ */
+
+ Evoral::Beats qn = i->quarters_at (sc).round_to_beat();
+
+ /* rule: all Tempo changes must be on-beat. So determine the nearest later beat to "sc"
+ */
+
+ Timecode::BBT_Time bbt = i->bbt_at (qn).round_up_to_beat ();
+
+ /* Modify the iterator to reference the point AFTER this new one, because STL insert is always "insert-before"
+ */
+
+ if (i != _points.end()) {
+ ++i;
+ }
+ _points.insert (i, TempoMapPoint (TempoMapPoint::ExplicitTempo, t, meter, sc, qn, bbt, AudioTime, ramp));
+ return true;
+}
+
+bool
+TempoMap::set_tempo (Tempo const & t, Timecode::BBT_Time const & bbt, bool ramp)
+{
+ Glib::Threads::RWLock::WriterLock lm (_lock);
+
+ /* tempo changes are required to be on-beat */
+
+ Timecode::BBT_Time on_beat = bbt.round_up_to_beat();
+
+ cerr << "Try to set tempo @ " << on_beat << " to " << t << endl;
+
+ assert (!_points.empty());
+
+ if (_points.front().bbt() > on_beat) {
+ cerr << "Cannot insert tempo at " << bbt << " before first point at " << _points.front().bbt() << endl;
+ return false;
+ }
+
+ if (_points.size() == 1 && _points.front().bbt() == on_beat) {
+ /* change Meter */
+ *((Tempo*) &_points.front().metric()) = t;
+ _points.front().make_explicit (TempoMapPoint::ExplicitTempo);
+ return true;
+ }
+
+ TempoMapPoints::iterator i = iterator_at (on_beat);
+
+ if (i->bbt() == on_beat) {
+ *((Tempo*) &i->metric()) = t;
+ i->make_explicit (TempoMapPoint::ExplicitTempo);
+ return true;
+ }
+
+ Meter const & meter (i->metric());
+ ++i;
+
+ /* stick a prototype music-locked point up front and let ::rebuild figure out the superclock and quarter time */
+ _points.insert (i, TempoMapPoint (TempoMapPoint::ExplicitTempo, t, meter, 0, Evoral::Beats(), on_beat, MusicTime, ramp));
+ return true;
+}
+
+bool
+TempoMap::set_meter (Meter const & m, Timecode::BBT_Time const & bbt)
+{
+ Glib::Threads::RWLock::WriterLock lm (_lock);
+ Timecode::BBT_Time measure_start (m.round_up_to_bar (bbt));
+
+ cerr << "Try to set meter @ " << measure_start << " to " << m << endl;
+
+ assert (!_points.empty());
+
+ if (_points.front().bbt() > measure_start) {
+ cerr << "Cannot insert meter at " << bbt << " before first point at " << _points.front().bbt() << endl;
+ return false;
+ }
+
+ if (_points.size() == 1 && _points.front().bbt() == measure_start) {
+ /* change Meter */
+ cerr << "Found the single point\n";
+ *((Meter*) &_points.front().metric()) = m;
+ cerr << "Updated meter to " << m << endl;
+ _points.front().make_explicit (TempoMapPoint::ExplicitMeter);
+ return true;
+ }
+
+ TempoMapPoints::iterator i = iterator_at (measure_start);
+
+ if (i->bbt() == measure_start) {
+ *((Meter*) &i->metric()) = m;
+ cerr << "Updated meter to " << m << endl;
+ i->make_explicit (TempoMapPoint::ExplicitMeter);
+ return true;
+ }
+
+ Evoral::Beats qn = i->quarters_at (measure_start);
+ superclock_t sc = i->sclock() + i->metric().superclock_at_qn (qn);
+
+ Tempo const & tempo (i->metric());
+ ++i;
+
+ cerr << "NEW METER, provisionally @ "
+ << TempoMapPoint (TempoMapPoint::ExplicitMeter, tempo, m, sc, qn, measure_start, MusicTime)
+ << endl;
+
+ _points.insert (i, TempoMapPoint (TempoMapPoint::ExplicitMeter, tempo, m, sc, qn, measure_start, MusicTime));
+ return true;
+}
+
+bool
+TempoMap::set_meter (Meter const & m, superclock_t sc)
+{
+ Glib::Threads::RWLock::WriterLock lm (_lock);
+ assert (!_points.empty());
+
+ /* special case #2: first map entry is later than the new point */
+
+ if (_points.front().sclock() > sc) {
+ /* determine quarters and BBT time for this new tempo point. Note that tempo changes (points) must be deemed to be on beat,
+ even if the user moves them later. Even after moving, the TempoMapPoint that was beat N is still beat N, and is not
+ fractional.
+ */
+
+ Evoral::Beats b = _points.front().quarters_at (sc).round_to_beat();
+ Timecode::BBT_Time bbt = _points.front().bbt_at (b).round_to_beat ();
+
+ _points.insert (_points.begin(), TempoMapPoint (TempoMapPoint::ExplicitMeter, _points.front().metric(), m, sc, b, bbt, AudioTime));
+ return true;
+ }
+
+ /* special case #3: only one map entry, at the same time as the new point.
+
+ This is the common case when editing tempo/meter in a session with a single tempo/meter
+ */
+
+ if (_points.size() == 1 && _points.front().sclock() == sc) {
+ /* change meter */
+ *((Meter*) &_points.front().metric()) = m;
+ _points.front().make_explicit (TempoMapPoint::ExplicitMeter);
+ return true;
+ }
+
+ TempoMapPoints::iterator i = iterator_at (sc);
+
+ if (i->sclock() == sc) {
+ /* change meter */
+ *((Meter*) &i->metric()) = m;
+
+ /* enforce rule described below regarding meter change positions */
+
+ if (i->bbt().beats != 1) {
+ i->set_bbt (Timecode::BBT_Time (i->bbt().bars + 1, 1, 0));
+ }
+
+ i->make_explicit (TempoMapPoint::ExplicitMeter);
+
+ return true;
+ }
+
+ if (sc - i->sclock() < i->metric().superclocks_per_note_type()) {
+ cerr << "new tempo too close to previous ...\n";
+ return false;
+ }
+
+ /* determine quarters and BBT time for this new tempo point. Note that tempo changes (points) must be deemed to be on beat,
+ even if the user moves them later. Even after moving, the TempoMapPoint that was beat N is still beat N, and is not
+ fractional.
+ */
+
+ Evoral::Beats b = i->quarters_at (sc).round_to_beat();
+
+ /* rule: all Meter changes must start a new measure. So determine the nearest, lower beat to "sc". If this is not
+ the first division of the measure, move to the next measure.
+ */
+
+ Timecode::BBT_Time bbt = i->bbt_at (b).round_down_to_beat ();
+
+ if (bbt.beats != 1) {
+ bbt.bars += 1;
+ bbt.beats = 1;
+ bbt.ticks = 0;
+ }
+
+ Tempo const & tempo (i->metric());
+ ++i;
+
+ _points.insert (i, TempoMapPoint (TempoMapPoint::ExplicitMeter, tempo, m, sc, b, bbt, AudioTime));
+ return true;
+}
+
+TempoMapPoints::iterator
+TempoMap::iterator_at (superclock_t sc)
+{
+ /* CALLER MUST HOLD LOCK */
+
+ if (_points.empty()) {
+ throw EmptyTempoMapException();
+ }
+
+ if (_points.size() == 1) {
+ return _points.begin();
+ }
+
+ /* Construct an arbitrary TempoMapPoint. The only property we care about is it's superclock time,
+ so other values used in the constructor are arbitrary and irrelevant.
+ */
+
+ TempoMetric const & metric (_points.front().metric());
+ const TempoMapPoint tp (TempoMapPoint::Flag (0), metric, metric, sc, Evoral::Beats(), Timecode::BBT_Time(), AudioTime);
+ TempoMapPoint::SuperClockComparator scmp;
+
+ TempoMapPoints::iterator tmp = upper_bound (_points.begin(), _points.end(), tp, scmp);
+
+ if (tmp != _points.begin()) {
+ return --tmp;
+ }
+
+ return tmp;
+}
+
+TempoMapPoints::iterator
+TempoMap::iterator_at (Evoral::Beats const & qn)
+{
+ /* CALLER MUST HOLD LOCK */
+
+ if (_points.empty()) {
+ throw EmptyTempoMapException();
+ }
+
+ if (_points.size() == 1) {
+ return _points.begin();
+ }
+
+ /* Construct an arbitrary TempoMapPoint. The only property we care about is its quarters time,
+ so other values used in the constructor are arbitrary and irrelevant.
+ */
+
+ TempoMetric const & metric (_points.front().metric());
+ const TempoMapPoint tp (TempoMapPoint::Flag (0), metric, metric, 0, qn, Timecode::BBT_Time(), AudioTime);
+ TempoMapPoint::QuarterComparator bcmp;
+
+ TempoMapPoints::iterator tmp = upper_bound (_points.begin(), _points.end(), tp, bcmp);
+
+ if (tmp != _points.begin()) {
+ return --tmp;
+ }
+
+ return tmp;
+}
+
+TempoMapPoints::iterator
+TempoMap::iterator_at (Timecode::BBT_Time const & bbt)
+{
+ /* CALLER MUST HOLD LOCK */
+
+ if (_points.empty()) {
+ throw EmptyTempoMapException();
+ }
+
+ if (_points.size() == 1) {
+ return _points.begin();
+ }
+
+ /* Construct an arbitrary TempoMapPoint. The only property we care about is its bbt time,
+ so other values used in the constructor are arbitrary and irrelevant.
+ */
+
+ TempoMetric const & metric (_points.front().metric());
+ const TempoMapPoint tp (TempoMapPoint::Flag(0), metric, metric, 0, Evoral::Beats(), bbt, MusicTime);
+ TempoMapPoint::BBTComparator bcmp;
+
+ TempoMapPoints::iterator tmp = upper_bound (_points.begin(), _points.end(), tp, bcmp);
+
+ if (tmp != _points.begin()) {
+ return --tmp;
+ }
+
+ return tmp;
+}
+
+Timecode::BBT_Time
+TempoMap::bbt_at (superclock_t sc) const
+{
+ Glib::Threads::RWLock::ReaderLock lm (_lock);
+ return bbt_at_locked (sc);
+}
+
+Timecode::BBT_Time
+TempoMap::bbt_at_locked (superclock_t sc) const
+{
+ TempoMapPoint point (const_point_at (sc));
+ Evoral::Beats b ((sc - point.sclock()) / (double) point.metric().superclocks_per_quarter_note());
+ return point.metric().bbt_add (point.bbt(), Timecode::BBT_Offset (0, b.get_beats(), b.get_ticks()));
+}
+
+Timecode::BBT_Time
+TempoMap::bbt_at (Evoral::Beats const & qn) const
+{
+ Glib::Threads::RWLock::ReaderLock lm (_lock);
+ return bbt_at_locked (qn);
+}
+
+Timecode::BBT_Time
+TempoMap::bbt_at_locked (Evoral::Beats const & qn) const
+{
+ //Glib::Threads::RWLock::ReaderLock lm (_lock);
+ TempoMapPoint const & point (const_point_at (qn));
+ Evoral::Beats delta (qn - point.quarters());
+ return point.metric().bbt_add (point.bbt(), Timecode::BBT_Offset (0, delta.get_beats(), delta.get_ticks()));
+}
+
+Evoral::Beats
+TempoMap::quarter_note_at (superclock_t sc) const
+{
+ Glib::Threads::RWLock::ReaderLock lm (_lock);
+ return quarter_note_at_locked (sc);
+}
+
+Evoral::Beats
+TempoMap::quarter_note_at_locked (superclock_t sc) const
+{
+ return const_point_at (sc).quarters_at (sc);
+}
+
+Evoral::Beats
+TempoMap::quarter_note_at (Timecode::BBT_Time const & bbt) const
+{
+ Glib::Threads::RWLock::ReaderLock lm (_lock);
+ return quarter_note_at_locked (bbt);
+}
+
+Evoral::Beats
+TempoMap::quarter_note_at_locked (Timecode::BBT_Time const & bbt) const
+{
+ //Glib::Threads::RWLock::ReaderLock lm (_lock);
+ TempoMapPoint const & point (const_point_at (bbt));
+
+ Timecode::BBT_Time bbt_delta (point.metric().bbt_subtract (bbt, point.bbt()));
+ /* XXX need to convert the metric division to quarters to match Evoral::Beats == Evoral::Quarters */
+ return point.quarters() + Evoral::Beats ((point.metric().divisions_per_bar() * bbt_delta.bars) + bbt_delta.beats, bbt_delta.ticks);
+}
+
+superclock_t
+TempoMap::superclock_at (Evoral::Beats const & qn) const
+{
+ Glib::Threads::RWLock::ReaderLock lm (_lock);
+ return superclock_at_locked (qn);
+}
+
+superclock_t
+TempoMap::superclock_at_locked (Evoral::Beats const & qn) const
+{
+ assert (!_points.empty());
+
+ /* only the quarters property matters, since we're just using it to compare */
+ const TempoMapPoint tmp (TempoMapPoint::Flag (0), Tempo (120), Meter (4, 4), 0, qn, Timecode::BBT_Time(), MusicTime);
+ TempoMapPoint::QuarterComparator bcmp;
+
+ TempoMapPoints::const_iterator i = upper_bound (_points.begin(), _points.end(), tmp, bcmp);
+
+ if (i == _points.end()) {
+ --i;
+ }
+
+ /* compute distance from reference point to b. Remember that Evoral::Beats is always interpreted as quarter notes */
+
+ const Evoral::Beats q_delta = qn - i->quarters();
+ superclock_t sclock_delta = i->metric().superclocks_per_quarter_note () * q_delta;
+ return i->sclock() + sclock_delta;
+}
+
+superclock_t
+TempoMap::superclock_at (Timecode::BBT_Time const & bbt) const
+{
+ //Glib::Threads::RWLock::ReaderLock lm (_lock);
+ return superclock_at_locked (bbt);
+}
+
+superclock_t
+TempoMap::superclock_at_locked (Timecode::BBT_Time const & bbt) const
+{
+ //Glib::Threads::RWLock::ReaderLock lm (_lock);
+ assert (!_points.empty());
+
+ /* only the bbt property matters, since we're just using it to compare */
+ const TempoMapPoint tmp (TempoMapPoint::Flag (0), Tempo (120), Meter (4, 4), 0, Evoral::Beats(), bbt, MusicTime);
+ TempoMapPoint::BBTComparator bcmp;
+
+ TempoMapPoints::const_iterator i = upper_bound (_points.begin(), _points.end(), tmp, bcmp);
+
+ if (i == _points.end()) {
+ --i;
+ }
+
+ /* compute distance from reference point to b. Remember that Evoral::Beats is always interpreted as quarter notes */
+
+ //const Evoral::Beats delta = b - i->beats();
+ //return i->sclock() + samples_to_superclock ((delta / i->metric().quarter_notes_per_minute ()).to_double() * _sample_rate, _sample_rate);
+ return 0;
+}
+
+void
+TempoMap::set_sample_rate (framecnt_t new_sr)
+{
+ //Glib::Threads::RWLock::ReaderLock lm (_lock);
+ double ratio = new_sr / (double) _sample_rate;
+
+ for (TempoMapPoints::iterator i = _points.begin(); i != _points.end(); ++i) {
+ i->map_reset_set_sclock_for_sr_change (llrint (ratio * i->sclock()));
+ }
+}
+ void
+TempoMap::dump (std::ostream& ostr)
+{
+ //Glib::Threads::RWLock::ReaderLock lm (_lock);
+ ostr << "\n\n------------\n";
+ for (TempoMapPoints::iterator i = _points.begin(); i != _points.end(); ++i) {
+ ostr << *i << std::endl;
+ }
+}
+
+void
+TempoMap::remove_explicit_point (superclock_t sc)
+{
+ //Glib::Threads::RWLock::WriterLock lm (_lock);
+ TempoMapPoints::iterator p = iterator_at (sc);
+
+ if (p->sclock() == sc) {
+ _points.erase (p);
+ }
+}
+
+void
+TempoMap::move_explicit (superclock_t current, superclock_t destination)
+{
+ //Glib::Threads::RWLock::WriterLock lm (_lock);
+ TempoMapPoints::iterator p = iterator_at (current);
+
+ if (p->sclock() != current) {
+ return;
+ }
+
+ move_explicit_to (p, destination);
+}
+
+void
+TempoMap::move_explicit_to (TempoMapPoints::iterator p, superclock_t destination)
+{
+ /* CALLER MUST HOLD LOCK */
+
+ TempoMapPoint point (*p);
+ point.set_sclock (destination);
+
+ TempoMapPoints::iterator prev;
+
+ prev = p;
+ if (p != _points.begin()) {
+ --prev;
+ }
+
+ /* remove existing */
+
+ _points.erase (p);
+ prev->set_dirty (true);
+
+ /* find insertion point */
+
+ p = iterator_at (destination);
+
+ /* STL insert semantics are always "insert-before", whereas ::iterator_at() returns iterator-at-or-before */
+ ++p;
+
+ _points.insert (p, point);
+}
+
+void
+TempoMap::move_implicit (superclock_t current, superclock_t destination)
+{
+ //Glib::Threads::RWLock::WriterLock lm (_lock);
+ TempoMapPoints::iterator p = iterator_at (current);
+
+ if (p->sclock() != current) {
+ return;
+ }
+
+ if (p->is_implicit()) {
+ p->make_explicit (TempoMapPoint::Flag (TempoMapPoint::ExplicitMeter|TempoMapPoint::ExplicitTempo));
+ }
+
+ move_explicit_to (p, destination);
+}
+
+/*******/
+
+#define SAMPLERATE 48000
+#define SECONDS_TO_SUPERCLOCK(s) (superclock_ticks_per_second * s)
+
+using std::cerr;
+using std::cout;
+using std::endl;
+
+void
+test_bbt_math ()
+{
+ using namespace Timecode;
+
+ BBT_Time a;
+ BBT_Time b1 (1,1,1919);
+ BBT_Time n1 (-1,1,1919);
+ std::vector<Meter> meters;
+
+ meters.push_back (Meter (4, 4));
+ meters.push_back (Meter (5, 8));
+ meters.push_back (Meter (11, 7));
+ meters.push_back (Meter (3, 4));
+
+#define PRINT_RESULT(m,str,op,op1,Bars,Beats,Ticks) cout << m << ' ' << (op1) << ' ' << str << ' ' << BBT_Offset ((Bars),(Beats),(Ticks)) << " = " << m.op ((op1), BBT_Offset ((Bars), (Beats), (Ticks))) << endl;
+
+ for (std::vector<Meter>::iterator m = meters.begin(); m != meters.end(); ++m) {
+ for (int B = 1; B < 4; ++B) {
+ for (int b = 1; b < 13; ++b) {
+ PRINT_RESULT((*m), "+", bbt_add, a, B, b, 0);
+ PRINT_RESULT((*m), "+", bbt_add, a, B, b, 1);
+ PRINT_RESULT((*m), "+", bbt_add, a, B, b, Evoral::Beats::PPQN/2);
+ PRINT_RESULT((*m), "+", bbt_add, a, B, b, Evoral::Beats::PPQN);
+ PRINT_RESULT((*m), "+", bbt_add, a, B, b, Evoral::Beats::PPQN - 1);
+ PRINT_RESULT((*m), "+", bbt_add, a, B, b, Evoral::Beats::PPQN - 2);
+
+ PRINT_RESULT((*m), "+", bbt_add, b1, B, b, 0);
+ PRINT_RESULT((*m), "+", bbt_add, b1, B, b, 1);
+ PRINT_RESULT((*m), "+", bbt_add, b1, B, b, Evoral::Beats::PPQN/2);
+ PRINT_RESULT((*m), "+", bbt_add, b1, B, b, Evoral::Beats::PPQN);
+ PRINT_RESULT((*m), "+", bbt_add, b1, B, b, Evoral::Beats::PPQN - 1);
+ PRINT_RESULT((*m), "+", bbt_add, b1, B, b, Evoral::Beats::PPQN - 2);
+
+ PRINT_RESULT((*m), "+", bbt_add, n1, B, b, 0);
+ PRINT_RESULT((*m), "+", bbt_add, n1, B, b, 1);
+ PRINT_RESULT((*m), "+", bbt_add, n1, B, b, Evoral::Beats::PPQN/2);
+ PRINT_RESULT((*m), "+", bbt_add, n1, B, b, Evoral::Beats::PPQN);
+ PRINT_RESULT((*m), "+", bbt_add, n1, B, b, Evoral::Beats::PPQN - 1);
+ PRINT_RESULT((*m), "+", bbt_add, n1, B, b, Evoral::Beats::PPQN - 2);
+ }
+ }
+
+ for (int B = 1; B < 4; ++B) {
+ for (int b = 1; b < 13; ++b) {
+ PRINT_RESULT ((*m), "-", bbt_subtract, a, B, b, 0);
+ PRINT_RESULT ((*m), "-", bbt_subtract, a, B, b, 1);
+ PRINT_RESULT ((*m), "-", bbt_subtract, a, B, b, Evoral::Beats::PPQN/2);
+ PRINT_RESULT ((*m), "-", bbt_subtract, a, B, b, Evoral::Beats::PPQN);
+ PRINT_RESULT ((*m), "-", bbt_subtract, a, B, b, Evoral::Beats::PPQN - 1);
+ PRINT_RESULT ((*m), "-", bbt_subtract, a, B, b, Evoral::Beats::PPQN - 2);
+ }
+ }
+ }
+}
+
+int
+main ()
+{
+ TempoMap tmap (Tempo (140), Meter (4,4), SAMPLERATE);
+
+ //test_bbt_math ();
+ //return 0;
+
+ tmap.set_tempo (Tempo (7), SECONDS_TO_SUPERCLOCK(7));
+ tmap.set_tempo (Tempo (23), SECONDS_TO_SUPERCLOCK(23));
+ tmap.set_tempo (Tempo (24), SECONDS_TO_SUPERCLOCK(24), true);
+ tmap.set_tempo (Tempo (40), SECONDS_TO_SUPERCLOCK(28), true);
+ tmap.set_tempo (Tempo (100), SECONDS_TO_SUPERCLOCK(100));
+ tmap.set_tempo (Tempo (123), SECONDS_TO_SUPERCLOCK(23));
+
+ tmap.set_meter (Meter (3, 4), SECONDS_TO_SUPERCLOCK(23));
+ tmap.set_meter (Meter (5, 8), SECONDS_TO_SUPERCLOCK(100));
+ tmap.set_meter (Meter (5, 7), SECONDS_TO_SUPERCLOCK(7));
+ tmap.set_meter (Meter (4, 4), SECONDS_TO_SUPERCLOCK(24));
+ tmap.set_meter (Meter (11, 7), SECONDS_TO_SUPERCLOCK(23));
+
+ tmap.set_meter (Meter (3, 8), Timecode::BBT_Time (17, 1, 0));
+
+ tmap.rebuild (SECONDS_TO_SUPERCLOCK (120));
+ tmap.dump (std::cout);
+
+ return 0;
+}
+
+std::ostream&
+operator<<(std::ostream& str, Meter const & m)
+{
+ return str << m.divisions_per_bar() << '/' << m.note_value();
+}
+
+std::ostream&
+operator<<(std::ostream& str, Tempo const & t)
+{
+ return str << t.note_types_per_minute() << " 1/" << t.note_type() << " notes per minute (" << t.superclocks_per_note_type() << " sc-per-1/" << t.note_type() << ')';
+}
+
+std::ostream&
+operator<<(std::ostream& str, TempoMapPoint const & tmp)
+{
+ str << '@' << std::setw (12) << tmp.sclock() << ' ' << tmp.sclock() / (double) superclock_ticks_per_second
+ << (tmp.is_explicit() ? " EXP" : " imp")
+ << " qn " << tmp.quarters ()
+ << " bbt " << tmp.bbt()
+ << " lock to " << tmp.lock_style()
+ ;
+
+ if (tmp.is_explicit()) {
+ str << " tempo " << *((Tempo*) &tmp.metric())
+ << " meter " << *((Meter*) &tmp.metric())
+ ;
+ }
+
+ if (tmp.is_explicit() && tmp.ramped()) {
+ str << " ramp c/sc = " << tmp.metric().c_per_superclock() << " c/qn " << tmp.metric().c_per_quarter();
+ }
+ return str;
+}
diff --git a/nutemp/t.h b/nutemp/t.h
new file mode 100644
index 0000000000..645893f51c
--- /dev/null
+++ b/nutemp/t.h
@@ -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__ */