X-Git-Url: https://main.carlh.net/gitweb/?a=blobdiff_plain;ds=sidebyside;f=libs%2Fardour%2Ftempo.cc;h=793d96c663aed501b318396a70590f920721ea3a;hb=3099d99e0b9c12c500507cc2e3e14015c7364823;hp=0ed5a014595887bc941f44dc25415f3129838967;hpb=24f64b3ea7386ace6d584503fe1397eb4f611dfe;p=ardour.git diff --git a/libs/ardour/tempo.cc b/libs/ardour/tempo.cc index 0ed5a01459..793d96c663 100644 --- a/libs/ardour/tempo.cc +++ b/libs/ardour/tempo.cc @@ -33,7 +33,7 @@ #include "ardour/lmath.h" #include "ardour/tempo.h" -#include "i18n.h" +#include "pbd/i18n.h" #include using namespace std; @@ -73,7 +73,7 @@ Meter::frames_per_bar (const Tempo& tempo, framecnt_t sr) const const string TempoSection::xml_state_node_name = "Tempo"; TempoSection::TempoSection (const XMLNode& node) - : MetricSection (0.0, 0, MusicTime) + : MetricSection (0.0, 0, MusicTime, true) , Tempo (TempoMap::default_tempo()) , _c_func (0.0) , _active (true) @@ -179,13 +179,13 @@ TempoSection::get_state() const char buf[256]; LocaleGuard lg; - snprintf (buf, sizeof (buf), "%f", pulse()); + snprintf (buf, sizeof (buf), "%lf", pulse()); root->add_property ("pulse", buf); snprintf (buf, sizeof (buf), "%li", frame()); root->add_property ("frame", buf); - snprintf (buf, sizeof (buf), "%f", _beats_per_minute); + snprintf (buf, sizeof (buf), "%lf", _beats_per_minute); root->add_property ("beats-per-minute", buf); - snprintf (buf, sizeof (buf), "%f", _note_type); + snprintf (buf, sizeof (buf), "%lf", _note_type); root->add_property ("note-type", buf); snprintf (buf, sizeof (buf), "%s", movable()?"yes":"no"); root->add_property ("movable", buf); @@ -453,7 +453,7 @@ TempoSection::time_at_pulse (const double& pulse) const const string MeterSection::xml_state_node_name = "Meter"; MeterSection::MeterSection (const XMLNode& node) - : MetricSection (0.0, 0, MusicTime), Meter (TempoMap::default_meter()) + : MetricSection (0.0, 0, MusicTime, false), Meter (TempoMap::default_meter()) { XMLProperty const * prop; LocaleGuard lg; @@ -569,12 +569,12 @@ MeterSection::get_state() const root->add_property ("bbt", buf); snprintf (buf, sizeof (buf), "%lf", beat()); root->add_property ("beat", buf); - snprintf (buf, sizeof (buf), "%f", _note_type); + snprintf (buf, sizeof (buf), "%lf", _note_type); root->add_property ("note-type", buf); snprintf (buf, sizeof (buf), "%li", frame()); root->add_property ("frame", buf); root->add_property ("lock-style", enum_2_string (position_lock_style())); - snprintf (buf, sizeof (buf), "%f", _divisions_per_bar); + snprintf (buf, sizeof (buf), "%lf", _divisions_per_bar); root->add_property ("divisions-per-bar", buf); snprintf (buf, sizeof (buf), "%s", movable()?"yes":"no"); root->add_property ("movable", buf); @@ -586,12 +586,16 @@ MeterSection::get_state() const /* Tempo Map Overview + The Shaggs - Things I Wonder + https://www.youtube.com/watch?v=9wQK6zMJOoQ + Tempo is the rate of the musical pulse. Meters divide the pulses into measures and beats. TempoSections - provide pulses in the form of beats_per_minute() and note_type() where note_type is the division of a whole pulse, and beats_per_minute is the number of note_types in one minute (unlike what its name suggests). - Note that Tempo::beats_per_minute() has nothing to do with musical beats. + Note that Tempo::beats_per_minute() has nothing to do with musical beats. It has been left that way because + a shorter one hasn't been found yet (pulse_divisions_per_minute()?). MeterSecions - divide pulses into measures (via divisions_per_bar) and beats (via note_divisor). @@ -629,6 +633,43 @@ MeterSection::get_state() const Because ramped MusicTime and AudioTime tempos can interact with each other, reordering is frequent. Care must be taken to keep _metrics in a solved state. Solved means ordered by frame or pulse with frame-accurate precision (see check_solved()). + + Music and Audio + + Music and audio-locked objects may seem interchangeable on the surface, but when translating + between audio samples and beats, keep in mind that a sample is only a quantised approximation + of the actual time (in minutes) of a beat. + Thus if a gui user points to the frame occupying the start of a music-locked object on 1|3|0, it does not + mean that this frame is the actual location in time of 1|3|0. + + You cannot use a frame measurement to determine beat distance except under special circumstances + (e.g. where the user has requested that a beat lie on a SMPTE frame or if the tempo is known to be constant over the duration). + + This means is that a user operating on a musical grid must supply the desired beat position and/or current beat quantization in order for the + sample space the user is operating at to be translated correctly to the object. + + The current approach is to interpret the supplied frame using the grid division the user has currently selected. + If the user has no musical grid set, they are actually operating in sample space (even SMPTE frames are rounded to audio frame), so + the supplied audio frame is interpreted as the desired musical location (beat_at_frame()). + + tldr: Beat, being a function of time, has nothing to do with sample rate, but time quantization can get in the way of precision. + + When frame_at_beat() is called, the position calculation is performed in pulses and minutes. + The result is rounded to audio frames. + When beat_at_frame() is called, the frame is converted to minutes, with no rounding performed on the result. + + So : + frame_at_beat (beat_at_frame (frame)) == frame + but : + beat_at_frame (frame_at_beat (beat)) != beat due to the time quantization of frame_at_beat(). + + Doing the second one will result in a beat distance error of up to 0.5 audio samples. + So instead work in pulses and/or beats and only use beat position to caclulate frame position (e.g. after tempo change). + For audio-locked objects, use frame position to calculate beat position. + + The above pointless example would then do: + beat_at_pulse (pulse_at_beat (beat)) to avoid rounding. + */ struct MetricSectionSorter { bool operator() (const MetricSection* a, const MetricSection* b) { @@ -731,13 +772,13 @@ TempoMap::remove_meter (const MeterSection& tempo, bool complete_operation) bool TempoMap::remove_meter_locked (const MeterSection& meter) { - Metrics::iterator i; - for (i = _metrics.begin(); i != _metrics.end(); ++i) { - TempoSection* t = 0; - if ((t = dynamic_cast (*i)) != 0) { - if (meter.frame() == (*i)->frame()) { - if (t->locked_to_meter()) { + if (meter.position_lock_style() == AudioTime) { + /* remove meter-locked tempo */ + for (Metrics::iterator i = _metrics.begin(); i != _metrics.end(); ++i) { + TempoSection* t = 0; + if ((t = dynamic_cast (*i)) != 0) { + if (t->locked_to_meter() && meter.frame() == (*i)->frame()) { delete (*i); _metrics.erase (i); break; @@ -746,7 +787,7 @@ TempoMap::remove_meter_locked (const MeterSection& meter) } } - for (i = _metrics.begin(); i != _metrics.end(); ++i) { + for (Metrics::iterator i = _metrics.begin(); i != _metrics.end(); ++i) { if (dynamic_cast (*i) != 0) { if (meter.frame() == (*i)->frame()) { if ((*i)->movable()) { @@ -939,18 +980,24 @@ TempoMap::add_tempo_locked (const Tempo& tempo, double pulse, framepos_t frame { TempoSection* t = new TempoSection (pulse, frame, tempo.beats_per_minute(), tempo.note_type(), type, pls); t->set_locked_to_meter (locked_to_meter); + bool solved = false; do_insert (t); if (recompute) { if (pls == AudioTime) { - solve_map_frame (_metrics, t, t->frame()); + solved = solve_map_frame (_metrics, t, t->frame()); } else { - solve_map_pulse (_metrics, t, t->pulse()); + solved = solve_map_pulse (_metrics, t, t->pulse()); } recompute_meters (_metrics); } + if (!solved && recompute) { + warning << "Adding tempo may have left the tempo map unsolved." << endmsg; + recompute_map (_metrics); + } + return t; } @@ -1005,29 +1052,49 @@ TempoMap::replace_meter (const MeterSection& ms, const Meter& meter, const BBT_T } MeterSection* -TempoMap::add_meter_locked (const Meter& meter, double beat, const Timecode::BBT_Time& where, framepos_t frame, PositionLockStyle pls, bool recompute) +TempoMap::add_meter_locked (const Meter& meter, double beat, const BBT_Time& where, framepos_t frame, PositionLockStyle pls, bool recompute) { const MeterSection& prev_m = meter_section_at_frame_locked (_metrics, frame - 1); const double pulse = ((where.bars - prev_m.bbt().bars) * (prev_m.divisions_per_bar() / prev_m.note_divisor())) + prev_m.pulse(); + TempoSection* mlt = 0; if (pls == AudioTime) { /* add meter-locked tempo */ - add_tempo_locked (tempo_at_frame_locked (_metrics, frame), pulse, frame, TempoSection::Ramp, AudioTime, true, true); + mlt = add_tempo_locked (tempo_at_frame_locked (_metrics, frame), pulse, frame, TempoSection::Ramp, AudioTime, true, true); + + if (!mlt) { + return 0; + } + } MeterSection* new_meter = new MeterSection (pulse, frame, beat, where, meter.divisions_per_bar(), meter.note_divisor(), pls); + bool solved = false; do_insert (new_meter); if (recompute) { if (pls == AudioTime) { - solve_map_frame (_metrics, new_meter, frame); + solved = solve_map_frame (_metrics, new_meter, frame); } else { - solve_map_bbt (_metrics, new_meter, where); + solved = solve_map_bbt (_metrics, new_meter, where); + /* required due to resetting the pulse of meter-locked tempi above. + Arguably solve_map_bbt() should use solve_map_pulse (_metrics, TempoSection) instead, + but afaict this cannot cause the map to be left unsolved (these tempi are all audio locked). + */ + recompute_map (_metrics); } } + if (!solved && recompute) { + /* if this has failed to solve, there is little we can do other than to ensure that + the new map is recalculated. + */ + warning << "Adding meter may have left the tempo map unsolved." << endmsg; + recompute_map (_metrics); + } + return new_meter; } @@ -1183,14 +1250,15 @@ TempoMap::first_tempo () return *t; } void -TempoMap::recompute_tempos (Metrics& metrics) +TempoMap::recompute_tempi (Metrics& metrics) { TempoSection* prev_t = 0; for (Metrics::const_iterator i = metrics.begin(); i != metrics.end(); ++i) { TempoSection* t; - if ((t = dynamic_cast (*i)) != 0) { + if ((*i)->is_tempo()) { + t = static_cast (*i); if (!t->active()) { continue; } @@ -1232,14 +1300,16 @@ TempoMap::recompute_meters (Metrics& metrics) MeterSection* prev_m = 0; for (Metrics::const_iterator mi = metrics.begin(); mi != metrics.end(); ++mi) { - if ((meter = dynamic_cast (*mi)) != 0) { + if (!(*mi)->is_tempo()) { + meter = static_cast (*mi); if (meter->position_lock_style() == AudioTime) { double pulse = 0.0; pair b_bbt; TempoSection* meter_locked_tempo = 0; for (Metrics::const_iterator ii = metrics.begin(); ii != metrics.end(); ++ii) { TempoSection* t; - if ((t = dynamic_cast (*ii)) != 0) { + if ((*ii)->is_tempo()) { + t = static_cast (*ii); if ((t->locked_to_meter() || !t->movable()) && t->frame() == meter->frame()) { meter_locked_tempo = t; break; @@ -1320,7 +1390,7 @@ TempoMap::recompute_map (Metrics& metrics, framepos_t end) return; } - recompute_tempos (metrics); + recompute_tempi (metrics); recompute_meters (metrics); } @@ -1369,7 +1439,8 @@ TempoMap::metric_at (BBT_Time bbt) const for (Metrics::const_iterator i = _metrics.begin(); i != _metrics.end(); ++i) { MeterSection* mw; - if ((mw = dynamic_cast (*i)) != 0) { + if (!(*i)->is_tempo()) { + mw = static_cast (*i); BBT_Time section_start (mw->bbt()); if (section_start.bars > bbt.bars || (section_start.bars == bbt.bars && section_start.beats > bbt.beats)) { @@ -1399,13 +1470,12 @@ TempoMap::beat_at_frame_locked (const Metrics& metrics, const framecnt_t& frame) MeterSection* next_m = 0; for (Metrics::const_iterator i = metrics.begin(); i != metrics.end(); ++i) { - MeterSection* m; - if ((m = dynamic_cast (*i)) != 0) { - if (prev_m && m->frame() > frame) { - next_m = m; + if (!(*i)->is_tempo()) { + if (prev_m && (*i)->frame() > frame) { + next_m = static_cast (*i); break; } - prev_m = m; + prev_m = static_cast (*i); } } if (frame < prev_m->frame()) { @@ -1421,19 +1491,44 @@ TempoMap::beat_at_frame_locked (const Metrics& metrics, const framecnt_t& frame) return beat; } -framecnt_t +framepos_t TempoMap::frame_at_beat (const double& beat) const { Glib::Threads::RWLock::ReaderLock lm (lock); return frame_at_beat_locked (_metrics, beat); } -/* meter section based */ -framecnt_t +/* meter & tempo section based */ +framepos_t TempoMap::frame_at_beat_locked (const Metrics& metrics, const double& beat) const { - const TempoSection* prev_t = &tempo_section_at_beat_locked (metrics, beat); - const MeterSection* prev_m = &meter_section_at_beat_locked (metrics, beat); + MeterSection* prev_m = 0; + TempoSection* prev_t = 0; + + MeterSection* m; + + for (Metrics::const_iterator i = metrics.begin(); i != metrics.end(); ++i) { + if (!(*i)->is_tempo()) { + m = static_cast (*i); + if (prev_m && m->beat() > beat) { + break; + } + prev_m = m; + } + } + + TempoSection* t; + + for (Metrics::const_iterator i = metrics.begin(); i != metrics.end(); ++i) { + if ((*i)->is_tempo()) { + t = static_cast (*i); + if (prev_t && ((t->pulse() - prev_m->pulse()) * prev_m->note_divisor()) + prev_m->beat() > beat) { + break; + } + prev_t = t; + } + + } return prev_t->frame_at_pulse (((beat - prev_m->beat()) / prev_m->note_divisor()) + prev_m->pulse(), _frame_rate); } @@ -1450,9 +1545,11 @@ TempoMap::tempo_at_frame_locked (const Metrics& metrics, const framepos_t& frame { TempoSection* prev_t = 0; + TempoSection* t; + for (Metrics::const_iterator i = _metrics.begin(); i != _metrics.end(); ++i) { - TempoSection* t; - if ((t = dynamic_cast (*i)) != 0) { + if ((*i)->is_tempo()) { + t = static_cast (*i); if (!t->active()) { continue; } @@ -1495,7 +1592,8 @@ TempoMap::frame_at_tempo_locked (const Metrics& metrics, const Tempo& tempo) con for (i = _metrics.begin(); i != _metrics.end(); ++i) { TempoSection* t; - if ((t = dynamic_cast (*i)) != 0) { + if ((*i)->is_tempo()) { + t = static_cast (*i); if (!t->active()) { continue; @@ -1511,8 +1609,7 @@ TempoMap::frame_at_tempo_locked (const Metrics& metrics, const Tempo& tempo) con const double prev_t_ppm = prev_t->beats_per_minute() / prev_t->note_type(); if ((t_ppm > tempo_ppm && prev_t_ppm < tempo_ppm) || (t_ppm < tempo_ppm && prev_t_ppm > tempo_ppm)) { - const framepos_t ret_frame = prev_t->frame_at_tempo (tempo_ppm, prev_t->pulse(), _frame_rate); - return ret_frame; + return prev_t->frame_at_tempo (tempo_ppm, prev_t->pulse(), _frame_rate); } } prev_t = t; @@ -1565,7 +1662,8 @@ TempoMap::beat_at_pulse_locked (const Metrics& metrics, const double& pulse) con for (Metrics::const_iterator i = metrics.begin(); i != metrics.end(); ++i) { MeterSection* m; - if ((m = dynamic_cast (*i)) != 0) { + if (!(*i)->is_tempo()) { + m = static_cast (*i); if (prev_m && m->pulse() > pulse) { if (((pulse - prev_m->pulse()) * prev_m->note_divisor()) + prev_m->beat() > m->beat()) { break; @@ -1580,7 +1678,7 @@ TempoMap::beat_at_pulse_locked (const Metrics& metrics, const double& pulse) con } double -TempoMap::pulse_at_frame (const framecnt_t& frame) const +TempoMap::pulse_at_frame (const framepos_t& frame) const { Glib::Threads::RWLock::ReaderLock lm (lock); return pulse_at_frame_locked (_metrics, frame); @@ -1588,14 +1686,15 @@ TempoMap::pulse_at_frame (const framecnt_t& frame) const /* tempo section based */ double -TempoMap::pulse_at_frame_locked (const Metrics& metrics, const framecnt_t& frame) const +TempoMap::pulse_at_frame_locked (const Metrics& metrics, const framepos_t& frame) const { /* HOLD (at least) THE READER LOCK */ TempoSection* prev_t = 0; for (Metrics::const_iterator i = metrics.begin(); i != metrics.end(); ++i) { TempoSection* t; - if ((t = dynamic_cast (*i)) != 0) { + if ((*i)->is_tempo()) { + t = static_cast (*i); if (!t->active()) { continue; } @@ -1614,7 +1713,7 @@ TempoMap::pulse_at_frame_locked (const Metrics& metrics, const framecnt_t& frame return pulses_in_section + prev_t->pulse(); } -framecnt_t +framepos_t TempoMap::frame_at_pulse (const double& pulse) const { Glib::Threads::RWLock::ReaderLock lm (lock); @@ -1622,7 +1721,7 @@ TempoMap::frame_at_pulse (const double& pulse) const } /* tempo section based */ -framecnt_t +framepos_t TempoMap::frame_at_pulse_locked (const Metrics& metrics, const double& pulse) const { /* HOLD THE READER LOCK */ @@ -1632,7 +1731,8 @@ TempoMap::frame_at_pulse_locked (const Metrics& metrics, const double& pulse) co for (Metrics::const_iterator i = metrics.begin(); i != metrics.end(); ++i) { TempoSection* t; - if ((t = dynamic_cast (*i)) != 0) { + if ((*i)->is_tempo()) { + t = static_cast (*i); if (!t->active()) { continue; } @@ -1644,12 +1744,9 @@ TempoMap::frame_at_pulse_locked (const Metrics& metrics, const double& pulse) co } } /* must be treated as constant, irrespective of _type */ - double const pulses_in_section = pulse - prev_t->pulse(); - double const dtime = pulses_in_section * prev_t->frames_per_pulse (_frame_rate); - - framecnt_t const ret = (framecnt_t) floor (dtime) + prev_t->frame(); + double const dtime = (pulse - prev_t->pulse()) * prev_t->frames_per_pulse (_frame_rate); - return ret; + return (framecnt_t) floor (dtime) + prev_t->frame(); } double @@ -1670,9 +1767,11 @@ TempoMap::beat_at_bbt_locked (const Metrics& metrics, const Timecode::BBT_Time& /* because audio-locked meters have 'fake' integral beats, there is no pulse offset here. */ + MeterSection* m; + for (Metrics::const_iterator i = metrics.begin(); i != metrics.end(); ++i) { - MeterSection* m; - if ((m = dynamic_cast (*i)) != 0) { + if (!(*i)->is_tempo()) { + m = static_cast (*i); if (prev_m) { const double bars_to_m = (m->beat() - prev_m->beat()) / prev_m->divisions_per_bar(); if ((bars_to_m + (prev_m->bbt().bars - 1)) > (bbt.bars - 1)) { @@ -1704,10 +1803,11 @@ TempoMap::bbt_at_beat_locked (const Metrics& metrics, const double& b) const MeterSection* prev_m = 0; const double beats = max (0.0, b); - for (Metrics::const_iterator i = metrics.begin(); i != metrics.end(); ++i) { - MeterSection* m = 0; + MeterSection* m = 0; - if ((m = dynamic_cast (*i)) != 0) { + for (Metrics::const_iterator i = metrics.begin(); i != metrics.end(); ++i) { + if (!(*i)->is_tempo()) { + m = static_cast (*i); if (prev_m) { if (m->beat() > beats) { /* this is the meter after the one our beat is on*/ @@ -1766,9 +1866,11 @@ TempoMap::pulse_at_bbt_locked (const Metrics& metrics, const Timecode::BBT_Time& /* because audio-locked meters have 'fake' integral beats, there is no pulse offset here. */ + MeterSection* m; + for (Metrics::const_iterator i = metrics.begin(); i != metrics.end(); ++i) { - MeterSection* m; - if ((m = dynamic_cast (*i)) != 0) { + if (!(*i)->is_tempo()) { + m = static_cast (*i); if (prev_m) { if (m->bbt().bars > bbt.bars) { break; @@ -1798,10 +1900,12 @@ TempoMap::bbt_at_pulse_locked (const Metrics& metrics, const double& pulse) cons { MeterSection* prev_m = 0; + MeterSection* m = 0; + for (Metrics::const_iterator i = metrics.begin(); i != metrics.end(); ++i) { - MeterSection* m = 0; - if ((m = dynamic_cast (*i)) != 0) { + if (!(*i)->is_tempo()) { + m = static_cast (*i); if (prev_m) { double const pulses_to_m = m->pulse() - prev_m->pulse(); @@ -1883,9 +1987,64 @@ TempoMap::bbt_at_frame_locked (const Metrics& metrics, const framepos_t& frame) warning << string_compose (_("tempo map asked for BBT time at frame %1\n"), frame) << endmsg; return bbt; } - const double beat = beat_at_frame_locked (metrics, frame); - return bbt_at_beat_locked (metrics, beat); + const TempoSection& ts = tempo_section_at_frame_locked (metrics, frame); + MeterSection* prev_m = 0; + MeterSection* next_m = 0; + + MeterSection* m; + + for (Metrics::const_iterator i = metrics.begin(); i != metrics.end(); ++i) { + if (!(*i)->is_tempo()) { + m = static_cast (*i); + if (prev_m && m->frame() > frame) { + next_m = m; + break; + } + prev_m = m; + } + } + + double beat = prev_m->beat() + (ts.pulse_at_frame (frame, _frame_rate) - prev_m->pulse()) * prev_m->note_divisor(); + + /* handle frame before first meter */ + if (frame < prev_m->frame()) { + beat = 0.0; + } + /* audio locked meters fake their beat */ + if (next_m && next_m->beat() < beat) { + beat = next_m->beat(); + } + + beat = max (0.0, beat); + + const double beats_in_ms = beat - prev_m->beat(); + const uint32_t bars_in_ms = (uint32_t) floor (beats_in_ms / prev_m->divisions_per_bar()); + const uint32_t total_bars = bars_in_ms + (prev_m->bbt().bars - 1); + const double remaining_beats = beats_in_ms - (bars_in_ms * prev_m->divisions_per_bar()); + const double remaining_ticks = (remaining_beats - floor (remaining_beats)) * BBT_Time::ticks_per_beat; + + BBT_Time ret; + + ret.ticks = (uint32_t) floor (remaining_ticks + 0.5); + ret.beats = (uint32_t) floor (remaining_beats); + ret.bars = total_bars; + + /* 0 0 0 to 1 1 0 - based mapping*/ + ++ret.bars; + ++ret.beats; + + if (ret.ticks >= BBT_Time::ticks_per_beat) { + ++ret.beats; + ret.ticks -= BBT_Time::ticks_per_beat; + } + + if (ret.beats >= prev_m->divisions_per_bar() + 1) { + ++ret.bars; + ret.beats = 1; + } + + return ret; } framepos_t @@ -1923,7 +2082,8 @@ TempoMap::check_solved (const Metrics& metrics) const for (Metrics::const_iterator i = metrics.begin(); i != metrics.end(); ++i) { TempoSection* t; MeterSection* m; - if ((t = dynamic_cast (*i)) != 0) { + if ((*i)->is_tempo()) { + t = static_cast (*i); if (!t->active()) { continue; } @@ -1951,13 +2111,21 @@ TempoMap::check_solved (const Metrics& metrics) const prev_t = t; } - if ((m = dynamic_cast (*i)) != 0) { + if (!(*i)->is_tempo()) { + m = static_cast (*i); if (prev_m && m->position_lock_style() == AudioTime) { - TempoSection* t = const_cast(&tempo_section_at_frame_locked (metrics, m->frame() - 1)); - const double nascent_m_pulse = ((m->beat() - prev_m->beat()) / prev_m->note_divisor()) + prev_m->pulse(); - const framepos_t nascent_m_frame = t->frame_at_pulse (nascent_m_pulse, _frame_rate); - - if (t && (nascent_m_frame > m->frame() || nascent_m_frame < 0)) { + const TempoSection* t = &tempo_section_at_frame_locked (metrics, m->frame() - 1); + const framepos_t nascent_m_frame = t->frame_at_pulse (m->pulse(), _frame_rate); + /* Here we check that a preceding section of music doesn't overlap a subsequent one. + It is complicated by the fact that audio locked meters represent a discontinuity in the pulse + (they place an exact pulse at a particular time expressed only in frames). + This has the effect of shifting the calculated frame at the meter pulse (wrt the previous section of music) + away from its actual frame (which is now the frame location of the exact pulse). + This can result in the calculated frame (from the previous musical section) + differing from the exact frame by one sample. + Allow for that. + */ + if (t && (nascent_m_frame > m->frame() + 1 || nascent_m_frame < 0)) { return false; } } @@ -1975,7 +2143,8 @@ TempoMap::set_active_tempos (const Metrics& metrics, const framepos_t& frame) { for (Metrics::const_iterator i = metrics.begin(); i != metrics.end(); ++i) { TempoSection* t; - if ((t = dynamic_cast (*i)) != 0) { + if ((*i)->is_tempo()) { + t = static_cast (*i); if (!t->movable()) { t->set_active (true); continue; @@ -2003,7 +2172,8 @@ TempoMap::solve_map_frame (Metrics& imaginary, TempoSection* section, const fram /* can't move a tempo before the first meter */ for (Metrics::iterator i = imaginary.begin(); i != imaginary.end(); ++i) { MeterSection* m; - if ((m = dynamic_cast (*i)) != 0) { + if (!(*i)->is_tempo()) { + m = static_cast (*i); if (!m->movable()) { first_m_frame = m->frame(); break; @@ -2019,7 +2189,8 @@ TempoMap::solve_map_frame (Metrics& imaginary, TempoSection* section, const fram for (Metrics::iterator i = imaginary.begin(); i != imaginary.end(); ++i) { TempoSection* t; - if ((t = dynamic_cast (*i)) != 0) { + if ((*i)->is_tempo()) { + t = static_cast (*i); if (!t->active()) { continue; @@ -2054,7 +2225,7 @@ TempoMap::solve_map_frame (Metrics& imaginary, TempoSection* section, const fram } #if (0) - recompute_tempos (imaginary); + recompute_tempi (imaginary); if (check_solved (imaginary)) { return true; @@ -2066,7 +2237,7 @@ TempoMap::solve_map_frame (Metrics& imaginary, TempoSection* section, const fram MetricSectionFrameSorter fcmp; imaginary.sort (fcmp); - recompute_tempos (imaginary); + recompute_tempi (imaginary); if (check_solved (imaginary)) { return true; @@ -2085,7 +2256,8 @@ TempoMap::solve_map_pulse (Metrics& imaginary, TempoSection* section, const doub for (Metrics::iterator i = imaginary.begin(); i != imaginary.end(); ++i) { TempoSection* t; - if ((t = dynamic_cast (*i)) != 0) { + if ((*i)->is_tempo()) { + t = static_cast (*i); if (!t->active()) { continue; } @@ -2119,7 +2291,7 @@ TempoMap::solve_map_pulse (Metrics& imaginary, TempoSection* section, const doub } #if (0) - recompute_tempos (imaginary); + recompute_tempi (imaginary); if (check_solved (imaginary)) { return true; @@ -2131,7 +2303,7 @@ TempoMap::solve_map_pulse (Metrics& imaginary, TempoSection* section, const doub MetricSectionSorter cmp; imaginary.sort (cmp); - recompute_tempos (imaginary); + recompute_tempi (imaginary); /* Reordering * XX need a restriction here, but only for this case, * as audio locked tempos don't interact in the same way. @@ -2169,7 +2341,8 @@ TempoMap::solve_map_frame (Metrics& imaginary, MeterSection* section, const fram for (Metrics::const_iterator ii = imaginary.begin(); ii != imaginary.end(); ++ii) { TempoSection* t; - if ((t = dynamic_cast (*ii)) != 0) { + if ((*ii)->is_tempo()) { + t = static_cast (*ii); if ((t->locked_to_meter() || !t->movable()) && t->frame() == section->frame()) { meter_locked_tempo = t; break; @@ -2188,7 +2361,8 @@ TempoMap::solve_map_frame (Metrics& imaginary, MeterSection* section, const fram for (Metrics::iterator i = imaginary.begin(); i != imaginary.end(); ++i) { MeterSection* m; - if ((m = dynamic_cast (*i)) != 0) { + if (!(*i)->is_tempo()) { + m = static_cast (*i); if (m == section){ if (prev_m && section->movable()) { const double beats = (pulse_at_frame_locked (imaginary, frame) - prev_m->pulse()) * prev_m->note_divisor(); @@ -2297,7 +2471,8 @@ TempoMap::solve_map_bbt (Metrics& imaginary, MeterSection* section, const BBT_Ti /* disallow setting section to an existing meter's bbt */ for (Metrics::iterator i = imaginary.begin(); i != imaginary.end(); ++i) { MeterSection* m; - if ((m = dynamic_cast (*i)) != 0) { + if (!(*i)->is_tempo()) { + m = static_cast (*i); if (m != section && m->bbt().bars == when.bars) { return false; } @@ -2309,7 +2484,8 @@ TempoMap::solve_map_bbt (Metrics& imaginary, MeterSection* section, const BBT_Ti for (Metrics::iterator i = imaginary.begin(); i != imaginary.end(); ++i) { MeterSection* m; - if ((m = dynamic_cast (*i)) != 0) { + if (!(*i)->is_tempo()) { + m = static_cast (*i); pair b_bbt; double new_pulse = 0.0; @@ -2331,7 +2507,8 @@ TempoMap::solve_map_bbt (Metrics& imaginary, MeterSection* section, const BBT_Ti for (Metrics::const_iterator ii = imaginary.begin(); ii != imaginary.end(); ++ii) { TempoSection* t; - if ((t = dynamic_cast (*ii)) != 0) { + if ((*ii)->is_tempo()) { + t = static_cast (*ii); if ((t->locked_to_meter() || !t->movable()) && t->frame() == m->frame()) { meter_locked_tempo = t; break; @@ -2414,7 +2591,8 @@ TempoMap::copy_metrics_and_point (const Metrics& metrics, Metrics& copy, TempoSe for (Metrics::const_iterator i = metrics.begin(); i != metrics.end(); ++i) { TempoSection* t; MeterSection* m; - if ((t = dynamic_cast (*i)) != 0) { + if ((*i)->is_tempo()) { + t = static_cast (*i); if (t == section) { ret = new TempoSection (*t); copy.push_back (ret); @@ -2424,7 +2602,8 @@ TempoMap::copy_metrics_and_point (const Metrics& metrics, Metrics& copy, TempoSe TempoSection* cp = new TempoSection (*t); copy.push_back (cp); } - if ((m = dynamic_cast (*i)) != 0) { + if (!(*i)->is_tempo()) { + m = static_cast (*i); MeterSection* cp = new MeterSection (*m); copy.push_back (cp); } @@ -2441,12 +2620,14 @@ TempoMap::copy_metrics_and_point (const Metrics& metrics, Metrics& copy, MeterSe for (Metrics::const_iterator i = metrics.begin(); i != metrics.end(); ++i) { TempoSection* t; MeterSection* m; - if ((t = dynamic_cast (*i)) != 0) { + if ((*i)->is_tempo()) { + t = static_cast (*i); TempoSection* cp = new TempoSection (*t); copy.push_back (cp); } - if ((m = dynamic_cast (*i)) != 0) { + if (!(*i)->is_tempo()) { + m = static_cast (*i); if (m == section) { ret = new MeterSection (*m); copy.push_back (ret); @@ -2545,22 +2726,9 @@ TempoMap::gui_move_tempo (TempoSection* ts, const framepos_t& frame, const int& /* if we're snapping to a musical grid, set the pulse exactly instead of via the supplied frame. */ Glib::Threads::RWLock::WriterLock lm (lock); TempoSection* tempo_copy = copy_metrics_and_point (_metrics, future_map, ts); - double beat = beat_at_frame_locked (future_map, frame); - - if (sub_num > 1) { - beat = floor (beat) + (floor (((beat - floor (beat)) * (double) sub_num) + 0.5) / sub_num); - } else if (sub_num == 1) { - /* snap to beat */ - beat = floor (beat + 0.5); - } - + const double beat = exact_beat_at_frame_locked (future_map, frame, sub_num); double pulse = pulse_at_beat_locked (future_map, beat); - if (sub_num == -1) { - /* snap to bar */ - pulse = floor (pulse + 0.5); - } - if (solve_map_pulse (future_map, tempo_copy, pulse)) { solve_map_pulse (_metrics, ts, pulse); recompute_meters (_metrics); @@ -2605,7 +2773,7 @@ TempoMap::gui_move_meter (MeterSection* ms, const framepos_t& frame) if (solve_map_frame (future_map, copy, frame)) { solve_map_frame (_metrics, ms, frame); - recompute_tempos (_metrics); + recompute_tempi (_metrics); } } } else { @@ -2618,7 +2786,7 @@ TempoMap::gui_move_meter (MeterSection* ms, const framepos_t& frame) if (solve_map_bbt (future_map, copy, bbt)) { solve_map_bbt (_metrics, ms, bbt); - recompute_tempos (_metrics); + recompute_tempi (_metrics); } } } @@ -2641,7 +2809,7 @@ TempoMap::gui_change_tempo (TempoSection* ts, const Tempo& bpm) Glib::Threads::RWLock::WriterLock lm (lock); TempoSection* tempo_copy = copy_metrics_and_point (_metrics, future_map, ts); tempo_copy->set_beats_per_minute (bpm.beats_per_minute()); - recompute_tempos (future_map); + recompute_tempi (future_map); if (check_solved (future_map)) { ts->set_beats_per_minute (bpm.beats_per_minute()); @@ -2692,7 +2860,8 @@ TempoMap::gui_dilate_tempo (TempoSection* ts, const framepos_t& frame, const fra TempoSection* next_t = 0; for (Metrics::iterator i = future_map.begin(); i != future_map.end(); ++i) { TempoSection* t = 0; - if ((t = dynamic_cast (*i)) != 0) { + if ((*i)->is_tempo()) { + t = static_cast (*i); if (t->frame() > ts->frame()) { next_t = t; break; @@ -2789,12 +2958,12 @@ TempoMap::gui_dilate_tempo (TempoSection* ts, const framepos_t& frame, const fra } new_bpm = min (new_bpm, (double) 1000.0); prev_t->set_beats_per_minute (new_bpm); - recompute_tempos (future_map); + recompute_tempi (future_map); recompute_meters (future_map); if (check_solved (future_map)) { ts->set_beats_per_minute (new_bpm); - recompute_tempos (_metrics); + recompute_tempi (_metrics); recompute_meters (_metrics); } } @@ -2808,6 +2977,33 @@ TempoMap::gui_dilate_tempo (TempoSection* ts, const framepos_t& frame, const fra MetricPositionChanged (); // Emit Signal } +double +TempoMap::exact_beat_at_frame (const framepos_t& frame, const int32_t sub_num) +{ + Glib::Threads::RWLock::ReaderLock lm (lock); + + return exact_beat_at_frame_locked (_metrics, frame, sub_num); +} + +double +TempoMap::exact_beat_at_frame_locked (const Metrics& metrics, const framepos_t& frame, const int32_t sub_num) +{ + double beat = beat_at_frame_locked (metrics, frame); + if (sub_num > 1) { + beat = floor (beat) + (floor (((beat - floor (beat)) * (double) sub_num) + 0.5) / sub_num); + } else if (sub_num == 1) { + /* snap to beat */ + beat = floor (beat + 0.5); + } else if (sub_num == -1) { + /* snap to bar */ + Timecode::BBT_Time bbt = bbt_at_beat_locked (metrics, beat); + bbt.beats = 1; + bbt.ticks = 0; + beat = beat_at_bbt_locked (metrics, bbt); + } + return beat; +} + framecnt_t TempoMap::bbt_duration_at (framepos_t pos, const BBT_Time& bbt, int dir) { @@ -3018,13 +3214,14 @@ TempoMap::tempo_section_at_frame (framepos_t frame) const const TempoSection& TempoMap::tempo_section_at_frame_locked (const Metrics& metrics, framepos_t frame) const { - Metrics::const_iterator i; TempoSection* prev = 0; - for (i = metrics.begin(); i != metrics.end(); ++i) { - TempoSection* t; + TempoSection* t; - if ((t = dynamic_cast (*i)) != 0) { + for (Metrics::const_iterator i = metrics.begin(); i != metrics.end(); ++i) { + + if ((*i)->is_tempo()) { + t = static_cast (*i); if (!t->active()) { continue; } @@ -3050,9 +3247,11 @@ TempoMap::tempo_section_at_beat_locked (const Metrics& metrics, const double& be TempoSection* prev_t = 0; const MeterSection* prev_m = &meter_section_at_beat_locked (metrics, beat); + TempoSection* t; + for (Metrics::const_iterator i = metrics.begin(); i != metrics.end(); ++i) { - TempoSection* t; - if ((t = dynamic_cast (*i)) != 0) { + if ((*i)->is_tempo()) { + t = static_cast (*i); if (prev_t && ((t->pulse() - prev_m->pulse()) * prev_m->note_divisor()) + prev_m->beat() > beat) { break; } @@ -3071,21 +3270,23 @@ TempoMap::frames_per_beat_at (const framepos_t& frame, const framecnt_t& sr) con { Glib::Threads::RWLock::ReaderLock lm (lock); - const TempoSection* ts_at = &tempo_section_at_frame_locked (_metrics, frame); + const TempoSection* ts_at = 0; const TempoSection* ts_after = 0; Metrics::const_iterator i; + TempoSection* t; for (i = _metrics.begin(); i != _metrics.end(); ++i) { - TempoSection* t; - if ((t = dynamic_cast (*i)) != 0) { + if ((*i)->is_tempo()) { + t = static_cast (*i); if (!t->active()) { continue; } - if ((*i)->frame() > frame) { + if (ts_at && (*i)->frame() > frame) { ts_after = t; break; } + ts_at = t; } } @@ -3102,10 +3303,12 @@ TempoMap::meter_section_at_frame_locked (const Metrics& metrics, framepos_t fram Metrics::const_iterator i; MeterSection* prev = 0; + MeterSection* m; + for (i = metrics.begin(); i != metrics.end(); ++i) { - MeterSection* m; - if ((m = dynamic_cast (*i)) != 0) { + if (!(*i)->is_tempo()) { + m = static_cast (*i); if (prev && (*i)->frame() > frame) { break; @@ -3138,7 +3341,8 @@ TempoMap::meter_section_at_beat_locked (const Metrics& metrics, const double& be for (Metrics::const_iterator i = metrics.begin(); i != metrics.end(); ++i) { MeterSection* m; - if ((m = dynamic_cast (*i)) != 0) { + if (!(*i)->is_tempo()) { + m = static_cast (*i); if (prev_m && m->beat() > beat) { break; } @@ -3372,7 +3576,7 @@ TempoMap::n_tempos() const int cnt = 0; for (Metrics::const_iterator i = _metrics.begin(); i != _metrics.end(); ++i) { - if (dynamic_cast(*i) != 0) { + if ((*i)->is_tempo()) { cnt++; } } @@ -3387,7 +3591,7 @@ TempoMap::n_meters() const int cnt = 0; for (Metrics::const_iterator i = _metrics.begin(); i != _metrics.end(); ++i) { - if (dynamic_cast(*i) != 0) { + if (!(*i)->is_tempo()) { cnt++; } }