X-Git-Url: https://main.carlh.net/gitweb/?a=blobdiff_plain;f=libs%2Fardour%2Ftempo.cc;h=48c1ae6ef006a932b8cdcb5cca09a8fd43212075;hb=eaca325ce81c63888ee70305ad384102e46757be;hp=67c3419b1b6a9f88724f3ccd68f5ab2ff677b6c5;hpb=ede4ecbb00ecc866c502454c81e711baea780ccd;p=ardour.git diff --git a/libs/ardour/tempo.cc b/libs/ardour/tempo.cc index 67c3419b1b..e2de5ba49f 100644 --- a/libs/ardour/tempo.cc +++ b/libs/ardour/tempo.cc @@ -19,17 +19,16 @@ #include #include - -#include - #include +#include -#include +#include #include "pbd/xml++.h" +#include "evoral/Beats.hpp" #include "ardour/debug.h" +#include "ardour/lmath.h" #include "ardour/tempo.h" -#include "ardour/utils.h" #include "i18n.h" #include @@ -38,24 +37,32 @@ using namespace std; using namespace ARDOUR; using namespace PBD; +using Timecode::BBT_Time; + /* _default tempo is 4/4 qtr=120 */ Meter TempoMap::_default_meter (4.0, 4.0); Tempo TempoMap::_default_tempo (120.0); -const double Meter::ticks_per_beat = 1920.0; +/***********************************************************************/ -double Tempo::frames_per_beat (nframes_t sr, const Meter& meter) const +double +Meter::frames_per_grid (const Tempo& tempo, framecnt_t sr) const { - return ((60.0 * sr) / (_beats_per_minute * meter.note_divisor()/_note_type)); -} + /* This is tempo- and meter-sensitive. The number it returns + is based on the interval between any two lines in the + grid that is constructed from tempo and meter sections. -/***********************************************************************/ + The return value IS NOT interpretable in terms of "beats". + */ + + return (60.0 * sr) / (tempo.beats_per_minute() * (_note_type/tempo.note_type())); +} double -Meter::frames_per_bar (const Tempo& tempo, nframes_t sr) const +Meter::frames_per_bar (const Tempo& tempo, framecnt_t sr) const { - return ((60.0 * sr * _beats_per_bar) / (tempo.beats_per_minute() * _note_type/tempo.note_type())); + return frames_per_grid (tempo, sr) * _divisions_per_bar; } /***********************************************************************/ @@ -67,7 +74,7 @@ TempoSection::TempoSection (const XMLNode& node) { const XMLProperty *prop; BBT_Time start; - LocaleGuard lg (X_("POSIX")); + LocaleGuard lg (X_("C")); if ((prop = node.property ("start")) == 0) { error << _("TempoSection XML node has no \"start\" property") << endmsg; @@ -110,6 +117,15 @@ TempoSection::TempoSection (const XMLNode& node) } set_movable (string_is_affirmative (prop->value())); + + if ((prop = node.property ("bar-offset")) == 0) { + _bar_offset = -1.0; + } else { + if (sscanf (prop->value().c_str(), "%lf", &_bar_offset) != 1 || _bar_offset < 0.0) { + error << _("TempoSection XML node has an illegal \"bar-offset\" value") << endmsg; + throw failed_constructor(); + } + } } XMLNode& @@ -117,7 +133,7 @@ TempoSection::get_state() const { XMLNode *root = new XMLNode (xml_state_node_name); char buf[256]; - LocaleGuard lg (X_("POSIX")); + LocaleGuard lg (X_("C")); snprintf (buf, sizeof (buf), "%" PRIu32 "|%" PRIu32 "|%" PRIu32, start().bars, @@ -128,12 +144,49 @@ TempoSection::get_state() const root->add_property ("beats-per-minute", buf); snprintf (buf, sizeof (buf), "%f", _note_type); root->add_property ("note-type", buf); + // snprintf (buf, sizeof (buf), "%f", _bar_offset); + // root->add_property ("bar-offset", buf); snprintf (buf, sizeof (buf), "%s", movable()?"yes":"no"); root->add_property ("movable", buf); return *root; } +void + +TempoSection::update_bar_offset_from_bbt (const Meter& m) +{ + _bar_offset = ((start().beats - 1) * BBT_Time::ticks_per_beat + start().ticks) / + (m.divisions_per_bar() * BBT_Time::ticks_per_beat); + + DEBUG_TRACE (DEBUG::TempoMath, string_compose ("Tempo set bar offset to %1 from %2 w/%3\n", _bar_offset, start(), m.divisions_per_bar())); +} + +void +TempoSection::update_bbt_time_from_bar_offset (const Meter& meter) +{ + BBT_Time new_start; + + if (_bar_offset < 0.0) { + /* not set yet */ + return; + } + + new_start.bars = start().bars; + + double ticks = BBT_Time::ticks_per_beat * meter.divisions_per_bar() * _bar_offset; + new_start.beats = (uint32_t) floor (ticks/BBT_Time::ticks_per_beat); + new_start.ticks = 0; /* (uint32_t) fmod (ticks, BBT_Time::ticks_per_beat); */ + + /* remember the 1-based counting properties of beats */ + new_start.beats += 1; + + DEBUG_TRACE (DEBUG::TempoMath, string_compose ("from bar offset %1 and dpb %2, ticks = %3->%4 beats = %5\n", + _bar_offset, meter.divisions_per_bar(), ticks, new_start.ticks, new_start.beats)); + + set_start (new_start); +} + /***********************************************************************/ const string MeterSection::xml_state_node_name = "Meter"; @@ -143,7 +196,7 @@ MeterSection::MeterSection (const XMLNode& node) { const XMLProperty *prop; BBT_Time start; - LocaleGuard lg (X_("POSIX")); + LocaleGuard lg (X_("C")); if ((prop = node.property ("start")) == 0) { error << _("MeterSection XML node has no \"start\" property") << endmsg; @@ -160,13 +213,17 @@ MeterSection::MeterSection (const XMLNode& node) set_start (start); - if ((prop = node.property ("beats-per-bar")) == 0) { - error << _("MeterSection XML node has no \"beats-per-bar\" property") << endmsg; - throw failed_constructor(); + /* beats-per-bar is old; divisions-per-bar is new */ + + if ((prop = node.property ("divisions-per-bar")) == 0) { + if ((prop = node.property ("beats-per-bar")) == 0) { + error << _("MeterSection XML node has no \"beats-per-bar\" or \"divisions-per-bar\" property") << endmsg; + throw failed_constructor(); + } } - if (sscanf (prop->value().c_str(), "%lf", &_beats_per_bar) != 1 || _beats_per_bar < 0.0) { - error << _("MeterSection XML node has an illegal \"beats-per-bar\" value") << endmsg; + if (sscanf (prop->value().c_str(), "%lf", &_divisions_per_bar) != 1 || _divisions_per_bar < 0.0) { + error << _("MeterSection XML node has an illegal \"beats-per-bar\" or \"divisions-per-bar\" value") << endmsg; throw failed_constructor(); } @@ -193,7 +250,7 @@ MeterSection::get_state() const { XMLNode *root = new XMLNode (xml_state_node_name); char buf[256]; - LocaleGuard lg (X_("POSIX")); + LocaleGuard lg (X_("C")); snprintf (buf, sizeof (buf), "%" PRIu32 "|%" PRIu32 "|%" PRIu32, start().bars, @@ -202,8 +259,8 @@ MeterSection::get_state() const root->add_property ("start", buf); snprintf (buf, sizeof (buf), "%f", _note_type); root->add_property ("note-type", buf); - snprintf (buf, sizeof (buf), "%f", _beats_per_bar); - root->add_property ("beats-per-bar", buf); + snprintf (buf, sizeof (buf), "%f", _divisions_per_bar); + root->add_property ("divisions-per-bar", buf); snprintf (buf, sizeof (buf), "%s", movable()?"yes":"no"); root->add_property ("movable", buf); @@ -218,11 +275,9 @@ struct MetricSectionSorter { } }; -TempoMap::TempoMap (nframes_t fr) +TempoMap::TempoMap (framecnt_t fr) { - metrics = new Metrics; _frame_rate = fr; - last_bbt_valid = false; BBT_Time start; start.bars = 1; @@ -230,317 +285,349 @@ TempoMap::TempoMap (nframes_t fr) start.ticks = 0; TempoSection *t = new TempoSection (start, _default_tempo.beats_per_minute(), _default_tempo.note_type()); - MeterSection *m = new MeterSection (start, _default_meter.beats_per_bar(), _default_meter.note_divisor()); + MeterSection *m = new MeterSection (start, _default_meter.divisions_per_bar(), _default_meter.note_divisor()); t->set_movable (false); m->set_movable (false); /* note: frame time is correct (zero) for both of these */ - metrics->push_back (t); - metrics->push_back (m); + metrics.push_back (t); + metrics.push_back (m); } TempoMap::~TempoMap () { } -int -TempoMap::move_metric_section (MetricSection& section, const BBT_Time& when) +void +TempoMap::remove_tempo (const TempoSection& tempo, bool complete_operation) { - if (when == section.start() || !section.movable()) { - return -1; - } - - Glib::RWLock::WriterLock lm (lock); - MetricSectionSorter cmp; - - if (when.beats != 1) { - - /* position by audio frame, then recompute BBT timestamps from the audio ones */ - - framepos_t frame = frame_time (when); - // cerr << "nominal frame time = " << frame << endl; - - framepos_t prev_frame = round_to_type (frame, -1, Beat); - framepos_t next_frame = round_to_type (frame, 1, Beat); - - // cerr << "previous beat at " << prev_frame << " next at " << next_frame << endl; - - /* use the closest beat */ + bool removed = false; - if ((frame - prev_frame) < (next_frame - frame)) { - frame = prev_frame; - } else { - frame = next_frame; + { + Glib::Threads::RWLock::WriterLock lm (lock); + if ((removed = remove_tempo_locked (tempo))) { + if (complete_operation) { + recompute_map (true); + } } - - // cerr << "actual frame time = " << frame << endl; - section.set_frame (frame); - // cerr << "frame time = " << section.frame() << endl; - timestamp_metrics (false); - // cerr << "new BBT time = " << section.start() << endl; - metrics->sort (cmp); - - } else { - - /* positioned at bar start already, so just put it there */ - - section.set_start (when); - metrics->sort (cmp); - timestamp_metrics (true); } - - return 0; -} - -void -TempoMap::move_tempo (TempoSection& tempo, const BBT_Time& when) -{ - if (move_metric_section (tempo, when) == 0) { + if (removed && complete_operation) { PropertyChanged (PropertyChange ()); } } -void -TempoMap::move_meter (MeterSection& meter, const BBT_Time& when) +bool +TempoMap::remove_tempo_locked (const TempoSection& tempo) { - if (move_metric_section (meter, when) == 0) { - PropertyChanged (PropertyChange ()); + Metrics::iterator i; + + for (i = metrics.begin(); i != metrics.end(); ++i) { + if (dynamic_cast (*i) != 0) { + if (tempo.frame() == (*i)->frame()) { + if ((*i)->movable()) { + metrics.erase (i); + return true; + } + } + } } + + return false; } void -TempoMap::remove_tempo (const TempoSection& tempo) +TempoMap::remove_meter (const MeterSection& tempo, bool complete_operation) { bool removed = false; { - Glib::RWLock::WriterLock lm (lock); - Metrics::iterator i; - - for (i = metrics->begin(); i != metrics->end(); ++i) { - if (dynamic_cast (*i) != 0) { - if (tempo.frame() == (*i)->frame()) { - if ((*i)->movable()) { - metrics->erase (i); - removed = true; - break; - } - } + Glib::Threads::RWLock::WriterLock lm (lock); + if ((removed = remove_meter_locked (tempo))) { + if (complete_operation) { + recompute_map (true); } } } - if (removed) { + if (removed && complete_operation) { PropertyChanged (PropertyChange ()); } } -void -TempoMap::remove_meter (const MeterSection& tempo) +bool +TempoMap::remove_meter_locked (const MeterSection& tempo) { - bool removed = false; - - { - Glib::RWLock::WriterLock lm (lock); - Metrics::iterator i; + Metrics::iterator i; - for (i = metrics->begin(); i != metrics->end(); ++i) { - if (dynamic_cast (*i) != 0) { - if (tempo.frame() == (*i)->frame()) { - if ((*i)->movable()) { - metrics->erase (i); - removed = true; - break; - } + for (i = metrics.begin(); i != metrics.end(); ++i) { + if (dynamic_cast (*i) != 0) { + if (tempo.frame() == (*i)->frame()) { + if ((*i)->movable()) { + metrics.erase (i); + return true; } } } } - if (removed) { - PropertyChanged (PropertyChange ()); - } + return false; } void -TempoMap::do_insert (MetricSection* section, bool with_bbt) +TempoMap::do_insert (MetricSection* section) { - Metrics::iterator i; + bool need_add = true; + + assert (section->start().ticks == 0); + + /* we only allow new meters to be inserted on beat 1 of an existing + * measure. + */ + + if (dynamic_cast(section)) { + + /* we need to (potentially) update the BBT times of tempo + sections based on this new meter. + */ + + if ((section->start().beats != 1) || (section->start().ticks != 0)) { + + BBT_Time corrected = section->start(); + corrected.beats = 1; + corrected.ticks = 0; + + warning << string_compose (_("Meter changes can only be positioned on the first beat of a bar. Moving from %1 to %2"), + section->start(), corrected) << endmsg; + + section->set_start (corrected); + } + } + + /* Look for any existing MetricSection that is of the same type and - at the same time as the new one, and remove it before adding - the new one. + in the same bar as the new one, and remove it before adding + the new one. Note that this means that if we find a matching, + existing section, we can break out of the loop since we're + guaranteed that there is only one such match. */ - Metrics::iterator to_remove = metrics->end (); + for (Metrics::iterator i = metrics.begin(); i != metrics.end(); ++i) { - for (i = metrics->begin(); i != metrics->end(); ++i) { + bool const iter_is_tempo = dynamic_cast (*i) != 0; + bool const insert_is_tempo = dynamic_cast (section) != 0; - int const c = (*i)->compare (section, with_bbt); + if (iter_is_tempo && insert_is_tempo) { - if (c < 0) { - /* this section is before the one to be added; go back round */ - continue; - } else if (c > 0) { - /* this section is after the one to be added; there can't be any at the same time */ - break; - } + /* Tempo sections */ - /* hacky comparison of type */ - bool const a = dynamic_cast (*i) != 0; - bool const b = dynamic_cast (section) != 0; + if ((*i)->start().bars == section->start().bars && + (*i)->start().beats == section->start().beats) { - if (a == b) { - to_remove = i; - break; + if (!(*i)->movable()) { + + /* can't (re)move this section, so overwrite + * its data content (but not its properties as + * a section). + */ + + *(dynamic_cast(*i)) = *(dynamic_cast(section)); + need_add = false; + } else { + metrics.erase (i); + } + break; + } + + } else if (!iter_is_tempo && !insert_is_tempo) { + + /* Meter Sections */ + + if ((*i)->start().bars == section->start().bars) { + + if (!(*i)->movable()) { + + /* can't (re)move this section, so overwrite + * its data content (but not its properties as + * a section + */ + + *(dynamic_cast(*i)) = *(dynamic_cast(section)); + need_add = false; + } else { + metrics.erase (i); + + } + + break; + } + } else { + /* non-matching types, so we don't care */ } } - if (to_remove != metrics->end()) { - /* remove the MetricSection at the same time as the one we are about to add */ - metrics->erase (to_remove); - } + /* Add the given MetricSection, if we didn't just reset an existing + * one above + */ - /* Add the given MetricSection */ + if (need_add) { - for (i = metrics->begin(); i != metrics->end(); ++i) { + Metrics::iterator i; - if ((*i)->compare (section, with_bbt) < 0) { - continue; + for (i = metrics.begin(); i != metrics.end(); ++i) { + if ((*i)->start() > section->start()) { + break; + } } - metrics->insert (i, section); - break; - } - - if (i == metrics->end()) { - metrics->insert (metrics->end(), section); + metrics.insert (i, section); } - - timestamp_metrics (with_bbt); } void -TempoMap::add_tempo (const Tempo& tempo, BBT_Time where) +TempoMap::replace_tempo (const TempoSection& ts, const Tempo& tempo, const BBT_Time& where) { { - Glib::RWLock::WriterLock lm (lock); - - /* new tempos always start on a beat */ - where.ticks = 0; + Glib::Threads::RWLock::WriterLock lm (lock); + TempoSection& first (first_tempo()); - do_insert (new TempoSection (where, tempo.beats_per_minute(), tempo.note_type()), true); + if (ts.start() != first.start()) { + remove_tempo_locked (ts); + add_tempo_locked (tempo, where, true); + } else { + { + /* cannot move the first tempo section */ + *static_cast(&first) = tempo; + recompute_map (false); + } + } } PropertyChanged (PropertyChange ()); } void -TempoMap::add_tempo (const Tempo& tempo, framepos_t where) +TempoMap::add_tempo (const Tempo& tempo, BBT_Time where) { { - Glib::RWLock::WriterLock lm (lock); - do_insert (new TempoSection (where, tempo.beats_per_minute(), tempo.note_type()), false); + Glib::Threads::RWLock::WriterLock lm (lock); + add_tempo_locked (tempo, where, true); } + PropertyChanged (PropertyChange ()); } void -TempoMap::replace_tempo (TempoSection& existing, const Tempo& replacement) +TempoMap::add_tempo_locked (const Tempo& tempo, BBT_Time where, bool recompute) { - bool replaced = false; + /* new tempos always start on a beat */ + where.ticks = 0; - { - Glib::RWLock::WriterLock lm (lock); - Metrics::iterator i; + TempoSection* ts = new TempoSection (where, tempo.beats_per_minute(), tempo.note_type()); + + /* find the meter to use to set the bar offset of this + * tempo section. + */ + + const Meter* meter = &first_meter(); + + /* as we start, we are *guaranteed* to have m.meter and m.tempo pointing + at something, because we insert the default tempo and meter during + TempoMap construction. - for (i = metrics->begin(); i != metrics->end(); ++i) { - TempoSection *ts; + now see if we can find better candidates. + */ - if ((ts = dynamic_cast(*i)) != 0 && ts == &existing) { + for (Metrics::const_iterator i = metrics.begin(); i != metrics.end(); ++i) { - *((Tempo *) ts) = replacement; + const MeterSection* m; - replaced = true; - timestamp_metrics (true); + if (where < (*i)->start()) { + break; + } - break; - } + if ((m = dynamic_cast(*i)) != 0) { + meter = m; } } - if (replaced) { - PropertyChanged (PropertyChange ()); + ts->update_bar_offset_from_bbt (*meter); + + /* and insert it */ + + do_insert (ts); + + if (recompute) { + recompute_map (false); } } void -TempoMap::add_meter (const Meter& meter, BBT_Time where) +TempoMap::replace_meter (const MeterSection& ms, const Meter& meter, const BBT_Time& where) { { - Glib::RWLock::WriterLock lm (lock); - - /* a new meter always starts a new bar on the first beat. so - round the start time appropriately. remember that - `where' is based on the existing tempo map, not - the result after we insert the new meter. - - */ + Glib::Threads::RWLock::WriterLock lm (lock); + MeterSection& first (first_meter()); - if (where.beats != 1) { - where.beats = 1; - where.bars++; + if (ms.start() != first.start()) { + remove_meter_locked (ms); + add_meter_locked (meter, where, true); + } else { + /* cannot move the first meter section */ + *static_cast(&first) = meter; + recompute_map (true); } - - /* new meters *always* start on a beat. */ - where.ticks = 0; - - do_insert (new MeterSection (where, meter.beats_per_bar(), meter.note_divisor()), true); } PropertyChanged (PropertyChange ()); } void -TempoMap::add_meter (const Meter& meter, framepos_t where) +TempoMap::add_meter (const Meter& meter, BBT_Time where) { { - Glib::RWLock::WriterLock lm (lock); - do_insert (new MeterSection (where, meter.beats_per_bar(), meter.note_divisor()), false); + Glib::Threads::RWLock::WriterLock lm (lock); + add_meter_locked (meter, where, true); } + +#ifndef NDEBUG + if (DEBUG_ENABLED(DEBUG::TempoMap)) { + dump (std::cerr); + } +#endif + PropertyChanged (PropertyChange ()); } void -TempoMap::replace_meter (MeterSection& existing, const Meter& replacement) +TempoMap::add_meter_locked (const Meter& meter, BBT_Time where, bool recompute) { - bool replaced = false; + /* a new meter always starts a new bar on the first beat. so + round the start time appropriately. remember that + `where' is based on the existing tempo map, not + the result after we insert the new meter. - { - Glib::RWLock::WriterLock lm (lock); - Metrics::iterator i; + */ - for (i = metrics->begin(); i != metrics->end(); ++i) { - MeterSection *ms; - if ((ms = dynamic_cast(*i)) != 0 && ms == &existing) { + if (where.beats != 1) { + where.beats = 1; + where.bars++; + } - *((Meter*) ms) = replacement; + /* new meters *always* start on a beat. */ + where.ticks = 0; - replaced = true; - timestamp_metrics (true); - break; - } - } - } + do_insert (new MeterSection (where, meter.divisions_per_bar(), meter.note_divisor())); - if (replaced) { - PropertyChanged (PropertyChange ()); + if (recompute) { + recompute_map (true); } + } void @@ -549,9 +636,13 @@ TempoMap::change_initial_tempo (double beats_per_minute, double note_type) Tempo newtempo (beats_per_minute, note_type); TempoSection* t; - for (Metrics::iterator i = metrics->begin(); i != metrics->end(); ++i) { + for (Metrics::iterator i = metrics.begin(); i != metrics.end(); ++i) { if ((t = dynamic_cast (*i)) != 0) { - *((Tempo*) t) = newtempo; + { + Glib::Threads::RWLock::WriterLock lm (lock); + *((Tempo*) t) = newtempo; + recompute_map (false); + } PropertyChanged (PropertyChange ()); break; } @@ -570,7 +661,7 @@ TempoMap::change_existing_tempo_at (framepos_t where, double beats_per_minute, d /* find the TempoSection immediately preceding "where" */ - for (first = 0, i = metrics->begin(), prev = 0; i != metrics->end(); ++i) { + for (first = 0, i = metrics.begin(), prev = 0; i != metrics.end(); ++i) { if ((*i)->frame() > where) { break; @@ -597,7 +688,13 @@ TempoMap::change_existing_tempo_at (framepos_t where, double beats_per_minute, d /* reset */ - *((Tempo*)prev) = newtempo; + { + Glib::Threads::RWLock::WriterLock lm (lock); + /* cannot move the first tempo section */ + *((Tempo*)prev) = newtempo; + recompute_map (false); + } + PropertyChanged (PropertyChange ()); } @@ -606,14 +703,32 @@ TempoMap::first_meter () const { const MeterSection *m = 0; - for (Metrics::const_iterator i = metrics->begin(); i != metrics->end(); ++i) { + for (Metrics::const_iterator i = metrics.begin(); i != metrics.end(); ++i) { if ((m = dynamic_cast (*i)) != 0) { return *m; } } fatal << _("programming error: no tempo section in tempo map!") << endmsg; - /*NOTREACHED*/ + abort(); /*NOTREACHED*/ + return *m; +} + +MeterSection& +TempoMap::first_meter () +{ + MeterSection *m = 0; + + /* CALLER MUST HOLD LOCK */ + + for (Metrics::iterator i = metrics.begin(); i != metrics.end(); ++i) { + if ((m = dynamic_cast (*i)) != 0) { + return *m; + } + } + + fatal << _("programming error: no tempo section in tempo map!") << endmsg; + abort(); /*NOTREACHED*/ return *m; } @@ -622,899 +737,862 @@ TempoMap::first_tempo () const { const TempoSection *t = 0; - for (Metrics::const_iterator i = metrics->begin(); i != metrics->end(); ++i) { + /* CALLER MUST HOLD LOCK */ + + for (Metrics::const_iterator i = metrics.begin(); i != metrics.end(); ++i) { if ((t = dynamic_cast (*i)) != 0) { return *t; } } fatal << _("programming error: no tempo section in tempo map!") << endmsg; - /*NOTREACHED*/ + abort(); /*NOTREACHED*/ return *t; } -void -TempoMap::timestamp_metrics (bool use_bbt) +TempoSection& +TempoMap::first_tempo () { - Metrics::iterator i; - const Meter* meter; - const Tempo* tempo; - Meter *m; - Tempo *t; + TempoSection *t = 0; - meter = &first_meter (); - tempo = &first_tempo (); + for (Metrics::const_iterator i = metrics.begin(); i != metrics.end(); ++i) { + if ((t = dynamic_cast (*i)) != 0) { + return *t; + } + } - if (use_bbt) { + fatal << _("programming error: no tempo section in tempo map!") << endmsg; + abort(); /*NOTREACHED*/ + return *t; +} - // cerr << "\n\n\n ######################\nTIMESTAMP via BBT ##############\n" << endl; +void +TempoMap::require_map_to (framepos_t pos) +{ + Glib::Threads::RWLock::WriterLock lm (lock); - framepos_t current = 0; - framepos_t section_frames; - BBT_Time start; - BBT_Time end; + if (_map.empty() || _map.back().frame < pos) { + extend_map (pos); + } +} - for (i = metrics->begin(); i != metrics->end(); ++i) { +void +TempoMap::require_map_to (const BBT_Time& bbt) +{ + Glib::Threads::RWLock::WriterLock lm (lock); - end = (*i)->start(); + /* since we have no idea where BBT is if its off the map, see the last + * point in the map is past BBT, and if not add an arbitrary amount of + * time until it is. + */ - section_frames = count_frames_between_metrics (*meter, *tempo, start, end); + int additional_minutes = 1; - current += section_frames; + while (1) { + if (!_map.empty() && _map.back().bar >= (bbt.bars + 1)) { + break; + } + /* add some more distance, using bigger steps each time */ + extend_map (_map.back().frame + (_frame_rate * 60 * additional_minutes)); + additional_minutes *= 2; + } +} - start = end; +void +TempoMap::recompute_map (bool reassign_tempo_bbt, framepos_t end) +{ + /* CALLER MUST HOLD WRITE LOCK */ - (*i)->set_frame (current); + MeterSection* meter = 0; + TempoSection* tempo = 0; + double current_frame; + BBT_Time current; + Metrics::iterator next_metric; - if ((t = dynamic_cast(*i)) != 0) { - tempo = t; - } else if ((m = dynamic_cast(*i)) != 0) { - meter = m; - } else { - fatal << _("programming error: unhandled MetricSection type") << endmsg; - /*NOTREACHED*/ - } - } + if (end < 0) { + + /* we will actually stop once we hit + the last metric. + */ + end = max_framepos; } else { + if (!_map.empty ()) { + /* never allow the map to be shortened */ + end = max (end, _map.back().frame); + } + } - // cerr << "\n\n\n ######################\nTIMESTAMP via AUDIO ##############\n" << endl; + DEBUG_TRACE (DEBUG::TempoMath, string_compose ("recomputing tempo map, zero to %1\n", end)); - bool first = true; - MetricSection* prev = 0; + for (Metrics::iterator i = metrics.begin(); i != metrics.end(); ++i) { + MeterSection* ms; - for (i = metrics->begin(); i != metrics->end(); ++i) { + if ((ms = dynamic_cast (*i)) != 0) { + meter = ms; + break; + } + } - BBT_Time bbt; - TempoMetric metric (*meter, *tempo); + assert(meter); - if (prev) { - metric.set_start (prev->start()); - metric.set_frame (prev->frame()); - } else { - // metric will be at frames=0 bbt=1|1|0 by default - // which is correct for our purpose - } + for (Metrics::iterator i = metrics.begin(); i != metrics.end(); ++i) { + TempoSection* ts; - bbt_time_with_metric ((*i)->frame(), bbt, metric); + if ((ts = dynamic_cast (*i)) != 0) { + tempo = ts; + break; + } + } - // cerr << "timestamp @ " << (*i)->frame() << " with " << bbt.bars << "|" << bbt.beats << "|" << bbt.ticks << " => "; + assert(tempo); + /* assumes that the first meter & tempo are at frame zero */ + current_frame = 0; + meter->set_frame (0); + tempo->set_frame (0); - if (first) { - first = false; - } else { + /* assumes that the first meter & tempo are at 1|1|0 */ + current.bars = 1; + current.beats = 1; + current.ticks = 0; - if (bbt.ticks > Meter::ticks_per_beat/2) { - /* round up to next beat */ - bbt.beats += 1; - } + if (reassign_tempo_bbt) { - bbt.ticks = 0; + MeterSection* rmeter = meter; - if (bbt.beats != 1) { - /* round up to next bar */ - bbt.bars += 1; - bbt.beats = 1; - } - } + DEBUG_TRACE (DEBUG::TempoMath, "\tUpdating tempo marks BBT time from bar offset\n"); - //s cerr << bbt.bars << "|" << bbt.beats << "|" << bbt.ticks << endl; + for (Metrics::iterator i = metrics.begin(); i != metrics.end(); ++i) { - (*i)->set_start (bbt); + TempoSection* ts; + MeterSection* ms; - if ((t = dynamic_cast(*i)) != 0) { - tempo = t; - // cerr << "NEW TEMPO, frame = " << (*i)->frame() << " start = " << (*i)->start() <(*i)) != 0) { - meter = m; - // cerr << "NEW METER, frame = " << (*i)->frame() << " start = " << (*i)->start() <(*i)) != 0) { + + /* reassign the BBT time of this tempo section + * based on its bar offset position. + */ + + ts->update_bbt_time_from_bar_offset (*rmeter); + + } else if ((ms = dynamic_cast(*i)) != 0) { + rmeter = ms; } else { fatal << _("programming error: unhandled MetricSection type") << endmsg; - /*NOTREACHED*/ + abort(); /*NOTREACHED*/ } - - prev = (*i); } } - // dump (cerr); - // cerr << "###############################################\n\n\n" << endl; - -} - -TempoMetric -TempoMap::metric_at (framepos_t frame) const -{ - TempoMetric m (first_meter(), first_tempo()); - const Meter* meter; - const Tempo* tempo; - - /* at this point, we are *guaranteed* to have m.meter and m.tempo pointing - at something, because we insert the default tempo and meter during - TempoMap construction. - - now see if we can find better candidates. - */ + DEBUG_TRACE (DEBUG::TempoMath, string_compose ("start with meter = %1 tempo = %2\n", *((Meter*)meter), *((Tempo*)tempo))); - for (Metrics::const_iterator i = metrics->begin(); i != metrics->end(); ++i) { + next_metric = metrics.begin(); + ++next_metric; // skip meter (or tempo) + ++next_metric; // skip tempo (or meter) - if ((*i)->frame() > frame) { - break; - } + _map.clear (); - if ((tempo = dynamic_cast(*i)) != 0) { - m.set_tempo (*tempo); - } else if ((meter = dynamic_cast(*i)) != 0) { - m.set_meter (*meter); - } + DEBUG_TRACE (DEBUG::TempoMath, string_compose ("Add first bar at 1|1 @ %2\n", current.bars, current_frame)); + _map.push_back (BBTPoint (*meter, *tempo,(framepos_t) llrint(current_frame), 1, 1)); - m.set_frame ((*i)->frame ()); - m.set_start ((*i)->start ()); + if (end == 0) { + /* silly call from Session::process() during startup + */ + return; } - return m; + _extend_map (tempo, meter, next_metric, current, current_frame, end); } -TempoMetric -TempoMap::metric_at (BBT_Time bbt) const +void +TempoMap::extend_map (framepos_t end) { - TempoMetric m (first_meter(), first_tempo()); - const Meter* meter; - const Tempo* tempo; + /* CALLER MUST HOLD WRITE LOCK */ - /* at this point, we are *guaranteed* to have m.meter and m.tempo pointing - at something, because we insert the default tempo and meter during - TempoMap construction. + if (_map.empty()) { + recompute_map (false, end); + return; + } - now see if we can find better candidates. - */ + BBTPointList::const_iterator i = _map.end(); + Metrics::iterator next_metric; - for (Metrics::const_iterator i = metrics->begin(); i != metrics->end(); ++i) { + --i; - BBT_Time section_start ((*i)->start()); + BBT_Time last_metric_start; - if (section_start.bars > bbt.bars || (section_start.bars == bbt.bars && section_start.beats > bbt.beats)) { - break; - } + if ((*i).tempo->frame() > (*i).meter->frame()) { + last_metric_start = (*i).tempo->start(); + } else { + last_metric_start = (*i).meter->start(); + } - if ((tempo = dynamic_cast(*i)) != 0) { - m.set_tempo (*tempo); - } else if ((meter = dynamic_cast(*i)) != 0) { - m.set_meter (*meter); - } + /* find the metric immediately after the tempo + meter sections for the + * last point in the map + */ - m.set_frame ((*i)->frame ()); - m.set_start (section_start); + for (next_metric = metrics.begin(); next_metric != metrics.end(); ++next_metric) { + if ((*next_metric)->start() > last_metric_start) { + break; + } } - return m; -} + /* we cast away const here because this is the one place where we need + * to actually modify the frame time of each metric section. + */ -void -TempoMap::bbt_time (framepos_t frame, BBT_Time& bbt) const -{ - { - Glib::RWLock::ReaderLock lm (lock); - bbt_time_unlocked (frame, bbt); - } + _extend_map (const_cast ((*i).tempo), + const_cast ((*i).meter), + next_metric, BBT_Time ((*i).bar, (*i).beat, 0), (*i).frame, end); } void -TempoMap::bbt_time_unlocked (framepos_t frame, BBT_Time& bbt) const +TempoMap::_extend_map (TempoSection* tempo, MeterSection* meter, + Metrics::iterator next_metric, + BBT_Time current, framepos_t current_frame, framepos_t end) { - bbt_time_with_metric (frame, bbt, metric_at (frame)); -} + /* CALLER MUST HOLD WRITE LOCK */ -void -TempoMap::bbt_time_with_metric (framepos_t frame, BBT_Time& bbt, const TempoMetric& metric) const -{ - framecnt_t frame_diff; + TempoSection* ts; + MeterSection* ms; + double beat_frames; + double current_frame_exact; + framepos_t bar_start_frame; - // cerr << "---- BBT time for " << frame << " using metric @ " << metric.frame() << " BBT " << metric.start() << endl; + DEBUG_TRACE (DEBUG::TempoMath, string_compose ("Extend map to %1 from %2 = %3\n", end, current, current_frame)); - const double beats_per_bar = metric.meter().beats_per_bar(); - const double ticks_per_frame = metric.tempo().frames_per_beat (_frame_rate, metric.meter()) / Meter::ticks_per_beat; + if (current.beats == 1) { + bar_start_frame = current_frame; + } else { + bar_start_frame = 0; + } - /* now compute how far beyond that point we actually are. */ + beat_frames = meter->frames_per_grid (*tempo,_frame_rate); + current_frame_exact = current_frame; - frame_diff = frame - metric.frame(); + while (current_frame < end) { - bbt.ticks = metric.start().ticks + (uint32_t)round((double)frame_diff / ticks_per_frame); - uint32_t xtra_beats = bbt.ticks / (uint32_t)Meter::ticks_per_beat; - bbt.ticks %= (uint32_t)Meter::ticks_per_beat; + current.beats++; + current_frame_exact += beat_frames; + current_frame = llrint(current_frame_exact); - bbt.beats = metric.start().beats + xtra_beats - 1; // correction for 1-based counting, see below for matching operation. - bbt.bars = metric.start().bars + (uint32_t)floor((double)bbt.beats / beats_per_bar); - bbt.beats = (uint32_t)fmod((double)bbt.beats, beats_per_bar); + if (current.beats > meter->divisions_per_bar()) { + current.bars++; + current.beats = 1; + } - /* if we have a fractional number of beats per bar, we see if - we're in the last beat (the fractional one). if so, we - round ticks appropriately and bump to the next bar. */ - double beat_fraction = beats_per_bar - floor(beats_per_bar); - /* XXX one problem here is that I'm not sure how to handle - fractional beats that don't evenly divide ticks_per_beat. - If they aren't handled consistently, I would guess we'll - continue to have strange discrepancies occuring. Perhaps - this will also behave badly in the case of meters like - 0.1/4, but I can't be bothered to test that. - */ - uint32_t ticks_on_last_beat = (uint32_t)floor(Meter::ticks_per_beat * beat_fraction); + if (next_metric != metrics.end()) { - if (bbt.beats > (uint32_t)floor(beats_per_bar) && bbt.ticks >= ticks_on_last_beat) { - bbt.ticks -= ticks_on_last_beat; - bbt.beats = 0; - bbt.bars++; - } + /* no operator >= so invert operator < */ - bbt.beats++; // correction for 1-based counting, see above for matching operation. + DEBUG_TRACE (DEBUG::TempoMath, string_compose ("now at %1 next metric @ %2\n", current, (*next_metric)->start())); - // cerr << "-----\t RETURN " << bbt << endl; -} + if (!(current < (*next_metric)->start())) { -framecnt_t -TempoMap::count_frames_between (const BBT_Time& start, const BBT_Time& end) const -{ - /* for this to work with fractional measure types, start and end have to be "legal" BBT types, - that means that the beats and ticks should be inside a bar - */ + set_metrics: + if (((ts = dynamic_cast (*next_metric)) != 0)) { - framecnt_t frames = 0; - framepos_t start_frame = 0; - framepos_t end_frame = 0; + tempo = ts; - TempoMetric m = metric_at (start); + /* new tempo section: if its on a beat, + * we don't have to do anything other + * than recompute various distances, + * done further below as we transition + * the next metric section. + * + * if its not on the beat, we have to + * compute the duration of the beat it + * is within, which will be different + * from the preceding following ones + * since it takes part of its duration + * from the preceding tempo and part + * from this new tempo. + */ - uint32_t bar_offset = start.bars - m.start().bars; + if (tempo->start().ticks != 0) { - double beat_offset = bar_offset*m.meter().beats_per_bar() - (m.start().beats-1) + (start.beats -1) - + start.ticks/Meter::ticks_per_beat; + double next_beat_frames = tempo->frames_per_beat (_frame_rate); + DEBUG_TRACE (DEBUG::TempoMath, string_compose ("bumped into non-beat-aligned tempo metric at %1 = %2, adjust next beat using %3\n", + tempo->start(), current_frame, tempo->bar_offset())); - start_frame = m.frame() + (framepos_t) rint( beat_offset * m.tempo().frames_per_beat(_frame_rate, m.meter())); + /* back up to previous beat */ + current_frame_exact -= beat_frames; + current_frame = llrint(current_frame_exact); - m = metric_at(end); + /* set tempo section location + * based on offset from last + * bar start + */ + tempo->set_frame (bar_start_frame + + llrint ((ts->bar_offset() * meter->divisions_per_bar() * beat_frames))); - bar_offset = end.bars - m.start().bars; + /* advance to the location of + * the new (adjusted) beat. do + * this by figuring out the + * offset within the beat that + * would have been there + * without the tempo + * change. then stretch the + * beat accordingly. + */ - beat_offset = bar_offset * m.meter().beats_per_bar() - (m.start().beats -1) + (end.beats - 1) - + end.ticks/Meter::ticks_per_beat; + double offset_within_old_beat = (tempo->frame() - current_frame) / beat_frames; - end_frame = m.frame() + (framepos_t) rint(beat_offset * m.tempo().frames_per_beat(_frame_rate, m.meter())); + current_frame_exact += (offset_within_old_beat * beat_frames) + ((1.0 - offset_within_old_beat) * next_beat_frames); + current_frame = llrint(current_frame_exact); - frames = end_frame - start_frame; + /* next metric doesn't have to + * match this precisely to + * merit a reloop ... + */ + DEBUG_TRACE (DEBUG::TempoMath, string_compose ("Adjusted last beat to %1\n", current_frame)); - return frames; + } else { -} + DEBUG_TRACE (DEBUG::TempoMath, string_compose ("bumped into beat-aligned tempo metric at %1 = %2\n", + tempo->start(), current_frame)); + tempo->set_frame (current_frame); + } -framecnt_t -TempoMap::count_frames_between_metrics (const Meter& meter, const Tempo& tempo, const BBT_Time& start, const BBT_Time& end) const -{ - /* this is used in timestamping the metrics by actually counting the beats */ + } else if ((ms = dynamic_cast(*next_metric)) != 0) { + + meter = ms; + + /* new meter section: always defines the + * start of a bar. + */ - framecnt_t frames = 0; - uint32_t bar = start.bars; - double beat = (double) start.beats; - double beats_counted = 0; - double beats_per_bar = 0; - double beat_frames = 0; + DEBUG_TRACE (DEBUG::TempoMath, string_compose ("bumped into meter section at %1 vs %2 (%3)\n", + meter->start(), current, current_frame)); - beats_per_bar = meter.beats_per_bar(); - beat_frames = tempo.frames_per_beat (_frame_rate,meter); + assert (current.beats == 1); - frames = 0; + meter->set_frame (current_frame); + } - while (bar < end.bars || (bar == end.bars && beat < end.beats)) { + beat_frames = meter->frames_per_grid (*tempo, _frame_rate); - if (beat >= beats_per_bar) { - beat = 1; - ++bar; - ++beats_counted; + DEBUG_TRACE (DEBUG::TempoMath, string_compose ("New metric with beat frames = %1 dpb %2 meter %3 tempo %4\n", + beat_frames, meter->divisions_per_bar(), *((Meter*)meter), *((Tempo*)tempo))); - if (beat > beats_per_bar) { + ++next_metric; - /* this is a fractional beat at the end of a fractional bar - so it should only count for the fraction - */ + if (next_metric != metrics.end() && ((*next_metric)->start() == current)) { + /* same position so go back and set this one up before advancing + */ + goto set_metrics; + } - beats_counted -= (ceil(beats_per_bar) - beats_per_bar); } + } + if (current.beats == 1) { + DEBUG_TRACE (DEBUG::TempoMath, string_compose ("Add Bar at %1|1 @ %2\n", current.bars, current_frame)); + _map.push_back (BBTPoint (*meter, *tempo, current_frame, current.bars, 1)); + bar_start_frame = current_frame; } else { - ++beat; - ++beats_counted; + DEBUG_TRACE (DEBUG::TempoMath, string_compose ("Add Beat at %1|%2 @ %3\n", current.bars, current.beats, current_frame)); + _map.push_back (BBTPoint (*meter, *tempo, current_frame, current.bars, current.beats)); + } + + if (next_metric == metrics.end()) { + /* no more metrics - we've timestamped them all, stop here */ + if (end == max_framepos) { + DEBUG_TRACE (DEBUG::TempoMath, string_compose ("stop extending map now that we've reach the end @ %1|%2 = %3\n", + current.bars, current.beats, current_frame)); + break; + } } } +} + +TempoMetric +TempoMap::metric_at (framepos_t frame, Metrics::const_iterator* last) const +{ + Glib::Threads::RWLock::ReaderLock lm (lock); + TempoMetric m (first_meter(), first_tempo()); + + /* at this point, we are *guaranteed* to have m.meter and m.tempo pointing + at something, because we insert the default tempo and meter during + TempoMap construction. - // cerr << "Counted " << beats_counted << " from " << start << " to " << end - // << " bpb were " << beats_per_bar - // << " fpb was " << beat_frames - // << endl; + now see if we can find better candidates. + */ - frames = (framecnt_t) llrint (floor (beats_counted * beat_frames)); + for (Metrics::const_iterator i = metrics.begin(); i != metrics.end(); ++i) { - return frames; + if ((*i)->frame() > frame) { + break; + } -} + m.set_metric(*i); -framepos_t -TempoMap::frame_time (const BBT_Time& bbt) const -{ - BBT_Time start ; /* 1|1|0 */ + if (last) { + *last = i; + } + } - return count_frames_between ( start, bbt); + return m; } -framecnt_t -TempoMap::bbt_duration_at (framepos_t pos, const BBT_Time& bbt, int dir) const +TempoMetric +TempoMap::metric_at (BBT_Time bbt) const { - framecnt_t frames = 0; + Glib::Threads::RWLock::ReaderLock lm (lock); + TempoMetric m (first_meter(), first_tempo()); - BBT_Time when; - bbt_time(pos, when); + /* at this point, we are *guaranteed* to have m.meter and m.tempo pointing + at something, because we insert the default tempo and meter during + TempoMap construction. - { - Glib::RWLock::ReaderLock lm (lock); - frames = bbt_duration_at_unlocked (when, bbt,dir); + now see if we can find better candidates. + */ + + for (Metrics::const_iterator i = metrics.begin(); i != metrics.end(); ++i) { + + BBT_Time section_start ((*i)->start()); + + if (section_start.bars > bbt.bars || (section_start.bars == bbt.bars && section_start.beats > bbt.beats)) { + break; + } + + m.set_metric (*i); } - return frames; + return m; } -framecnt_t -TempoMap::bbt_duration_at_unlocked (const BBT_Time& when, const BBT_Time& bbt, int dir) const +void +TempoMap::bbt_time (framepos_t frame, BBT_Time& bbt) { - framecnt_t frames = 0; - - double beats_per_bar; - BBT_Time result; + require_map_to (frame); - result.bars = max(1U, when.bars + dir * bbt.bars) ; - result.beats = 1; - result.ticks = 0; - - TempoMetric metric = metric_at(result); - beats_per_bar = metric.meter().beats_per_bar(); + Glib::Threads::RWLock::ReaderLock lm (lock); + if (frame < 0) { + bbt.bars = 1; + bbt.beats = 1; + bbt.ticks = 0; + warning << string_compose (_("tempo map asked for BBT time at frame %1\n"), frame) << endmsg; + return; + } + return bbt_time (frame, bbt, bbt_before_or_at (frame)); +} - /*reduce things to legal bbt values - we have to handle possible fractional=shorter beats at the end of measures - and things like 0|11|9000 as a duration in a 4.5/4 measure - the musical decision is that the fractional beat is also a beat , although a shorter one - */ +void +TempoMap::bbt_time_rt (framepos_t frame, BBT_Time& bbt) +{ + Glib::Threads::RWLock::ReaderLock lm (lock, Glib::Threads::TRY_LOCK); + if (!lm.locked()) { + throw std::logic_error ("TempoMap::bbt_time_rt() could not lock tempo map"); + } - if (dir >= 0) { - result.beats = when.beats + bbt.beats; - result.ticks = when.ticks + bbt.ticks; + if (_map.empty() || _map.back().frame < frame) { + throw std::logic_error (string_compose ("map not long enough to reach %1", frame)); + } - while (result.beats >= (beats_per_bar + 1)) { - result.bars++; - result.beats -= (uint32_t) ceil(beats_per_bar); - metric = metric_at(result); // maybe there is a meter change - beats_per_bar = metric.meter().beats_per_bar(); + return bbt_time (frame, bbt, bbt_before_or_at (frame)); +} - } - /*we now counted the beats and landed in the target measure, now deal with ticks - this seems complicated, but we want to deal with the corner case of a sequence of time signatures like 0.2/4-0.7/4 - and with request like bbt = 3|2|9000 ,so we repeat the same loop but add ticks - */ +void +TempoMap::bbt_time (framepos_t frame, BBT_Time& bbt, const BBTPointList::const_iterator& i) +{ + /* CALLER MUST HOLD READ LOCK */ - /* of course gtk_ardour only allows bar with at least 1.0 beats ..... - */ + bbt.bars = (*i).bar; + bbt.beats = (*i).beat; - uint32_t ticks_at_beat = (uint32_t) ( result.beats == ceil(beats_per_bar) ? - (1 - (ceil(beats_per_bar) - beats_per_bar))* Meter::ticks_per_beat - : Meter::ticks_per_beat ); - - while (result.ticks >= ticks_at_beat) { - result.beats++; - result.ticks -= ticks_at_beat; - if (result.beats >= (beats_per_bar + 1)) { - result.bars++; - result.beats = 1; - metric = metric_at(result); // maybe there is a meter change - beats_per_bar = metric.meter().beats_per_bar(); - } - ticks_at_beat= (uint32_t) ( result.beats == ceil(beats_per_bar) ? - (1 - (ceil(beats_per_bar) - beats_per_bar) ) * Meter::ticks_per_beat - : Meter::ticks_per_beat); + if ((*i).frame == frame) { + bbt.ticks = 0; + } else { + bbt.ticks = llrint (((frame - (*i).frame) / (*i).tempo->frames_per_beat(_frame_rate)) * + BBT_Time::ticks_per_beat); + } +} - } +framepos_t +TempoMap::frame_time (const BBT_Time& bbt) +{ + if (bbt.bars < 1) { + warning << string_compose (_("tempo map asked for frame time at bar < 1 (%1)\n"), bbt) << endmsg; + return 0; + } + if (bbt.beats < 1) { + throw std::logic_error ("beats are counted from one"); + } - } else { - uint32_t b = bbt.beats; + require_map_to (bbt); - /* count beats */ - while( b > when.beats ) { + Glib::Threads::RWLock::ReaderLock lm (lock); - result.bars = max(1U,result.bars-- ) ; - metric = metric_at(result); // maybe there is a meter change - beats_per_bar = metric.meter().beats_per_bar(); - if (b >= ceil(beats_per_bar)) { + BBTPointList::const_iterator s = bbt_before_or_at (BBT_Time (1, 1, 0)); + BBTPointList::const_iterator e = bbt_before_or_at (BBT_Time (bbt.bars, bbt.beats, 0)); - b -= (uint32_t) ceil(beats_per_bar); - } else { - b = (uint32_t) ceil(beats_per_bar) - b + when.beats ; - } - } - result.beats = when.beats - b; + if (bbt.ticks != 0) { + return ((*e).frame - (*s).frame) + + llrint ((*e).tempo->frames_per_beat (_frame_rate) * (bbt.ticks/BBT_Time::ticks_per_beat)); + } else { + return ((*e).frame - (*s).frame); + } +} - /*count ticks */ +framecnt_t +TempoMap::bbt_duration_at (framepos_t pos, const BBT_Time& bbt, int dir) +{ + BBT_Time when; + bbt_time (pos, when); - if (bbt.ticks <= when.ticks) { - result.ticks = when.ticks - bbt.ticks; - } else { + Glib::Threads::RWLock::ReaderLock lm (lock); + return bbt_duration_at_unlocked (when, bbt, dir); +} - uint32_t ticks_at_beat= (uint32_t) Meter::ticks_per_beat; - uint32_t t = bbt.ticks - when.ticks; +framecnt_t +TempoMap::bbt_duration_at_unlocked (const BBT_Time& when, const BBT_Time& bbt, int /*dir*/) +{ + if (bbt.bars == 0 && bbt.beats == 0 && bbt.ticks == 0) { + return 0; + } - do { + /* round back to the previous precise beat */ + BBTPointList::const_iterator wi = bbt_before_or_at (BBT_Time (when.bars, when.beats, 0)); + BBTPointList::const_iterator start (wi); - if (result.beats == 1) { - result.bars = max(1U, result.bars-- ) ; - metric = metric_at(result); // maybe there is a meter change - beats_per_bar = metric.meter().beats_per_bar(); - result.beats = (uint32_t) ceil(beats_per_bar); - ticks_at_beat = (uint32_t) ((1 - (ceil(beats_per_bar) - beats_per_bar)) * Meter::ticks_per_beat) ; - } else { - result.beats --; - ticks_at_beat = (uint32_t) Meter::ticks_per_beat; - } + assert (wi != _map.end()); - if (t <= ticks_at_beat) { - result.ticks = ticks_at_beat - t; - } else { - t-= ticks_at_beat; - } - } while (t > ticks_at_beat); + uint32_t bars = 0; + uint32_t beats = 0; + while (wi != _map.end() && bars < bbt.bars) { + ++wi; + if ((*wi).is_bar()) { + ++bars; } + } + assert (wi != _map.end()); - + while (wi != _map.end() && beats < bbt.beats) { + ++wi; + ++beats; } + assert (wi != _map.end()); + + /* add any additional frames related to ticks in the added value */ - if (dir < 0 ) { - frames = count_frames_between( result,when); + if (bbt.ticks != 0) { + return ((*wi).frame - (*start).frame) + + (*wi).tempo->frames_per_beat (_frame_rate) * (bbt.ticks/BBT_Time::ticks_per_beat); } else { - frames = count_frames_between(when,result); + return ((*wi).frame - (*start).frame); } - - return frames; } - - framepos_t -TempoMap::round_to_bar (framepos_t fr, int dir) +TempoMap::round_to_bar (framepos_t fr, RoundMode dir) { - { - Glib::RWLock::ReaderLock lm (lock); - return round_to_type (fr, dir, Bar); - } + return round_to_type (fr, dir, Bar); } - framepos_t -TempoMap::round_to_beat (framepos_t fr, int dir) +TempoMap::round_to_beat (framepos_t fr, RoundMode dir) { - { - Glib::RWLock::ReaderLock lm (lock); - return round_to_type (fr, dir, Beat); - } + return round_to_type (fr, dir, Beat); } framepos_t -TempoMap::round_to_beat_subdivision (framepos_t fr, int sub_num, int dir) +TempoMap::round_to_beat_subdivision (framepos_t fr, int sub_num, RoundMode dir) { + require_map_to (fr); + + Glib::Threads::RWLock::ReaderLock lm (lock); + BBTPointList::const_iterator i = bbt_before_or_at (fr); BBT_Time the_beat; - uint32_t ticks_one_half_subdivisions_worth; uint32_t ticks_one_subdivisions_worth; - uint32_t difference; - bbt_time(fr, the_beat); + bbt_time (fr, the_beat, i); + + DEBUG_TRACE (DEBUG::SnapBBT, string_compose ("round %1 to nearest 1/%2 beat, before-or-at = %3 @ %4|%5 precise = %6\n", + fr, sub_num, (*i).frame, (*i).bar, (*i).beat, the_beat)); - ticks_one_subdivisions_worth = (uint32_t)Meter::ticks_per_beat / sub_num; - ticks_one_half_subdivisions_worth = ticks_one_subdivisions_worth / 2; + ticks_one_subdivisions_worth = (uint32_t)BBT_Time::ticks_per_beat / sub_num; if (dir > 0) { - /* round to next */ + /* round to next (or same iff dir == RoundUpMaybe) */ uint32_t mod = the_beat.ticks % ticks_one_subdivisions_worth; - if (mod == 0) { + if (mod == 0 && dir == RoundUpMaybe) { + /* right on the subdivision, which is fine, so do nothing */ + + } else if (mod == 0) { /* right on the subdivision, so the difference is just the subdivision ticks */ - difference = ticks_one_subdivisions_worth; + the_beat.ticks += ticks_one_subdivisions_worth; } else { /* not on subdivision, compute distance to next subdivision */ - difference = ticks_one_subdivisions_worth - mod; + the_beat.ticks += ticks_one_subdivisions_worth - mod; + } + + if (the_beat.ticks > BBT_Time::ticks_per_beat) { + assert (i != _map.end()); + ++i; + assert (i != _map.end()); + the_beat.ticks -= BBT_Time::ticks_per_beat; } - the_beat = bbt_add (the_beat, BBT_Time (0, 0, difference)); } else if (dir < 0) { - /* round to previous */ + /* round to previous (or same iff dir == RoundDownMaybe) */ - uint32_t mod = the_beat.ticks % ticks_one_subdivisions_worth; + uint32_t difference = the_beat.ticks % ticks_one_subdivisions_worth; - if (mod == 0) { - /* right on the subdivision, so the difference is just the subdivision ticks */ + if (difference == 0 && dir == RoundDownAlways) { + /* right on the subdivision, but force-rounding down, + so the difference is just the subdivision ticks */ difference = ticks_one_subdivisions_worth; - } else { - /* not on subdivision, compute distance to previous subdivision, which - is just the modulus. - */ - - difference = mod; } - try { - the_beat = bbt_subtract (the_beat, BBT_Time (0, 0, difference)); - } catch (...) { - /* can't go backwards from wherever pos is, so just return it */ - return fr; - } - + if (the_beat.ticks < difference) { + if (i == _map.begin()) { + /* can't go backwards from wherever pos is, so just return it */ + return fr; + } + --i; + the_beat.ticks = BBT_Time::ticks_per_beat - the_beat.ticks; + } else { + the_beat.ticks -= difference; + } } else { /* round to nearest */ - if (the_beat.ticks % ticks_one_subdivisions_worth > ticks_one_half_subdivisions_worth) { - difference = ticks_one_subdivisions_worth - (the_beat.ticks % ticks_one_subdivisions_worth); - the_beat = bbt_add (the_beat, BBT_Time (0, 0, difference)); + double rem; + + /* compute the distance to the previous and next subdivision */ + + if ((rem = fmod ((double) the_beat.ticks, (double) ticks_one_subdivisions_worth)) > ticks_one_subdivisions_worth/2.0) { + + /* closer to the next subdivision, so shift forward */ + + the_beat.ticks = lrint (the_beat.ticks + (ticks_one_subdivisions_worth - rem)); + + DEBUG_TRACE (DEBUG::SnapBBT, string_compose ("moved forward to %1\n", the_beat.ticks)); + + if (the_beat.ticks > BBT_Time::ticks_per_beat) { + assert (i != _map.end()); + ++i; + assert (i != _map.end()); + the_beat.ticks -= BBT_Time::ticks_per_beat; + DEBUG_TRACE (DEBUG::SnapBBT, string_compose ("fold beat to %1\n", the_beat)); + } + + } else if (rem > 0) { + + /* closer to previous subdivision, so shift backward */ + + if (rem > the_beat.ticks) { + if (i == _map.begin()) { + /* can't go backwards past zero, so ... */ + return 0; + } + /* step back to previous beat */ + --i; + the_beat.ticks = lrint (BBT_Time::ticks_per_beat - rem); + DEBUG_TRACE (DEBUG::SnapBBT, string_compose ("step back beat to %1\n", the_beat)); + } else { + the_beat.ticks = lrint (the_beat.ticks - rem); + DEBUG_TRACE (DEBUG::SnapBBT, string_compose ("moved backward to %1\n", the_beat.ticks)); + } } else { - // difference = ticks_one_subdivisions_worth - (the_beat.ticks % ticks_one_subdivisions_worth); - the_beat.ticks -= the_beat.ticks % ticks_one_subdivisions_worth; + /* on the subdivision, do nothing */ } } - return frame_time (the_beat); + return (*i).frame + (the_beat.ticks/BBT_Time::ticks_per_beat) * + (*i).tempo->frames_per_beat (_frame_rate); } framepos_t -TempoMap::round_to_type (framepos_t frame, int dir, BBTPointType type) +TempoMap::round_to_type (framepos_t frame, RoundMode dir, BBTPointType type) { - TempoMetric metric = metric_at (frame); - BBT_Time bbt; - BBT_Time start; - BBT_Time one_bar (1,0,0); - BBT_Time one_beat (0,1,0); + require_map_to (frame); + + Glib::Threads::RWLock::ReaderLock lm (lock); + BBTPointList::const_iterator fi; + + if (dir > 0) { + fi = bbt_after_or_at (frame); + } else { + fi = bbt_before_or_at (frame); + } - bbt_time_with_metric (frame, bbt, metric); + assert (fi != _map.end()); + + DEBUG_TRACE (DEBUG::SnapBBT, string_compose ("round from %1 (%3|%4 @ %5) to %6 in direction %2\n", frame, dir, (*fi).bar, (*fi).beat, (*fi).frame, + (type == Bar ? "bar" : "beat"))); switch (type) { case Bar: - DEBUG_TRACE(DEBUG::SnapBBT, string_compose ("round from %1 (%3) to bars in direction %2\n", frame, dir, bbt)); - if (dir < 0) { + /* find bar previous to 'frame' */ - /* find bar position preceding frame */ - - try { - bbt = bbt_subtract (bbt, one_bar); + if (fi == _map.begin()) { + return 0; } - catch (...) { - return frame; + if ((*fi).is_bar() && (*fi).frame == frame) { + if (dir == RoundDownMaybe) { + return frame; + } + --fi; } + while (!(*fi).is_bar()) { + if (fi == _map.begin()) { + break; + } + fi--; + } + DEBUG_TRACE (DEBUG::SnapBBT, string_compose ("rounded to bar: map iter at %1|%2 %3, return\n", + (*fi).bar, (*fi).beat, (*fi).frame)); + return (*fi).frame; } else if (dir > 0) { - /* find bar position following frame */ + /* find bar following 'frame' */ - try { - bbt = bbt_add (bbt, one_bar, metric); + if ((*fi).is_bar() && (*fi).frame == frame) { + if (dir == RoundUpMaybe) { + return frame; + } + ++fi; } - catch (...) { - return frame; + + while (!(*fi).is_bar()) { + fi++; + if (fi == _map.end()) { + --fi; + break; + } } + DEBUG_TRACE (DEBUG::SnapBBT, string_compose ("rounded to bar: map iter at %1|%2 %3, return\n", + (*fi).bar, (*fi).beat, (*fi).frame)); + return (*fi).frame; + } else { - /* "true" rounding */ + /* true rounding: find nearest bar */ - float midbar_beats; - float midbar_ticks; + BBTPointList::const_iterator prev = fi; + BBTPointList::const_iterator next = fi; - midbar_beats = metric.meter().beats_per_bar() / 2 + 1; - midbar_ticks = Meter::ticks_per_beat * fmod (midbar_beats, 1.0f); - midbar_beats = floor (midbar_beats); - - BBT_Time midbar (bbt.bars, lrintf (midbar_beats), lrintf (midbar_ticks)); + if ((*fi).frame == frame) { + return frame; + } - if (bbt < midbar) { - /* round down */ - bbt.beats = 1; - bbt.ticks = 0; + while ((*prev).beat != 1) { + if (prev == _map.begin()) { + break; + } + prev--; + } + + while ((next != _map.end()) && (*next).beat != 1) { + next++; + } + + if ((next == _map.end()) || (frame - (*prev).frame) < ((*next).frame - frame)) { + return (*prev).frame; } else { - /* round up */ - bbt.bars++; - bbt.beats = 1; - bbt.ticks = 0; + return (*next).frame; } + } - /* force beats & ticks to their values at the start of a bar */ - bbt.beats = 1; - bbt.ticks = 0; + break; case Beat: - DEBUG_TRACE(DEBUG::SnapBBT, string_compose ("round from %1 (%3) to beat in direction %2\n", frame, (dir < 0 ? "back" : "forward"), bbt)); - if (dir < 0) { - /* find beat position preceding frame */ - - try { - bbt = bbt_subtract (bbt, one_beat); + if (fi == _map.begin()) { + return 0; } - catch (...) { - return frame; + if ((*fi).frame > frame || ((*fi).frame == frame && dir == RoundDownAlways)) { + DEBUG_TRACE (DEBUG::SnapBBT, "requested frame is on beat, step back\n"); + --fi; } - - + DEBUG_TRACE (DEBUG::SnapBBT, string_compose ("rounded to beat: map iter at %1|%2 %3, return\n", + (*fi).bar, (*fi).beat, (*fi).frame)); + return (*fi).frame; } else if (dir > 0) { - - /* find beat position following frame */ - - try { - bbt = bbt_add (bbt, one_beat, metric); + if ((*fi).frame < frame || ((*fi).frame == frame && dir == RoundUpAlways)) { + DEBUG_TRACE (DEBUG::SnapBBT, "requested frame is on beat, step forward\n"); + ++fi; } - catch (...) { + DEBUG_TRACE (DEBUG::SnapBBT, string_compose ("rounded to beat: map iter at %1|%2 %3, return\n", + (*fi).bar, (*fi).beat, (*fi).frame)); + return (*fi).frame; + } else { + /* find beat nearest to frame */ + if ((*fi).frame == frame) { return frame; } - } else { - - /* "true" rounding */ + BBTPointList::const_iterator prev = fi; + BBTPointList::const_iterator next = fi; - /* round to nearest beat */ - if (bbt.ticks >= (Meter::ticks_per_beat/2)) { + /* fi is already the beat before_or_at frame, and + we've just established that its not at frame, so its + the beat before frame. + */ + ++next; - try { - bbt = bbt_add (bbt, one_beat, metric); - } - catch (...) { - return frame; - } + if ((next == _map.end()) || (frame - (*prev).frame) < ((*next).frame - frame)) { + return (*prev).frame; + } else { + return (*next).frame; } } - /* force ticks to the value at the start of a beat */ - bbt.ticks = 0; break; - } - DEBUG_TRACE(DEBUG::SnapBBT, string_compose ("\tat %1 count frames from %2 to %3 = %4\n", metric.frame(), metric.start(), bbt, count_frames_between (metric.start(), bbt))); - return metric.frame() + count_frames_between (metric.start(), bbt); + abort(); /* NOTREACHED */ + return 0; } -TempoMap::BBTPointList * -TempoMap::get_points (framepos_t lower, framepos_t upper) const +void +TempoMap::get_grid (TempoMap::BBTPointList::const_iterator& begin, + TempoMap::BBTPointList::const_iterator& end, + framepos_t lower, framepos_t upper) { - - Metrics::const_iterator i; - BBTPointList *points; - double current; - const MeterSection* meter; - const MeterSection* m; - const TempoSection* tempo; - const TempoSection* t; - uint32_t bar; - uint32_t beat; - double beats_per_bar; - double beat_frame; - double beat_frames; - double frames_per_bar; - double delta_bars; - double delta_beats; - double dummy; - framepos_t limit; - - meter = &first_meter (); - tempo = &first_tempo (); - - /* find the starting point */ - - for (i = metrics->begin(); i != metrics->end(); ++i) { - - if ((*i)->frame() > lower) { - break; - } - - if ((t = dynamic_cast(*i)) != 0) { - tempo = t; - } else if ((m = dynamic_cast(*i)) != 0) { - meter = m; + { + Glib::Threads::RWLock::WriterLock lm (lock); + if (_map.empty() || (_map.back().frame < upper)) { + recompute_map (false, upper); } } - /* We now have: - - meter -> the Meter for "lower" - tempo -> the Tempo for "lower" - i -> for first new metric after "lower", possibly metrics->end() - - Now start generating points. - */ - - beats_per_bar = meter->beats_per_bar (); - frames_per_bar = meter->frames_per_bar (*tempo, _frame_rate); - beat_frames = tempo->frames_per_beat (_frame_rate, *meter); - - if (meter->frame() > tempo->frame()) { - bar = meter->start().bars; - beat = meter->start().beats; - current = meter->frame(); - } else { - bar = tempo->start().bars; - beat = tempo->start().beats; - current = tempo->frame(); - } - - /* initialize current to point to the bar/beat just prior to the - lower frame bound passed in. assumes that current is initialized - above to be on a beat. - */ - - delta_bars = (lower-current) / frames_per_bar; - delta_beats = modf(delta_bars, &dummy) * beats_per_bar; - current += (floor(delta_bars) * frames_per_bar) + (floor(delta_beats) * beat_frames); - - // adjust bars and beats too - bar += (uint32_t) (floor(delta_bars)); - beat += (uint32_t) (floor(delta_beats)); - - points = new BBTPointList; - - do { - - if (i == metrics->end()) { - limit = upper; - // cerr << "== limit set to end of request @ " << limit << endl; - } else { - // cerr << "== limit set to next metric @ " << (*i)->frame() << endl; - limit = (*i)->frame(); - } - - limit = min (limit, upper); - - while (current < limit) { - - /* if we're at the start of a bar, add bar point */ - - if (beat == 1) { - if (current >= lower) { - // cerr << "Add Bar at " << bar << "|1" << " @ " << current << endl; - points->push_back (BBTPoint (*meter, *tempo,(framepos_t)rint(current), Bar, bar, 1)); - - } - } - - /* add some beats if we can */ - - beat_frame = current; - - while (beat <= ceil( beats_per_bar) && beat_frame < limit) { - if (beat_frame >= lower) { - // cerr << "Add Beat at " << bar << '|' << beat << " @ " << beat_frame << endl; - points->push_back (BBTPoint (*meter, *tempo, (framepos_t) rint(beat_frame), Beat, bar, beat)); - } - beat_frame += beat_frames; - current+= beat_frames; - - beat++; - } - - // cerr << "out of beats, @ end ? " << (i == metrics->end()) << " out of bpb ? " - // << (beat > ceil(beats_per_bar)) - // << endl; - - if (beat > ceil(beats_per_bar) || i != metrics->end()) { - - /* we walked an entire bar. its - important to move `current' forward - by the actual frames_per_bar, not move it to - an integral beat_frame, so that metrics with - non-integral beats-per-bar have - their bar positions set - correctly. consider a metric with - 9-1/2 beats-per-bar. the bar we - just filled had 10 beat marks, - but the bar end is 1/2 beat before - the last beat mark. - And it is also possible that a tempo - change occured in the middle of a bar, - so we subtract the possible extra fraction from the current - */ - - if (beat > ceil (beats_per_bar)) { - /* next bar goes where the numbers suggest */ - current -= beat_frames * (ceil(beats_per_bar)-beats_per_bar); - // cerr << "++ next bar from numbers\n"; - } else { - /* next bar goes where the next metric is */ - current = limit; - // cerr << "++ next bar at next metric\n"; - } - bar++; - beat = 1; - } - - } - - /* if we're done, then we're done */ - - if (current >= upper) { - break; - } - - /* i is an iterator that refers to the next metric (or none). - if there is a next metric, move to it, and continue. - */ - - if (i != metrics->end()) { - - if ((t = dynamic_cast(*i)) != 0) { - tempo = t; - } else if ((m = dynamic_cast(*i)) != 0) { - meter = m; - /* new MeterSection, beat always returns to 1 */ - beat = 1; - } - - current = (*i)->frame (); - // cerr << "loop around with current @ " << current << endl; - - beats_per_bar = meter->beats_per_bar (); - frames_per_bar = meter->frames_per_bar (*tempo, _frame_rate); - beat_frames = tempo->frames_per_beat (_frame_rate, *meter); - - ++i; - } - - } while (1); - - return points; + begin = lower_bound (_map.begin(), _map.end(), lower); + end = upper_bound (_map.begin(), _map.end(), upper); } const TempoSection& -TempoMap::tempo_section_at (framepos_t frame) +TempoMap::tempo_section_at (framepos_t frame) const { - Glib::RWLock::ReaderLock lm (lock); - Metrics::iterator i; + Glib::Threads::RWLock::ReaderLock lm (lock); + Metrics::const_iterator i; TempoSection* prev = 0; - for (i = metrics->begin(); i != metrics->end(); ++i) { + for (i = metrics.begin(); i != metrics.end(); ++i) { TempoSection* t; if ((t = dynamic_cast (*i)) != 0) { @@ -1529,6 +1607,7 @@ TempoMap::tempo_section_at (framepos_t frame) if (prev == 0) { fatal << endmsg; + abort(); /*NOTREACHED*/ } return *prev; @@ -1541,6 +1620,33 @@ TempoMap::tempo_at (framepos_t frame) const return m.tempo(); } +const MeterSection& +TempoMap::meter_section_at (framepos_t frame) const +{ + Glib::Threads::RWLock::ReaderLock lm (lock); + Metrics::const_iterator i; + MeterSection* prev = 0; + + for (i = metrics.begin(); i != metrics.end(); ++i) { + MeterSection* t; + + if ((t = dynamic_cast (*i)) != 0) { + + if ((*i)->frame() > frame) { + break; + } + + prev = t; + } + } + + if (prev == 0) { + fatal << endmsg; + abort(); /*NOTREACHED*/ + } + + return *prev; +} const Meter& TempoMap::meter_at (framepos_t frame) const @@ -1556,9 +1662,9 @@ TempoMap::get_state () XMLNode *root = new XMLNode ("TempoMap"); { - Glib::RWLock::ReaderLock lm (lock); - for (i = metrics->begin(); i != metrics->end(); ++i) { - root->add_child_nocopy ((*i)->get_state()); + Glib::Threads::RWLock::ReaderLock lm (lock); + for (i = metrics.begin(); i != metrics.end(); ++i) { + root->add_child_nocopy ((*i)->get_state()); } } @@ -1569,13 +1675,13 @@ int TempoMap::set_state (const XMLNode& node, int /*version*/) { { - Glib::RWLock::WriterLock lm (lock); + Glib::Threads::RWLock::WriterLock lm (lock); XMLNodeList nlist; XMLNodeConstIterator niter; - Metrics old_metrics (*metrics); - - metrics->clear(); + Metrics old_metrics (metrics); + MeterSection* last_meter = 0; + metrics.clear(); nlist = node.children(); @@ -1585,35 +1691,68 @@ TempoMap::set_state (const XMLNode& node, int /*version*/) if (child->name() == TempoSection::xml_state_node_name) { try { - metrics->push_back (new TempoSection (*child)); + TempoSection* ts = new TempoSection (*child); + metrics.push_back (ts); + + if (ts->bar_offset() < 0.0) { + if (last_meter) { + ts->update_bar_offset_from_bbt (*last_meter); + } + } } catch (failed_constructor& err){ error << _("Tempo map: could not set new state, restoring old one.") << endmsg; - *metrics = old_metrics; + metrics = old_metrics; break; } } else if (child->name() == MeterSection::xml_state_node_name) { try { - metrics->push_back (new MeterSection (*child)); + MeterSection* ms = new MeterSection (*child); + metrics.push_back (ms); + last_meter = ms; } catch (failed_constructor& err) { error << _("Tempo map: could not set new state, restoring old one.") << endmsg; - *metrics = old_metrics; + metrics = old_metrics; break; } } } if (niter == nlist.end()) { - MetricSectionSorter cmp; - metrics->sort (cmp); - timestamp_metrics (true); + metrics.sort (cmp); + } + + /* check for multiple tempo/meters at the same location, which + ardour2 somehow allowed. + */ + + Metrics::iterator prev = metrics.end(); + for (Metrics::iterator i = metrics.begin(); i != metrics.end(); ++i) { + if (prev != metrics.end()) { + if (dynamic_cast(*prev) && dynamic_cast(*i)) { + if ((*prev)->start() == (*i)->start()) { + cerr << string_compose (_("Multiple meter definitions found at %1"), (*prev)->start()) << endmsg; + error << string_compose (_("Multiple meter definitions found at %1"), (*prev)->start()) << endmsg; + return -1; + } + } else if (dynamic_cast(*prev) && dynamic_cast(*i)) { + if ((*prev)->start() == (*i)->start()) { + cerr << string_compose (_("Multiple tempo definitions found at %1"), (*prev)->start()) << endmsg; + error << string_compose (_("Multiple tempo definitions found at %1"), (*prev)->start()) << endmsg; + return -1; + } + } + } + prev = i; } + + recompute_map (true, -1); } PropertyChanged (PropertyChange ()); @@ -1624,17 +1763,18 @@ TempoMap::set_state (const XMLNode& node, int /*version*/) void TempoMap::dump (std::ostream& o) const { + Glib::Threads::RWLock::ReaderLock lm (lock, Glib::Threads::TRY_LOCK); const MeterSection* m; const TempoSection* t; - for (Metrics::const_iterator i = metrics->begin(); i != metrics->end(); ++i) { + for (Metrics::const_iterator i = metrics.begin(); i != metrics.end(); ++i) { if ((t = dynamic_cast(*i)) != 0) { - o << "Tempo @ " << *i << ' ' << t->beats_per_minute() << " BPM (denom = " << t->note_type() << ") at " << t->start() << " frame= " << t->frame() << " (move? " + o << "Tempo @ " << *i << " (Bar-offset: " << t->bar_offset() << ") " << t->beats_per_minute() << " BPM (pulse = 1/" << t->note_type() << ") at " << t->start() << " frame= " << t->frame() << " (movable? " << t->movable() << ')' << endl; } else if ((m = dynamic_cast(*i)) != 0) { - o << "Meter @ " << *i << ' ' << m->beats_per_bar() << '/' << m->note_divisor() << " at " << m->start() << " frame= " << m->frame() - << " (move? " << m->movable() << ')' << endl; + o << "Meter @ " << *i << ' ' << m->divisions_per_bar() << '/' << m->note_divisor() << " at " << m->start() << " frame= " << m->frame() + << " (movable? " << m->movable() << ')' << endl; } } } @@ -1642,10 +1782,10 @@ TempoMap::dump (std::ostream& o) const int TempoMap::n_tempos() const { - Glib::RWLock::ReaderLock lm (lock); + Glib::Threads::RWLock::ReaderLock lm (lock); int cnt = 0; - for (Metrics::const_iterator i = metrics->begin(); i != metrics->end(); ++i) { + for (Metrics::const_iterator i = metrics.begin(); i != metrics.end(); ++i) { if (dynamic_cast(*i) != 0) { cnt++; } @@ -1657,10 +1797,10 @@ TempoMap::n_tempos() const int TempoMap::n_meters() const { - Glib::RWLock::ReaderLock lm (lock); + Glib::Threads::RWLock::ReaderLock lm (lock); int cnt = 0; - for (Metrics::const_iterator i = metrics->begin(); i != metrics->end(); ++i) { + for (Metrics::const_iterator i = metrics.begin(); i != metrics.end(); ++i) { if (dynamic_cast(*i) != 0) { cnt++; } @@ -1672,262 +1812,691 @@ TempoMap::n_meters() const void TempoMap::insert_time (framepos_t where, framecnt_t amount) { - for (Metrics::iterator i = metrics->begin(); i != metrics->end(); ++i) { - if ((*i)->frame() >= where) { - (*i)->set_frame ((*i)->frame() + amount); + { + Glib::Threads::RWLock::WriterLock lm (lock); + for (Metrics::iterator i = metrics.begin(); i != metrics.end(); ++i) { + if ((*i)->frame() >= where && (*i)->movable ()) { + (*i)->set_frame ((*i)->frame() + amount); + } } + + /* now reset the BBT time of all metrics, based on their new + * audio time. This is the only place where we do this reverse + * timestamp. + */ + + Metrics::iterator i; + const MeterSection* meter; + const TempoSection* tempo; + MeterSection *m; + TempoSection *t; + + meter = &first_meter (); + tempo = &first_tempo (); + + BBT_Time start; + BBT_Time end; + + // cerr << "\n###################### TIMESTAMP via AUDIO ##############\n" << endl; + + bool first = true; + MetricSection* prev = 0; + + for (i = metrics.begin(); i != metrics.end(); ++i) { + + BBT_Time bbt; + TempoMetric metric (*meter, *tempo); + + if (prev) { + metric.set_start (prev->start()); + metric.set_frame (prev->frame()); + } else { + // metric will be at frames=0 bbt=1|1|0 by default + // which is correct for our purpose + } + + BBTPointList::const_iterator bi = bbt_before_or_at ((*i)->frame()); + bbt_time ((*i)->frame(), bbt, bi); + + // cerr << "timestamp @ " << (*i)->frame() << " with " << bbt.bars << "|" << bbt.beats << "|" << bbt.ticks << " => "; + + if (first) { + first = false; + } else { + + if (bbt.ticks > BBT_Time::ticks_per_beat/2) { + /* round up to next beat */ + bbt.beats += 1; + } + + bbt.ticks = 0; + + if (bbt.beats != 1) { + /* round up to next bar */ + bbt.bars += 1; + bbt.beats = 1; + } + } + + // cerr << bbt << endl; + + (*i)->set_start (bbt); + + if ((t = dynamic_cast(*i)) != 0) { + tempo = t; + // cerr << "NEW TEMPO, frame = " << (*i)->frame() << " start = " << (*i)->start() <(*i)) != 0) { + meter = m; + // cerr << "NEW METER, frame = " << (*i)->frame() << " start = " << (*i)->start() < metric_kill_list; + + TempoSection* last_tempo = NULL; + MeterSection* last_meter = NULL; + bool tempo_after = false; // is there a tempo marker at the first sample after the removed range? + bool meter_after = false; // is there a meter marker likewise? + { + Glib::Threads::RWLock::WriterLock lm (lock); + for (Metrics::iterator i = metrics.begin(); i != metrics.end(); ++i) { + if ((*i)->frame() >= where && (*i)->frame() < where+amount) { + metric_kill_list.push_back(*i); + TempoSection *lt = dynamic_cast (*i); + if (lt) + last_tempo = lt; + MeterSection *lm = dynamic_cast (*i); + if (lm) + last_meter = lm; + } + else if ((*i)->frame() >= where) { + // TODO: make sure that moved tempo/meter markers are rounded to beat/bar boundaries + (*i)->set_frame ((*i)->frame() - amount); + if ((*i)->frame() == where) { + // marker was immediately after end of range + tempo_after = dynamic_cast (*i); + meter_after = dynamic_cast (*i); + } + moved = true; + } + } + + //find the last TEMPO and METER metric (if any) and move it to the cut point so future stuff is correct + if (last_tempo && !tempo_after) { + metric_kill_list.remove(last_tempo); + last_tempo->set_frame(where); + moved = true; + } + if (last_meter && !meter_after) { + metric_kill_list.remove(last_meter); + last_meter->set_frame(where); + moved = true; + } + + //remove all the remaining metrics + for (std::list::iterator i = metric_kill_list.begin(); i != metric_kill_list.end(); ++i) { + metrics.remove(*i); + moved = true; + } + + if (moved) { + recompute_map (true); + } + } + PropertyChanged (PropertyChange ()); + return moved; } -/** - * add the BBT interval @param increment to @param start and return the result +/** Add some (fractional) beats to a session frame position, and return the result in frames. + * pos can be -ve, if required. */ -BBT_Time -TempoMap::bbt_add (const BBT_Time& start, const BBT_Time& increment, const TempoMetric& /*metric*/) const +framepos_t +TempoMap::framepos_plus_beats (framepos_t pos, Evoral::Beats beats) const { - BBT_Time result = start; - BBT_Time op = increment; /* argument is const, but we need to modify it */ - uint32_t ticks = result.ticks + op.ticks; + Glib::Threads::RWLock::ReaderLock lm (lock); + Metrics::const_iterator next_tempo; + const TempoSection* tempo = 0; - if (ticks >= Meter::ticks_per_beat) { - op.beats++; - result.ticks = ticks % (uint32_t) Meter::ticks_per_beat; - } else { - result.ticks += op.ticks; - } + /* Find the starting tempo metric */ - /* now comes the complicated part. we have to add one beat a time, - checking for a new metric on every beat. - */ - - /* grab all meter sections */ - - list meter_sections; - - for (Metrics::const_iterator x = metrics->begin(); x != metrics->end(); ++x) { - const MeterSection* ms; - if ((ms = dynamic_cast(*x)) != 0) { - meter_sections.push_back (ms); - } - } - - assert (!meter_sections.empty()); - - list::const_iterator next_meter; - const Meter* meter = 0; - - /* go forwards through the meter sections till we get to the one - covering the current value of result. this positions i to point to - the next meter section too, or the end. - */ - - for (next_meter = meter_sections.begin(); next_meter != meter_sections.end(); ++next_meter) { - - if (result < (*next_meter)->start()) { - /* this metric is past the result time. stop looking, we have what we need */ - break; - } + for (next_tempo = metrics.begin(); next_tempo != metrics.end(); ++next_tempo) { - if (result == (*next_meter)->start()) { - /* this meter section starts at result, push i beyond it so that it points - to the NEXT section, opwise we will get stuck later, and use this meter section. + const TempoSection* t; + + if ((t = dynamic_cast(*next_tempo)) != 0) { + + /* This is a bit of a hack, but pos could be -ve, and if it is, + we consider the initial metric changes (at time 0) to actually + be in effect at pos. */ - meter = *next_meter; - ++next_meter; - break; + + framepos_t f = (*next_tempo)->frame (); + + if (pos < 0 && f == 0) { + f = pos; + } + + if (f > pos) { + break; + } + + tempo = t; } - - meter = *next_meter; } - - assert (meter != 0); - - /* OK, now have the meter for the bar start we are on, and i is an iterator - that points to the metric after the one we are currently dealing with - (or to metrics->end(), of course) + + /* We now have: + + tempo -> the Tempo for "pos" + next_tempo -> first tempo after "pos", possibly metrics.end() */ - - while (op.beats) { - - /* given the current meter, have we gone past the end of the bar ? */ - - if (result.beats >= meter->beats_per_bar()) { - /* move to next bar, first beat */ - result.bars++; - result.beats = 1; - } else { - result.beats++; + assert(tempo); + + DEBUG_TRACE (DEBUG::TempoMath, + string_compose ("frame %1 plus %2 beats, start with tempo = %3 @ %4\n", + pos, beats, *((const Tempo*)tempo), tempo->frame())); + + while (!!beats) { + + /* Distance to the end of this section in frames */ + framecnt_t distance_frames = (next_tempo == metrics.end() ? max_framepos : ((*next_tempo)->frame() - pos)); + + /* Distance to the end in beats */ + Evoral::Beats distance_beats = Evoral::Beats::ticks_at_rate( + distance_frames, tempo->frames_per_beat (_frame_rate)); + + /* Amount to subtract this time */ + Evoral::Beats const delta = min (distance_beats, beats); + + DEBUG_TRACE (DEBUG::TempoMath, string_compose ("\tdistance to %1 = %2 (%3 beats)\n", + (next_tempo == metrics.end() ? max_framepos : (*next_tempo)->frame()), + distance_frames, distance_beats)); + + /* Update */ + beats -= delta; + pos += delta.to_ticks(tempo->frames_per_beat (_frame_rate)); + + DEBUG_TRACE (DEBUG::TempoMath, string_compose ("\tnow at %1, %2 beats left\n", pos, beats)); + + /* step forwards to next tempo section */ + + if (next_tempo != metrics.end()) { + + tempo = dynamic_cast(*next_tempo); + + DEBUG_TRACE (DEBUG::TempoMath, string_compose ("\tnew tempo = %1 @ %2 fpb = %3\n", + *((const Tempo*)tempo), tempo->frame(), + tempo->frames_per_beat (_frame_rate))); + + while (next_tempo != metrics.end ()) { + + ++next_tempo; + + if (next_tempo != metrics.end() && dynamic_cast(*next_tempo)) { + break; + } + } } - - /* one down ... */ - - op.beats--; - - /* check if we need to use a new meter section: has adding beats to result taken us - to or after the start of the next meter section? in which case, use it. - */ + } + + return pos; +} + +/** Subtract some (fractional) beats from a frame position, and return the result in frames */ +framepos_t +TempoMap::framepos_minus_beats (framepos_t pos, Evoral::Beats beats) const +{ + Glib::Threads::RWLock::ReaderLock lm (lock); + Metrics::const_reverse_iterator prev_tempo; + const TempoSection* tempo = 0; + + /* Find the starting tempo metric */ - if (next_meter != meter_sections.end() && (((*next_meter)->start () < result) || (result == (*next_meter)->start()))) { - meter = *next_meter; - ++next_meter; + for (prev_tempo = metrics.rbegin(); prev_tempo != metrics.rend(); ++prev_tempo) { + + const TempoSection* t; + + if ((t = dynamic_cast(*prev_tempo)) != 0) { + + /* This is a bit of a hack, but pos could be -ve, and if it is, + we consider the initial metric changes (at time 0) to actually + be in effect at pos. + */ + + framepos_t f = (*prev_tempo)->frame (); + + if (pos < 0 && f == 0) { + f = pos; + } + + /* this is slightly more complex than the forward case + because we reach the tempo in effect at pos after + passing through pos (rather before, as in the + forward case). having done that, we then need to + keep going to get the previous tempo (or + metrics.rend()) + */ + + if (f <= pos) { + if (tempo == 0) { + /* first tempo with position at or + before pos + */ + tempo = t; + } else if (f < pos) { + /* some other tempo section that + is even earlier than 'tempo' + */ + break; + } + } } } - /* finally, add bars */ + assert(tempo); + DEBUG_TRACE (DEBUG::TempoMath, + string_compose ("frame %1 minus %2 beats, start with tempo = %3 @ %4 prev at beg? %5\n", + pos, beats, *((const Tempo*)tempo), tempo->frame(), + prev_tempo == metrics.rend())); - result.bars += op.bars++; + /* We now have: + + tempo -> the Tempo for "pos" + prev_tempo -> the first metric before "pos", possibly metrics.rend() + */ + + while (!!beats) { + + /* Distance to the start of this section in frames */ + framecnt_t distance_frames = (pos - tempo->frame()); + + /* Distance to the start in beats */ + Evoral::Beats distance_beats = Evoral::Beats::ticks_at_rate( + distance_frames, tempo->frames_per_beat (_frame_rate)); + + /* Amount to subtract this time */ + Evoral::Beats const sub = min (distance_beats, beats); + + DEBUG_TRACE (DEBUG::TempoMath, string_compose ("\tdistance to %1 = %2 (%3 beats)\n", + tempo->frame(), distance_frames, distance_beats)); + /* Update */ + + beats -= sub; + pos -= sub.to_double() * tempo->frames_per_beat (_frame_rate); + + DEBUG_TRACE (DEBUG::TempoMath, string_compose ("\tnow at %1, %2 beats left, prev at end ? %3\n", pos, beats, + (prev_tempo == metrics.rend()))); + + /* step backwards to prior TempoSection */ + + if (prev_tempo != metrics.rend()) { + + tempo = dynamic_cast(*prev_tempo); + + DEBUG_TRACE (DEBUG::TempoMath, + string_compose ("\tnew tempo = %1 @ %2 fpb = %3\n", + *((const Tempo*)tempo), tempo->frame(), + tempo->frames_per_beat (_frame_rate))); - return result; + while (prev_tempo != metrics.rend ()) { + + ++prev_tempo; + + if (prev_tempo != metrics.rend() && dynamic_cast(*prev_tempo) != 0) { + break; + } + } + } else { + pos -= llrint (beats.to_double() * tempo->frames_per_beat (_frame_rate)); + beats = Evoral::Beats(); + } + } + + return pos; } -/** - * subtract the BBT interval @param decrement from @param start and return the result - */ -BBT_Time -TempoMap::bbt_subtract (const BBT_Time& start, const BBT_Time& decrement) const +/** Add the BBT interval op to pos and return the result */ +framepos_t +TempoMap::framepos_plus_bbt (framepos_t pos, BBT_Time op) const { - BBT_Time result = start; - BBT_Time op = decrement; /* argument is const, but we need to modify it */ + Glib::Threads::RWLock::ReaderLock lm (lock); + Metrics::const_iterator i; + const MeterSection* meter; + const MeterSection* m; + const TempoSection* tempo; + const TempoSection* t; + double frames_per_beat; + framepos_t effective_pos = max (pos, (framepos_t) 0); - if (op.ticks > result.ticks) { - /* subtract an extra beat later; meanwhile set ticks to the right "carry" value */ - op.beats++; - result.ticks = Meter::ticks_per_beat - (op.ticks - result.ticks); - } else { - result.ticks -= op.ticks; + meter = &first_meter (); + tempo = &first_tempo (); + + assert (meter); + assert (tempo); + + /* find the starting metrics for tempo & meter */ + + for (i = metrics.begin(); i != metrics.end(); ++i) { + + if ((*i)->frame() > effective_pos) { + break; + } + + if ((t = dynamic_cast(*i)) != 0) { + tempo = t; + } else if ((m = dynamic_cast(*i)) != 0) { + meter = m; + } } - /* now comes the complicated part. we have to subtract one beat a time, - checking for a new metric on every beat. + /* We now have: + + meter -> the Meter for "pos" + tempo -> the Tempo for "pos" + i -> for first new metric after "pos", possibly metrics.end() */ - - /* grab all meter sections */ - - list meter_sections; - - for (Metrics::const_iterator x = metrics->begin(); x != metrics->end(); ++x) { - const MeterSection* ms; - if ((ms = dynamic_cast(*x)) != 0) { - meter_sections.push_back (ms); - } - } - - assert (!meter_sections.empty()); - - /* go backwards through the meter sections till we get to the one - covering the current value of result. this positions i to point to - the next (previous) meter section too, or the end. + + /* now comes the complicated part. we have to add one beat a time, + checking for a new metric on every beat. */ - - const MeterSection* meter = 0; - list::reverse_iterator next_meter; // older versions of GCC don't - // support const_reverse_iterator::operator!=() - - for (next_meter = meter_sections.rbegin(); next_meter != meter_sections.rend(); ++next_meter) { - - /* when we find the first meter section that is before or at result, use it, - and set next_meter to the previous one + + frames_per_beat = tempo->frames_per_beat (_frame_rate); + + uint64_t bars = 0; + + while (op.bars) { + + bars++; + op.bars--; + + /* check if we need to use a new metric section: has adding frames moved us + to or after the start of the next metric section? in which case, use it. */ - - if ((*next_meter)->start() < result || (*next_meter)->start() == result) { - meter = *next_meter; - ++next_meter; - break; + + if (i != metrics.end()) { + if ((*i)->frame() <= pos) { + + /* about to change tempo or meter, so add the + * number of frames for the bars we've just + * traversed before we change the + * frames_per_beat value. + */ + + pos += llrint (frames_per_beat * (bars * meter->divisions_per_bar())); + bars = 0; + + if ((t = dynamic_cast(*i)) != 0) { + tempo = t; + } else if ((m = dynamic_cast(*i)) != 0) { + meter = m; + } + ++i; + frames_per_beat = tempo->frames_per_beat (_frame_rate); + + } } + } - assert (meter != 0); - - /* OK, now have the meter for the bar start we are on, and i is an iterator - that points to the metric after the one we are currently dealing with - (or to metrics->end(), of course) - */ - + pos += llrint (frames_per_beat * (bars * meter->divisions_per_bar())); + + uint64_t beats = 0; + while (op.beats) { - /* have we reached the start of the bar? if so, move to the last beat of the previous - bar. opwise, just step back 1 beat. + /* given the current meter, have we gone past the end of the bar ? */ + + beats++; + op.beats--; + + /* check if we need to use a new metric section: has adding frames moved us + to or after the start of the next metric section? in which case, use it. */ - - if (result.beats == 1) { - - /* move to previous bar, last beat */ - - if (result.bars <= 1) { - /* i'm sorry dave, i can't do that */ - throw std::out_of_range ("illegal BBT subtraction"); + + if (i != metrics.end()) { + if ((*i)->frame() <= pos) { + + /* about to change tempo or meter, so add the + * number of frames for the beats we've just + * traversed before we change the + * frames_per_beat value. + */ + + pos += llrint (beats * frames_per_beat); + beats = 0; + + if ((t = dynamic_cast(*i)) != 0) { + tempo = t; + } else if ((m = dynamic_cast(*i)) != 0) { + meter = m; + } + ++i; + frames_per_beat = tempo->frames_per_beat (_frame_rate); } - - result.bars--; - result.beats = meter->beats_per_bar(); + } + } + + pos += llrint (beats * frames_per_beat); + + if (op.ticks) { + if (op.ticks >= BBT_Time::ticks_per_beat) { + pos += llrint (frames_per_beat + /* extra beat */ + (frames_per_beat * ((op.ticks % (uint32_t) BBT_Time::ticks_per_beat) / + (double) BBT_Time::ticks_per_beat))); } else { + pos += llrint (frames_per_beat * (op.ticks / (double) BBT_Time::ticks_per_beat)); + } + } + + return pos; +} + +/** Count the number of beats that are equivalent to distance when going forward, + starting at pos. +*/ +Evoral::Beats +TempoMap::framewalk_to_beats (framepos_t pos, framecnt_t distance) const +{ + Glib::Threads::RWLock::ReaderLock lm (lock); + Metrics::const_iterator next_tempo; + const TempoSection* tempo = 0; + framepos_t effective_pos = max (pos, (framepos_t) 0); + + /* Find the relevant initial tempo metric */ + + for (next_tempo = metrics.begin(); next_tempo != metrics.end(); ++next_tempo) { + + const TempoSection* t; - /* back one beat */ + if ((t = dynamic_cast(*next_tempo)) != 0) { + + if ((*next_tempo)->frame() > effective_pos) { + break; + } - result.beats--; + tempo = t; } - - /* one down ... */ - op.beats--; - - /* check if we need to use a new meter section: has subtracting beats to result taken us - to before the start of the current meter section? in which case, use the prior one. - */ + } - if (result < meter->start() && next_meter != meter_sections.rend()) { - meter = *next_meter; - ++next_meter; + /* We now have: + + tempo -> the Tempo for "pos" + next_tempo -> the next tempo after "pos", possibly metrics.end() + */ + assert (tempo); + + DEBUG_TRACE (DEBUG::TempoMath, + string_compose ("frame %1 walk by %2 frames, start with tempo = %3 @ %4\n", + pos, distance, *((const Tempo*)tempo), tempo->frame())); + + Evoral::Beats beats = Evoral::Beats(); + + while (distance) { + + /* End of this section */ + framepos_t end; + /* Distance to `end' in frames */ + framepos_t distance_to_end; + + if (next_tempo == metrics.end ()) { + /* We can't do (end - pos) if end is max_framepos, as it will overflow if pos is -ve */ + end = max_framepos; + distance_to_end = max_framepos; + } else { + end = (*next_tempo)->frame (); + distance_to_end = end - pos; } + + /* Amount to subtract this time in frames */ + framecnt_t const sub = min (distance, distance_to_end); + + DEBUG_TRACE (DEBUG::TempoMath, string_compose ("to reach end at %1 (end ? %2), distance= %3 sub=%4\n", end, (next_tempo == metrics.end()), + distance_to_end, sub)); + + /* Update */ + pos += sub; + distance -= sub; + assert (tempo); + beats += Evoral::Beats::ticks_at_rate(sub, tempo->frames_per_beat (_frame_rate)); + + DEBUG_TRACE (DEBUG::TempoMath, string_compose ("now at %1, beats = %2 distance left %3\n", + pos, beats, distance)); + + /* Move on if there's anything to move to */ + + if (next_tempo != metrics.end()) { + + tempo = dynamic_cast(*next_tempo); + + DEBUG_TRACE (DEBUG::TempoMath, + string_compose ("\tnew tempo = %1 @ %2 fpb = %3\n", + *((const Tempo*)tempo), tempo->frame(), + tempo->frames_per_beat (_frame_rate))); + + while (next_tempo != metrics.end ()) { + + ++next_tempo; + + if (next_tempo != metrics.end() && dynamic_cast(*next_tempo)) { + break; + } + } + + if (next_tempo == metrics.end()) { + DEBUG_TRACE (DEBUG::TempoMath, "no more tempo sections\n"); + } else { + DEBUG_TRACE (DEBUG::TempoMath, string_compose ("next tempo section is %1 @ %2\n", + **next_tempo, (*next_tempo)->frame())); + } + + } + assert (tempo); } - /* finally, subtract bars */ + return beats; +} - if (op.bars >= result.bars) { - /* i'm sorry dave, i can't do that */ - throw std::out_of_range ("illegal BBT subtraction"); +TempoMap::BBTPointList::const_iterator +TempoMap::bbt_before_or_at (framepos_t pos) +{ + /* CALLER MUST HOLD READ LOCK */ + + BBTPointList::const_iterator i; + + if (pos < 0) { + /* not really correct, but we should catch pos < 0 at a higher + level + */ + return _map.begin(); } - result.bars -= op.bars; - return result; + i = lower_bound (_map.begin(), _map.end(), pos); + assert (i != _map.end()); + if ((*i).frame > pos) { + assert (i != _map.begin()); + --i; + } + return i; } -/** Compare the time of this with that of another MetricSection. - * @param with_bbt True to compare using ::start(), false to use ::frame(). - * @return -1 for less than, 0 for equal, 1 for greater than. - */ +struct bbtcmp { + bool operator() (const BBT_Time& a, const BBT_Time& b) { + return a < b; + } +}; -int -MetricSection::compare (MetricSection* other, bool with_bbt) const +TempoMap::BBTPointList::const_iterator +TempoMap::bbt_before_or_at (const BBT_Time& bbt) { - if (with_bbt) { - if (start() == other->start()) { - return 0; - } else if (start() < other->start()) { - return -1; - } else { - return 1; - } - } else { - if (frame() == other->frame()) { - return 0; - } else if (frame() < other->frame()) { - return -1; - } else { - return 1; - } + BBTPointList::const_iterator i; + bbtcmp cmp; + + i = lower_bound (_map.begin(), _map.end(), bbt, cmp); + assert (i != _map.end()); + if ((*i).bar > bbt.bars || (*i).beat > bbt.beats) { + assert (i != _map.begin()); + --i; } + return i; +} - /* NOTREACHED */ - return 0; +TempoMap::BBTPointList::const_iterator +TempoMap::bbt_after_or_at (framepos_t pos) +{ + /* CALLER MUST HOLD READ LOCK */ + + BBTPointList::const_iterator i; + + if (_map.back().frame == pos) { + i = _map.end(); + assert (i != _map.begin()); + --i; + return i; + } + + i = upper_bound (_map.begin(), _map.end(), pos); + assert (i != _map.end()); + return i; +} + +std::ostream& +operator<< (std::ostream& o, const Meter& m) { + return o << m.divisions_per_bar() << '/' << m.note_divisor(); +} + +std::ostream& +operator<< (std::ostream& o, const Tempo& t) { + return o << t.beats_per_minute() << " 1/" << t.note_type() << "'s per minute"; +} + +std::ostream& +operator<< (std::ostream& o, const MetricSection& section) { + + o << "MetricSection @ " << section.frame() << " aka " << section.start() << ' '; + + const TempoSection* ts; + const MeterSection* ms; + + if ((ts = dynamic_cast (§ion)) != 0) { + o << *((const Tempo*) ts); + } else if ((ms = dynamic_cast (§ion)) != 0) { + o << *((const Meter*) ms); + } + + return o; }