13
0

Tempo ramps - Remove the tick walk, store c_func, document the approach.

This commit is contained in:
nick_m 2016-02-26 03:10:15 +11:00
parent f72eef1651
commit 28127b1768
3 changed files with 139 additions and 90 deletions

View File

@ -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<TempoMap>(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<TempoMap>(map, before_state, &after));
_editor->commit_reversible_command ();

View File

@ -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;
};

View File

@ -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:
WHERES 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<TempoSection*> (*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;