X-Git-Url: https://main.carlh.net/gitweb/?a=blobdiff_plain;ds=sidebyside;f=libs%2Fardour%2Ftempo.cc;h=383abfa3afe68ba7366ede5d1b572400e0e150db;hb=7fc3b0c34c552d7be862897bd0aaa542453e9973;hp=95deb48ef50b2f6d750201df4721bae16bd2b9d1;hpb=f135947606e8d8374ff5567cf4bb0e0450ed3f84;p=ardour.git diff --git a/libs/ardour/tempo.cc b/libs/ardour/tempo.cc index 95deb48ef5..383abfa3af 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/types.hpp" +#include "evoral/Beats.hpp" #include "ardour/debug.h" +#include "ardour/lmath.h" #include "ardour/tempo.h" -#include "ardour/utils.h" #include "i18n.h" #include @@ -45,24 +44,25 @@ using Timecode::BBT_Time; Meter TempoMap::_default_meter (4.0, 4.0); Tempo TempoMap::_default_tempo (120.0); -double -Tempo::frames_per_beat (framecnt_t sr) const -{ - return (60.0 * sr) / _beats_per_minute; -} - /***********************************************************************/ -double -Meter::frames_per_division (const Tempo& tempo, framecnt_t sr) const +double +Meter::frames_per_grid (const Tempo& tempo, framecnt_t sr) const { + /* 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, framecnt_t sr) const { - return frames_per_division (tempo, sr) * _divisions_per_bar; + return frames_per_grid (tempo, sr) * _divisions_per_bar; } /***********************************************************************/ @@ -72,9 +72,9 @@ const string TempoSection::xml_state_node_name = "Tempo"; TempoSection::TempoSection (const XMLNode& node) : MetricSection (BBT_Time()), Tempo (TempoMap::default_tempo()) { - const XMLProperty *prop; + XMLProperty const * prop; BBT_Time start; - LocaleGuard lg (X_("POSIX")); + LocaleGuard lg; if ((prop = node.property ("start")) == 0) { error << _("TempoSection XML node has no \"start\" property") << endmsg; @@ -126,6 +126,16 @@ TempoSection::TempoSection (const XMLNode& node) throw failed_constructor(); } } + + if ((prop = node.property ("tempo-type")) == 0) { + _type = TempoSectionType::Ramp; + } else { + if (strstr(prop->value().c_str(),"Constant")) { + _type = TempoSectionType::Constant; + } else { + _type = TempoSectionType::Ramp; + } + } } XMLNode& @@ -133,7 +143,7 @@ TempoSection::get_state() const { XMLNode *root = new XMLNode (xml_state_node_name); char buf[256]; - LocaleGuard lg (X_("POSIX")); + LocaleGuard lg; snprintf (buf, sizeof (buf), "%" PRIu32 "|%" PRIu32 "|%" PRIu32, start().bars, @@ -149,6 +159,9 @@ TempoSection::get_state() const snprintf (buf, sizeof (buf), "%s", movable()?"yes":"no"); root->add_property ("movable", buf); + snprintf (buf, sizeof (buf), "%s", _type == Constant?"Constant":"Ramp"); + root->add_property ("tempo-type", buf); + return *root; } @@ -156,12 +169,149 @@ void TempoSection::update_bar_offset_from_bbt (const Meter& m) { - _bar_offset = ((start().beats - 1) * BBT_Time::ticks_per_bar_division + start().ticks) / - (m.divisions_per_bar() * BBT_Time::ticks_per_bar_division); + _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::set_type (TempoSectionType type) +{ + _type = type; +} + +double +TempoSection::tempo_at_frame (framepos_t frame, double end_bpm, framepos_t end_frame, framecnt_t frame_rate) const +{ + + if (_type == Constant) { + return beats_per_minute(); + } + + return tick_tempo_at_time (frame_to_minute (frame, frame_rate), end_bpm * BBT_Time::ticks_per_beat, frame_to_minute (end_frame, frame_rate)) / BBT_Time::ticks_per_beat; +} + +framepos_t +TempoSection::frame_at_tempo (double tempo, double end_bpm, framepos_t end_frame, framecnt_t frame_rate) const +{ + if (_type == Constant) { + return 0; + } + + return minute_to_frame (time_at_tick_tempo (tempo * BBT_Time::ticks_per_beat, end_bpm * BBT_Time::ticks_per_beat, frame_to_minute (end_frame, frame_rate)), frame_rate); +} + +double +TempoSection::tick_at_frame (framepos_t frame, double end_bpm, framepos_t end_frame, framecnt_t frame_rate) const +{ + if (_type == Constant) { + return frame / frames_per_beat (frame_rate); + } + + return tick_at_time (frame_to_minute (frame, frame_rate), end_bpm * BBT_Time::ticks_per_beat, frame_to_minute (end_frame, frame_rate)); +} + +framepos_t +TempoSection::frame_at_tick (double tick, double end_bpm, framepos_t end_frame, framecnt_t frame_rate) const +{ + if (_type == Constant) { + return (framepos_t) floor (tick * frames_per_beat(frame_rate)); + } + + return minute_to_frame (time_at_tick (tick, end_bpm * BBT_Time::ticks_per_beat, frame_to_minute (end_frame, frame_rate)), frame_rate); +} + +double TempoSection::beat_at_frame (framepos_t frame, double end_bpm, framepos_t end_frame, framecnt_t frame_rate) const +{ + return tick_at_frame (frame, end_bpm, end_frame, frame_rate) / BBT_Time::ticks_per_beat; +} + +framepos_t TempoSection::frame_at_beat (double beat, double end_bpm, framepos_t end_frame, framecnt_t frame_rate) const +{ + return frame_at_tick (beat * BBT_Time::ticks_per_beat, end_bpm, end_frame, frame_rate); +} + +framecnt_t +TempoSection::minute_to_frame (double time, framecnt_t frame_rate) const +{ + return time * 60.0 * frame_rate; +} + +double +TempoSection::frame_to_minute (framecnt_t frame, framecnt_t frame_rate) const +{ + return (frame / (double) frame_rate) / 60.0; +} + +/* constant for exp */ +double +TempoSection::a_func (double begin_tpm, double end_tpm, double end_time) const +{ + return log (end_tpm / ticks_per_minute()) / c_func (end_tpm, end_time); +} +double +TempoSection::c_func (double end_tpm, double end_time) const +{ + return log (end_tpm / ticks_per_minute()) / end_time; +} + +/* tempo in tpm at time in minutes */ +double +TempoSection::tick_tempo_at_time (double time, double end_tpm, double end_time) const +{ + return exp (c_func (end_tpm, end_time) * time) * ticks_per_minute(); +} + +/* time in minutes at tempo in tpm */ +double +TempoSection::time_at_tick_tempo (double tick_tempo, double end_tpm, double end_time) const +{ + return log (tick_tempo / ticks_per_minute()) / c_func (end_tpm, end_time); +} + +/* tempo in bpm at time in minutes */ +double +TempoSection::tempo_at_time (double time, double end_bpm, double end_time) const +{ + return tick_tempo_at_time (time, end_bpm * BBT_Time::ticks_per_beat, end_time) / BBT_Time::ticks_per_beat; +} + +/* time in minutes at tempo in bpm */ +double +TempoSection::time_at_tempo (double tempo, double end_bpm, double end_time) const +{ + return time_at_tick_tempo (tempo * BBT_Time::ticks_per_beat, end_bpm * BBT_Time::ticks_per_beat, end_time); +} + +/* tick at time in minutes */ +double +TempoSection::tick_at_time (double time, double end_tpm, double end_time) const +{ + return ((exp (c_func (end_tpm, end_time) * time)) - 1) * ticks_per_minute() / c_func (end_tpm, end_time); +} + +/* time in minutes at tick */ +double +TempoSection::time_at_tick (double tick, double end_tpm, double end_time) const +{ + return log (((c_func (end_tpm, end_time) * tick) / ticks_per_minute()) + 1) / c_func (end_tpm, end_time); +} + +/* beat at time in minutes */ +double +TempoSection::beat_at_time (double time, double end_tpm, double end_time) const +{ + return tick_at_time (time, end_tpm, end_time) / BBT_Time::ticks_per_beat; +} + +/* time in munutes at beat */ +double +TempoSection::time_at_beat (double beat, double end_tpm, double end_time) const +{ + return time_at_tick (beat * BBT_Time::ticks_per_beat, end_tpm, end_time); +} + void TempoSection::update_bbt_time_from_bar_offset (const Meter& meter) { @@ -173,15 +323,14 @@ TempoSection::update_bbt_time_from_bar_offset (const Meter& meter) } new_start.bars = start().bars; - - double ticks = BBT_Time::ticks_per_bar_division * meter.divisions_per_bar() * _bar_offset; - new_start.beats = (uint32_t) floor(ticks/BBT_Time::ticks_per_bar_division); - new_start.ticks = (uint32_t) fmod (ticks, BBT_Time::ticks_per_bar_division); + + 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", + 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); @@ -194,9 +343,9 @@ const string MeterSection::xml_state_node_name = "Meter"; MeterSection::MeterSection (const XMLNode& node) : MetricSection (BBT_Time()), Meter (TempoMap::default_meter()) { - const XMLProperty *prop; + XMLProperty const * prop; BBT_Time start; - LocaleGuard lg (X_("POSIX")); + LocaleGuard lg; if ((prop = node.property ("start")) == 0) { error << _("MeterSection XML node has no \"start\" property") << endmsg; @@ -219,7 +368,7 @@ MeterSection::MeterSection (const XMLNode& node) 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", &_divisions_per_bar) != 1 || _divisions_per_bar < 0.0) { @@ -250,7 +399,7 @@ MeterSection::get_state() const { XMLNode *root = new XMLNode (xml_state_node_name); char buf[256]; - LocaleGuard lg (X_("POSIX")); + LocaleGuard lg; snprintf (buf, sizeof (buf), "%" PRIu32 "|%" PRIu32 "|%" PRIu32, start().bars, @@ -277,16 +426,14 @@ struct MetricSectionSorter { TempoMap::TempoMap (framecnt_t fr) { - metrics = new Metrics; _frame_rate = fr; - last_bbt_valid = false; BBT_Time start; start.bars = 1; start.beats = 1; start.ticks = 0; - TempoSection *t = new TempoSection (start, _default_tempo.beats_per_minute(), _default_tempo.note_type()); + TempoSection *t = new TempoSection (start, _default_tempo.beats_per_minute(), _default_tempo.note_type(), TempoSection::TempoSectionType::Ramp); MeterSection *m = new MeterSection (start, _default_meter.divisions_per_bar(), _default_meter.note_divisor()); t->set_movable (false); @@ -294,8 +441,8 @@ TempoMap::TempoMap (framecnt_t fr) /* 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 () @@ -308,24 +455,12 @@ TempoMap::remove_tempo (const TempoSection& 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_tempo_locked (tempo))) { + if (complete_operation) { + recompute_map (true); } } - - if (removed && complete_operation) { - recompute_map (false); - } } if (removed && complete_operation) { @@ -333,32 +468,37 @@ TempoMap::remove_tempo (const TempoSection& tempo, bool complete_operation) } } +bool +TempoMap::remove_tempo_locked (const TempoSection& tempo) +{ + 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_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 && complete_operation) { - recompute_map (true); - } - - } if (removed && complete_operation) { @@ -366,17 +506,34 @@ TempoMap::remove_meter (const MeterSection& tempo, bool complete_operation) } } +bool +TempoMap::remove_meter_locked (const MeterSection& tempo) +{ + 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::do_insert (MetricSection* section) { - /* CALLER MUST HOLD WRITE LOCK */ - - bool reassign_tempo_bbt = false; + bool need_add = true; assert (section->start().ticks == 0); /* we only allow new meters to be inserted on beat 1 of an existing - * measure. + * measure. */ if (dynamic_cast(section)) { @@ -384,155 +541,195 @@ TempoMap::do_insert (MetricSection* section) /* we need to (potentially) update the BBT times of tempo sections based on this new meter. */ - - reassign_tempo_bbt = true; 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); } } - Metrics::iterator i; + /* 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); + 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) < 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); } - - recompute_map (reassign_tempo_bbt); } void -TempoMap::replace_tempo (const TempoSection& ts, const Tempo& tempo, const BBT_Time& where) +TempoMap::replace_tempo (const TempoSection& ts, const Tempo& tempo, const BBT_Time& where, TempoSection::TempoSectionType type) { - const TempoSection& first (first_tempo()); + { + Glib::Threads::RWLock::WriterLock lm (lock); + TempoSection& first (first_tempo()); + TempoSection::TempoSectionType tt = first.type(); - if (ts != first) { - remove_tempo (ts, false); - add_tempo (tempo, where); - } else { - Glib::RWLock::WriterLock lm (lock); - /* cannot move the first tempo section */ - *((Tempo*)&first) = tempo; - recompute_map (false); + if (ts.start() != first.start()) { + remove_tempo_locked (ts); + add_tempo_locked (tempo, where, true, tt); + } else { + { + /* cannot move the first tempo section */ + *static_cast(&first) = tempo; + recompute_map (false); + } + } } PropertyChanged (PropertyChange ()); } void -TempoMap::add_tempo (const Tempo& tempo, BBT_Time where) +TempoMap::add_tempo (const Tempo& tempo, BBT_Time where, ARDOUR::TempoSection::TempoSectionType type) { { - Glib::RWLock::WriterLock lm (lock); + Glib::Threads::RWLock::WriterLock lm (lock); + add_tempo_locked (tempo, where, true, type); + } - /* new tempos always start on a beat */ - where.ticks = 0; - 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. - */ + PropertyChanged (PropertyChange ()); +} - 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. - - now see if we can find better candidates. - */ - - for (Metrics::const_iterator i = metrics->begin(); i != metrics->end(); ++i) { - - const MeterSection* m; - - if (where < (*i)->start()) { - break; - } - - if ((m = dynamic_cast(*i)) != 0) { - meter = m; - } - } +void +TempoMap::add_tempo_locked (const Tempo& tempo, BBT_Time where, bool recompute, ARDOUR::TempoSection::TempoSectionType type) +{ + /* new tempos always start on a beat */ + where.ticks = 0; + TempoSection* ts = new TempoSection (where, tempo.beats_per_minute(), tempo.note_type(), 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. - ts->update_bar_offset_from_bbt (*meter); + now see if we can find better candidates. + */ + + for (Metrics::const_iterator i = metrics.begin(); i != metrics.end(); ++i) { + + const MeterSection* m; - /* and insert it */ + if (where < (*i)->start()) { + break; + } - do_insert (ts); + if ((m = dynamic_cast(*i)) != 0) { + meter = m; + } } - PropertyChanged (PropertyChange ()); + ts->update_bar_offset_from_bbt (*meter); + + /* and insert it */ + + do_insert (ts); + + if (recompute) { + recompute_map (false); + } } void TempoMap::replace_meter (const MeterSection& ms, const Meter& meter, const BBT_Time& where) { - const MeterSection& first (first_meter()); + { + Glib::Threads::RWLock::WriterLock lm (lock); + MeterSection& first (first_meter()); - if (ms != first) { - remove_meter (ms, false); - add_meter (meter, where); - } else { - Glib::RWLock::WriterLock lm (lock); - /* cannot move the first meter section */ - *((Meter*)&first) = meter; - recompute_map (true); + 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); + } } PropertyChanged (PropertyChange ()); @@ -542,26 +739,11 @@ void TempoMap::add_meter (const Meter& meter, 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. - - */ - - if (where.beats != 1) { - where.beats = 1; - where.bars++; - } - - /* new meters *always* start on a beat. */ - where.ticks = 0; - - do_insert (new MeterSection (where, meter.divisions_per_bar(), meter.note_divisor())); + Glib::Threads::RWLock::WriterLock lm (lock); + add_meter_locked (meter, where, true); } + #ifndef NDEBUG if (DEBUG_ENABLED(DEBUG::TempoMap)) { dump (std::cerr); @@ -571,16 +753,42 @@ TempoMap::add_meter (const Meter& meter, BBT_Time where) PropertyChanged (PropertyChange ()); } +void +TempoMap::add_meter_locked (const Meter& meter, BBT_Time where, bool recompute) +{ + /* 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. + + */ + + if (where.beats != 1) { + where.beats = 1; + where.bars++; + } + + /* new meters *always* start on a beat. */ + where.ticks = 0; + + do_insert (new MeterSection (where, meter.divisions_per_bar(), meter.note_divisor())); + + if (recompute) { + recompute_map (true); + } + +} + void 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) { { - Glib::RWLock::WriterLock lm (lock); + Glib::Threads::RWLock::WriterLock lm (lock); *((Tempo*) t) = newtempo; recompute_map (false); } @@ -602,7 +810,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; @@ -630,7 +838,7 @@ TempoMap::change_existing_tempo_at (framepos_t where, double beats_per_minute, d /* reset */ { - Glib::RWLock::WriterLock lm (lock); + Glib::Threads::RWLock::WriterLock lm (lock); /* cannot move the first tempo section */ *((Tempo*)prev) = newtempo; recompute_map (false); @@ -644,14 +852,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; } @@ -660,155 +886,86 @@ 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_from_audio_time () +TempoSection& +TempoMap::first_tempo () { - Metrics::iterator i; - const MeterSection* meter; - const TempoSection* tempo; - MeterSection *m; - TempoSection *t; - - meter = &first_meter (); - tempo = &first_tempo (); + TempoSection *t = 0; - 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 - } - - bbt_time_unlocked ((*i)->frame(), bbt); - - // cerr << "timestamp @ " << (*i)->frame() << " with " << bbt.bars << "|" << bbt.beats << "|" << bbt.ticks << " => "; - - if (first) { - first = false; - } else { - - if (bbt.ticks > BBT_Time::ticks_per_bar_division/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() < (*i)) != 0) { + return *t; } - - prev = (*i); - } - -#ifndef NDEBUG - if (DEBUG_ENABLED(DEBUG::TempoMap)) { - dump (cerr); - } -#endif - -} - -void -TempoMap::require_map_to (framepos_t pos) -{ - if (_map.empty() || _map.back().frame < pos) { - recompute_map (false, pos); } -} -void -TempoMap::require_map_to (const BBT_Time& bbt) -{ - if (_map.empty() || _map.back().bbt() < bbt) { - recompute_map (false, 99); - } + fatal << _("programming error: no tempo section in tempo map!") << endmsg; + abort(); /*NOTREACHED*/ + return *t; } void TempoMap::recompute_map (bool reassign_tempo_bbt, framepos_t end) { - MeterSection* meter; - TempoSection* tempo; - TempoSection* ts; - MeterSection* ms; - double divisions_per_bar; - double beat_frames; - double frames_per_bar; + /* CALLER MUST HOLD WRITE LOCK */ + + MeterSection* meter = 0; + TempoSection* tempo = 0; double current_frame; BBT_Time current; Metrics::iterator next_metric; if (end < 0) { - if (_map.empty()) { - /* compute 1 mins worth */ - end = _frame_rate * 60; - } else { - end = _map.back().frame; + + /* 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); } + */ } DEBUG_TRACE (DEBUG::TempoMath, string_compose ("recomputing tempo map, zero to %1\n", end)); - - _map.clear (); - for (Metrics::iterator i = metrics->begin(); i != metrics->end(); ++i) { + for (Metrics::iterator i = metrics.begin(); i != metrics.end(); ++i) { + MeterSection* ms; + if ((ms = dynamic_cast (*i)) != 0) { meter = ms; break; } } - for (Metrics::iterator i = metrics->begin(); i != metrics->end(); ++i) { + assert(meter); + + for (Metrics::iterator i = metrics.begin(); i != metrics.end(); ++i) { + TempoSection* ts; + if ((ts = dynamic_cast (*i)) != 0) { tempo = ts; break; } } + assert(tempo); + /* assumes that the first meter & tempo are at frame zero */ current_frame = 0; meter->set_frame (0); @@ -818,20 +975,17 @@ TempoMap::recompute_map (bool reassign_tempo_bbt, framepos_t end) current.bars = 1; current.beats = 1; current.ticks = 0; - - divisions_per_bar = meter->divisions_per_bar (); - frames_per_bar = meter->frames_per_bar (*tempo, _frame_rate); - beat_frames = meter->frames_per_division (*tempo,_frame_rate); - if (reassign_tempo_bbt) { - TempoSection* rtempo = tempo; MeterSection* rmeter = meter; DEBUG_TRACE (DEBUG::TempoMath, "\tUpdating tempo marks BBT time from bar offset\n"); - for (Metrics::iterator i = metrics->begin(); i != metrics->end(); ++i) { - + for (Metrics::iterator i = metrics.begin(); i != metrics.end(); ++i) { + + TempoSection* ts; + MeterSection* ms; + if ((ts = dynamic_cast(*i)) != 0) { /* reassign the BBT time of this tempo section @@ -839,130 +993,105 @@ TempoMap::recompute_map (bool reassign_tempo_bbt, framepos_t end) */ ts->update_bbt_time_from_bar_offset (*rmeter); - rtempo = ts; } else if ((ms = dynamic_cast(*i)) != 0) { rmeter = ms; } else { fatal << _("programming error: unhandled MetricSection type") << endmsg; - /*NOTREACHED*/ + abort(); /*NOTREACHED*/ } } } - DEBUG_TRACE (DEBUG::TempoMath, string_compose ("start with meter = %1 tempo = %2 dpb %3 fpb %4\n", - *((Meter*)meter), *((Tempo*)tempo), divisions_per_bar, beat_frames)); + DEBUG_TRACE (DEBUG::TempoMath, string_compose ("start with meter = %1 tempo = %2\n", *((Meter*)meter), *((Tempo*)tempo))); - next_metric = metrics->begin(); + next_metric = metrics.begin(); ++next_metric; // skip meter (or tempo) ++next_metric; // skip tempo (or 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), Bar, 1, 1)); - while (current_frame < end) { - - current.beats++; - current_frame += beat_frames; + if (end == 0) { + /* silly call from Session::process() during startup + */ + return; + } - if (current.beats > meter->divisions_per_bar()) { - current.bars++; - current.beats = 1; - } + _extend_map (tempo, meter, next_metric, current, current_frame, end); +} - if (next_metric != metrics->end()) { +void +TempoMap::_extend_map (TempoSection* tempo, MeterSection* meter, + Metrics::iterator next_metric, + BBT_Time current, framepos_t current_frame, framepos_t end) +{ + /* CALLER MUST HOLD WRITE LOCK */ - /* no operator >= so invert operator < */ + uint32_t first_tick_in_new_meter = 0; + Metrics::const_iterator i; + TempoSection* prev_ts = tempo; - DEBUG_TRACE (DEBUG::TempoMath, string_compose ("now at %1 next metric @ %2\n", current, (*next_metric)->start())); + for (i = metrics.begin(); i != metrics.end(); ++i) { + MeterSection* m = 0; - if (!(current < (*next_metric)->start())) { - + if ((m = dynamic_cast (*i)) != 0) { - if (((ts = dynamic_cast (*next_metric)) != 0)) { + if (m->start() >= prev_ts->start()) { + first_tick_in_new_meter = ((((m->start().bars - 1) * meter->divisions_per_bar()) + (m->start().beats - 1)) * BBT_Time::ticks_per_beat) + m->start().ticks; // expressed in ticks from the previous meter + for (i = metrics.begin(); i != metrics.end(); ++i) { + TempoSection* t; - tempo = ts; + if ((t = dynamic_cast (*i)) != 0) { - /* 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. - */ + if (t->start() >= m->start() && t->start() > prev_ts->start()) { + //cerr << "new ts start bars = " << t->start().bars << " beats = " << t->start().beats << " ticks = " << t->start().ticks << endl; + //cerr << "prev ts start bars = " << prev_ts->start().bars << " beats = " << prev_ts->start().beats << " ticks = " << prev_ts->start().ticks << endl; - if (tempo->start().ticks != 0) { - - double next_beat_frames = meter->frames_per_division (*tempo,_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())); - - /* back up to previous beat */ - current_frame -= beat_frames; - /* set tempo section location based on offset from last beat */ - tempo->set_frame (current_frame + (ts->bar_offset() * beat_frames)); - /* advance to the location of the new (adjusted) beat */ - current_frame += (ts->bar_offset() * beat_frames) + ((1.0 - ts->bar_offset()) * next_beat_frames); - DEBUG_TRACE (DEBUG::TempoMath, string_compose ("Adjusted last beat to %1\n", current_frame)); - } 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); - } + /*tempo section (t) lies in the previous meter */ + double ticks_at_ts = ((((t->start().bars - 1 ) * meter->divisions_per_bar()) + (t->start().beats - 1) ) * BBT_Time::ticks_per_beat) + t->start().ticks; - } else if ((ms = dynamic_cast(*next_metric)) != 0) { - - meter = ms; - /* new meter section: always defines the - * start of a bar. - */ - - DEBUG_TRACE (DEBUG::TempoMath, string_compose ("bumped into meter section at %1 (%2)\n", - meter->start(), current_frame)); - - assert (current.beats == 1); + double ticks_at_prev_ts = ((((prev_ts->start().bars - 1) * meter->divisions_per_bar()) + (prev_ts->start().beats - 1)) * BBT_Time::ticks_per_beat) + prev_ts->start().ticks; + + double ticks_relative_to_prev_ts = ticks_at_ts - ticks_at_prev_ts; + /* assume (falsely) that the target tempo is constant */ + double length_estimate = (ticks_relative_to_prev_ts / BBT_Time::ticks_per_beat) * meter->frames_per_grid (*t, _frame_rate); + double system_precision_at_target_tempo = (_frame_rate / t->ticks_per_minute()); + cerr << " system_precision_at_target_tempo = " << system_precision_at_target_tempo << endl; + double tick_error = system_precision_at_target_tempo + 1.0; // sorry for the wtf + + while (fabs (tick_error) >= system_precision_at_target_tempo) { + + double actual_ticks = prev_ts->tick_at_frame (length_estimate, t->beats_per_minute(), (framepos_t) length_estimate, _frame_rate); + tick_error = ticks_relative_to_prev_ts - actual_ticks; + length_estimate += (tick_error / BBT_Time::ticks_per_beat) * meter->frames_per_grid (*t, _frame_rate); + cerr << "actual ticks = " << actual_ticks << endl; - meter->set_frame (current_frame); + cerr << "tick error = " << tick_error << endl; + } + t->set_frame (length_estimate + prev_ts->frame()); + + if (m->start() < t->start() && m->start() == prev_ts->start()) { + m->set_frame (prev_ts->frame()); + } else if (m->start() < t->start() && m->start() > prev_ts->start()) { + m->set_frame (prev_ts->frame_at_tick ((first_tick_in_new_meter - ticks_at_prev_ts), t->beats_per_minute(), (framepos_t) length_estimate, _frame_rate)); + } + } + prev_ts = t; + } } - - divisions_per_bar = meter->divisions_per_bar (); - frames_per_bar = meter->frames_per_bar (*tempo, _frame_rate); - beat_frames = meter->frames_per_division (*tempo, _frame_rate); - - DEBUG_TRACE (DEBUG::TempoMath, string_compose ("New metric with beat frames = %1 dpb %2 meter %3 tempo %4\n", - beat_frames, divisions_per_bar, *((Meter*)meter), *((Tempo*)tempo))); - - ++next_metric; } - } - - 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,(framepos_t) llrint(current_frame), Bar, current.bars, 1)); - } else { - 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, (framepos_t) llrint(current_frame), Beat, current.bars, current.beats)); + meter = m; } } } + TempoMetric -TempoMap::metric_at (framepos_t frame) const +TempoMap::metric_at (framepos_t frame, Metrics::const_iterator* last) const { - Glib::RWLock::ReaderLock lm (lock); + Glib::Threads::RWLock::ReaderLock lm (lock); 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 @@ -971,35 +1100,27 @@ TempoMap::metric_at (framepos_t frame) const now see if we can find better candidates. */ - for (Metrics::const_iterator i = metrics->begin(); i != metrics->end(); ++i) { - - // cerr << "Looking at a metric section " << **i << endl; + for (Metrics::const_iterator i = metrics.begin(); i != metrics.end(); ++i) { if ((*i)->frame() > frame) { break; } - if ((tempo = dynamic_cast(*i)) != 0) { - m.set_tempo (*tempo); - } else if ((meter = dynamic_cast(*i)) != 0) { - m.set_meter (*meter); - } + m.set_metric(*i); - m.set_frame ((*i)->frame ()); - m.set_start ((*i)->start ()); + if (last) { + *last = i; + } } - - // cerr << "for framepos " << frame << " returning " << m.meter() << " @ " << m.tempo() << " location " << m.frame() << " = " << m.start() << endl; + return m; } TempoMetric TempoMap::metric_at (BBT_Time bbt) const { - Glib::RWLock::ReaderLock lm (lock); + Glib::Threads::RWLock::ReaderLock lm (lock); 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 @@ -1008,7 +1129,7 @@ TempoMap::metric_at (BBT_Time bbt) const now see if we can find better candidates. */ - for (Metrics::const_iterator i = metrics->begin(); i != metrics->end(); ++i) { + for (Metrics::const_iterator i = metrics.begin(); i != metrics.end(); ++i) { BBT_Time section_start ((*i)->start()); @@ -1016,14 +1137,7 @@ TempoMap::metric_at (BBT_Time bbt) const break; } - if ((tempo = dynamic_cast(*i)) != 0) { - m.set_tempo (*tempo); - } else if ((meter = dynamic_cast(*i)) != 0) { - m.set_meter (*meter); - } - - m.set_frame ((*i)->frame ()); - m.set_start (section_start); + m.set_metric (*i); } return m; @@ -1032,344 +1146,642 @@ TempoMap::metric_at (BBT_Time bbt) const void TempoMap::bbt_time (framepos_t frame, BBT_Time& bbt) { - { - Glib::RWLock::ReaderLock lm (lock); - bbt_time_unlocked (frame, bbt); + 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; } + bbt = beats_to_bbt (beat_at_frame (frame)); } -void -TempoMap::bbt_time_unlocked (framepos_t frame, BBT_Time& bbt) +int32_t +TempoMap::bars_in_meter_section (MeterSection* ms) const { - BBTPointList::const_iterator i = bbt_before_or_at (frame); - - bbt.bars = (*i).bar; - bbt.beats = (*i).beat; + /* YOU MUST HAVE THE READ LOCK */ + Metrics::const_iterator i; - if ((*i).frame == frame) { - bbt.ticks = 0; - } else { - bbt.ticks = llrint (((frame - (*i).frame) / (*i).meter->frames_per_division(*((*i).tempo), _frame_rate)) / - BBT_Time::ticks_per_bar_division); + MeterSection* next_ms = 0; + const MeterSection* prev_ms = &first_meter(); + + for (i = metrics.begin(); i != metrics.end(); ++i) { + MeterSection* m; + if ((m = dynamic_cast (*i)) != 0) { + if (ms->frame() < m->frame()) { + next_ms = m; + break; + } + prev_ms = m; + } } + if (next_ms) { + double ticks_at_next = tick_at_frame (next_ms->frame()); + double ticks_at_prev = tick_at_frame (prev_ms->frame()); + double ticks_in_meter = ticks_at_next - ticks_at_prev; + + return (int32_t) floor ((ticks_in_meter / BBT_Time::ticks_per_beat) / prev_ms->note_divisor()); + } + return -1; } -framepos_t -TempoMap::frame_time (const BBT_Time& bbt) +Timecode::BBT_Time +TempoMap::beats_to_bbt (double beats) { - Glib::RWLock::ReaderLock lm (lock); + /* CALLER HOLDS READ LOCK */ + BBT_Time ret; + MeterSection* prev_ms = &first_meter(); - BBTPointList::const_iterator s = bbt_point_for (BBT_Time (1, 1, 0)); - BBTPointList::const_iterator e = bbt_point_for (BBT_Time (bbt.bars, bbt.beats, 0)); + framecnt_t frame = frame_at_beat (beats); + uint32_t cnt = 0; - if (bbt.ticks != 0) { - return ((*e).frame - (*s).frame) + - llrint ((*e).meter->frames_per_division (*(*e).tempo, _frame_rate) * (bbt.ticks/BBT_Time::ticks_per_bar_division)); - } else { - return ((*e).frame - (*s).frame); + if (n_meters() < 2) { + uint32_t bars = (uint32_t) floor (beats / prev_ms->note_divisor()); + double remaining_beats = beats - (bars * prev_ms->note_divisor()); + double remaining_ticks = (remaining_beats - floor (remaining_beats)) * BBT_Time::ticks_per_beat; + + ret.ticks = (uint32_t) floor (remaining_ticks + 0.5); + ret.beats = (uint32_t) floor (remaining_beats); + ret.bars = 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_ms->note_divisor()) { + ++ret.bars; + ret.beats = 1; + } + + return ret; + } + + uint32_t first_beat_in_meter = 0; + uint32_t accumulated_bars = 0; + Metrics::const_iterator i; + + for (i = metrics.begin(); i != metrics.end(); ++i) { + MeterSection* m = 0; + + if ((m = dynamic_cast (*i)) != 0) { + first_beat_in_meter = beat_at_frame (m->frame()); + + if (beats < first_beat_in_meter) { + /* this is the meter after the one our beat is on*/ + break; + } + int32_t const bars_in_ms = bars_in_meter_section (m); + + if (bars_in_ms > 0) { + accumulated_bars += bars_in_ms; + } + + prev_ms = m; + ++cnt; + } + } + //cerr << "beats to bbr with beats = " << beats << " first_beat_in_meter = " << first_beat_in_meter << " accumulated_bars = " << accumulated_bars << endl; + + if (beats > first_beat_in_meter) { + /* prev_ms is the relevant one here */ + + /* now get the ticks at frame */ + double ticks_at_frame = tick_at_frame (frame); + + /* find the number of ticks at the beginning of the meter section (bar 1)*/ + double ticks_at_ms = tick_at_frame (prev_ms->frame()); + + double beats_used_by_ms = (ticks_at_frame - ticks_at_ms) / BBT_Time::ticks_per_beat; + + uint32_t bars = (uint32_t) floor (beats_used_by_ms / prev_ms->note_divisor()); + double remaining_beats = beats_used_by_ms - (bars * prev_ms->note_divisor()); + double remaining_ticks = (remaining_beats - floor (remaining_beats)) * BBT_Time::ticks_per_beat; + + ret.bars = bars + accumulated_bars; + ret.beats = (uint32_t) floor (remaining_beats); + ret.ticks = (uint32_t) floor (remaining_ticks + 0.5); + + /* now ensure we srtart at 1 1 0 */ + ++ret.bars; + ++ret.beats; + //cerr << "part 1 ret bars = " << ret.bars << " ret beats = " << ret.beats << " ret ticks = " << ret.ticks << endl; + if (ret.ticks >= BBT_Time::ticks_per_beat) { + ++ret.beats; + ret.ticks -= BBT_Time::ticks_per_beat; + } + + if (ret.beats > prev_ms->note_divisor()) { + ++ret.bars; + ret.beats = 1; + } + + return ret; + } + + /* find the number of ticks at the beginning of the meter section (bar 1)*/ + double ticks_at_ms = tick_at_frame (prev_ms->frame()); + + /* now get the ticks at frame */ + double ticks_at_frame = tick_at_frame (frame); + + double ticks_within_ms = ticks_at_frame - ticks_at_ms; + + ret.bars = (uint32_t) floor (((ticks_within_ms / BBT_Time::ticks_per_beat) / prev_ms->note_divisor())) + accumulated_bars; + uint32_t remaining_ticks = ticks_within_ms - (ret.bars * prev_ms->note_divisor() * BBT_Time::ticks_per_beat); + ret.beats = (uint32_t) floor (remaining_ticks); + remaining_ticks -= ret.beats * BBT_Time::ticks_per_beat; + + /* only round ticks */ + ret.ticks = (uint32_t) floor (remaining_ticks + 0.5); + + /* now ensure we srtart at 1 1 0 */ + ++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_ms->note_divisor()) { + ++ret.bars; + ret.beats = 1; + } + + return ret; +} + +double +TempoMap::tick_at_frame (framecnt_t frame) const +{ + Metrics::const_iterator i; + const TempoSection* prev_ts = &first_tempo(); + double accumulated_ticks = 0.0; + uint32_t cnt = 0; + + for (i = metrics.begin(); i != metrics.end(); ++i) { + TempoSection* t; + + if ((t = dynamic_cast (*i)) != 0) { + + if (frame < t->frame()) { + /*the previous ts is the one containing the frame */ + + framepos_t time = frame - prev_ts->frame(); + framepos_t last_frame = t->frame() - prev_ts->frame(); + double last_beats_per_minute = t->beats_per_minute(); + + return prev_ts->tick_at_frame (time, last_beats_per_minute, last_frame, _frame_rate) + accumulated_ticks; + } + + if (cnt > 0 && t->frame() > prev_ts->frame()) { + framepos_t time = t->frame() - prev_ts->frame(); + framepos_t last_frame = t->frame() - prev_ts->frame(); + double last_beats_per_minute = t->beats_per_minute(); + accumulated_ticks += prev_ts->tick_at_frame (time, last_beats_per_minute, last_frame, _frame_rate); + } + + prev_ts = t; + ++cnt; + } } + + /* treated s linear for this ts */ + framecnt_t frames_in_section = frame - prev_ts->frame(); + double ticks_in_section = (frames_in_section / prev_ts->frames_per_beat (_frame_rate)) * Timecode::BBT_Time::ticks_per_beat; + + return ticks_in_section + accumulated_ticks; + } framecnt_t -TempoMap::bbt_duration_at (framepos_t pos, const BBT_Time& bbt, int dir) +TempoMap::frame_at_tick (double tick) const { - Glib::RWLock::ReaderLock lm (lock); - framecnt_t frames = 0; - BBT_Time when; + double accumulated_ticks = 0.0; + const TempoSection* prev_ts = &first_tempo(); + uint32_t cnt = 0; + + Metrics::const_iterator i; - bbt_time (pos, when); - frames = bbt_duration_at_unlocked (when, bbt,dir); + for (i = metrics.begin(); i != metrics.end(); ++i) { + TempoSection* t; + if ((t = dynamic_cast (*i)) != 0) { - return frames; + if (cnt > 0 && t->frame() > prev_ts->frame()) { + framepos_t time = t->frame() - prev_ts->frame(); + framepos_t last_time = t->frame() - prev_ts->frame(); + double last_beats_per_minute = t->beats_per_minute(); + accumulated_ticks += prev_ts->tick_at_frame (time, last_beats_per_minute, last_time, _frame_rate); + } + + if (tick < accumulated_ticks) { + /* prev_ts is the one affecting us. */ + + double ticks_in_section = tick - tick_at_frame (prev_ts->frame()); + framepos_t section_start = prev_ts->frame(); + framepos_t last_time = t->frame() - prev_ts->frame(); + double last_beats_per_minute = t->beats_per_minute(); + return prev_ts->frame_at_tick (ticks_in_section, last_beats_per_minute, last_time, _frame_rate) + section_start; + } + + prev_ts = t; + ++cnt; + } + } + double ticks_in_section = tick - tick_at_frame (prev_ts->frame()); + double dtime = (ticks_in_section / BBT_Time::ticks_per_beat) * prev_ts->frames_per_beat(_frame_rate); + framecnt_t ret = ((framecnt_t) floor (dtime)) + prev_ts->frame(); + + return ret; +} + +double +TempoMap::beat_at_frame (framecnt_t frame) const +{ + return tick_at_frame (frame) / BBT_Time::ticks_per_beat; } framecnt_t -TempoMap::bbt_duration_at_unlocked (const BBT_Time& when, const BBT_Time& bbt, int dir) +TempoMap::frame_at_beat (double beat) const +{ + return frame_at_tick (beat * BBT_Time::ticks_per_beat); +} + +framepos_t +TempoMap::frame_time (const BBT_Time& bbt) { - if (bbt.bars == 0 && bbt.beats == 0 && bbt.ticks == 0) { + if (bbt.bars < 1) { + warning << string_compose (_("tempo map asked for frame time at bar < 1 (%1)\n"), bbt) << endmsg; return 0; } - /* round back to the previous precise beat */ - BBTPointList::const_iterator wi = bbt_point_for (BBT_Time (when.bars, when.beats, 0)); - BBTPointList::const_iterator start (wi); - double tick_frames = 0; + if (bbt.beats < 1) { + throw std::logic_error ("beats are counted from one"); + } + + Glib::Threads::RWLock::ReaderLock lm (lock); + + Metrics::const_iterator i; + uint32_t accumulated_bars = 0; - assert (wi != _map.end()); + MeterSection* prev_ms = &first_meter(); - /* compute how much rounding we did because of non-zero ticks */ + for (i = metrics.begin(); i != metrics.end(); ++i) { + MeterSection* m; + if ((m = dynamic_cast (*i)) != 0) { + int32_t const bims = bars_in_meter_section (m); - if (when.ticks != 0) { - tick_frames = (*wi).meter->frames_per_division (*(*wi).tempo, _frame_rate) * (when.ticks/BBT_Time::ticks_per_bar_division); + if (bims < 0 || bbt.bars <= (accumulated_bars + bims)) { + break; + } + if (bims > 0 ) { + accumulated_bars += bims; + } + prev_ms = m; + } } - - uint32_t bars = 0; - uint32_t beats = 0; - while (wi != _map.end() && bars < bbt.bars) { - ++wi; - if ((*wi).type == Bar) { - ++bars; + uint32_t remaining_bars = bbt.bars - accumulated_bars - 1; // back to zero - based bars + double const ticks_within_prev_taken_by_remaining_bars = remaining_bars * prev_ms->note_divisor() * BBT_Time::ticks_per_beat; + double const ticks_after_space_used_by_bars = ((bbt.beats - 1) * BBT_Time::ticks_per_beat) + bbt.ticks; // back to zero - based beats + double const ticks_target = ticks_within_prev_taken_by_remaining_bars + ticks_after_space_used_by_bars; + + TempoSection* prev_ts = &first_tempo(); + double accumulated_ticks = 0.0; + uint32_t cnt = 0; + + for (i = metrics.begin(); i != metrics.end(); ++i) { + TempoSection* t; + if ((t = dynamic_cast (*i)) != 0) { + if (t->frame() < prev_ms->frame()) { + continue; + } + + if (cnt > 0 && t->frame() > prev_ts->frame()) { + /*find the number of ticke in this section */ + framepos_t const time = t->frame() - prev_ts->frame(); + framepos_t const last_time = t->frame() - prev_ts->frame(); + double const last_beats_per_minute = t->beats_per_minute(); + accumulated_ticks += prev_ts->tick_at_frame (time, last_beats_per_minute, last_time, _frame_rate); + } + + if (ticks_target < accumulated_ticks) { + double const ticks_in_section = ticks_target - tick_at_frame (prev_ts->frame()); + framepos_t const section_start_time = prev_ts->frame(); + framepos_t const last_time = t->frame() - prev_ts->frame(); + double const last_beats_per_minute = t->beats_per_minute(); + framepos_t const ret = prev_ts->frame_at_tick (ticks_in_section, last_beats_per_minute, last_time, _frame_rate) + section_start_time; + return ret; + } + + prev_ts = t; + ++cnt; } } - assert (wi != _map.end()); - while (wi != _map.end() && beats < bbt.beats) { - ++wi; - ++beats; + /*treat this ts as constant tempo */ + double const ticks_in_this_ts = ticks_target - tick_at_frame (prev_ts->frame()); + double const dtime = (ticks_in_this_ts / BBT_Time::ticks_per_beat) * prev_ts->frames_per_beat(_frame_rate); + framecnt_t const ret = ((framecnt_t) floor (dtime)) + prev_ts->frame(); + return ret; +} + + +framecnt_t +TempoMap::bbt_duration_at (framepos_t pos, const BBT_Time& bbt, int dir) +{ + + Glib::Threads::RWLock::ReaderLock lm (lock); + + Metrics::const_iterator i; + TempoSection* first = 0; + TempoSection* second = 0; + + for (i = metrics.begin(); i != metrics.end(); ++i) { + TempoSection* t; + + if ((t = dynamic_cast (*i)) != 0) { + + if ((*i)->frame() > pos) { + second = t; + break; + } + + first = t; + } } - assert (wi != _map.end()); + if (first && second) { + framepos_t const last_time = second->frame() - first->frame(); + double const last_beats_per_minute = second->beats_per_minute(); + + framepos_t const time = pos - first->frame(); + double const tick_at_time = first->tick_at_frame (time, last_beats_per_minute, last_time, _frame_rate); + double const bbt_ticks = bbt.ticks + (bbt.beats * BBT_Time::ticks_per_beat); - /* add any additional frames related to ticks in the added value */ + double const time_at_bbt = first->frame_at_tick (tick_at_time + bbt_ticks, last_beats_per_minute, last_time, _frame_rate); - if (bbt.ticks != 0) { - tick_frames += (*wi).meter->frames_per_division (*(*wi).tempo, _frame_rate) * (bbt.ticks/BBT_Time::ticks_per_bar_division); + return time_at_bbt - time; } - return ((*wi).frame - (*start).frame) + llrint (tick_frames); + double const ticks = bbt.ticks + (bbt.beats * BBT_Time::ticks_per_beat); + return (framecnt_t) floor ((ticks / BBT_Time::ticks_per_beat) * first->frames_per_beat(_frame_rate)); } 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) { - 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); + uint32_t ticks = (uint32_t) floor (tick_at_frame (fr) + 0.5); + uint32_t beats = (uint32_t) floor (ticks / BBT_Time::ticks_per_beat); + uint32_t ticks_one_subdivisions_worth = (uint32_t)BBT_Time::ticks_per_beat / sub_num; - ticks_one_subdivisions_worth = (uint32_t)BBT_Time::ticks_per_bar_division / sub_num; - ticks_one_half_subdivisions_worth = ticks_one_subdivisions_worth / 2; + ticks -= beats * BBT_Time::ticks_per_beat; if (dir > 0) { + /* round to next (or same iff dir == RoundUpMaybe) */ - /* round to next */ + uint32_t mod = ticks % ticks_one_subdivisions_worth; - uint32_t mod = the_beat.ticks % ticks_one_subdivisions_worth; + if (mod == 0 && dir == RoundUpMaybe) { + /* right on the subdivision, which is fine, so do nothing */ - if (mod == 0) { + } else if (mod == 0) { /* right on the subdivision, so the difference is just the subdivision ticks */ - difference = ticks_one_subdivisions_worth; + ticks += ticks_one_subdivisions_worth; } else { /* not on subdivision, compute distance to next subdivision */ - difference = ticks_one_subdivisions_worth - mod; + ticks += ticks_one_subdivisions_worth - mod; } - the_beat = bbt_add (the_beat, BBT_Time (0, 0, difference)); - + if (ticks >= BBT_Time::ticks_per_beat) { + ticks -= BBT_Time::ticks_per_beat; + } } 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 = 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 (ticks < difference) { + ticks = BBT_Time::ticks_per_beat - ticks; + } else { + ticks -= difference; } } else { /* round to nearest */ + double rem; + + /* compute the distance to the previous and next subdivision */ + + if ((rem = fmod ((double) ticks, (double) ticks_one_subdivisions_worth)) > ticks_one_subdivisions_worth/2.0) { + + /* closer to the next subdivision, so shift forward */ + + ticks = lrint (ticks + (ticks_one_subdivisions_worth - rem)); + + DEBUG_TRACE (DEBUG::SnapBBT, string_compose ("moved forward to %1\n", ticks)); - 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)); + if (ticks > BBT_Time::ticks_per_beat) { + ++beats; + ticks -= BBT_Time::ticks_per_beat; + DEBUG_TRACE (DEBUG::SnapBBT, string_compose ("fold beat to %1\n", beats)); + } + + } else if (rem > 0) { + + /* closer to previous subdivision, so shift backward */ + + if (rem > ticks) { + if (beats == 0) { + /* can't go backwards past zero, so ... */ + return 0; + } + /* step back to previous beat */ + --beats; + ticks = lrint (BBT_Time::ticks_per_beat - rem); + DEBUG_TRACE (DEBUG::SnapBBT, string_compose ("step back beat to %1\n", beats)); + } else { + ticks = lrint (ticks - rem); + DEBUG_TRACE (DEBUG::SnapBBT, string_compose ("moved backward to %1\n", 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 frame_at_tick ((beats * BBT_Time::ticks_per_beat) + ticks); } framepos_t -TempoMap::round_to_type (framepos_t frame, int dir, BBTPointType type) +TempoMap::round_to_type (framepos_t frame, RoundMode dir, BBTPointType type) { - BBTPointList::const_iterator fi; + Glib::Threads::RWLock::ReaderLock lm (lock); - if (dir > 0) { - fi = bbt_after_or_at (frame); - } else { - fi = bbt_before_or_at (frame); - } + double const beat_at_framepos = beat_at_frame (frame); - assert (fi != _map.end()); + BBT_Time bbt (beats_to_bbt (beat_at_framepos)); - DEBUG_TRACE(DEBUG::SnapBBT, string_compose ("round from %1 (%3|%4 @ %5) to bars in direction %2\n", frame, dir, (*fi).bar, (*fi).beat, (*fi).frame)); - switch (type) { case Bar: if (dir < 0) { /* find bar previous to 'frame' */ - - if ((*fi).beat == 1 && (*fi).frame == frame) { - --fi; - } - - while ((*fi).beat > 1) { - 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; + bbt.beats = 1; + bbt.ticks = 0; + return frame_time (bbt); } else if (dir > 0) { - /* find bar following 'frame' */ - - if ((*fi).beat == 1 && (*fi).frame == frame) { - ++fi; - } - - while ((*fi).beat != 1) { - 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; - + ++bbt.bars; + bbt.beats = 1; + bbt.ticks = 0; + return frame_time (bbt); } else { - /* true rounding: find nearest bar */ - BBTPointList::const_iterator prev = fi; - BBTPointList::const_iterator next = fi; - - while ((*prev).beat != 1) { - if (prev == _map.begin()) { - break; - } - prev--; - } - - while ((*next).beat != 1) { - next++; - if (next == _map.end()) { - --next; - break; - } - } + framepos_t raw_ft = frame_time (bbt); + bbt.beats = 1; + bbt.ticks = 0; + framepos_t prev_ft = frame_time (bbt); + ++bbt.bars; + framepos_t next_ft = frame_time (bbt); - if ((frame - (*prev).frame) < ((*next).frame - frame)) { - return (*prev).frame; + if ((raw_ft - prev_ft) > (next_ft - prev_ft) / 2) { + return next_ft; } else { - return (*next).frame; + return prev_ft; } - } break; case Beat: if (dir < 0) { - if ((*fi).frame >= frame) { - 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; + return frame_at_beat (floor (beat_at_framepos)); } else if (dir > 0) { - if ((*fi).frame <= frame) { - DEBUG_TRACE (DEBUG::SnapBBT, "requested frame is on beat, step forward\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; + return frame_at_beat (ceil (beat_at_framepos)); } else { - /* find beat nearest to frame */ - if ((*fi).frame == frame) { - return frame; - } - - BBTPointList::const_iterator prev = fi; - BBTPointList::const_iterator next = fi; - --prev; - ++next; - - if ((frame - (*prev).frame) < ((*next).frame - frame)) { - return (*prev).frame; - } else { - return (*next).frame; - } + return frame_at_beat (floor (beat_at_framepos + 0.5)); } break; } + + return 0; } void -TempoMap::map (TempoMap::BBTPointList& points, framepos_t lower, framepos_t upper) +TempoMap::get_grid (vector& points, + framepos_t lower, framepos_t upper) +{ + Glib::Threads::RWLock::ReaderLock lm (lock); + uint32_t const upper_beat = (uint32_t) floor (beat_at_frame (upper)); + uint32_t cnt = (uint32_t) ceil (beat_at_frame (lower)); + + while (cnt <= upper_beat) { + framecnt_t const pos = frame_at_beat (cnt); + MeterSection const meter = meter_section_at (pos); + Tempo const tempo = tempo_at (pos); + BBT_Time const bbt = beats_to_bbt ((double) cnt); + + points.push_back (BBTPoint (meter, tempo, pos, bbt.bars, bbt.beats)); + ++cnt; + } +} + +const TempoSection& +TempoMap::tempo_section_at (framepos_t frame) const { - if (_map.empty() || upper >= _map.back().frame) { - recompute_map (false, upper); + Glib::Threads::RWLock::ReaderLock lm (lock); + + Metrics::const_iterator i; + TempoSection* prev = 0; + + for (i = metrics.begin(); i != metrics.end(); ++i) { + TempoSection* t; + + if ((t = dynamic_cast (*i)) != 0) { + + if ((*i)->frame() > frame) { + break; + } + + prev = t; + } + } + + if (prev == 0) { + fatal << endmsg; + abort(); /*NOTREACHED*/ } - for (BBTPointList::const_iterator i = _map.begin(); i != _map.end(); ++i) { - if ((*i).frame < lower) { - continue; - } - if ((*i).frame >= upper) { - break; + return *prev; +} + +const Tempo +TempoMap::tempo_at (framepos_t frame) const +{ + Glib::Threads::RWLock::ReaderLock lm (lock); + + TempoMetric m (metric_at (frame)); + TempoSection* prev_ts = 0; + + Metrics::const_iterator i; + + for (i = metrics.begin(); i != metrics.end(); ++i) { + TempoSection* t; + if ((t = dynamic_cast (*i)) != 0) { + if ((prev_ts) && t->frame() > frame) { + /* this is the one past frame */ + framepos_t const time = frame - prev_ts->frame(); + framepos_t const last_time = t->frame() - prev_ts->frame(); + double const last_beats_per_minute = t->beats_per_minute(); + double const ret = prev_ts->tempo_at_frame (time, last_beats_per_minute, last_time, _frame_rate); + Tempo const ret_tempo (ret, m.tempo().note_type ()); + return ret_tempo; + } + prev_ts = t; } - points.push_back (*i); } + + return m.tempo(); + } -const TempoSection& -TempoMap::tempo_section_at (framepos_t frame) const +const MeterSection& +TempoMap::meter_section_at (framepos_t frame) const { - Glib::RWLock::ReaderLock lm (lock); + Glib::Threads::RWLock::ReaderLock lm (lock); Metrics::const_iterator i; - TempoSection* prev = 0; + MeterSection* prev = 0; - for (i = metrics->begin(); i != metrics->end(); ++i) { - TempoSection* t; + for (i = metrics.begin(); i != metrics.end(); ++i) { + MeterSection* t; - if ((t = dynamic_cast (*i)) != 0) { + if ((t = dynamic_cast (*i)) != 0) { if ((*i)->frame() > frame) { break; @@ -1381,19 +1793,12 @@ TempoMap::tempo_section_at (framepos_t frame) const if (prev == 0) { fatal << endmsg; + abort(); /*NOTREACHED*/ } return *prev; } -const Tempo& -TempoMap::tempo_at (framepos_t frame) const -{ - TempoMetric m (metric_at (frame)); - return m.tempo(); -} - - const Meter& TempoMap::meter_at (framepos_t frame) const { @@ -1408,8 +1813,8 @@ TempoMap::get_state () XMLNode *root = new XMLNode ("TempoMap"); { - Glib::RWLock::ReaderLock lm (lock); - for (i = metrics->begin(); i != metrics->end(); ++i) { + Glib::Threads::RWLock::ReaderLock lm (lock); + for (i = metrics.begin(); i != metrics.end(); ++i) { root->add_child_nocopy ((*i)->get_state()); } } @@ -1421,17 +1826,16 @@ 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 old_metrics (metrics); MeterSection* last_meter = 0; - - metrics->clear(); + metrics.clear(); nlist = node.children(); - + for (niter = nlist.begin(); niter != nlist.end(); ++niter) { XMLNode* child = *niter; @@ -1439,18 +1843,18 @@ TempoMap::set_state (const XMLNode& node, int /*version*/) try { TempoSection* ts = new TempoSection (*child); - metrics->push_back (ts); + 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; } @@ -1458,24 +1862,48 @@ TempoMap::set_state (const XMLNode& node, int /*version*/) try { MeterSection* ms = new MeterSection (*child); - metrics->push_back (ms); + 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); - recompute_map (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 ()); @@ -1486,10 +1914,11 @@ 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 << " (Bar-offset: " << t->bar_offset() << ") " << t->beats_per_minute() << " BPM (pulse = 1/" << t->note_type() << ") at " << t->start() << " frame= " << t->frame() << " (movable? " @@ -1504,10 +1933,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++; } @@ -1519,10 +1948,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++; } @@ -1534,399 +1963,184 @@ 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)->movable ()) { - (*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); + } } - } - - timestamp_metrics_from_audio_time (); - - PropertyChanged (PropertyChange ()); -} -BBT_Time -TempoMap::bbt_add (const BBT_Time& start, const BBT_Time& other) const -{ - TempoMetric metric = metric_at (start); - return bbt_add (start, other, metric); -} + /* 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. + */ -/** - * add the BBT interval @param increment to @param start and return the result - */ -BBT_Time -TempoMap::bbt_add (const BBT_Time& start, const BBT_Time& increment, const TempoMetric& /*metric*/) 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; + Metrics::iterator i; + const MeterSection* meter; + const TempoSection* tempo; + MeterSection *m; + TempoSection *t; - if (ticks >= BBT_Time::ticks_per_bar_division) { - op.beats++; - result.ticks = ticks % (uint32_t) BBT_Time::ticks_per_bar_division; - } else { - result.ticks += op.ticks; - } + meter = &first_meter (); + tempo = &first_tempo (); - /* now comes the complicated part. we have to add one beat a time, - checking for a new metric on every beat. - */ + BBT_Time start; + BBT_Time end; - /* grab all meter sections */ + // cerr << "\n###################### TIMESTAMP via AUDIO ##############\n" << endl; - list meter_sections; + bool first = true; + MetricSection* prev = 0; - 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); - } - } + for (i = metrics.begin(); i != metrics.end(); ++i) { - assert (!meter_sections.empty()); + BBT_Time bbt; + TempoMetric metric (*meter, *tempo); - list::const_iterator next_meter; - const Meter* meter = 0; + 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 + } - /* 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. - */ + bbt_time ((*i)->frame(), bbt); - for (next_meter = meter_sections.begin(); next_meter != meter_sections.end(); ++next_meter) { + // cerr << "timestamp @ " << (*i)->frame() << " with " << bbt.bars << "|" << bbt.beats << "|" << bbt.ticks << " => "; - if (result < (*next_meter)->start()) { - /* this metric is past the result time. stop looking, we have what we need */ - break; - } + if (first) { + first = false; + } else { - 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. - */ - meter = *next_meter; - ++next_meter; - break; - } + if (bbt.ticks > BBT_Time::ticks_per_beat/2) { + /* round up to next beat */ + bbt.beats += 1; + } - meter = *next_meter; - } + bbt.ticks = 0; - assert (meter != 0); + if (bbt.beats != 1) { + /* round up to next bar */ + bbt.bars += 1; + bbt.beats = 1; + } + } - /* 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) - */ + // cerr << bbt << endl; - while (op.beats) { + (*i)->set_start (bbt); - /* given the current meter, have we gone past the end of the bar ? */ + 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() <= meter->divisions_per_bar()) { - /* move to next bar, first beat */ - result.bars++; - result.beats = 1; - } else { - result.beats++; + prev = (*i); } - /* 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. - */ - - if (next_meter != meter_sections.end() && (((*next_meter)->start () < result) || (result == (*next_meter)->start()))) { - meter = *next_meter; - ++next_meter; - } + recompute_map (true); } - /* finally, add bars */ - result.bars += op.bars++; - - return result; + PropertyChanged (PropertyChange ()); } - -/** - * 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 +bool +TempoMap::remove_time (framepos_t where, framecnt_t amount) { - BBT_Time result = start; - BBT_Time op = decrement; /* argument is const, but we need to modify it */ - - if (op.ticks > result.ticks) { - /* subtract an extra beat later; meanwhile set ticks to the right "carry" value */ - op.beats++; - result.ticks = BBT_Time::ticks_per_bar_division - (op.ticks - result.ticks); - } else { - result.ticks -= op.ticks; - } - - - /* now comes the complicated part. we have to subtract one beat a time, - checking for a new metric on every beat. - */ + bool moved = false; - /* grab all meter sections */ + std::list metric_kill_list; - 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); - } + 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; + } } - 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. - */ - - 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 - */ - - if ((*next_meter)->start() < result || (*next_meter)->start() == result) { - meter = *next_meter; - ++next_meter; - break; + //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; } - } - - 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) - */ - - 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. - */ - - 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"); - } - - result.bars--; - result.beats = meter->divisions_per_bar(); - } else { - - /* back one beat */ - - result.beats--; + if (last_meter && !meter_after) { + metric_kill_list.remove(last_meter); + last_meter->set_frame(where); + moved = true; } - /* 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; + //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; } - } - - /* finally, subtract bars */ - if (op.bars >= result.bars) { - /* i'm sorry dave, i can't do that */ - throw std::out_of_range ("illegal BBT subtraction"); + if (moved) { + recompute_map (true); + } } - - result.bars -= op.bars; - return result; + PropertyChanged (PropertyChange ()); + return moved; } /** Add some (fractional) beats to a session frame position, and return the result in frames. * pos can be -ve, if required. */ framepos_t -TempoMap::framepos_plus_beats (framepos_t pos, Evoral::MusicalTime beats) +TempoMap::framepos_plus_beats (framepos_t pos, Evoral::Beats beats) const { - Metrics::const_iterator i; - const TempoSection* tempo; - - /* Find the starting tempo */ - - for (i = metrics->begin(); i != metrics->end(); ++i) { - - /* 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 = (*i)->frame (); - if (pos < 0 && f == 0) { - f = pos; - } - - if (f > pos) { - break; - } - - const TempoSection* t; - - if ((t = dynamic_cast(*i)) != 0) { - tempo = t; - } - } - - /* We now have: - - tempo -> the Tempo for "pos" - i -> for first new metric after "pos", possibly metrics->end() - */ - - while (beats) { - - /* Distance to the end of this section in frames */ - framecnt_t distance_frames = i == metrics->end() ? max_framepos : ((*i)->frame() - pos); - - /* Distance to the end in beats */ - Evoral::MusicalTime distance_beats = distance_frames / tempo->frames_per_beat (_frame_rate); - - /* Amount to subtract this time */ - double const sub = min (distance_beats, beats); - - /* Update */ - beats -= sub; - pos += sub * tempo->frames_per_beat (_frame_rate); - - /* Move on if there's anything to move to */ - if (i != metrics->end ()) { - const TempoSection* t; - - if ((t = dynamic_cast(*i)) != 0) { - tempo = t; - } - - ++i; - } - } - - return pos; + return frame_at_beat (beat_at_frame (pos) + beats.to_double()); } -/** Subtract some (fractional) beats to a frame position, and return the result in frames */ +/** Subtract some (fractional) beats from a frame position, and return the result in frames */ framepos_t -TempoMap::framepos_minus_beats (framepos_t pos, Evoral::MusicalTime beats) +TempoMap::framepos_minus_beats (framepos_t pos, Evoral::Beats beats) const { - Metrics::const_iterator i; - const TempoSection* tempo = 0; - const TempoSection* t; - - /* Find the starting tempo */ - - for (i = metrics->begin(); i != metrics->end(); ++i) { - - /* 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 = (*i)->frame (); - if (pos < 0 && f == 0) { - f = pos; - } - - if ((*i)->frame() > pos) { - break; - } - - if ((t = dynamic_cast(*i)) != 0) { - tempo = t; - } - } - - bool no_more_tempos = false; - - /* Move i back to the tempo before "pos" */ - if (i != metrics->begin ()) { - while (i != metrics->begin ()) { - --i; - t = dynamic_cast (*i); - if (t) { - break; - } - } - } else { - no_more_tempos = true; - } - - /* We now have: - - tempo -> the Tempo for "pos" - i -> the first metric before "pos", unless no_more_tempos is true - */ - - while (beats) { - - /* Distance to the end of this section in frames */ - framecnt_t distance_frames = no_more_tempos ? max_framepos : (pos - (*i)->frame()); - - /* Distance to the end in beats */ - Evoral::MusicalTime distance_beats = distance_frames / tempo->frames_per_beat (_frame_rate); - - /* Amount to subtract this time */ - double const sub = min (distance_beats, beats); - - /* Update */ - beats -= sub; - pos -= sub * tempo->frames_per_beat (_frame_rate); - - /* Move i and tempo back, if there's anything to move to */ - if (i != metrics->begin ()) { - while (i != metrics->begin ()) { - --i; - if ((t = dynamic_cast(*i)) != 0) { - tempo = t; - break; - } - } - } else { - no_more_tempos = true; - } - } - - return pos; + return frame_at_beat (beat_at_frame (pos) - beats.to_double()); } /** Add the BBT interval op to pos and return the result */ framepos_t -TempoMap::framepos_plus_bbt (framepos_t pos, BBT_Time op) +TempoMap::framepos_plus_bbt (framepos_t pos, BBT_Time op) const { + 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); meter = &first_meter (); tempo = &first_tempo (); @@ -1936,9 +2150,9 @@ TempoMap::framepos_plus_bbt (framepos_t pos, BBT_Time op) /* find the starting metrics for tempo & meter */ - for (i = metrics->begin(); i != metrics->end(); ++i) { + for (i = metrics.begin(); i != metrics.end(); ++i) { - if ((*i)->frame() > pos) { + if ((*i)->frame() > effective_pos) { break; } @@ -1953,7 +2167,7 @@ TempoMap::framepos_plus_bbt (framepos_t pos, BBT_Time op) meter -> the Meter for "pos" tempo -> the Tempo for "pos" - i -> for first new metric after "pos", possibly metrics->end() + i -> for first new metric after "pos", possibly metrics.end() */ /* now comes the complicated part. we have to add one beat a time, @@ -1973,7 +2187,7 @@ TempoMap::framepos_plus_bbt (framepos_t pos, BBT_Time op) to or after the start of the next metric section? in which case, use it. */ - if (i != metrics->end()) { + if (i != metrics.end()) { if ((*i)->frame() <= pos) { /* about to change tempo or meter, so add the @@ -1981,7 +2195,7 @@ TempoMap::framepos_plus_bbt (framepos_t pos, BBT_Time op) * traversed before we change the * frames_per_beat value. */ - + pos += llrint (frames_per_beat * (bars * meter->divisions_per_bar())); bars = 0; @@ -2013,7 +2227,7 @@ TempoMap::framepos_plus_bbt (framepos_t pos, BBT_Time op) to or after the start of the next metric section? in which case, use it. */ - if (i != metrics->end()) { + if (i != metrics.end()) { if ((*i)->frame() <= pos) { /* about to change tempo or meter, so add the @@ -2039,71 +2253,26 @@ TempoMap::framepos_plus_bbt (framepos_t pos, BBT_Time op) pos += llrint (beats * frames_per_beat); if (op.ticks) { - if (op.ticks >= BBT_Time::ticks_per_bar_division) { + 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_bar_division) / - (double) BBT_Time::ticks_per_bar_division))); + (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_bar_division)); + 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::MusicalTime -TempoMap::framewalk_to_beats (framepos_t pos, framecnt_t distance) -{ - BBTPointList::const_iterator i = bbt_after_or_at (pos); - Evoral::MusicalTime beats = 0; - framepos_t end = pos + distance; - - require_map_to (end); - - /* if our starting BBTPoint is after pos, add a fractional beat - to represent that distance. - */ - - if ((*i).frame != pos) { - beats += ((*i).frame - pos) / (*i).meter->frames_per_division (*(*i).tempo, _frame_rate); - } - - while (i != _map.end() && (*i).frame < end) { - ++i; - beats++; - } - assert (i != _map.end()); - - /* if our ending BBTPoint is after the end, subtract a fractional beat - to represent that distance. - */ - - if ((*i).frame > end) { - beats -= ((*i).frame - end) / (*i).meter->frames_per_division (*(*i).tempo, _frame_rate); - } - - return beats; -} - -TempoMap::BBTPointList::const_iterator -TempoMap::bbt_before_or_at (framepos_t pos) -{ - require_map_to (pos); - BBTPointList::const_iterator i = lower_bound (_map.begin(), _map.end(), pos); - assert (i != _map.end()); - return i; -} - -TempoMap::BBTPointList::const_iterator -TempoMap::bbt_after_or_at (framepos_t pos) +Evoral::Beats +TempoMap::framewalk_to_beats (framepos_t pos, framecnt_t distance) const { - require_map_to (pos); - BBTPointList::const_iterator i = upper_bound (_map.begin(), _map.end(), pos); - assert (i != _map.end()); - return i; + return Evoral::Beats(beat_at_frame (pos + distance) - beat_at_frame (pos)); } struct bbtcmp { @@ -2112,67 +2281,17 @@ struct bbtcmp { } }; -TempoMap::BBTPointList::const_iterator -TempoMap::bbt_point_for (const BBT_Time& bbt) -{ - bbtcmp cmp; - int additional_minutes = 1; - - while (_map.empty() || _map.back().bar < (bbt.bars + 1)) { - /* add some more distance, using bigger steps each time */ - require_map_to (_map.back().frame + (_frame_rate * 60 * additional_minutes)); - additional_minutes *= 2; - } - - BBTPointList::const_iterator i = lower_bound (_map.begin(), _map.end(), bbt, cmp); - assert (i != _map.end()); - 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. - */ - -int -MetricSection::compare (const MetricSection& other) const -{ - if (start() == other.start()) { - return 0; - } else if (start() < other.start()) { - return -1; - } else { - return 1; - } - - /* NOTREACHED */ - return 0; -} - -bool -MetricSection::operator== (const MetricSection& other) const -{ - return compare (other) == 0; -} - -bool -MetricSection::operator!= (const MetricSection& other) const -{ - return compare (other) != 0; -} - -std::ostream& +std::ostream& operator<< (std::ostream& o, const Meter& m) { return o << m.divisions_per_bar() << '/' << m.note_divisor(); } -std::ostream& +std::ostream& operator<< (std::ostream& o, const Tempo& t) { return o << t.beats_per_minute() << " 1/" << t.note_type() << "'s per minute"; } -std::ostream& +std::ostream& operator<< (std::ostream& o, const MetricSection& section) { o << "MetricSection @ " << section.frame() << " aka " << section.start() << ' '; @@ -2181,9 +2300,9 @@ operator<< (std::ostream& o, const MetricSection& section) { const MeterSection* ms; if ((ts = dynamic_cast (§ion)) != 0) { - o << *((Tempo*) ts); + o << *((const Tempo*) ts); } else if ((ms = dynamic_cast (§ion)) != 0) { - o << *((Meter*) ms); + o << *((const Meter*) ms); } return o;