X-Git-Url: https://main.carlh.net/gitweb/?a=blobdiff_plain;f=libs%2Fardour%2Ftempo.cc;h=445fc7bab38546a959c843c2fc4ece75379fd4a9;hb=00967a19829cc6aab30eefecdd1deef29c7c9d33;hp=096bcd8a67fb1c5a6b5ff7b86c2aefd8b182dd33;hpb=355183f1abea75d8fab0926cd7e7130796574cb0;p=ardour.git diff --git a/libs/ardour/tempo.cc b/libs/ardour/tempo.cc index 096bcd8a67..445fc7bab3 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,19 +44,13 @@ 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 +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 + 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". @@ -79,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; @@ -140,7 +133,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, @@ -163,7 +156,7 @@ void TempoSection::update_bar_offset_from_bbt (const Meter& m) { - _bar_offset = ((start().beats - 1) * BBT_Time::ticks_per_beat + start().ticks) / + _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())); @@ -180,15 +173,15 @@ TempoSection::update_bbt_time_from_bar_offset (const Meter& meter) } 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 = (uint32_t) fmod (ticks, BBT_Time::ticks_per_beat); + 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); @@ -201,9 +194,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; @@ -226,7 +219,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) { @@ -257,7 +250,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, @@ -291,6 +284,7 @@ TempoMap::TempoMap (framecnt_t fr) start.beats = 1; start.ticks = 0; + // these leak memory, well Metrics does TempoSection *t = new TempoSection (start, _default_tempo.beats_per_minute(), _default_tempo.note_type()); MeterSection *m = new MeterSection (start, _default_meter.divisions_per_bar(), _default_meter.note_divisor()); @@ -313,24 +307,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) { @@ -338,30 +320,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) { @@ -369,6 +358,25 @@ 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) { @@ -377,7 +385,7 @@ TempoMap::do_insert (MetricSection* section) 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)) { @@ -385,106 +393,117 @@ TempoMap::do_insert (MetricSection* 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); } } - 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 iter_is_tempo = dynamic_cast (*i) != 0; - bool const insert_is_tempo = dynamic_cast (section) != 0; + if ((*i)->start().bars == section->start().bars && + (*i)->start().beats == section->start().beats) { - if (iter_is_tempo == insert_is_tempo) { + if (!(*i)->movable()) { - if (!(*i)->movable()) { + /* can't (re)move this section, so overwrite + * its data content (but not its properties as + * a section). + */ - /* 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 + */ - if (!iter_is_tempo) { *(dynamic_cast(*i)) = *(dynamic_cast(section)); + need_add = false; } else { - *(dynamic_cast(*i)) = *(dynamic_cast(section)); + metrics.erase (i); + } - need_add = false; + break; } - - to_remove = 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 */ + /* Add the given MetricSection, if we didn't just reset an existing + * one above + */ if (need_add) { + + Metrics::iterator i; + for (i = metrics.begin(); i != metrics.end(); ++i) { - - if ((*i)->compare (*section) < 0) { - continue; + if ((*i)->start() > section->start()) { + break; } - - metrics.insert (i, section); - break; } - if (i == metrics.end()) { - metrics.insert (metrics.end(), section); - } + metrics.insert (i, section); } } void TempoMap::replace_tempo (const TempoSection& ts, const Tempo& tempo, const BBT_Time& where) { - const TempoSection& first (first_tempo()); + { + Glib::Threads::RWLock::WriterLock lm (lock); + TempoSection& first (first_tempo()); - 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); + } else { + { + /* cannot move the first tempo section */ + *static_cast(&first) = tempo; + recompute_map (false); + } } } @@ -495,65 +514,72 @@ void TempoMap::add_tempo (const Tempo& tempo, BBT_Time where) { { - Glib::RWLock::WriterLock lm (lock); + Glib::Threads::RWLock::WriterLock lm (lock); + add_tempo_locked (tempo, where, true); + } - /* 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) +{ + /* new tempos always start on a beat */ + where.ticks = 0; + + TempoSection* ts = new TempoSection (where, tempo.beats_per_minute(), tempo.note_type()); - ts->update_bar_offset_from_bbt (*meter); + /* find the meter to use to set the bar offset of this + * tempo section. + */ - /* and insert it */ - - do_insert (ts); + const Meter* meter = &first_meter(); - recompute_map (false); + /* 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; + } } + ts->update_bar_offset_from_bbt (*meter); - PropertyChanged (PropertyChange ()); + /* 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); + if (ms.start() != first.start()) { + remove_meter_locked (ms); + add_meter_locked (meter, where, true); + } else { /* cannot move the first meter section */ - *((Meter*)&first) = meter; + *static_cast(&first) = meter; recompute_map (true); } } @@ -565,28 +591,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())); - recompute_map (true); + Glib::Threads::RWLock::WriterLock lm (lock); + add_meter_locked (meter, where, true); } - + #ifndef NDEBUG if (DEBUG_ENABLED(DEBUG::TempoMap)) { dump (std::cerr); @@ -596,6 +605,32 @@ 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) { @@ -604,8 +639,8 @@ TempoMap::change_initial_tempo (double beats_per_minute, double note_type) 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); } @@ -655,7 +690,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); @@ -676,7 +711,25 @@ TempoMap::first_meter () const } 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; } @@ -685,6 +738,8 @@ TempoMap::first_tempo () const { const TempoSection *t = 0; + /* CALLER MUST HOLD LOCK */ + for (Metrics::const_iterator i = metrics.begin(); i != metrics.end(); ++i) { if ((t = dynamic_cast (*i)) != 0) { return *t; @@ -692,14 +747,30 @@ TempoMap::first_tempo () const } fatal << _("programming error: no tempo section in tempo map!") << endmsg; - /*NOTREACHED*/ + abort(); /*NOTREACHED*/ + return *t; +} + +TempoSection& +TempoMap::first_tempo () +{ + TempoSection *t = 0; + + 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; + abort(); /*NOTREACHED*/ return *t; } void TempoMap::require_map_to (framepos_t pos) { - Glib::RWLock::WriterLock lm (lock); + Glib::Threads::RWLock::WriterLock lm (lock); if (_map.empty() || _map.back().frame < pos) { extend_map (pos); @@ -709,7 +780,7 @@ TempoMap::require_map_to (framepos_t pos) void TempoMap::require_map_to (const BBT_Time& bbt) { - Glib::RWLock::WriterLock lm (lock); + Glib::Threads::RWLock::WriterLock lm (lock); /* 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 @@ -717,7 +788,7 @@ TempoMap::require_map_to (const BBT_Time& bbt) */ int additional_minutes = 1; - + while (1) { if (!_map.empty() && _map.back().bar >= (bbt.bars + 1)) { break; @@ -741,12 +812,11 @@ TempoMap::recompute_map (bool reassign_tempo_bbt, framepos_t end) 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 */ @@ -765,6 +835,8 @@ TempoMap::recompute_map (bool reassign_tempo_bbt, framepos_t end) } } + assert(meter); + for (Metrics::iterator i = metrics.begin(); i != metrics.end(); ++i) { TempoSection* ts; @@ -774,6 +846,8 @@ TempoMap::recompute_map (bool reassign_tempo_bbt, framepos_t end) } } + assert(tempo); + /* assumes that the first meter & tempo are at frame zero */ current_frame = 0; meter->set_frame (0); @@ -794,7 +868,7 @@ TempoMap::recompute_map (bool reassign_tempo_bbt, framepos_t end) TempoSection* ts; MeterSection* ms; - + if ((ts = dynamic_cast(*i)) != 0) { /* reassign the BBT time of this tempo section @@ -807,7 +881,7 @@ TempoMap::recompute_map (bool reassign_tempo_bbt, framepos_t end) rmeter = ms; } else { fatal << _("programming error: unhandled MetricSection type") << endmsg; - /*NOTREACHED*/ + abort(); /*NOTREACHED*/ } } } @@ -842,7 +916,7 @@ TempoMap::extend_map (framepos_t end) return; } - BBTPointList::const_iterator i = _map.end(); + BBTPointList::const_iterator i = _map.end(); Metrics::iterator next_metric; --i; @@ -856,7 +930,7 @@ TempoMap::extend_map (framepos_t end) } /* find the metric immediately after the tempo + meter sections for the - * last point in the map + * last point in the map */ for (next_metric = metrics.begin(); next_metric != metrics.end(); ++next_metric) { @@ -866,16 +940,16 @@ TempoMap::extend_map (framepos_t end) } /* we cast away const here because this is the one place where we need - * to actually modify the frame time of each metric section. + * to actually modify the frame time of each metric section. */ - _extend_map (const_cast ((*i).tempo), + _extend_map (const_cast ((*i).tempo), const_cast ((*i).meter), next_metric, BBT_Time ((*i).bar, (*i).beat, 0), (*i).frame, end); } void -TempoMap::_extend_map (TempoSection* tempo, MeterSection* meter, +TempoMap::_extend_map (TempoSection* tempo, MeterSection* meter, Metrics::iterator next_metric, BBT_Time current, framepos_t current_frame, framepos_t end) { @@ -883,16 +957,26 @@ TempoMap::_extend_map (TempoSection* tempo, MeterSection* meter, TempoSection* ts; MeterSection* ms; - double divisions_per_bar; double beat_frames; + double current_frame_exact; + framepos_t bar_start_frame; + + DEBUG_TRACE (DEBUG::TempoMath, string_compose ("Extend map to %1 from %2 = %3\n", end, current, current_frame)); + + if (current.beats == 1) { + bar_start_frame = current_frame; + } else { + bar_start_frame = 0; + } - divisions_per_bar = meter->divisions_per_bar (); beat_frames = meter->frames_per_grid (*tempo,_frame_rate); + current_frame_exact = current_frame; while (current_frame < end) { - + current.beats++; - current_frame += beat_frames; + current_frame_exact += beat_frames; + current_frame = llrint(current_frame_exact); if (current.beats > meter->divisions_per_bar()) { current.bars++; @@ -907,7 +991,7 @@ TempoMap::_extend_map (TempoSection* tempo, MeterSection* meter, if (!(current < (*next_metric)->start())) { - set_metrics: + set_metrics: if (((ts = dynamic_cast (*next_metric)) != 0)) { tempo = ts; @@ -923,85 +1007,113 @@ TempoMap::_extend_map (TempoSection* tempo, MeterSection* meter, * 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 the preceding tempo and part * from this new tempo. */ if (tempo->start().ticks != 0) { - - double next_beat_frames = tempo->frames_per_beat (_frame_rate); - + + 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())); - + 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); + current_frame_exact -= beat_frames; + current_frame = llrint(current_frame_exact); + + /* 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))); + + /* 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. + */ + + double offset_within_old_beat = (tempo->frame() - current_frame) / beat_frames; + + current_frame_exact += (offset_within_old_beat * beat_frames) + ((1.0 - offset_within_old_beat) * next_beat_frames); + current_frame = llrint(current_frame_exact); + /* 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)); - + } else { - + DEBUG_TRACE (DEBUG::TempoMath, string_compose ("bumped into beat-aligned tempo metric at %1 = %2\n", - tempo->start(), current_frame)); + tempo->start(), current_frame)); tempo->set_frame (current_frame); } } 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 vs %2 (%3)\n", - meter->start(), current, current_frame)); - + meter->start(), current, current_frame)); + assert (current.beats == 1); meter->set_frame (current_frame); } - - divisions_per_bar = meter->divisions_per_bar (); + beat_frames = meter->frames_per_grid (*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))); - + + 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))); + ++next_metric; if (next_metric != metrics.end() && ((*next_metric)->start() == current)) { /* same position so go back and set this one up before advancing - */ + */ goto set_metrics; } + } } 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), current.bars, 1)); + _map.push_back (BBTPoint (*meter, *tempo, current_frame, current.bars, 1)); + bar_start_frame = current_frame; } 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), current.bars, current.beats)); + _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) 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 @@ -1012,33 +1124,25 @@ TempoMap::metric_at (framepos_t frame) const for (Metrics::const_iterator i = metrics.begin(); i != metrics.end(); ++i) { - // cerr << "Looking at a metric section " << **i << endl; - 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 @@ -1055,14 +1159,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; @@ -1073,19 +1170,28 @@ TempoMap::bbt_time (framepos_t frame, BBT_Time& bbt) { require_map_to (frame); - Glib::RWLock::ReaderLock lm (lock); + 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)); } void TempoMap::bbt_time_rt (framepos_t frame, BBT_Time& bbt) { - Glib::RWLock::ReaderLock lm (lock, Glib::TRY_LOCK); + 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 (_map.empty() || _map.back().frame < frame) { throw std::logic_error (string_compose ("map not long enough to reach %1", frame)); } @@ -1105,22 +1211,31 @@ TempoMap::bbt_time (framepos_t frame, BBT_Time& bbt, const BBTPointList::const_i bbt.ticks = 0; } else { bbt.ticks = llrint (((frame - (*i).frame) / (*i).tempo->frames_per_beat(_frame_rate)) * - BBT_Time::ticks_per_beat); + 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"); + } + require_map_to (bbt); - Glib::RWLock::ReaderLock lm (lock); + Glib::Threads::RWLock::ReaderLock lm (lock); 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)); if (bbt.ticks != 0) { - return ((*e).frame - (*s).frame) + + 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); @@ -1130,18 +1245,15 @@ TempoMap::frame_time (const BBT_Time& bbt) framecnt_t TempoMap::bbt_duration_at (framepos_t pos, const BBT_Time& bbt, int dir) { - Glib::RWLock::ReaderLock lm (lock); - framecnt_t frames = 0; BBT_Time when; - bbt_time (pos, when); - frames = bbt_duration_at_unlocked (when, bbt,dir); - return frames; + Glib::Threads::RWLock::ReaderLock lm (lock); + return bbt_duration_at_unlocked (when, bbt, dir); } framecnt_t -TempoMap::bbt_duration_at_unlocked (const BBT_Time& when, const BBT_Time& bbt, int dir) +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; @@ -1150,16 +1262,9 @@ TempoMap::bbt_duration_at_unlocked (const BBT_Time& when, const BBT_Time& bbt, i /* 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); - double tick_frames = 0; assert (wi != _map.end()); - /* compute how much rounding we did because of non-zero ticks */ - - if (when.ticks != 0) { - tick_frames = (*wi).tempo->frames_per_beat (_frame_rate) * (when.ticks/BBT_Time::ticks_per_beat); - } - uint32_t bars = 0; uint32_t beats = 0; @@ -1180,34 +1285,34 @@ TempoMap::bbt_duration_at_unlocked (const BBT_Time& when, const BBT_Time& bbt, i /* add any additional frames related to ticks in the added value */ if (bbt.ticks != 0) { - tick_frames += (*wi).tempo->frames_per_beat (_frame_rate) * (bbt.ticks/BBT_Time::ticks_per_beat); + return ((*wi).frame - (*start).frame) + + (*wi).tempo->frames_per_beat (_frame_rate) * (bbt.ticks/BBT_Time::ticks_per_beat); + } else { + return ((*wi).frame - (*start).frame); } - - return ((*wi).frame - (*start).frame) + llrint (tick_frames); } framepos_t -TempoMap::round_to_bar (framepos_t fr, int dir) +TempoMap::round_to_bar (framepos_t fr, RoundMode dir) { 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) { 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::RWLock::ReaderLock lm (lock); + Glib::Threads::RWLock::ReaderLock lm (lock); BBTPointList::const_iterator i = bbt_before_or_at (fr); BBT_Time the_beat; uint32_t ticks_one_subdivisions_worth; - uint32_t difference; bbt_time (fr, the_beat, i); @@ -1218,11 +1323,14 @@ TempoMap::round_to_beat_subdivision (framepos_t fr, int sub_num, int dir) if (dir > 0) { - /* round to next (even if we're on a subdivision */ + /* 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 */ the_beat.ticks += ticks_one_subdivisions_worth; @@ -1237,24 +1345,19 @@ TempoMap::round_to_beat_subdivision (framepos_t fr, int sub_num, int dir) ++i; assert (i != _map.end()); the_beat.ticks -= BBT_Time::ticks_per_beat; - } + } } else if (dir < 0) { - /* round to previous (even if we're on a subdivision) */ + /* 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; } if (the_beat.ticks < difference) { @@ -1274,9 +1377,9 @@ TempoMap::round_to_beat_subdivision (framepos_t fr, int sub_num, int dir) 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)); @@ -1289,10 +1392,10 @@ TempoMap::round_to_beat_subdivision (framepos_t fr, int sub_num, int dir) 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) { @@ -1313,16 +1416,16 @@ TempoMap::round_to_beat_subdivision (framepos_t fr, int sub_num, int dir) } } - return (*i).frame + (the_beat.ticks/BBT_Time::ticks_per_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) { require_map_to (frame); - Glib::RWLock::ReaderLock lm (lock); + Glib::Threads::RWLock::ReaderLock lm (lock); BBTPointList::const_iterator fi; if (dir > 0) { @@ -1333,8 +1436,9 @@ TempoMap::round_to_type (framepos_t frame, int dir, BBTPointType type) assert (fi != _map.end()); - 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)); - + 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: if (dir < 0) { @@ -1345,6 +1449,9 @@ TempoMap::round_to_type (framepos_t frame, int dir, BBTPointType type) } if ((*fi).is_bar() && (*fi).frame == frame) { + if (dir == RoundDownMaybe) { + return frame; + } --fi; } @@ -1354,7 +1461,7 @@ TempoMap::round_to_type (framepos_t frame, int dir, BBTPointType type) } fi--; } - DEBUG_TRACE (DEBUG::SnapBBT, string_compose ("rounded to bar: map iter at %1|%2 %3, return\n", + 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; @@ -1363,6 +1470,9 @@ TempoMap::round_to_type (framepos_t frame, int dir, BBTPointType type) /* find bar following 'frame' */ if ((*fi).is_bar() && (*fi).frame == frame) { + if (dir == RoundUpMaybe) { + return frame; + } ++fi; } @@ -1374,12 +1484,12 @@ TempoMap::round_to_type (framepos_t frame, int dir, BBTPointType type) } } - DEBUG_TRACE (DEBUG::SnapBBT, string_compose ("rounded to bar: map iter at %1|%2 %3, return\n", + 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: find nearest bar */ BBTPointList::const_iterator prev = fi; @@ -1405,7 +1515,7 @@ TempoMap::round_to_type (framepos_t frame, int dir, BBTPointType type) } else { return (*next).frame; } - + } break; @@ -1417,19 +1527,19 @@ TempoMap::round_to_type (framepos_t frame, int dir, BBTPointType type) return 0; } - if ((*fi).frame >= 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", + 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) { - if ((*fi).frame <= frame) { + if ((*fi).frame < frame || ((*fi).frame == frame && dir == RoundUpAlways)) { 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", + 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 { @@ -1440,11 +1550,13 @@ TempoMap::round_to_type (framepos_t frame, int dir, BBTPointType type) BBTPointList::const_iterator prev = fi; BBTPointList::const_iterator next = fi; - if (prev != _map.begin()) { - --prev; - } + + /* 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; - + if ((next == _map.end()) || (frame - (*prev).frame) < ((*next).frame - frame)) { return (*prev).frame; } else { @@ -1454,18 +1566,17 @@ TempoMap::round_to_type (framepos_t frame, int dir, BBTPointType type) break; } - /* NOTREACHED */ - assert (false); + abort(); /* NOTREACHED */ return 0; } void -TempoMap::get_grid (TempoMap::BBTPointList::const_iterator& begin, - TempoMap::BBTPointList::const_iterator& end, - framepos_t lower, framepos_t upper) +TempoMap::get_grid (TempoMap::BBTPointList::const_iterator& begin, + TempoMap::BBTPointList::const_iterator& end, + framepos_t lower, framepos_t upper) { - { - Glib::RWLock::WriterLock lm (lock); + { + Glib::Threads::RWLock::WriterLock lm (lock); if (_map.empty() || (_map.back().frame < upper)) { recompute_map (false, upper); } @@ -1478,7 +1589,7 @@ TempoMap::get_grid (TempoMap::BBTPointList::const_iterator& begin, const TempoSection& TempoMap::tempo_section_at (framepos_t frame) const { - Glib::RWLock::ReaderLock lm (lock); + Glib::Threads::RWLock::ReaderLock lm (lock); Metrics::const_iterator i; TempoSection* prev = 0; @@ -1497,6 +1608,7 @@ TempoMap::tempo_section_at (framepos_t frame) const if (prev == 0) { fatal << endmsg; + abort(); /*NOTREACHED*/ } return *prev; @@ -1509,6 +1621,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 @@ -1524,7 +1663,7 @@ TempoMap::get_state () XMLNode *root = new XMLNode ("TempoMap"); { - Glib::RWLock::ReaderLock lm (lock); + Glib::Threads::RWLock::ReaderLock lm (lock); for (i = metrics.begin(); i != metrics.end(); ++i) { root->add_child_nocopy ((*i)->get_state()); } @@ -1537,17 +1676,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); MeterSection* last_meter = 0; - metrics.clear(); nlist = node.children(); - + for (niter = nlist.begin(); niter != nlist.end(); ++niter) { XMLNode* child = *niter; @@ -1560,7 +1698,7 @@ TempoMap::set_state (const XMLNode& node, int /*version*/) if (ts->bar_offset() < 0.0) { if (last_meter) { ts->update_bar_offset_from_bbt (*last_meter); - } + } } } @@ -1591,7 +1729,31 @@ TempoMap::set_state (const XMLNode& node, int /*version*/) metrics.sort (cmp); } - recompute_map (true); + /* 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 ()); @@ -1602,7 +1764,7 @@ TempoMap::set_state (const XMLNode& node, int /*version*/) void TempoMap::dump (std::ostream& o) const { - Glib::RWLock::ReaderLock lm (lock); + Glib::Threads::RWLock::ReaderLock lm (lock, Glib::Threads::TRY_LOCK); const MeterSection* m; const TempoSection* t; @@ -1621,7 +1783,7 @@ 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) { @@ -1636,7 +1798,7 @@ 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) { @@ -1652,7 +1814,7 @@ void TempoMap::insert_time (framepos_t where, framecnt_t amount) { { - Glib::RWLock::WriterLock lm (lock); + 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); @@ -1669,23 +1831,23 @@ TempoMap::insert_time (framepos_t where, framecnt_t amount) 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()); @@ -1693,34 +1855,34 @@ TempoMap::insert_time (framepos_t where, framecnt_t amount) // 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() < 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 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) const +TempoMap::framepos_plus_beats (framepos_t pos, Evoral::Beats beats) const { - Glib::RWLock::ReaderLock lm (lock); + Glib::Threads::RWLock::ReaderLock lm (lock); Metrics::const_iterator next_tempo; - const TempoSection* tempo; + const TempoSection* tempo = 0; /* Find the starting tempo metric */ @@ -1770,11 +1992,11 @@ TempoMap::framepos_plus_beats (framepos_t pos, Evoral::MusicalTime beats) const if (pos < 0 && f == 0) { f = pos; } - + if (f > pos) { break; } - + tempo = t; } } @@ -1784,20 +2006,23 @@ TempoMap::framepos_plus_beats (framepos_t pos, Evoral::MusicalTime beats) const tempo -> the Tempo for "pos" next_tempo -> first tempo after "pos", possibly metrics.end() */ + assert(tempo); - DEBUG_TRACE (DEBUG::TempoMath, string_compose ("frame %1 plus %2 beats, start with tempo = %3 @ %4\n", - pos, beats, *((Tempo*)tempo), tempo->frame())); + 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) { + 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::MusicalTime distance_beats = distance_frames / tempo->frames_per_beat (_frame_rate); + Evoral::Beats distance_beats = Evoral::Beats::ticks_at_rate( + distance_frames, tempo->frames_per_beat (_frame_rate)); /* Amount to subtract this time */ - double const delta = min (distance_beats, beats); + 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()), @@ -1805,7 +2030,7 @@ TempoMap::framepos_plus_beats (framepos_t pos, Evoral::MusicalTime beats) const /* Update */ beats -= delta; - pos += delta * tempo->frames_per_beat (_frame_rate); + 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)); @@ -1816,13 +2041,13 @@ TempoMap::framepos_plus_beats (framepos_t pos, Evoral::MusicalTime beats) const tempo = dynamic_cast(*next_tempo); DEBUG_TRACE (DEBUG::TempoMath, string_compose ("\tnew tempo = %1 @ %2 fpb = %3\n", - *((Tempo*)tempo), tempo->frame(), + *((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; } @@ -1833,11 +2058,11 @@ TempoMap::framepos_plus_beats (framepos_t pos, Evoral::MusicalTime beats) const return pos; } -/** 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) const +TempoMap::framepos_minus_beats (framepos_t pos, Evoral::Beats beats) const { - Glib::RWLock::ReaderLock lm (lock); + Glib::Threads::RWLock::ReaderLock lm (lock); Metrics::const_reverse_iterator prev_tempo; const TempoSection* tempo = 0; @@ -1867,7 +2092,7 @@ TempoMap::framepos_minus_beats (framepos_t pos, Evoral::MusicalTime beats) const keep going to get the previous tempo (or metrics.rend()) */ - + if (f <= pos) { if (tempo == 0) { /* first tempo with position at or @@ -1884,27 +2109,39 @@ TempoMap::framepos_minus_beats (framepos_t pos, Evoral::MusicalTime beats) const } } + 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())); + /* We now have: tempo -> the Tempo for "pos" prev_tempo -> the first metric before "pos", possibly metrics.rend() */ - while (beats) { - + while (!!beats) { + /* Distance to the start of this section in frames */ - framecnt_t distance_frames = ((prev_tempo == metrics.rend()) ? max_framepos : (pos - (*prev_tempo)->frame())); + framecnt_t distance_frames = (pos - tempo->frame()); /* Distance to the start in beats */ - Evoral::MusicalTime distance_beats = distance_frames / tempo->frames_per_beat (_frame_rate); + Evoral::Beats distance_beats = Evoral::Beats::ticks_at_rate( + distance_frames, tempo->frames_per_beat (_frame_rate)); /* Amount to subtract this time */ - double const sub = min (distance_beats, beats); + 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 * tempo->frames_per_beat (_frame_rate); + 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 */ @@ -1912,6 +2149,11 @@ TempoMap::framepos_minus_beats (framepos_t pos, Evoral::MusicalTime beats) const 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))); + while (prev_tempo != metrics.rend ()) { ++prev_tempo; @@ -1920,6 +2162,9 @@ TempoMap::framepos_minus_beats (framepos_t pos, Evoral::MusicalTime beats) const break; } } + } else { + pos -= llrint (beats.to_double() * tempo->frames_per_beat (_frame_rate)); + beats = Evoral::Beats(); } } @@ -1930,13 +2175,14 @@ TempoMap::framepos_minus_beats (framepos_t pos, Evoral::MusicalTime beats) const framepos_t TempoMap::framepos_plus_bbt (framepos_t pos, BBT_Time op) const { - Glib::RWLock::ReaderLock lm (lock); + 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 (); @@ -1948,7 +2194,7 @@ TempoMap::framepos_plus_bbt (framepos_t pos, BBT_Time op) const for (i = metrics.begin(); i != metrics.end(); ++i) { - if ((*i)->frame() > pos) { + if ((*i)->frame() > effective_pos) { break; } @@ -1991,7 +2237,7 @@ TempoMap::framepos_plus_bbt (framepos_t pos, BBT_Time op) const * traversed before we change the * frames_per_beat value. */ - + pos += llrint (frames_per_beat * (bars * meter->divisions_per_bar())); bars = 0; @@ -2051,7 +2297,7 @@ TempoMap::framepos_plus_bbt (framepos_t pos, BBT_Time op) const 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) / + (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)); @@ -2064,13 +2310,14 @@ TempoMap::framepos_plus_bbt (framepos_t pos, BBT_Time op) const /** Count the number of beats that are equivalent to distance when going forward, starting at pos. */ -Evoral::MusicalTime +Evoral::Beats TempoMap::framewalk_to_beats (framepos_t pos, framecnt_t distance) const { - Glib::RWLock::ReaderLock lm (lock); + Glib::Threads::RWLock::ReaderLock lm (lock); Metrics::const_iterator next_tempo; - const TempoSection* 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) { @@ -2079,7 +2326,7 @@ TempoMap::framewalk_to_beats (framepos_t pos, framecnt_t distance) const if ((t = dynamic_cast(*next_tempo)) != 0) { - if ((*next_tempo)->frame() > pos) { + if ((*next_tempo)->frame() > effective_pos) { break; } @@ -2092,37 +2339,74 @@ TempoMap::framewalk_to_beats (framepos_t pos, framecnt_t distance) const 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::MusicalTime beats = 0; + Evoral::Beats beats = Evoral::Beats(); while (distance) { /* End of this section */ - framepos_t const end = ((next_tempo == metrics.end()) ? max_framepos : (*next_tempo)->frame ()); + 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; + } - /* Distance to the end in frames */ - framecnt_t const distance_to_end = end - pos; + /* Amount to subtract this time in frames */ + framecnt_t const sub = min (distance, distance_to_end); - /* Amount to subtract this time */ - double 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; - beats += sub / tempo->frames_per_beat (_frame_rate); - + 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 */ - while (next_tempo != metrics.end ()) { - const TempoSection* t; - - ++next_tempo; - if (next_tempo != metrics.end() && (t = dynamic_cast(*next_tempo)) != 0) { - tempo = t; - break; + 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); } return beats; @@ -2135,6 +2419,13 @@ TempoMap::bbt_before_or_at (framepos_t pos) BBTPointList::const_iterator i; + if (pos < 0) { + /* not really correct, but we should catch pos < 0 at a higher + level + */ + return _map.begin(); + } + i = lower_bound (_map.begin(), _map.end(), pos); assert (i != _map.end()); if ((*i).frame > pos) { @@ -2166,7 +2457,7 @@ TempoMap::bbt_before_or_at (const BBT_Time& bbt) } TempoMap::BBTPointList::const_iterator -TempoMap::bbt_after_or_at (framepos_t pos) +TempoMap::bbt_after_or_at (framepos_t pos) { /* CALLER MUST HOLD READ LOCK */ @@ -2184,49 +2475,17 @@ TempoMap::bbt_after_or_at (framepos_t pos) 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() << ' '; @@ -2235,9 +2494,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;