From 28127b1768667aa5433d7e994105a8de78c7d6d2 Mon Sep 17 00:00:00 2001 From: nick_m Date: Fri, 26 Feb 2016 03:10:15 +1100 Subject: [PATCH] Tempo ramps - Remove the tick walk, store c_func, document the approach. --- gtk2_ardour/editor_drag.cc | 4 +- libs/ardour/ardour/tempo.h | 37 +++++--- libs/ardour/tempo.cc | 188 +++++++++++++++++++++++-------------- 3 files changed, 139 insertions(+), 90 deletions(-) diff --git a/gtk2_ardour/editor_drag.cc b/gtk2_ardour/editor_drag.cc index 4bde8e908c..45a6245bc7 100644 --- a/gtk2_ardour/editor_drag.cc +++ b/gtk2_ardour/editor_drag.cc @@ -3343,14 +3343,14 @@ TempoMarkerDrag::finished (GdkEvent* event, bool movement_occurred) if (_copy == true) { _editor->begin_reversible_command (_("copy tempo mark")); XMLNode &before = map.get_state(); - map.add_tempo (_marker->tempo(), map.beat_at_frame (_marker->position()), _marker->tempo().type()); + map.add_tempo (_marker->tempo(), _real_section->beat(), _marker->tempo().type()); XMLNode &after = map.get_state(); _editor->session()->add_command (new MementoCommand(map, &before, &after)); _editor->commit_reversible_command (); } else { /* we removed it before, so add it back now */ - map.replace_tempo (*_real_section, _marker->tempo().beats_per_minute() , map.beat_at_frame (_marker->position()), _marker->tempo().type()); + map.replace_tempo (*_real_section, _marker->tempo().beats_per_minute() , _real_section->beat(), _marker->tempo().type()); XMLNode &after = map.get_state(); _editor->session()->add_command (new MementoCommand(map, before_state, &after)); _editor->commit_reversible_command (); diff --git a/libs/ardour/ardour/tempo.h b/libs/ardour/ardour/tempo.h index bc52c48902..d0c8381b2b 100644 --- a/libs/ardour/ardour/tempo.h +++ b/libs/ardour/ardour/tempo.h @@ -165,9 +165,9 @@ class LIBARDOUR_API TempoSection : public MetricSection, public Tempo { }; TempoSection (const double& beat, double qpm, double note_type, Type tempo_type) - : MetricSection (beat), Tempo (qpm, note_type), _bar_offset (-1.0), _type (tempo_type) {} + : MetricSection (beat), Tempo (qpm, note_type), _bar_offset (-1.0), _type (tempo_type), _c_func (0.0) {} TempoSection (framepos_t frame, double qpm, double note_type, Type tempo_type) - : MetricSection (frame), Tempo (qpm, note_type), _bar_offset (-1.0), _type (tempo_type) {} + : MetricSection (frame), Tempo (qpm, note_type), _bar_offset (-1.0), _type (tempo_type), _c_func (0.0) {} TempoSection (const XMLNode&); static const std::string xml_state_node_name; @@ -181,14 +181,20 @@ class LIBARDOUR_API TempoSection : public MetricSection, public Tempo { void set_type (Type type); Type type () const { return _type; } - double tempo_at_frame (framepos_t frame, double end_bpm, framepos_t end_frame, framecnt_t frame_rate) const; - framepos_t frame_at_tempo (double tempo, double end_bpm, framepos_t end_frame, framecnt_t frame_rate) const; + double tempo_at_frame (framepos_t frame, framecnt_t frame_rate) const; + framepos_t frame_at_tempo (double tempo, framecnt_t frame_rate) const; - double tick_at_frame (framepos_t frame, double end_bpm, framepos_t end_frame, framecnt_t frame_rate) const; - framepos_t frame_at_tick (double tick, double end_bpm, framepos_t end_frame, framecnt_t frame_rate) const; + double tick_at_frame (framepos_t frame, framecnt_t frame_rate) const; + framepos_t frame_at_tick (double tick, framecnt_t frame_rate) const; - double beat_at_frame (framepos_t frame, double end_bpm, framepos_t end_frame, framecnt_t frame_rate) const; - framepos_t frame_at_beat (double beat, double end_bpm, framepos_t end_frame, framecnt_t frame_rate) const; + double beat_at_frame (framepos_t frame, framecnt_t frame_rate) const; + framepos_t frame_at_beat (double beat, framecnt_t frame_rate) const; + + framecnt_t ramp_duration_from_tempo_and_beat (double end_tpm, double end_beat, framecnt_t frame_rate); + + double compute_c_func (double end_bpm, framepos_t end_frame, framecnt_t frame_rate) const; + double get_c_func () const { return _c_func; } + void set_c_func (double c_func) { _c_func = c_func; } Timecode::BBT_Time legacy_bbt () { return _legacy_bbt; } @@ -201,17 +207,17 @@ class LIBARDOUR_API TempoSection : public MetricSection, public Tempo { * 'tick tempo' in ticks per minute and tempo in bpm. * time relative to section start. */ - double c_func (double end_tpm, double end_time) const; double a_func (double end_tpm, double c_func) const; + double c_func (double end_tpm, double end_time) const; - double tick_tempo_at_time (double time, double end_tpm, double end_time) const; - double time_at_tick_tempo (double tick_tempo, double end_tpm, double end_time) const; + double tick_tempo_at_time (double time) const; + double time_at_tick_tempo (double tick_tempo) const; - double tick_at_time (double time, double end_tpm, double end_time) const; - double time_at_tick (double tick, double end_tpm, double end_time) const; + double tick_at_time (double time) const; + double time_at_tick (double tick) const; - double beat_at_time (double time, double end_tpm, double end_time) const; - double time_at_beat (double beat, double end_tpm, double end_time) const; + double beat_at_time (double time) const; + double time_at_beat (double beat) const; /* this value provides a fractional offset into the bar in which the tempo section is located in. A value of 0.0 indicates that @@ -223,6 +229,7 @@ class LIBARDOUR_API TempoSection : public MetricSection, public Tempo { */ double _bar_offset; Type _type; + double _c_func; Timecode::BBT_Time _legacy_bbt; }; diff --git a/libs/ardour/tempo.cc b/libs/ardour/tempo.cc index 08a70c4574..dc19d8141e 100644 --- a/libs/ardour/tempo.cc +++ b/libs/ardour/tempo.cc @@ -106,7 +106,6 @@ TempoSection::TempoSection (const XMLNode& node) error << _("TempoSection XML node has no \"beat\" property") << endmsg; } - if ((prop = node.property ("beats-per-minute")) == 0) { error << _("TempoSection XML node has no \"beats-per-minute\" property") << endmsg; throw failed_constructor(); @@ -191,27 +190,27 @@ TempoSection::set_type (Type type) /** returns the tempo at the zero-based (relative to tempo section) frame. */ double -TempoSection::tempo_at_frame (framepos_t frame, double end_bpm, framepos_t end_frame, framecnt_t frame_rate) const +TempoSection::tempo_at_frame (framepos_t frame, framecnt_t frame_rate) const { if (_type == Constant) { return beats_per_minute(); } - return tick_tempo_at_time (frame_to_minute (frame, frame_rate), end_bpm * BBT_Time::ticks_per_beat, frame_to_minute (end_frame, frame_rate)) / BBT_Time::ticks_per_beat; + return tick_tempo_at_time (frame_to_minute (frame, frame_rate)) / BBT_Time::ticks_per_beat; } /** returns the zero-based frame (relative to tempo section) where the tempo occurs. */ framepos_t -TempoSection::frame_at_tempo (double tempo, double end_bpm, framepos_t end_frame, framecnt_t frame_rate) const +TempoSection::frame_at_tempo (double bpm, framecnt_t frame_rate) const { if (_type == Constant) { return 0; } - return minute_to_frame (time_at_tick_tempo (tempo * BBT_Time::ticks_per_beat, end_bpm * BBT_Time::ticks_per_beat, frame_to_minute (end_frame, frame_rate)), frame_rate); + return minute_to_frame (time_at_tick_tempo (bpm * BBT_Time::ticks_per_beat), frame_rate); } /** returns the zero-based tick (relative to tempo section) @@ -219,13 +218,13 @@ TempoSection::frame_at_tempo (double tempo, double end_bpm, framepos_t end_frame lies. */ double -TempoSection::tick_at_frame (framepos_t frame, double end_bpm, framepos_t end_frame, framecnt_t frame_rate) const +TempoSection::tick_at_frame (framepos_t frame, framecnt_t frame_rate) const { if (_type == Constant) { return (frame / frames_per_beat (frame_rate)) * BBT_Time::ticks_per_beat; } - return tick_at_time (frame_to_minute (frame, frame_rate), end_bpm * BBT_Time::ticks_per_beat, frame_to_minute (end_frame, frame_rate)); + return tick_at_time (frame_to_minute (frame, frame_rate)); } /** returns the zero-based frame (relative to tempo section) @@ -233,13 +232,13 @@ TempoSection::tick_at_frame (framepos_t frame, double end_bpm, framepos_t end_fr falls. */ framepos_t -TempoSection::frame_at_tick (double tick, double end_bpm, framepos_t end_frame, framecnt_t frame_rate) const +TempoSection::frame_at_tick (double tick, framecnt_t frame_rate) const { if (_type == Constant) { return (framepos_t) floor ((tick / BBT_Time::ticks_per_beat) * frames_per_beat (frame_rate)); } - return minute_to_frame (time_at_tick (tick, end_bpm * BBT_Time::ticks_per_beat, frame_to_minute (end_frame, frame_rate)), frame_rate); + return minute_to_frame (time_at_tick (tick), frame_rate); } /** returns the zero-based beat (relative to tempo section) @@ -247,9 +246,9 @@ TempoSection::frame_at_tick (double tick, double end_bpm, framepos_t end_frame, lies. */ double -TempoSection::beat_at_frame (framepos_t frame, double end_bpm, framepos_t end_frame, framecnt_t frame_rate) const +TempoSection::beat_at_frame (framepos_t frame, framecnt_t frame_rate) const { - return tick_at_frame (frame, end_bpm, end_frame, frame_rate) / BBT_Time::ticks_per_beat; + return tick_at_frame (frame, frame_rate) / BBT_Time::ticks_per_beat; } /** returns the zero-based frame (relative to tempo section start frame) @@ -258,15 +257,80 @@ TempoSection::beat_at_frame (framepos_t frame, double end_bpm, framepos_t end_fr */ framepos_t -TempoSection::frame_at_beat (double beat, double end_bpm, framepos_t end_frame, framecnt_t frame_rate) const +TempoSection::frame_at_beat (double beat, framecnt_t frame_rate) const { - return frame_at_tick (beat * BBT_Time::ticks_per_beat, end_bpm, end_frame, frame_rate); + return frame_at_tick (beat * BBT_Time::ticks_per_beat, frame_rate); +} +/* +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)) + +where c is the function constant: +c = log(Ta/T0)/a +and +a = log(Ta/T0)/c + +We define c for a tempo ramp by placing a new tempo at some distance t away from our existing one. + +Given the function constant, the beat function (duration in beats at some time t) is: +b(t) = T0(e^(ct) - 1) / c + +To find the time t at any beat b on the curve, we use the inverse function of the beat function: +t(b) = log((cb / T0) + 1) / c + +When we add or move the next tempo section, we change the function constant. We take advantage of t being equal to a here. +The problem is that we usually don't know t. +We do know the beat duration until the next tempo section. +Substituting t = a into the beat function allows us to find a in terms of beat duration b and the two relevant tempos: +a = b log (Ta / T0) / (T0 (e^(log (Ta / T0)) - 1)) + +We then use a to set the function constant c (above). + +Most of this stuff is taken from this paper: +WHERE’S THE BEAT? +TOOLS FOR DYNAMIC TEMPO CALCULATIONS +Jan C. Schacher +Martin Neukom + +https://www.zhdk.ch/fileadmin/data_subsites/data_icst/Downloads/Timegrid/ICST_Tempopolyphony_ICMC07.pdf + +*/ + +/* compute the duration of this tempo section givan the end tempo and duration in beats of some later tempo section*/ +framecnt_t +TempoSection::ramp_duration_from_tempo_and_beat (double end_bpm, double end_beat, framecnt_t frame_rate) +{ + double const log_tempo_ratio = log ((end_bpm * BBT_Time::ticks_per_beat) / ticks_per_minute()); + return minute_to_frame (((end_beat * BBT_Time::ticks_per_beat) * log_tempo_ratio) / (ticks_per_minute() * (exp (log_tempo_ratio) - 1)), frame_rate); +} + +/* compute the function constant from some later tempo section, given tempo (beats/min.) and distance (in frames) from this tempo section */ +double +TempoSection::compute_c_func (double end_bpm, framepos_t end_frame, framecnt_t frame_rate) const +{ + return c_func (end_bpm * BBT_Time::ticks_per_beat, frame_to_minute (end_frame, frame_rate)); } framecnt_t TempoSection::minute_to_frame (double time, framecnt_t frame_rate) const { - return time * 60.0 * frame_rate; + return (framecnt_t) floor ((time * 60.0 * frame_rate) + 0.5); } double @@ -291,44 +355,44 @@ TempoSection::c_func (double end_tpm, double end_time) const /* tempo in tpm at time in minutes */ double -TempoSection::tick_tempo_at_time (double time, double end_tpm, double end_time) const +TempoSection::tick_tempo_at_time (double time) const { - return exp (c_func (end_tpm, end_time) * time) * ticks_per_minute(); + return exp (_c_func * time) * ticks_per_minute(); } /* time in minutes at tempo in tpm */ double -TempoSection::time_at_tick_tempo (double tick_tempo, double end_tpm, double end_time) const +TempoSection::time_at_tick_tempo (double tick_tempo) const { - return log (tick_tempo / ticks_per_minute()) / c_func (end_tpm, end_time); + return log (tick_tempo / ticks_per_minute()) / _c_func; } /* tick at time in minutes */ double -TempoSection::tick_at_time (double time, double end_tpm, double end_time) const +TempoSection::tick_at_time (double time) const { - return ((exp (c_func (end_tpm, end_time) * time)) - 1) * ticks_per_minute() / c_func (end_tpm, end_time); + return ((exp (_c_func * time)) - 1) * ticks_per_minute() / _c_func; } /* time in minutes at tick */ double -TempoSection::time_at_tick (double tick, double end_tpm, double end_time) const +TempoSection::time_at_tick (double tick) const { - return log (((c_func (end_tpm, end_time) * tick) / ticks_per_minute()) + 1) / c_func (end_tpm, end_time); + return log (((_c_func * tick) / ticks_per_minute()) + 1) / _c_func; } /* beat at time in minutes */ double -TempoSection::beat_at_time (double time, double end_tpm, double end_time) const +TempoSection::beat_at_time (double time) const { - return tick_at_time (time, end_tpm, end_time) / BBT_Time::ticks_per_beat; + return tick_at_time (time) / BBT_Time::ticks_per_beat; } /* time in munutes at beat */ double -TempoSection::time_at_beat (double beat, double end_tpm, double end_time) const +TempoSection::time_at_beat (double beat) const { - return time_at_tick (beat * BBT_Time::ticks_per_beat, end_tpm, end_time); + return time_at_tick (beat * BBT_Time::ticks_per_beat); } void @@ -707,7 +771,6 @@ TempoMap::replace_tempo (const TempoSection& ts, const Tempo& tempo, const doubl { Glib::Threads::RWLock::WriterLock lm (lock); TempoSection& first (first_tempo()); - if (ts.beat() != first.beat()) { remove_tempo_locked (ts); add_tempo_locked (tempo, where, true, type); @@ -740,7 +803,6 @@ TempoMap::gui_set_tempo_frame (TempoSection& ts, framepos_t frame, double beat_ } else { /*AudioTime*/ ts.set_frame (frame); - MetricSectionFrameSorter fcmp; metrics.sort (fcmp); @@ -772,8 +834,10 @@ TempoMap::gui_set_tempo_frame (TempoSection& ts, framepos_t frame, double beat_ } if (prev_ts) { - /* set the start beat */ - double beats_to_ts = prev_ts->beat_at_frame (frame - prev_ts->frame(), ts.beats_per_minute(), frame - prev_ts->frame(), _frame_rate); + /* set the start beat - we need to reset the function constant before beat calculations make sense*/ + prev_ts->set_c_func (prev_ts->compute_c_func (ts.beats_per_minute(), frame - prev_ts->frame(), _frame_rate)); + + double beats_to_ts = prev_ts->beat_at_frame (frame - prev_ts->frame(), _frame_rate); double beats = beats_to_ts + prev_ts->beat(); if (next_ts) { @@ -791,6 +855,8 @@ TempoMap::gui_set_tempo_frame (TempoSection& ts, framepos_t frame, double beat_ } } else { ts.set_beat (beats); + ts.set_c_func (0.0); + } MetricSectionSorter cmp; metrics.sort (cmp); @@ -1058,31 +1124,17 @@ TempoMap::recompute_map (bool reassign_tempo_bbt, framepos_t end) if ((t = dynamic_cast (*i)) != 0) { if (prev_ts) { - double const beats_relative_to_prev_ts = t->beat() - prev_ts->beat(); - double const ticks_relative_to_prev_ts = beats_relative_to_prev_ts * BBT_Time::ticks_per_beat; + double const ticks_relative_to_prev = (t->beat() - prev_ts->beat()) * BBT_Time::ticks_per_beat; - /* assume (falsely) that the target tempo is constant */ - double const t_fpb = t->frames_per_beat (_frame_rate); - double const av_fpb = (prev_ts->frames_per_beat (_frame_rate) + t_fpb) / 2.0; - /* this walk shouldn't be needed as given c, time a = log (Ta / T0) / c. what to do? */ - double length_estimate = beats_relative_to_prev_ts * av_fpb; - - if (prev_ts->type() == TempoSection::Constant) { - length_estimate = beats_relative_to_prev_ts * prev_ts->frames_per_beat (_frame_rate); + framecnt_t duration; + if (prev_ts->type() == TempoSection::Ramp) { + duration = prev_ts->ramp_duration_from_tempo_and_beat (t->beats_per_minute(), t->beat() - prev_ts->beat(), _frame_rate); + } else { + duration = (framecnt_t) floor (ticks_relative_to_prev * prev_ts->frames_per_beat (_frame_rate) * BBT_Time::ticks_per_beat); } - double const system_precision_at_target_tempo = (_frame_rate / t->ticks_per_minute()) * 1.5; - double tick_error = system_precision_at_target_tempo + 1.0; // sorry for the wtf - - while (fabs (tick_error) > system_precision_at_target_tempo) { - - double const actual_ticks = prev_ts->tick_at_frame (length_estimate, t->beats_per_minute(), - (framepos_t) length_estimate, _frame_rate); - tick_error = ticks_relative_to_prev_ts - actual_ticks; - length_estimate += tick_error * (t->ticks_per_minute() / _frame_rate); - } - - t->set_frame (length_estimate + prev_ts->frame()); + prev_ts->set_c_func (prev_ts->compute_c_func (t->beats_per_minute(), duration, _frame_rate)); + t->set_frame (duration + prev_ts->frame()); } prev_ts = t; } @@ -1298,10 +1350,8 @@ TempoMap::tick_at_frame (framecnt_t frame) const /*the previous ts is the one containing the frame */ framepos_t const time = frame - prev_ts->frame(); - framepos_t const last_frame = t->frame() - prev_ts->frame(); - double const last_beats_per_minute = t->beats_per_minute(); - return prev_ts->tick_at_frame (time, last_beats_per_minute, last_frame, _frame_rate) + accumulated_ticks; + return prev_ts->tick_at_frame (time, _frame_rate) + accumulated_ticks; } if (prev_ts && t->frame() > prev_ts->frame()) { @@ -1343,10 +1393,8 @@ TempoMap::frame_at_tick (double tick) const /* prev_ts is the one affecting us. */ double const ticks_in_section = tick - accumulated_ticks_to_prev; - framepos_t const last_time = t->frame() - prev_ts->frame(); - double const last_beats_per_minute = t->beats_per_minute(); - return prev_ts->frame_at_tick (ticks_in_section, last_beats_per_minute, last_time, _frame_rate) + prev_ts->frame(); + return prev_ts->frame_at_tick (ticks_in_section, _frame_rate) + prev_ts->frame(); } accumulated_ticks_to_prev = accumulated_ticks; prev_ts = t; @@ -1420,14 +1468,10 @@ TempoMap::bbt_duration_at (framepos_t pos, const BBT_Time& bbt, int dir) } } if (first && second) { - framepos_t const last_time = second->frame() - first->frame(); - double const last_beats_per_minute = second->beats_per_minute(); - framepos_t const time = pos - first->frame(); - double const tick_at_time = first->tick_at_frame (time, last_beats_per_minute, last_time, _frame_rate); + double const tick_at_time = first->tick_at_frame (time, _frame_rate); double const bbt_ticks = bbt.ticks + (bbt.beats * BBT_Time::ticks_per_beat); - - double const time_at_bbt = first->frame_at_tick (tick_at_time + bbt_ticks, last_beats_per_minute, last_time, _frame_rate); + double const time_at_bbt = first->frame_at_tick (tick_at_time + bbt_ticks, _frame_rate); return time_at_bbt - time; } @@ -1670,7 +1714,7 @@ TempoMap::frames_per_beat_at (framepos_t frame, framecnt_t sr) const } if (ts_after) { - return (60.0 * _frame_rate) / (ts_at->tempo_at_frame (frame - ts_at->frame(), ts_after->beats_per_minute(), ts_after->frame(), _frame_rate)); + return (60.0 * _frame_rate) / (ts_at->tempo_at_frame (frame - ts_at->frame(), _frame_rate)); } /* must be treated as constant tempo */ return ts_at->frames_per_beat (_frame_rate); @@ -1692,9 +1736,7 @@ TempoMap::tempo_at (framepos_t frame) const if ((prev_ts) && t->frame() > frame) { /* this is the one past frame */ framepos_t const time = frame - prev_ts->frame(); - framepos_t const last_time = t->frame() - prev_ts->frame(); - double const last_beats_per_minute = t->beats_per_minute(); - double const ret = prev_ts->tempo_at_frame (time, last_beats_per_minute, last_time, _frame_rate); + double const ret = prev_ts->tempo_at_frame (time, _frame_rate); Tempo const ret_tempo (ret, m.tempo().note_type ()); return ret_tempo; } @@ -2191,7 +2233,7 @@ TempoMap::framepos_plus_bbt (framepos_t pos, BBT_Time op) const } if (next_tempo) { - pos += tempo->frame_at_beat (bars * meter->divisions_per_bar(), next_tempo->beats_per_minute(), next_tempo->frame(), _frame_rate); + pos += tempo->frame_at_beat (bars * meter->divisions_per_bar(), _frame_rate); } else { pos += llrint (frames_per_beat * (bars * meter->divisions_per_bar())); } @@ -2211,7 +2253,7 @@ TempoMap::framepos_plus_bbt (framepos_t pos, BBT_Time op) const } if (next_tempo) { - pos += tempo->frame_at_beat (bars * meter->divisions_per_bar(), next_tempo->beats_per_minute(), next_tempo->frame(), _frame_rate); + pos += tempo->frame_at_beat (bars * meter->divisions_per_bar(), _frame_rate); } else { pos += llrint (frames_per_beat * (bars * meter->divisions_per_bar())); } @@ -2243,7 +2285,7 @@ TempoMap::framepos_plus_bbt (framepos_t pos, BBT_Time op) const } if (next_tempo) { - pos += tempo->frame_at_beat (beats, next_tempo->beats_per_minute(), next_tempo->frame(), _frame_rate); + pos += tempo->frame_at_beat (beats, _frame_rate); } else { pos += llrint (beats * frames_per_beat); } @@ -2262,13 +2304,13 @@ TempoMap::framepos_plus_bbt (framepos_t pos, BBT_Time op) const } if (next_tempo) { - pos += tempo->frame_at_beat (beats, next_tempo->beats_per_minute(), next_tempo->frame(), _frame_rate); + pos += tempo->frame_at_beat (beats, _frame_rate); } else { pos += llrint (beats * frames_per_beat); } if (op.ticks) { - pos += tempo->frame_at_tick (op.ticks, next_tempo->beats_per_minute(), next_tempo->frame(), _frame_rate); + pos += tempo->frame_at_tick (op.ticks, _frame_rate); } return pos;