13
0

temporal: API changes and implementation to support new twist operation

this includes using two different omega members for tempo objects,
a change that likely will not persist beyond the merge back to master.
This commit is contained in:
Paul Davis 2023-03-11 20:33:50 -07:00
parent 3036414e08
commit 7040ad1b74
2 changed files with 153 additions and 144 deletions

View File

@ -25,6 +25,7 @@
#include "pbd/convert.h" #include "pbd/convert.h"
#include "pbd/enumwriter.h" #include "pbd/enumwriter.h"
#include "pbd/error.h" #include "pbd/error.h"
#include "pbd/integer_division.h"
#include "pbd/failed_constructor.h" #include "pbd/failed_constructor.h"
#include "pbd/stacktrace.h" #include "pbd/stacktrace.h"
#include "pbd/string_convert.h" #include "pbd/string_convert.h"
@ -405,8 +406,11 @@ TempoPoint::set_state (XMLNode const & node, int version)
int ret; int ret;
if ((ret = Tempo::set_state (node, version)) == 0) { if ((ret = Tempo::set_state (node, version)) == 0) {
if (node.get_property (X_("omega"), _omega)) { if (node.get_property (X_("omega_beats"), _omega_beats)) {
/* XXX ?? */ /* Older versions only defined a single omega value */
if (node.get_property (X_("omega"), _omega_beats)) {
/* ???? */
}
} }
} }
@ -418,29 +422,41 @@ TempoPoint::get_state () const
{ {
XMLNode& base (Tempo::get_state()); XMLNode& base (Tempo::get_state());
Point::add_state (base); Point::add_state (base);
base.set_property (X_("omega"), _omega); base.set_property (X_("omega_beats"), _omega_beats);
return base; return base;
} }
TempoPoint::TempoPoint (TempoMap const & map, XMLNode const & node) TempoPoint::TempoPoint (TempoMap const & map, XMLNode const & node)
: Point (map, node) : Point (map, node)
, Tempo (node) , Tempo (node)
, _omega (0) , _omega_beats (0.)
{ {
node.get_property (X_("omega"), _omega); if (node.get_property (X_("omega_beats"), _omega_beats)) {
/* Older versions only defined a single omega value */
if (node.get_property (X_("omega"), _omega_beats)) {
/* ???? */
}
}
} }
void
TempoPoint::set_omega_beats (double ob)
{
_omega_beats = ob;
}
/* To understand the math(s) behind ramping, see the file doc/tempo.{pdf,tex} /* To understand the math(s) behind ramping, see the file doc/tempo.{pdf,tex}
*/ */
void void
TempoPoint::compute_omega_from_next_tempo (TempoPoint const & next) TempoPoint::compute_omega_beats_from_next_tempo (TempoPoint const & next)
{ {
compute_omega_from_distance_and_next_tempo (next.beats() - beats(), next); compute_omega_beats_from_distance_and_next_tempo (next.beats() - beats(), next);
} }
void void
TempoPoint::compute_omega_from_distance_and_next_tempo (Beats const & quarter_duration, TempoPoint const & next) TempoPoint::compute_omega_beats_from_distance_and_next_tempo (Beats const & quarter_duration, TempoPoint const & next)
{ {
superclock_t end_scpqn; superclock_t end_scpqn;
@ -453,25 +469,21 @@ TempoPoint::compute_omega_from_distance_and_next_tempo (Beats const & quarter_du
} }
if (superclocks_per_quarter_note () == end_scpqn) { if (superclocks_per_quarter_note () == end_scpqn) {
_omega = 0.0; _omega_beats = 0.0;
return; return;
} }
compute_omega_from_quarter_duration (quarter_duration, end_scpqn); compute_omega_beats_from_quarter_duration (quarter_duration, end_scpqn);
} }
void void
TempoPoint::compute_omega_from_quarter_duration (Beats const & quarter_duration, superclock_t end_scpqn) TempoPoint::compute_omega_beats_from_quarter_duration (Beats const & quarter_duration, superclock_t end_scpqn)
{ {
_omega = ((1.0/end_scpqn) - (1.0/superclocks_per_quarter_note())) / DoubleableBeats (quarter_duration).to_double(); _omega_beats = ((1.0/end_scpqn) - (1.0/superclocks_per_quarter_note())) / DoubleableBeats (quarter_duration).to_double();
DEBUG_TRACE (DEBUG::TemporalMap, string_compose ("quarter-computed omega from qtr duration = %1 dur was %2 start speed %3 end speed [%4]\n", _omega, quarter_duration.str(), superclocks_per_quarter_note(), end_scpqn)); if (!isfinite (_omega_beats)) {
} abort ();
}
void DEBUG_TRACE (DEBUG::TemporalMap, string_compose ("quarter-computed omega from qtr duration = %1 dur was %2 start speed %3 end speed [%4]\n", _omega_beats, quarter_duration.str(), superclocks_per_quarter_note(), end_scpqn));
TempoPoint::compute_omega_from_audio_duration (samplecnt_t audio_duration, superclock_t end_scpqn)
{
_omega = (1.0 / (samples_to_superclock (audio_duration, TEMPORAL_SAMPLE_RATE))) * log ((double) superclocks_per_note_type() / end_scpqn);
DEBUG_TRACE (DEBUG::TemporalMap, string_compose ("computed omega from audio duration= %1%2 dur was %3\n", std::setprecision(12), _omega, audio_duration));
} }
superclock_t superclock_t
@ -483,6 +495,7 @@ TempoPoint::superclock_at (Temporal::Beats const & qn) const
if (qn < Beats()) { if (qn < Beats()) {
/* negative */ /* negative */
assert (_quarters == Beats()); assert (_quarters == Beats());
} else { } else {
/* positive */ /* positive */
@ -497,30 +510,36 @@ TempoPoint::superclock_at (Temporal::Beats const & qn) const
} }
superclock_t r; superclock_t r;
const double log_expr = superclocks_per_quarter_note() * _omega * DoubleableBeats (qn - _quarters).to_double(); const double log_expr = superclocks_per_quarter_note() * _omega_beats * DoubleableBeats (qn - _quarters).to_double();
// std::cerr << "logexpr " << log_expr << " from " << superclocks_per_quarter_note() << " * " << _omega_beats << " * " << (qn - _quarters) << std::endl;
if (log_expr < -1) { if (log_expr < -1) {
r = _sclock + llrint (log (-log_expr - 1.0) / -_omega);
r = _sclock + llrint (log (-log_expr - 1.0) / -_omega_beats);
if (r < 0) { if (r < 0) {
std::cerr << "CASE 1: " << *this << endl << " scpqn = " << superclocks_per_quarter_note() << std::endl; std::cerr << "CASE 1: " << *this << endl << " scpqn = " << superclocks_per_quarter_note() << std::endl;
std::cerr << " for " << qn << " @ " << _quarters << " | " << _sclock << " + log (" << log_expr << ") " std::cerr << " for " << qn << " @ " << _quarters << " | " << _sclock << " + log (" << log_expr << ") "
<< log (-log_expr - 1.0) << log (-log_expr - 1.0)
<< " - omega = " << -_omega << " - omega = " << -_omega_beats
<< " => " << " => "
<< r << std::endl; << r << std::endl;
abort (); abort ();
} }
} else { } else {
r = _sclock + llrint (log1p (log_expr) / _omega); r = _sclock + llrint (log1p (log_expr) / _omega_beats);
// std::cerr << "r = " << _sclock << " + " << log1p (log_expr) / _omega_beats << " => " << r << std::endl;
if (r < 0) { if (r < 0) {
std::cerr << "CASE 2: scpqn = " << superclocks_per_quarter_note() << std::endl; std::cerr << "CASE 2: scpqn = " << superclocks_per_quarter_note() << std::endl;
std::cerr << " for " << qn << " @ " << _quarters << " | " << _sclock << " + log1p (" << superclocks_per_quarter_note() * _omega * DoubleableBeats (qn - _quarters).to_double() << " = " std::cerr << " for " << qn << " @ " << _quarters << " | " << _sclock << " + log1p (" << superclocks_per_quarter_note() * _omega_beats * DoubleableBeats (qn - _quarters).to_double() << " = "
<< log1p (superclocks_per_quarter_note() * _omega * DoubleableBeats (qn - _quarters).to_double()) << log1p (superclocks_per_quarter_note() * _omega_beats * DoubleableBeats (qn - _quarters).to_double())
<< " => " << " => "
<< r << std::endl; << r << std::endl;
_map->dump (std::cerr);
abort (); abort ();
} }
} }
@ -535,14 +554,14 @@ TempoPoint::superclocks_per_note_type_at (timepos_t const &pos) const
return _superclocks_per_note_type; return _superclocks_per_note_type;
} }
return _superclocks_per_note_type * exp (-_omega * (pos.superclocks() - sclock())); return _superclocks_per_note_type * exp (-_omega_beats * (pos.superclocks() - sclock()));
} }
Temporal::Beats Temporal::Beats
TempoPoint::quarters_at_superclock (superclock_t sc) const TempoPoint::quarters_at_superclock (superclock_t sc) const
{ {
/* catch a special case. The maximum superclock_t value cannot be /* catch a special case. The maximum superclock_t value cannot be
converted into a 32bit beat + 32 bit tick value for common tempos. converted into a 64 bit tick value for common tempos.
Obviously, values less than this can also cause overflow, but are Obviously, values less than this can also cause overflow, but are
unlikely to be encountered. unlikely to be encountered.
@ -591,7 +610,7 @@ TempoPoint::quarters_at_superclock (superclock_t sc) const
return ret; return ret;
} }
const double b = (exp (_omega * (sc - _sclock)) - 1) / (superclocks_per_quarter_note() * _omega); const double b = (exp (_omega_beats * (sc - _sclock)) - 1) / (superclocks_per_quarter_note() * _omega_beats);
return _quarters + Beats::from_double (b); return _quarters + Beats::from_double (b);
} }
@ -1009,7 +1028,13 @@ TempoMap::add_tempo (TempoPoint * tp)
delete tp; delete tp;
} }
reset_starting_at (ret->sclock()); TempoPoint* prev = const_cast<TempoPoint*> (previous_tempo (*ret));
if (prev) {
reset_starting_at (prev->sclock());
} else {
reset_starting_at (ret->sclock());
}
return ret; return ret;
} }
@ -1255,7 +1280,7 @@ TempoMap::reset_starting_at (superclock_t sc)
if (need_initial_ramp_reset) { if (need_initial_ramp_reset) {
const TempoPoint *nxt = next_tempo (metric.tempo()); const TempoPoint *nxt = next_tempo (metric.tempo());
if (nxt) { if (nxt) {
const_cast<TempoPoint*> (&metric.tempo())->compute_omega_from_next_tempo (*nxt); const_cast<TempoPoint*> (&metric.tempo())->compute_omega_beats_from_next_tempo (*nxt);
} }
need_initial_ramp_reset = false; need_initial_ramp_reset = false;
} }
@ -1296,7 +1321,7 @@ TempoMap::reset_starting_at (superclock_t sc)
DEBUG_TRACE (DEBUG::MapReset, string_compose ("considering omega comp for %1 with nxt = %2\n", *tp, nxt_tempo)); DEBUG_TRACE (DEBUG::MapReset, string_compose ("considering omega comp for %1 with nxt = %2\n", *tp, nxt_tempo));
if (tp->ramped() && nxt_tempo) { if (tp->ramped() && nxt_tempo) {
tp->compute_omega_from_next_tempo (*nxt_tempo); tp->compute_omega_beats_from_next_tempo (*nxt_tempo);
} }
} }
@ -1522,7 +1547,7 @@ TempoMap::move_tempo (TempoPoint const & tp, timepos_t const & when, bool push)
*/ */
if (prev_t->actually_ramped()) { if (prev_t->actually_ramped()) {
prev_t->compute_omega_from_distance_and_next_tempo (beats - prev_t->beats(), tp); prev_t->compute_omega_beats_from_distance_and_next_tempo (beats - prev_t->beats(), tp);
} }
TempoMetric metric (*prev_t, *prev_m); TempoMetric metric (*prev_t, *prev_m);
@ -1954,6 +1979,10 @@ TempoMap::_get_tempo_and_meter (typename const_traits_t::tempo_point_type & tp,
void void
TempoMap::get_grid (TempoMapPoints& ret, superclock_t start, superclock_t end, uint32_t bar_mod, uint32_t beat_div) const TempoMap::get_grid (TempoMapPoints& ret, superclock_t start, superclock_t end, uint32_t bar_mod, uint32_t beat_div) const
{ {
if (start == end) {
return;
}
/* note: @p bar_mod is "bar modulo", and describes the N in "give /* note: @p bar_mod is "bar modulo", and describes the N in "give
me every Nth bar". If the caller wants every 4th bar, bar_mod == me every Nth bar". If the caller wants every 4th bar, bar_mod ==
4. If we want every point defined by the tempo note type (e.g. every 4. If we want every point defined by the tempo note type (e.g. every
@ -2151,6 +2180,9 @@ TempoMap::get_grid (TempoMapPoints& ret, superclock_t start, superclock_t end, u
* Skip metrics until p->bbt() is at or after up to next grid mod div. * Skip metrics until p->bbt() is at or after up to next grid mod div.
*/ */
if (p->bbt() > bbt) {
std::cerr << "Point at " << *p << " wrong for " << bbt << std::endl;
}
assert (p->bbt() <= bbt); assert (p->bbt() <= bbt);
/* If we just arrived at a point (indicated by bbt == /* If we just arrived at a point (indicated by bbt ==
@ -2319,7 +2351,7 @@ std::operator<<(std::ostream& str, TempoPoint const & t)
} else { } else {
str << ' ' << " !ramp to " << t.end_note_types_per_minute(); str << ' ' << " !ramp to " << t.end_note_types_per_minute();
} }
str << " omega = " << std::setprecision(12) << t.omega(); str << " omega_beats = " << std::setprecision(12) << t.omega_beats();
} }
return str; return str;
} }
@ -2361,7 +2393,7 @@ std::operator<<(std::ostream& str, TempoMapPoint const & tmp)
} }
if (tmp.is_explicit_tempo() && tmp.tempo().ramped()) { if (tmp.is_explicit_tempo() && tmp.tempo().ramped()) {
str << " ramp omega = " << tmp.tempo().omega(); str << " ramp omega(beats) = " << tmp.tempo().omega_beats();
} }
return str; return str;
@ -3296,116 +3328,85 @@ TempoMap::stretch_tempo_end (TempoPoint* ts, samplepos_t sample, samplepos_t end
reset_starting_at (prev_t->sclock()); reset_starting_at (prev_t->sclock());
} }
void void
TempoMap::twist_tempi (TempoPoint* ts, samplepos_t start_sample, samplepos_t end_sample) TempoMap::twist_tempi (TempoPoint& prev, TempoPoint& focus, TempoPoint& next, double tempo_value)
{ {
if (!ts) { if (tempo_value < 4.0 || tempo_value > 800) {
return; return;
} }
TempoPoint* next_t = 0; TempoPoint old_prev (prev);
TempoPoint* next_to_next_t = 0; TempoPoint old_focus (focus);
/* minimum allowed measurement distance in superclocks */ /* fix end tempo of prev tempo marker then recompute its omega */
const superclock_t min_delta_sclock = samples_to_superclock (2, TEMPORAL_SAMPLE_RATE); prev.set_end_npm (tempo_value);
const superclock_t start_sclock = samples_to_superclock (start_sample, TEMPORAL_SAMPLE_RATE); prev.compute_omega_beats_from_next_tempo (focus);
const superclock_t end_sclock = samples_to_superclock (end_sample, TEMPORAL_SAMPLE_RATE);
TempoPoint* prev_t = 0; /* reposition focus, using prev to define audio time; leave beat time
const superclock_t sclock_offset = end_sclock - start_sclock; * and BBT alone
*/
focus.set (prev.superclock_at (focus.beats()), focus.beats(), focus.bbt());
/* set focus start & end tempos appropriately */
focus.set_note_types_per_minute (tempo_value);
focus.set_end_npm (next.note_types_per_minute());
/* recompute focus omega */
focus.compute_omega_beats_from_next_tempo (next);
/* Now iteratively adjust focus.end_superclocks_per_quarter_note() so
* that next.sclock() remains within 1 sample of its actual position
*/
superclock_t err = focus.superclock_at (next.beats()) - next.sclock();
const superclock_t one_sample = superclock_ticks_per_second() / TEMPORAL_SAMPLE_RATE;
Beats b (next.beats() - focus.beats());
double end_scpqn = focus.end_superclocks_per_quarter_note();
double new_end_npm;
while (std::abs(err) >= one_sample) {
if (ts->beats() > Beats()) { if (err > 0) {
prev_t = const_cast<TempoPoint*> (previous_tempo (*ts)); /* estimated > actual: speed end tempo up a little aka
} reduce scpqn
*/
next_t = const_cast<TempoPoint*> (next_tempo (*ts)); end_scpqn *= 0.99;
if (!next_t) {
return;
}
next_to_next_t = const_cast<TempoPoint*> (next_tempo (*next_t));
if (!next_to_next_t) {
return;
}
double prev_contribution = 0.0;
if (next_t && prev_t && prev_t->type() == TempoPoint::Ramped) {
prev_contribution = (ts->sclock() - prev_t->sclock()) / (double) (next_t->sclock() - prev_t->sclock());
}
const sampleoffset_t ts_sclock_contribution = sclock_offset - (prev_contribution * (double) sclock_offset);
superclock_t old_tc_sclock = ts->sclock();
superclock_t old_next_sclock = next_t->sclock();
superclock_t old_next_to_next_sclock = next_to_next_t->sclock();
double new_bpm;
double new_next_bpm;
double new_copy_end_bpm;
if (start_sclock > ts->sclock() + min_delta_sclock && (start_sclock + ts_sclock_contribution) > ts->sclock() + min_delta_sclock) {
new_bpm = ts->note_types_per_minute() * ((start_sclock - ts->sclock()) / (double) (end_sclock - ts->sclock()));
} else {
new_bpm = ts->note_types_per_minute();
}
/* don't clamp and proceed here.
testing has revealed that this can go negative,
which is an entirely different thing to just being too low.
*/
if (new_bpm < 0.5) {
return;
}
new_bpm = std::min (new_bpm, (double) 1000.0);
bool was_constant = (ts->type() == TempoPoint::Constant);
ts->set_note_types_per_minute (new_bpm);
if (was_constant) {
ts->set_end_npm (new_bpm);
}
if (!next_t->actually_ramped()) {
if (start_sclock > ts->sclock() + min_delta_sclock && end_sclock > ts->sclock() + min_delta_sclock) {
new_next_bpm = next_t->note_types_per_minute() * ((next_to_next_t->sclock() - old_next_sclock) / (double) ((old_next_to_next_sclock) - old_next_sclock));
} else { } else {
new_next_bpm = next_t->note_types_per_minute(); /* estimated < actual: reduce end tempo a little, aka
increase scpqn
*/
end_scpqn *= 1.01;
} }
next_t->set_note_types_per_minute (new_next_bpm); if (end_scpqn < 1.0) {
goto no_can_do;
} else {
double next_sclock_ratio = 1.0;
double copy_sclock_ratio = 1.0;
if (next_to_next_t) {
next_sclock_ratio = (next_to_next_t->sclock() - old_next_sclock) / (double) (old_next_to_next_sclock - old_next_sclock);
copy_sclock_ratio = ((old_tc_sclock - next_t->sclock()) / (double) (old_tc_sclock - old_next_sclock));
} }
new_next_bpm = next_t->note_types_per_minute() * next_sclock_ratio; /* recompute omega with this new end_scpqn value, and then
new_copy_end_bpm = ts->end_note_types_per_minute() * copy_sclock_ratio; * recompute the error in predicted position of next and its
* actual position.
*/
ts->set_end_npm (new_copy_end_bpm); focus.compute_omega_beats_from_quarter_duration (b, end_scpqn);
err = focus.superclock_at (next.beats()) - next.sclock();
if (next_t->continuing()) {
next_t->set_note_types_per_minute (new_copy_end_bpm);
} else {
next_t->set_note_types_per_minute (new_next_bpm);
}
ts->set_end_npm (new_copy_end_bpm);
} }
reset_starting_at (ts->sclock()); new_end_npm = ((superclock_ticks_per_second() * 60.0) / end_scpqn) * (focus.note_type() / 4.0);
/* limit range of possible discovered tempo */
if (new_end_npm > 4.0 && new_end_npm < 800) {
focus.set_end_npm (new_end_npm);
return;
}
no_can_do:
prev = old_prev;
focus = old_focus;
return;
} }
void void

View File

@ -390,8 +390,8 @@ typedef boost::intrusive::list_base_hook<boost::intrusive::tag<struct tempo_tag>
class /*LIBTEMPORAL_API*/ TempoPoint : public Tempo, public tempo_hook, public virtual Point class /*LIBTEMPORAL_API*/ TempoPoint : public Tempo, public tempo_hook, public virtual Point
{ {
public: public:
LIBTEMPORAL_API TempoPoint (TempoMap const & map, Tempo const & t, superclock_t sc, Beats const & b, BBT_Time const & bbt) : Point (map, sc, b, bbt), Tempo (t), _omega (0.) {} LIBTEMPORAL_API TempoPoint (TempoMap const & map, Tempo const & t, superclock_t sc, Beats const & b, BBT_Time const & bbt) : Point (map, sc, b, bbt), Tempo (t), _omega_beats (0.), _omega_sc (0.) {}
LIBTEMPORAL_API TempoPoint (Tempo const & t, Point const & p) : Point (p), Tempo (t), _omega (0.) {} LIBTEMPORAL_API TempoPoint (Tempo const & t, Point const & p) : Point (p), Tempo (t), _omega_beats (0.), _omega_sc (0.) {}
LIBTEMPORAL_API TempoPoint (TempoMap const & map, XMLNode const &); LIBTEMPORAL_API TempoPoint (TempoMap const & map, XMLNode const &);
virtual ~TempoPoint () {} virtual ~TempoPoint () {}
@ -423,10 +423,18 @@ class /*LIBTEMPORAL_API*/ TempoPoint : public Tempo, public tempo_hook, public v
return (superclock_ticks_per_second() * 60.0) / superclocks_per_note_type_at (pos); return (superclock_ticks_per_second() * 60.0) / superclocks_per_note_type_at (pos);
} }
LIBTEMPORAL_API double omega() const { return _omega; } LIBTEMPORAL_API double omega_beats() const { return _omega_beats; }
LIBTEMPORAL_API void compute_omega_from_next_tempo (TempoPoint const & next_tempo); LIBTEMPORAL_API double omega_sc() const { return _omega_sc; }
LIBTEMPORAL_API void compute_omega_from_distance_and_next_tempo (Beats const & quarter_duration, TempoPoint const & next_tempo);
LIBTEMPORAL_API bool actually_ramped () const { return Tempo::ramped() && ( _omega != 0); } LIBTEMPORAL_API void compute_omega_beats_from_next_tempo (TempoPoint const & next_tempo);
LIBTEMPORAL_API void compute_omega_beats_from_distance_and_next_tempo (Beats const & quarter_duration, TempoPoint const & next_tempo);
LIBTEMPORAL_API void compute_omega_beats_from_quarter_duration (Beats const & quarter_duration, superclock_t end_scpqn);
LIBTEMPORAL_API void compute_omega_sc_from_next_tempo (TempoPoint const & next_tempo);
LIBTEMPORAL_API void compute_omega_sc_from_distance_and_next_tempo (samplecnt_t audio_duration, TempoPoint const & next_tempo);
LIBTEMPORAL_API void compute_omega_sc_from_audio_duration (superclock_t audio_duration, superclock_t end_scpqn);
LIBTEMPORAL_API bool actually_ramped () const { return Tempo::ramped() && ( _omega_beats != 0); /* do not need to check both omegas */ }
LIBTEMPORAL_API XMLNode& get_state () const; LIBTEMPORAL_API XMLNode& get_state () const;
LIBTEMPORAL_API int set_state (XMLNode const&, int version); LIBTEMPORAL_API int set_state (XMLNode const&, int version);
@ -444,10 +452,11 @@ class /*LIBTEMPORAL_API*/ TempoPoint : public Tempo, public tempo_hook, public v
LIBTEMPORAL_API timepos_t time() const { return timepos_t (beats()); } LIBTEMPORAL_API timepos_t time() const { return timepos_t (beats()); }
private: private:
double _omega; double _omega_beats;
double _omega_sc;
void compute_omega_from_quarter_duration (Beats const & quarter_duration, superclock_t end_scpqn); friend TempoMap;
void compute_omega_from_audio_duration (samplecnt_t audio_duration, superclock_t end_scpqn); void set_omega_beats (double v);
}; };
/** Helper class to perform computations that require both Tempo and Meter /** Helper class to perform computations that require both Tempo and Meter
@ -515,11 +524,7 @@ class LIBTEMPORAL_API TempoMetric
if (!_tempo->actually_ramped()) { if (!_tempo->actually_ramped()) {
return _tempo->superclocks_per_note_type (); return _tempo->superclocks_per_note_type ();
} }
return _tempo->superclocks_per_note_type() * exp (-_tempo->omega() * (sc - _tempo->sclock())); return _tempo->superclocks_per_note_type() * exp (-_tempo->omega_sc() * (sc - _tempo->sclock()));
}
superclock_t superclocks_per_grid_at (superclock_t sc) const {
return int_div_round (superclocks_per_note_type_at_superclock (sc) * _tempo->note_type(), (int64_t) _meter->note_value());
} }
BBT_Argument bbt_at (timepos_t const &) const; BBT_Argument bbt_at (timepos_t const &) const;
@ -760,7 +765,7 @@ class /*LIBTEMPORAL_API*/ TempoMap : public PBD::StatefulDestructible
LIBTEMPORAL_API int set_state (XMLNode const&, int version); LIBTEMPORAL_API int set_state (XMLNode const&, int version);
LIBTEMPORAL_API void twist_tempi (TempoPoint* ts, samplepos_t start_sample, samplepos_t end_sample); LIBTEMPORAL_API void twist_tempi (TempoPoint& prev, TempoPoint& focus, TempoPoint& next, double tempo_delta);
LIBTEMPORAL_API void stretch_tempo (TempoPoint* ts, samplepos_t sample, samplepos_t end_sample, Beats const & start_qnote, Beats const & end_qnote); LIBTEMPORAL_API void stretch_tempo (TempoPoint* ts, samplepos_t sample, samplepos_t end_sample, Beats const & start_qnote, Beats const & end_qnote);
LIBTEMPORAL_API void stretch_tempo_end (TempoPoint* ts, samplepos_t sample, samplepos_t end_sample); LIBTEMPORAL_API void stretch_tempo_end (TempoPoint* ts, samplepos_t sample, samplepos_t end_sample);
@ -790,6 +795,9 @@ class /*LIBTEMPORAL_API*/ TempoMap : public PBD::StatefulDestructible
LIBTEMPORAL_API TempoPoint const* previous_tempo (TempoPoint const &) const; LIBTEMPORAL_API TempoPoint const* previous_tempo (TempoPoint const &) const;
LIBTEMPORAL_API TempoPoint const* next_tempo (TempoPoint const &) const; LIBTEMPORAL_API TempoPoint const* next_tempo (TempoPoint const &) const;
LIBTEMPORAL_API bool tempo_exists_before (TempoPoint const & t) const { return (bool) previous_tempo (t); }
LIBTEMPORAL_API bool tempo_exists_after (TempoPoint const & t) const { return (bool) next_tempo (t); }
LIBTEMPORAL_API Meter const* next_meter (Meter const &) const; LIBTEMPORAL_API Meter const* next_meter (Meter const &) const;
LIBTEMPORAL_API TempoMetric metric_at (timepos_t const &) const; LIBTEMPORAL_API TempoMetric metric_at (timepos_t const &) const;