#include <algorithm>
#include <stdexcept>
-
-#include <unistd.h>
-
#include <cmath>
+#include <unistd.h>
-#include <glibmm/thread.h>
+#include <glibmm/threads.h>
#include "pbd/xml++.h"
+#include "evoral/types.hpp"
#include "ardour/debug.h"
#include "ardour/tempo.h"
-#include "ardour/utils.h"
#include "i18n.h"
#include <locale.h>
using namespace ARDOUR;
using namespace PBD;
+using Timecode::BBT_Time;
+
/* _default tempo is 4/4 qtr=120 */
Meter TempoMap::_default_meter (4.0, 4.0);
Tempo TempoMap::_default_tempo (120.0);
-const double Meter::ticks_per_beat = 1920.0;
+/***********************************************************************/
-double Tempo::frames_per_beat (nframes_t sr, const Meter& meter) const
+double
+Meter::frames_per_grid (const Tempo& tempo, framecnt_t sr) const
{
- return ((60.0 * sr) / (_beats_per_minute * meter.note_divisor()/_note_type));
-}
+ /* This is tempo- and meter-sensitive. The number it returns
+ is based on the interval between any two lines in the
+ grid that is constructed from tempo and meter sections.
-/***********************************************************************/
+ The return value IS NOT interpretable in terms of "beats".
+ */
+
+ return (60.0 * sr) / (tempo.beats_per_minute() * (_note_type/tempo.note_type()));
+}
double
-Meter::frames_per_bar (const Tempo& tempo, nframes_t sr) const
+Meter::frames_per_bar (const Tempo& tempo, framecnt_t sr) const
{
- return ((60.0 * sr * _beats_per_bar) / (tempo.beats_per_minute() * _note_type/tempo.note_type()));
+ return frames_per_grid (tempo, sr) * _divisions_per_bar;
}
/***********************************************************************/
}
set_movable (string_is_affirmative (prop->value()));
+
+ if ((prop = node.property ("bar-offset")) == 0) {
+ _bar_offset = -1.0;
+ } else {
+ if (sscanf (prop->value().c_str(), "%lf", &_bar_offset) != 1 || _bar_offset < 0.0) {
+ error << _("TempoSection XML node has an illegal \"bar-offset\" value") << endmsg;
+ throw failed_constructor();
+ }
+ }
}
XMLNode&
root->add_property ("beats-per-minute", buf);
snprintf (buf, sizeof (buf), "%f", _note_type);
root->add_property ("note-type", buf);
+ // snprintf (buf, sizeof (buf), "%f", _bar_offset);
+ // root->add_property ("bar-offset", buf);
snprintf (buf, sizeof (buf), "%s", movable()?"yes":"no");
root->add_property ("movable", buf);
return *root;
}
+void
+
+TempoSection::update_bar_offset_from_bbt (const Meter& m)
+{
+ _bar_offset = ((start().beats - 1) * BBT_Time::ticks_per_beat + start().ticks) /
+ (m.divisions_per_bar() * BBT_Time::ticks_per_beat);
+
+ DEBUG_TRACE (DEBUG::TempoMath, string_compose ("Tempo set bar offset to %1 from %2 w/%3\n", _bar_offset, start(), m.divisions_per_bar()));
+}
+
+void
+TempoSection::update_bbt_time_from_bar_offset (const Meter& meter)
+{
+ BBT_Time new_start;
+
+ if (_bar_offset < 0.0) {
+ /* not set yet */
+ return;
+ }
+
+ new_start.bars = start().bars;
+
+ double ticks = BBT_Time::ticks_per_beat * meter.divisions_per_bar() * _bar_offset;
+ new_start.beats = (uint32_t) floor (ticks/BBT_Time::ticks_per_beat);
+ new_start.ticks = 0; /* (uint32_t) fmod (ticks, BBT_Time::ticks_per_beat); */
+
+ /* remember the 1-based counting properties of beats */
+ new_start.beats += 1;
+
+ DEBUG_TRACE (DEBUG::TempoMath, string_compose ("from bar offset %1 and dpb %2, ticks = %3->%4 beats = %5\n",
+ _bar_offset, meter.divisions_per_bar(), ticks, new_start.ticks, new_start.beats));
+
+ set_start (new_start);
+}
+
/***********************************************************************/
const string MeterSection::xml_state_node_name = "Meter";
set_start (start);
- if ((prop = node.property ("beats-per-bar")) == 0) {
- error << _("MeterSection XML node has no \"beats-per-bar\" property") << endmsg;
- throw failed_constructor();
+ /* beats-per-bar is old; divisions-per-bar is new */
+
+ if ((prop = node.property ("divisions-per-bar")) == 0) {
+ if ((prop = node.property ("beats-per-bar")) == 0) {
+ error << _("MeterSection XML node has no \"beats-per-bar\" or \"divisions-per-bar\" property") << endmsg;
+ throw failed_constructor();
+ }
}
- if (sscanf (prop->value().c_str(), "%lf", &_beats_per_bar) != 1 || _beats_per_bar < 0.0) {
- error << _("MeterSection XML node has an illegal \"beats-per-bar\" value") << endmsg;
+ if (sscanf (prop->value().c_str(), "%lf", &_divisions_per_bar) != 1 || _divisions_per_bar < 0.0) {
+ error << _("MeterSection XML node has an illegal \"beats-per-bar\" or \"divisions-per-bar\" value") << endmsg;
throw failed_constructor();
}
root->add_property ("start", buf);
snprintf (buf, sizeof (buf), "%f", _note_type);
root->add_property ("note-type", buf);
- snprintf (buf, sizeof (buf), "%f", _beats_per_bar);
- root->add_property ("beats-per-bar", buf);
+ snprintf (buf, sizeof (buf), "%f", _divisions_per_bar);
+ root->add_property ("divisions-per-bar", buf);
snprintf (buf, sizeof (buf), "%s", movable()?"yes":"no");
root->add_property ("movable", buf);
}
};
-TempoMap::TempoMap (nframes64_t fr)
+TempoMap::TempoMap (framecnt_t fr)
{
- metrics = new Metrics;
_frame_rate = fr;
- last_bbt_valid = false;
BBT_Time start;
start.bars = 1;
start.ticks = 0;
TempoSection *t = new TempoSection (start, _default_tempo.beats_per_minute(), _default_tempo.note_type());
- MeterSection *m = new MeterSection (start, _default_meter.beats_per_bar(), _default_meter.note_divisor());
+ MeterSection *m = new MeterSection (start, _default_meter.divisions_per_bar(), _default_meter.note_divisor());
t->set_movable (false);
m->set_movable (false);
/* note: frame time is correct (zero) for both of these */
- metrics->push_back (t);
- metrics->push_back (m);
+ metrics.push_back (t);
+ metrics.push_back (m);
}
TempoMap::~TempoMap ()
{
}
-int
-TempoMap::move_metric_section (MetricSection& section, const BBT_Time& when)
-{
- if (when == section.start() || !section.movable()) {
- return -1;
- }
-
- Glib::RWLock::WriterLock lm (lock);
- MetricSectionSorter cmp;
-
- if (when.beats != 1) {
-
- /* position by audio frame, then recompute BBT timestamps from the audio ones */
-
- nframes64_t frame = frame_time (when);
- // cerr << "nominal frame time = " << frame << endl;
-
- nframes64_t prev_frame = round_to_type (frame, -1, Beat);
- nframes64_t next_frame = round_to_type (frame, 1, Beat);
-
- // cerr << "previous beat at " << prev_frame << " next at " << next_frame << endl;
-
- /* use the closest beat */
-
- if ((frame - prev_frame) < (next_frame - frame)) {
- frame = prev_frame;
- } else {
- frame = next_frame;
- }
-
- // cerr << "actual frame time = " << frame << endl;
- section.set_frame (frame);
- // cerr << "frame time = " << section.frame() << endl;
- timestamp_metrics (false);
- // cerr << "new BBT time = " << section.start() << endl;
- metrics->sort (cmp);
-
- } else {
-
- /* positioned at bar start already, so just put it there */
-
- section.set_start (when);
- metrics->sort (cmp);
- timestamp_metrics (true);
- }
-
-
- return 0;
-}
-
-void
-TempoMap::move_tempo (TempoSection& tempo, const BBT_Time& when)
-{
- if (move_metric_section (tempo, when) == 0) {
- PropertyChanged (PropertyChange ());
- }
-}
-
-void
-TempoMap::move_meter (MeterSection& meter, const BBT_Time& when)
-{
- if (move_metric_section (meter, when) == 0) {
- PropertyChanged (PropertyChange ());
- }
-}
-
void
-TempoMap::remove_tempo (const TempoSection& tempo)
+TempoMap::remove_tempo (const TempoSection& tempo, bool complete_operation)
{
bool removed = false;
{
- Glib::RWLock::WriterLock lm (lock);
+ Glib::Threads::RWLock::WriterLock lm (lock);
Metrics::iterator i;
- for (i = metrics->begin(); i != metrics->end(); ++i) {
+ for (i = metrics.begin(); i != metrics.end(); ++i) {
if (dynamic_cast<TempoSection*> (*i) != 0) {
if (tempo.frame() == (*i)->frame()) {
if ((*i)->movable()) {
- metrics->erase (i);
+ metrics.erase (i);
removed = true;
break;
}
}
}
}
+
+ if (removed && complete_operation) {
+ recompute_map (false);
+ }
}
- if (removed) {
+ if (removed && complete_operation) {
PropertyChanged (PropertyChange ());
}
}
void
-TempoMap::remove_meter (const MeterSection& tempo)
+TempoMap::remove_meter (const MeterSection& tempo, bool complete_operation)
{
bool removed = false;
{
- Glib::RWLock::WriterLock lm (lock);
+ Glib::Threads::RWLock::WriterLock lm (lock);
Metrics::iterator i;
- for (i = metrics->begin(); i != metrics->end(); ++i) {
+ for (i = metrics.begin(); i != metrics.end(); ++i) {
if (dynamic_cast<MeterSection*> (*i) != 0) {
if (tempo.frame() == (*i)->frame()) {
if ((*i)->movable()) {
- metrics->erase (i);
+ metrics.erase (i);
removed = true;
break;
}
}
}
}
+
+ if (removed && complete_operation) {
+ recompute_map (true);
+ }
}
- if (removed) {
+ if (removed && complete_operation) {
PropertyChanged (PropertyChange ());
}
}
void
-TempoMap::do_insert (MetricSection* section, bool with_bbt)
+TempoMap::do_insert (MetricSection* section)
{
- Metrics::iterator i;
+ bool need_add = true;
- for (i = metrics->begin(); i != metrics->end(); ++i) {
+ assert (section->start().ticks == 0);
- if (with_bbt) {
- if ((*i)->start() < section->start()) {
- continue;
- }
- } else {
- if ((*i)->frame() < section->frame()) {
- continue;
- }
- }
+ /* we only allow new meters to be inserted on beat 1 of an existing
+ * measure.
+ */
- metrics->insert (i, section);
- break;
- }
+ if (dynamic_cast<MeterSection*>(section)) {
- if (i == metrics->end()) {
- metrics->insert (metrics->end(), 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);
+ }
}
- timestamp_metrics (with_bbt);
-}
+
-void
-TempoMap::add_tempo (const Tempo& tempo, BBT_Time where)
-{
- {
- Glib::RWLock::WriterLock lm (lock);
+ /* Look for any existing MetricSection that is of the same type and
+ 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.
+ */
- /* new tempos always start on a beat */
+ for (Metrics::iterator i = metrics.begin(); i != metrics.end(); ++i) {
- where.ticks = 0;
+ bool const iter_is_tempo = dynamic_cast<TempoSection*> (*i) != 0;
+ bool const insert_is_tempo = dynamic_cast<TempoSection*> (section) != 0;
- do_insert (new TempoSection (where, tempo.beats_per_minute(), tempo.note_type()), true);
- }
+ if (iter_is_tempo && insert_is_tempo) {
- PropertyChanged (PropertyChange ());
-}
+ /* Tempo sections */
-void
-TempoMap::add_tempo (const Tempo& tempo, nframes64_t where)
-{
- {
- Glib::RWLock::WriterLock lm (lock);
- do_insert (new TempoSection (where, tempo.beats_per_minute(), tempo.note_type()), false);
- }
+ if ((*i)->start().bars == section->start().bars &&
+ (*i)->start().beats == section->start().beats) {
- PropertyChanged (PropertyChange ());
-}
+ if (!(*i)->movable()) {
+
+ /* can't (re)move this section, so overwrite
+ * its data content (but not its properties as
+ * a section).
+ */
+
+ *(dynamic_cast<Tempo*>(*i)) = *(dynamic_cast<Tempo*>(section));
+ need_add = false;
+ } else {
+ metrics.erase (i);
+ }
+ break;
+ }
-void
-TempoMap::replace_tempo (TempoSection& existing, const Tempo& replacement)
-{
- bool replaced = false;
+ } else if (!iter_is_tempo && !insert_is_tempo) {
- {
- Glib::RWLock::WriterLock lm (lock);
- Metrics::iterator i;
+ /* 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<Meter*>(*i)) = *(dynamic_cast<Meter*>(section));
+ need_add = false;
+ } else {
+ metrics.erase (i);
+
+ }
- for (i = metrics->begin(); i != metrics->end(); ++i) {
- TempoSection *ts;
+ break;
+ }
+ } else {
+ /* non-matching types, so we don't care */
+ }
+ }
- if ((ts = dynamic_cast<TempoSection*>(*i)) != 0 && ts == &existing) {
+ /* Add the given MetricSection, if we didn't just reset an existing
+ * one above
+ */
- *((Tempo *) ts) = replacement;
+ if (need_add) {
- replaced = true;
- timestamp_metrics (true);
+ Metrics::iterator i;
+ for (i = metrics.begin(); i != metrics.end(); ++i) {
+ if ((*i)->start() > section->start()) {
break;
}
}
+
+ metrics.insert (i, section);
}
+}
- if (replaced) {
- PropertyChanged (PropertyChange ());
+void
+TempoMap::replace_tempo (const TempoSection& ts, const Tempo& tempo, const BBT_Time& where)
+{
+ const TempoSection& first (first_tempo());
+
+ if (ts.start() != first.start()) {
+ remove_tempo (ts, false);
+ add_tempo (tempo, where);
+ } else {
+ {
+ Glib::Threads::RWLock::WriterLock lm (lock);
+ /* cannot move the first tempo section */
+ *((Tempo*)&first) = tempo;
+ recompute_map (false);
+ }
}
+
+ PropertyChanged (PropertyChange ());
}
void
-TempoMap::add_meter (const Meter& meter, BBT_Time where)
+TempoMap::add_tempo (const Tempo& tempo, BBT_Time where)
{
{
- Glib::RWLock::WriterLock lm (lock);
+ Glib::Threads::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.
+ /* 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.
+ */
- if (where.beats != 1) {
- where.beats = 1;
- where.bars++;
+ 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<const MeterSection*>(*i)) != 0) {
+ meter = m;
+ }
}
- /* new meters *always* start on a beat. */
+ ts->update_bar_offset_from_bbt (*meter);
- where.ticks = 0;
+ /* and insert it */
+
+ do_insert (ts);
- do_insert (new MeterSection (where, meter.beats_per_bar(), meter.note_divisor()), true);
+ recompute_map (false);
}
+
PropertyChanged (PropertyChange ());
}
void
-TempoMap::add_meter (const Meter& meter, nframes64_t where)
+TempoMap::replace_meter (const MeterSection& ms, const Meter& meter, const BBT_Time& where)
{
- {
- Glib::RWLock::WriterLock lm (lock);
- do_insert (new MeterSection (where, meter.beats_per_bar(), meter.note_divisor()), false);
+ const MeterSection& first (first_meter());
+
+ if (ms.start() != first.start()) {
+ remove_meter (ms, false);
+ add_meter (meter, where);
+ } else {
+ {
+ Glib::Threads::RWLock::WriterLock lm (lock);
+ /* cannot move the first meter section */
+ *((Meter*)&first) = meter;
+ recompute_map (true);
+ }
}
PropertyChanged (PropertyChange ());
}
void
-TempoMap::replace_meter (MeterSection& existing, const Meter& replacement)
+TempoMap::add_meter (const Meter& meter, BBT_Time where)
{
- bool replaced = false;
-
{
- Glib::RWLock::WriterLock lm (lock);
- Metrics::iterator i;
+ Glib::Threads::RWLock::WriterLock lm (lock);
- for (i = metrics->begin(); i != metrics->end(); ++i) {
- MeterSection *ms;
- if ((ms = dynamic_cast<MeterSection*>(*i)) != 0 && ms == &existing) {
+ /* 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.
- *((Meter*) ms) = replacement;
+ */
- replaced = true;
- timestamp_metrics (true);
- break;
- }
+ 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);
}
- if (replaced) {
- PropertyChanged (PropertyChange ());
+
+#ifndef NDEBUG
+ if (DEBUG_ENABLED(DEBUG::TempoMap)) {
+ dump (std::cerr);
}
+#endif
+
+ PropertyChanged (PropertyChange ());
}
void
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<TempoSection*> (*i)) != 0) {
- *((Tempo*) t) = newtempo;
+ {
+ Glib::Threads::RWLock::WriterLock lm (lock);
+ *((Tempo*) t) = newtempo;
+ recompute_map (false);
+ }
PropertyChanged (PropertyChange ());
break;
}
}
void
-TempoMap::change_existing_tempo_at (nframes64_t where, double beats_per_minute, double note_type)
+TempoMap::change_existing_tempo_at (framepos_t where, double beats_per_minute, double note_type)
{
Tempo newtempo (beats_per_minute, note_type);
/* 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;
/* reset */
- *((Tempo*)prev) = newtempo;
+ {
+ Glib::Threads::RWLock::WriterLock lm (lock);
+ /* cannot move the first tempo section */
+ *((Tempo*)prev) = newtempo;
+ recompute_map (false);
+ }
+
PropertyChanged (PropertyChange ());
}
{
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<const MeterSection *> (*i)) != 0) {
return *m;
}
{
const TempoSection *t = 0;
- 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<const TempoSection *> (*i)) != 0) {
return *t;
}
}
void
-TempoMap::timestamp_metrics (bool use_bbt)
+TempoMap::require_map_to (framepos_t pos)
{
- Metrics::iterator i;
- const Meter* meter;
- const Tempo* tempo;
- Meter *m;
- Tempo *t;
+ Glib::Threads::RWLock::WriterLock lm (lock);
- meter = &first_meter ();
- tempo = &first_tempo ();
+ if (_map.empty() || _map.back().frame < pos) {
+ extend_map (pos);
+ }
+}
+
+void
+TempoMap::require_map_to (const BBT_Time& bbt)
+{
+ Glib::Threads::RWLock::WriterLock lm (lock);
- if (use_bbt) {
+ /* since we have no idea where BBT is if its off the map, see the last
+ * point in the map is past BBT, and if not add an arbitrary amount of
+ * time until it is.
+ */
- // cerr << "\n\n\n ######################\nTIMESTAMP via BBT ##############\n" << endl;
+ int additional_minutes = 1;
+
+ while (1) {
+ if (!_map.empty() && _map.back().bar >= (bbt.bars + 1)) {
+ break;
+ }
+ /* add some more distance, using bigger steps each time */
+ extend_map (_map.back().frame + (_frame_rate * 60 * additional_minutes));
+ additional_minutes *= 2;
+ }
+}
- nframes64_t current = 0;
- nframes64_t section_frames;
- BBT_Time start;
- BBT_Time end;
+void
+TempoMap::recompute_map (bool reassign_tempo_bbt, framepos_t end)
+{
+ /* CALLER MUST HOLD WRITE LOCK */
- for (i = metrics->begin(); i != metrics->end(); ++i) {
+ MeterSection* meter = 0;
+ TempoSection* tempo = 0;
+ double current_frame;
+ BBT_Time current;
+ Metrics::iterator next_metric;
- end = (*i)->start();
+ if (end < 0) {
- section_frames = count_frames_between_metrics (*meter, *tempo, start, end);
+ /* we will actually stop once we hit
+ the last metric.
+ */
+ end = max_framepos;
- current += section_frames;
+ } else {
+ if (!_map.empty ()) {
+ /* never allow the map to be shortened */
+ end = max (end, _map.back().frame);
+ }
+ }
- start = end;
+ DEBUG_TRACE (DEBUG::TempoMath, string_compose ("recomputing tempo map, zero to %1\n", end));
- (*i)->set_frame (current);
+ for (Metrics::iterator i = metrics.begin(); i != metrics.end(); ++i) {
+ MeterSection* ms;
- if ((t = dynamic_cast<TempoSection*>(*i)) != 0) {
- tempo = t;
- } else if ((m = dynamic_cast<MeterSection*>(*i)) != 0) {
- meter = m;
- } else {
- fatal << _("programming error: unhandled MetricSection type") << endmsg;
- /*NOTREACHED*/
- }
+ if ((ms = dynamic_cast<MeterSection *> (*i)) != 0) {
+ meter = ms;
+ break;
+ }
+ }
+
+ for (Metrics::iterator i = metrics.begin(); i != metrics.end(); ++i) {
+ TempoSection* ts;
+
+ if ((ts = dynamic_cast<TempoSection *> (*i)) != 0) {
+ tempo = ts;
+ break;
}
+ }
- } else {
+ /* assumes that the first meter & tempo are at frame zero */
+ current_frame = 0;
+ meter->set_frame (0);
+ tempo->set_frame (0);
- // cerr << "\n\n\n ######################\nTIMESTAMP via AUDIO ##############\n" << endl;
+ /* assumes that the first meter & tempo are at 1|1|0 */
+ current.bars = 1;
+ current.beats = 1;
+ current.ticks = 0;
- bool first = true;
- MetricSection* prev = 0;
+ if (reassign_tempo_bbt) {
- for (i = metrics->begin(); i != metrics->end(); ++i) {
+ MeterSection* rmeter = meter;
- BBT_Time bbt;
- TempoMetric metric (*meter, *tempo);
+ DEBUG_TRACE (DEBUG::TempoMath, "\tUpdating tempo marks BBT time from bar offset\n");
- if (prev) {
- metric.set_start (prev->start());
- metric.set_frame (prev->frame());
- } else {
- // metric will be at frames=0 bbt=1|1|0 by default
- // which is correct for our purpose
- }
+ for (Metrics::iterator i = metrics.begin(); i != metrics.end(); ++i) {
- bbt_time_with_metric ((*i)->frame(), bbt, metric);
+ TempoSection* ts;
+ MeterSection* ms;
+
+ if ((ts = dynamic_cast<TempoSection*>(*i)) != 0) {
- // cerr << "timestamp @ " << (*i)->frame() << " with " << bbt.bars << "|" << bbt.beats << "|" << bbt.ticks << " => ";
+ /* reassign the BBT time of this tempo section
+ * based on its bar offset position.
+ */
+ ts->update_bbt_time_from_bar_offset (*rmeter);
- if (first) {
- first = false;
+ } else if ((ms = dynamic_cast<MeterSection*>(*i)) != 0) {
+ rmeter = ms;
} else {
+ fatal << _("programming error: unhandled MetricSection type") << endmsg;
+ /*NOTREACHED*/
+ }
+ }
+ }
- if (bbt.ticks > Meter::ticks_per_beat/2) {
- /* round up to next beat */
- bbt.beats += 1;
- }
+ DEBUG_TRACE (DEBUG::TempoMath, string_compose ("start with meter = %1 tempo = %2\n", *((Meter*)meter), *((Tempo*)tempo)));
- bbt.ticks = 0;
+ next_metric = metrics.begin();
+ ++next_metric; // skip meter (or tempo)
+ ++next_metric; // skip tempo (or meter)
- if (bbt.beats != 1) {
- /* round up to next bar */
- bbt.bars += 1;
- bbt.beats = 1;
- }
- }
+ _map.clear ();
- //s cerr << bbt.bars << "|" << bbt.beats << "|" << bbt.ticks << endl;
+ DEBUG_TRACE (DEBUG::TempoMath, string_compose ("Add first bar at 1|1 @ %2\n", current.bars, current_frame));
+ _map.push_back (BBTPoint (*meter, *tempo,(framepos_t) llrint(current_frame), 1, 1));
- (*i)->set_start (bbt);
+ if (end == 0) {
+ /* silly call from Session::process() during startup
+ */
+ return;
+ }
- if ((t = dynamic_cast<TempoSection*>(*i)) != 0) {
- tempo = t;
- // cerr << "NEW TEMPO, frame = " << (*i)->frame() << " start = " << (*i)->start() <<endl;
- } else if ((m = dynamic_cast<MeterSection*>(*i)) != 0) {
- meter = m;
- // cerr << "NEW METER, frame = " << (*i)->frame() << " start = " << (*i)->start() <<endl;
- } else {
- fatal << _("programming error: unhandled MetricSection type") << endmsg;
- /*NOTREACHED*/
- }
+ _extend_map (tempo, meter, next_metric, current, current_frame, end);
+}
- prev = (*i);
+void
+TempoMap::extend_map (framepos_t end)
+{
+ /* CALLER MUST HOLD WRITE LOCK */
+
+ if (_map.empty()) {
+ recompute_map (false, end);
+ return;
+ }
+
+ BBTPointList::const_iterator i = _map.end();
+ Metrics::iterator next_metric;
+
+ --i;
+
+ BBT_Time last_metric_start;
+
+ if ((*i).tempo->frame() > (*i).meter->frame()) {
+ last_metric_start = (*i).tempo->start();
+ } else {
+ last_metric_start = (*i).meter->start();
+ }
+
+ /* find the metric immediately after the tempo + meter sections for the
+ * last point in the map
+ */
+
+ for (next_metric = metrics.begin(); next_metric != metrics.end(); ++next_metric) {
+ if ((*next_metric)->start() > last_metric_start) {
+ break;
}
}
- // dump (cerr);
- // cerr << "###############################################\n\n\n" << endl;
+ /* we cast away const here because this is the one place where we need
+ * to actually modify the frame time of each metric section.
+ */
+
+ _extend_map (const_cast<TempoSection*> ((*i).tempo),
+ const_cast<MeterSection*> ((*i).meter),
+ next_metric, BBT_Time ((*i).bar, (*i).beat, 0), (*i).frame, 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 */
+
+ TempoSection* ts;
+ MeterSection* ms;
+ 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;
+ }
+
+ beat_frames = meter->frames_per_grid (*tempo,_frame_rate);
+ current_frame_exact = current_frame;
+
+ while (current_frame < end) {
+
+ current.beats++;
+ current_frame_exact += beat_frames;
+ current_frame = llrint(current_frame_exact);
+
+ if (current.beats > meter->divisions_per_bar()) {
+ current.bars++;
+ current.beats = 1;
+ }
+
+ if (next_metric != metrics.end()) {
+
+ /* no operator >= so invert operator < */
+
+ DEBUG_TRACE (DEBUG::TempoMath, string_compose ("now at %1 next metric @ %2\n", current, (*next_metric)->start()));
+
+ if (!(current < (*next_metric)->start())) {
+
+ set_metrics:
+ if (((ts = dynamic_cast<TempoSection*> (*next_metric)) != 0)) {
+
+ tempo = ts;
+
+ /* 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 (tempo->start().ticks != 0) {
+
+ 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()));
+
+ /* back up to previous beat */
+ 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->set_frame (current_frame);
+ }
+
+ } else if ((ms = dynamic_cast<MeterSection*>(*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));
+
+ assert (current.beats == 1);
+
+ meter->set_frame (current_frame);
+ }
+
+ 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, 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, 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, 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 (nframes64_t frame) const
+TempoMap::metric_at (framepos_t frame, Metrics::const_iterator* last) const
{
+ 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
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) {
if ((*i)->frame() > frame) {
break;
}
- if ((tempo = dynamic_cast<const TempoSection*>(*i)) != 0) {
- m.set_tempo (*tempo);
- } else if ((meter = dynamic_cast<const MeterSection*>(*i)) != 0) {
- m.set_meter (*meter);
- }
+ m.set_metric(*i);
- m.set_frame ((*i)->frame ());
- m.set_start ((*i)->start ());
+ if (last) {
+ *last = i;
+ }
}
-
+
return m;
}
TempoMetric
TempoMap::metric_at (BBT_Time bbt) const
{
+ 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
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());
break;
}
- if ((tempo = dynamic_cast<const TempoSection*>(*i)) != 0) {
- m.set_tempo (*tempo);
- } else if ((meter = dynamic_cast<const MeterSection*>(*i)) != 0) {
- m.set_meter (*meter);
- }
-
- m.set_frame ((*i)->frame ());
- m.set_start (section_start);
+ m.set_metric (*i);
}
return m;
}
void
-TempoMap::bbt_time (nframes64_t frame, BBT_Time& bbt) const
+TempoMap::bbt_time (framepos_t frame, BBT_Time& bbt)
{
- {
- Glib::RWLock::ReaderLock lm (lock);
- bbt_time_unlocked (frame, bbt);
+ require_map_to (frame);
+
+ 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;
}
-}
-void
-TempoMap::bbt_time_unlocked (nframes64_t frame, BBT_Time& bbt) const
-{
- bbt_time_with_metric (frame, bbt, metric_at (frame));
+ return bbt_time (frame, bbt, bbt_before_or_at (frame));
}
void
-TempoMap::bbt_time_with_metric (nframes64_t frame, BBT_Time& bbt, const TempoMetric& metric) const
+TempoMap::bbt_time_rt (framepos_t frame, BBT_Time& bbt)
{
- nframes64_t frame_diff;
-
- // cerr << "---- BBT time for " << frame << " using metric @ " << metric.frame() << " BBT " << metric.start() << endl;
-
- const double beats_per_bar = metric.meter().beats_per_bar();
- const double ticks_per_frame = metric.tempo().frames_per_beat (_frame_rate, metric.meter()) / Meter::ticks_per_beat;
-
- /* now compute how far beyond that point we actually are. */
-
- frame_diff = frame - metric.frame();
-
- bbt.ticks = metric.start().ticks + (uint32_t)round((double)frame_diff / ticks_per_frame);
- uint32_t xtra_beats = bbt.ticks / (uint32_t)Meter::ticks_per_beat;
- bbt.ticks %= (uint32_t)Meter::ticks_per_beat;
-
- bbt.beats = metric.start().beats + xtra_beats - 1; // correction for 1-based counting, see below for matching operation.
- bbt.bars = metric.start().bars + (uint32_t)floor((double)bbt.beats / beats_per_bar);
- bbt.beats = (uint32_t)fmod((double)bbt.beats, beats_per_bar);
+ Glib::Threads::RWLock::ReaderLock lm (lock, Glib::Threads::TRY_LOCK);
- /* if we have a fractional number of beats per bar, we see if
- we're in the last beat (the fractional one). if so, we
- round ticks appropriately and bump to the next bar. */
- double beat_fraction = beats_per_bar - floor(beats_per_bar);
- /* XXX one problem here is that I'm not sure how to handle
- fractional beats that don't evenly divide ticks_per_beat.
- If they aren't handled consistently, I would guess we'll
- continue to have strange discrepancies occuring. Perhaps
- this will also behave badly in the case of meters like
- 0.1/4, but I can't be bothered to test that.
- */
- uint32_t ticks_on_last_beat = (uint32_t)floor(Meter::ticks_per_beat * beat_fraction);
-
- if (bbt.beats > (uint32_t)floor(beats_per_bar) && bbt.ticks >= ticks_on_last_beat) {
- bbt.ticks -= ticks_on_last_beat;
- bbt.beats = 0;
- bbt.bars++;
- }
-
- bbt.beats++; // correction for 1-based counting, see above for matching operation.
+ 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));
+ }
- // cerr << "-----\t RETURN " << bbt << endl;
+ return bbt_time (frame, bbt, bbt_before_or_at (frame));
}
-nframes64_t
-TempoMap::count_frames_between ( const BBT_Time& start, const BBT_Time& end) const
+void
+TempoMap::bbt_time (framepos_t frame, BBT_Time& bbt, const BBTPointList::const_iterator& i)
{
- /* for this to work with fractional measure types, start and end have to be "legal" BBT types,
- that means that the beats and ticks should be inside a bar
- */
-
- nframes64_t frames = 0;
- nframes64_t start_frame = 0;
- nframes64_t end_frame = 0;
+ /* CALLER MUST HOLD READ LOCK */
- TempoMetric m = metric_at (start);
-
- uint32_t bar_offset = start.bars - m.start().bars;
-
- double beat_offset = bar_offset*m.meter().beats_per_bar() - (m.start().beats-1) + (start.beats -1)
- + start.ticks/Meter::ticks_per_beat;
-
-
- start_frame = m.frame() + (nframes64_t) rint( beat_offset * m.tempo().frames_per_beat(_frame_rate, m.meter()));
-
- m = metric_at(end);
-
- bar_offset = end.bars - m.start().bars;
-
- beat_offset = bar_offset * m.meter().beats_per_bar() - (m.start().beats -1) + (end.beats - 1)
- + end.ticks/Meter::ticks_per_beat;
-
- end_frame = m.frame() + (nframes64_t) rint(beat_offset * m.tempo().frames_per_beat(_frame_rate, m.meter()));
-
- frames = end_frame - start_frame;
-
- return frames;
+ bbt.bars = (*i).bar;
+ bbt.beats = (*i).beat;
+ if ((*i).frame == frame) {
+ bbt.ticks = 0;
+ } else {
+ bbt.ticks = llrint (((frame - (*i).frame) / (*i).tempo->frames_per_beat(_frame_rate)) *
+ BBT_Time::ticks_per_beat);
+ }
}
-nframes64_t
-TempoMap::count_frames_between_metrics (const Meter& meter, const Tempo& tempo, const BBT_Time& start, const BBT_Time& end) const
+framepos_t
+TempoMap::frame_time (const BBT_Time& bbt)
{
- /* this is used in timestamping the metrics by actually counting the beats */
-
- nframes64_t frames = 0;
- uint32_t bar = start.bars;
- double beat = (double) start.beats;
- double beats_counted = 0;
- double beats_per_bar = 0;
- double beat_frames = 0;
-
- beats_per_bar = meter.beats_per_bar();
- beat_frames = tempo.frames_per_beat (_frame_rate,meter);
-
- frames = 0;
-
- while (bar < end.bars || (bar == end.bars && beat < end.beats)) {
-
- if (beat >= beats_per_bar) {
- beat = 1;
- ++bar;
- ++beats_counted;
-
- if (beat > beats_per_bar) {
-
- /* this is a fractional beat at the end of a fractional bar
- so it should only count for the fraction
- */
-
- beats_counted -= (ceil(beats_per_bar) - beats_per_bar);
- }
-
- } else {
- ++beat;
- ++beats_counted;
- }
+ 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");
}
- // cerr << "Counted " << beats_counted << " from " << start << " to " << end
- // << " bpb were " << beats_per_bar
- // << " fpb was " << beat_frames
- // << endl;
-
- frames = (nframes64_t) floor (beats_counted * beat_frames);
-
- return frames;
+ require_map_to (bbt);
-}
+ Glib::Threads::RWLock::ReaderLock lm (lock);
-nframes64_t
-TempoMap::frame_time (const BBT_Time& bbt) const
-{
- BBT_Time start ; /* 1|1|0 */
+ 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));
- return count_frames_between ( start, bbt);
+ if (bbt.ticks != 0) {
+ return ((*e).frame - (*s).frame) +
+ llrint ((*e).tempo->frames_per_beat (_frame_rate) * (bbt.ticks/BBT_Time::ticks_per_beat));
+ } else {
+ return ((*e).frame - (*s).frame);
+ }
}
-nframes64_t
-TempoMap::bbt_duration_at (nframes64_t pos, const BBT_Time& bbt, int dir) const
+framecnt_t
+TempoMap::bbt_duration_at (framepos_t pos, const BBT_Time& bbt, int dir)
{
- nframes64_t frames = 0;
-
BBT_Time when;
- bbt_time(pos, when);
-
- {
- Glib::RWLock::ReaderLock lm (lock);
- frames = bbt_duration_at_unlocked (when, bbt,dir);
- }
-
- return frames;
+ bbt_time (pos, when);
+
+ Glib::Threads::RWLock::ReaderLock lm (lock);
+ return bbt_duration_at_unlocked (when, bbt, dir);
}
-nframes64_t
-TempoMap::bbt_duration_at_unlocked (const BBT_Time& when, const BBT_Time& bbt, int dir) const
+framecnt_t
+TempoMap::bbt_duration_at_unlocked (const BBT_Time& when, const BBT_Time& bbt, int /*dir*/)
{
+ if (bbt.bars == 0 && bbt.beats == 0 && bbt.ticks == 0) {
+ return 0;
+ }
- nframes64_t frames = 0;
-
- double beats_per_bar;
- BBT_Time result;
-
- result.bars = max(1U, when.bars + dir * bbt.bars) ;
- result.beats = 1;
- result.ticks = 0;
-
- TempoMetric metric = metric_at(result);
- beats_per_bar = metric.meter().beats_per_bar();
-
-
-
- /*reduce things to legal bbt values
- we have to handle possible fractional=shorter beats at the end of measures
- and things like 0|11|9000 as a duration in a 4.5/4 measure
- the musical decision is that the fractional beat is also a beat , although a shorter one
- */
-
-
- if (dir >= 0) {
- result.beats = when.beats + bbt.beats;
- result.ticks = when.ticks + bbt.ticks;
-
- while (result.beats >= (beats_per_bar + 1)) {
- result.bars++;
- result.beats -= (uint32_t) ceil(beats_per_bar);
- metric = metric_at(result); // maybe there is a meter change
- beats_per_bar = metric.meter().beats_per_bar();
-
- }
- /*we now counted the beats and landed in the target measure, now deal with ticks
- this seems complicated, but we want to deal with the corner case of a sequence of time signatures like 0.2/4-0.7/4
- and with request like bbt = 3|2|9000 ,so we repeat the same loop but add ticks
- */
-
- /* of course gtk_ardour only allows bar with at least 1.0 beats .....
- */
-
- uint32_t ticks_at_beat = (uint32_t) ( result.beats == ceil(beats_per_bar) ?
- (1 - (ceil(beats_per_bar) - beats_per_bar))* Meter::ticks_per_beat
- : Meter::ticks_per_beat );
-
- while (result.ticks >= ticks_at_beat) {
- result.beats++;
- result.ticks -= ticks_at_beat;
- if (result.beats >= (beats_per_bar + 1)) {
- result.bars++;
- result.beats = 1;
- metric = metric_at(result); // maybe there is a meter change
- beats_per_bar = metric.meter().beats_per_bar();
- }
- ticks_at_beat= (uint32_t) ( result.beats == ceil(beats_per_bar) ?
- (1 - (ceil(beats_per_bar) - beats_per_bar) ) * Meter::ticks_per_beat
- : Meter::ticks_per_beat);
-
- }
-
-
- } else {
- uint32_t b = bbt.beats;
-
- /* count beats */
- while( b > when.beats ) {
-
- result.bars = max(1U,result.bars-- ) ;
- metric = metric_at(result); // maybe there is a meter change
- beats_per_bar = metric.meter().beats_per_bar();
- if (b >= ceil(beats_per_bar)) {
-
- b -= (uint32_t) ceil(beats_per_bar);
- } else {
- b = (uint32_t) ceil(beats_per_bar) - b + when.beats ;
- }
- }
- result.beats = when.beats - b;
-
- /*count ticks */
-
- if (bbt.ticks <= when.ticks) {
- result.ticks = when.ticks - bbt.ticks;
- } else {
-
- uint32_t ticks_at_beat= (uint32_t) Meter::ticks_per_beat;
- uint32_t t = bbt.ticks - when.ticks;
-
- do {
+ /* round back to the previous precise beat */
+ BBTPointList::const_iterator wi = bbt_before_or_at (BBT_Time (when.bars, when.beats, 0));
+ BBTPointList::const_iterator start (wi);
- if (result.beats == 1) {
- result.bars = max(1U, result.bars-- ) ;
- metric = metric_at(result); // maybe there is a meter change
- beats_per_bar = metric.meter().beats_per_bar();
- result.beats = (uint32_t) ceil(beats_per_bar);
- ticks_at_beat = (uint32_t) ((1 - (ceil(beats_per_bar) - beats_per_bar)) * Meter::ticks_per_beat) ;
- } else {
- result.beats --;
- ticks_at_beat = (uint32_t) Meter::ticks_per_beat;
- }
+ assert (wi != _map.end());
- if (t <= ticks_at_beat) {
- result.ticks = ticks_at_beat - t;
- } else {
- t-= ticks_at_beat;
- }
- } while (t > ticks_at_beat);
+ uint32_t bars = 0;
+ uint32_t beats = 0;
+ while (wi != _map.end() && bars < bbt.bars) {
+ ++wi;
+ if ((*wi).is_bar()) {
+ ++bars;
}
+ }
+ assert (wi != _map.end());
-
+ while (wi != _map.end() && beats < bbt.beats) {
+ ++wi;
+ ++beats;
}
+ assert (wi != _map.end());
+
+ /* add any additional frames related to ticks in the added value */
- if (dir < 0 ) {
- frames = count_frames_between( result,when);
+ if (bbt.ticks != 0) {
+ return ((*wi).frame - (*start).frame) +
+ (*wi).tempo->frames_per_beat (_frame_rate) * (bbt.ticks/BBT_Time::ticks_per_beat);
} else {
- frames = count_frames_between(when,result);
+ return ((*wi).frame - (*start).frame);
}
-
- return frames;
}
-
-
-nframes64_t
-TempoMap::round_to_bar (nframes64_t fr, int dir)
+framepos_t
+TempoMap::round_to_bar (framepos_t fr, int dir)
{
- {
- Glib::RWLock::ReaderLock lm (lock);
- return round_to_type (fr, dir, Bar);
- }
+ return round_to_type (fr, dir, Bar);
}
-
-nframes64_t
-TempoMap::round_to_beat (nframes64_t fr, int dir)
+framepos_t
+TempoMap::round_to_beat (framepos_t fr, int dir)
{
- {
- Glib::RWLock::ReaderLock lm (lock);
- return round_to_type (fr, dir, Beat);
- }
+ return round_to_type (fr, dir, Beat);
}
-nframes64_t
-TempoMap::round_to_beat_subdivision (nframes64_t fr, int sub_num, int dir)
+framepos_t
+TempoMap::round_to_beat_subdivision (framepos_t fr, int sub_num, int dir)
{
+ require_map_to (fr);
+ Glib::Threads::RWLock::ReaderLock lm (lock);
+ BBTPointList::const_iterator i = bbt_before_or_at (fr);
BBT_Time the_beat;
- uint32_t ticks_one_half_subdivisions_worth;
uint32_t ticks_one_subdivisions_worth;
uint32_t difference;
- bbt_time(fr, the_beat);
+ bbt_time (fr, the_beat, i);
- ticks_one_subdivisions_worth = (uint32_t)Meter::ticks_per_beat / sub_num;
- ticks_one_half_subdivisions_worth = ticks_one_subdivisions_worth / 2;
+ DEBUG_TRACE (DEBUG::SnapBBT, string_compose ("round %1 to nearest 1/%2 beat, before-or-at = %3 @ %4|%5 precise = %6\n",
+ fr, sub_num, (*i).frame, (*i).bar, (*i).beat, the_beat));
+
+ ticks_one_subdivisions_worth = (uint32_t)BBT_Time::ticks_per_beat / sub_num;
if (dir > 0) {
- /* round to next */
+ /* round to next (even if we're on a subdivision */
uint32_t mod = the_beat.ticks % ticks_one_subdivisions_worth;
if (mod == 0) {
/* right on the subdivision, so the difference is just the subdivision ticks */
- difference = ticks_one_subdivisions_worth;
+ the_beat.ticks += ticks_one_subdivisions_worth;
} else {
/* not on subdivision, compute distance to next subdivision */
- difference = ticks_one_subdivisions_worth - mod;
+ the_beat.ticks += ticks_one_subdivisions_worth - mod;
}
- if (the_beat.ticks + difference >= (uint32_t)Meter::ticks_per_beat) {
- the_beat.beats++;
- the_beat.ticks += difference;
- the_beat.ticks -= (uint32_t)Meter::ticks_per_beat;
- } else {
- the_beat.ticks += difference;
- }
+ if (the_beat.ticks > BBT_Time::ticks_per_beat) {
+ assert (i != _map.end());
+ ++i;
+ assert (i != _map.end());
+ the_beat.ticks -= BBT_Time::ticks_per_beat;
+ }
+
} else if (dir < 0) {
- /* round to previous */
+ /* round to previous (even if we're on a subdivision) */
uint32_t mod = the_beat.ticks % ticks_one_subdivisions_worth;
if (mod == 0) {
/* right on the subdivision, so the difference is just the subdivision ticks */
difference = ticks_one_subdivisions_worth;
- cerr << "On the sub, move by 1 sub = " << difference << endl;
} else {
/* not on subdivision, compute distance to previous subdivision, which
is just the modulus.
*/
difference = mod;
- cerr << "off the sub, move by 1 sub = " << difference << endl;
}
-
- cerr << "ticks = " << the_beat.ticks << endl;
-
if (the_beat.ticks < difference) {
- cerr << "backup beats, set ticks to "
- << (uint32_t)Meter::ticks_per_beat - difference << endl;
- the_beat.beats--;
- the_beat.ticks = (uint32_t)Meter::ticks_per_beat - difference;
+ if (i == _map.begin()) {
+ /* can't go backwards from wherever pos is, so just return it */
+ return fr;
+ }
+ --i;
+ the_beat.ticks = BBT_Time::ticks_per_beat - the_beat.ticks;
} else {
- cerr << " reduce ticks\n";
the_beat.ticks -= difference;
}
} else {
/* round to nearest */
- if (the_beat.ticks % ticks_one_subdivisions_worth > ticks_one_half_subdivisions_worth) {
- difference = ticks_one_subdivisions_worth - (the_beat.ticks % ticks_one_subdivisions_worth);
- if (the_beat.ticks + difference >= (uint32_t)Meter::ticks_per_beat) {
- the_beat.beats++;
- the_beat.ticks += difference;
- the_beat.ticks -= (uint32_t)Meter::ticks_per_beat;
+ double rem;
+
+ /* compute the distance to the previous and next subdivision */
+
+ if ((rem = fmod ((double) the_beat.ticks, (double) ticks_one_subdivisions_worth)) > ticks_one_subdivisions_worth/2.0) {
+
+ /* closer to the next subdivision, so shift forward */
+
+ the_beat.ticks = lrint (the_beat.ticks + (ticks_one_subdivisions_worth - rem));
+
+ DEBUG_TRACE (DEBUG::SnapBBT, string_compose ("moved forward to %1\n", the_beat.ticks));
+
+ if (the_beat.ticks > BBT_Time::ticks_per_beat) {
+ assert (i != _map.end());
+ ++i;
+ assert (i != _map.end());
+ the_beat.ticks -= BBT_Time::ticks_per_beat;
+ DEBUG_TRACE (DEBUG::SnapBBT, string_compose ("fold beat to %1\n", the_beat));
+ }
+
+ } else if (rem > 0) {
+
+ /* closer to previous subdivision, so shift backward */
+
+ if (rem > the_beat.ticks) {
+ if (i == _map.begin()) {
+ /* can't go backwards past zero, so ... */
+ return 0;
+ }
+ /* step back to previous beat */
+ --i;
+ the_beat.ticks = lrint (BBT_Time::ticks_per_beat - rem);
+ DEBUG_TRACE (DEBUG::SnapBBT, string_compose ("step back beat to %1\n", the_beat));
} else {
- the_beat.ticks += difference;
+ the_beat.ticks = lrint (the_beat.ticks - rem);
+ DEBUG_TRACE (DEBUG::SnapBBT, string_compose ("moved backward to %1\n", the_beat.ticks));
}
} else {
- // difference = ticks_one_subdivisions_worth - (the_beat.ticks % ticks_one_subdivisions_worth);
- the_beat.ticks -= the_beat.ticks % ticks_one_subdivisions_worth;
+ /* on the subdivision, do nothing */
}
}
- return frame_time (the_beat);
+ return (*i).frame + (the_beat.ticks/BBT_Time::ticks_per_beat) *
+ (*i).tempo->frames_per_beat (_frame_rate);
}
-nframes64_t
-TempoMap::round_to_type (nframes64_t frame, int dir, BBTPointType type)
+framepos_t
+TempoMap::round_to_type (framepos_t frame, int dir, BBTPointType type)
{
- TempoMetric metric = metric_at (frame);
- BBT_Time bbt;
- BBT_Time start;
- BBT_Time one_bar (1,0,0);
- BBT_Time one_beat (0,1,0);
+ require_map_to (frame);
+
+ Glib::Threads::RWLock::ReaderLock lm (lock);
+ BBTPointList::const_iterator fi;
+
+ if (dir > 0) {
+ fi = bbt_after_or_at (frame);
+ } else {
+ fi = bbt_before_or_at (frame);
+ }
- bbt_time_with_metric (frame, bbt, metric);
+ assert (fi != _map.end());
+ DEBUG_TRACE (DEBUG::SnapBBT, string_compose ("round from %1 (%3|%4 @ %5) to %6 in direction %2\n", frame, dir, (*fi).bar, (*fi).beat, (*fi).frame,
+ (type == Bar ? "bar" : "beat")));
+
switch (type) {
case Bar:
- DEBUG_TRACE(DEBUG::SnapBBT, string_compose ("round from %1 (%3) to bars in direction %2\n", frame, dir, bbt));
-
if (dir < 0) {
+ /* find bar previous to 'frame' */
- /* find bar position preceding frame */
-
- try {
- bbt = bbt_subtract (bbt, one_bar);
+ if (fi == _map.begin()) {
+ return 0;
}
- catch (...) {
- return frame;
+ if ((*fi).is_bar() && (*fi).frame == frame) {
+ --fi;
}
+ while (!(*fi).is_bar()) {
+ if (fi == _map.begin()) {
+ break;
+ }
+ fi--;
+ }
+ DEBUG_TRACE (DEBUG::SnapBBT, string_compose ("rounded to bar: map iter at %1|%2 %3, return\n",
+ (*fi).bar, (*fi).beat, (*fi).frame));
+ return (*fi).frame;
} else if (dir > 0) {
- /* find bar position following frame */
+ /* find bar following 'frame' */
- try {
- bbt = bbt_add (bbt, one_bar, metric);
+ if ((*fi).is_bar() && (*fi).frame == frame) {
+ ++fi;
}
- catch (...) {
- return frame;
+
+ while (!(*fi).is_bar()) {
+ fi++;
+ if (fi == _map.end()) {
+ --fi;
+ break;
+ }
}
+ DEBUG_TRACE (DEBUG::SnapBBT, string_compose ("rounded to bar: map iter at %1|%2 %3, return\n",
+ (*fi).bar, (*fi).beat, (*fi).frame));
+ return (*fi).frame;
+
} else {
+
+ /* true rounding: find nearest bar */
- /* "true" rounding */
+ BBTPointList::const_iterator prev = fi;
+ BBTPointList::const_iterator next = fi;
- float midbar_beats;
- float midbar_ticks;
+ if ((*fi).frame == frame) {
+ return frame;
+ }
- midbar_beats = metric.meter().beats_per_bar() / 2 + 1;
- midbar_ticks = Meter::ticks_per_beat * fmod (midbar_beats, 1.0f);
- midbar_beats = floor (midbar_beats);
-
- BBT_Time midbar (bbt.bars, lrintf (midbar_beats), lrintf (midbar_ticks));
+ while ((*prev).beat != 1) {
+ if (prev == _map.begin()) {
+ break;
+ }
+ prev--;
+ }
- if (bbt < midbar) {
- /* round down */
- bbt.beats = 1;
- bbt.ticks = 0;
+ while ((next != _map.end()) && (*next).beat != 1) {
+ next++;
+ }
+
+ if ((next == _map.end()) || (frame - (*prev).frame) < ((*next).frame - frame)) {
+ return (*prev).frame;
} else {
- /* round up */
- bbt.bars++;
- bbt.beats = 1;
- bbt.ticks = 0;
+ return (*next).frame;
}
+
}
- /* force beats & ticks to their values at the start of a bar */
- bbt.beats = 1;
- bbt.ticks = 0;
+
break;
case Beat:
- DEBUG_TRACE(DEBUG::SnapBBT, string_compose ("round from %1 (%3) to beat in direction %2\n", frame, (dir < 0 ? "back" : "forward"), bbt));
-
if (dir < 0) {
- /* find beat position preceding frame */
-
- try {
- bbt = bbt_subtract (bbt, one_beat);
+ if (fi == _map.begin()) {
+ return 0;
}
- catch (...) {
- return frame;
+ if ((*fi).frame >= frame) {
+ DEBUG_TRACE (DEBUG::SnapBBT, "requested frame is on beat, step back\n");
+ --fi;
}
-
-
+ DEBUG_TRACE (DEBUG::SnapBBT, string_compose ("rounded to beat: map iter at %1|%2 %3, return\n",
+ (*fi).bar, (*fi).beat, (*fi).frame));
+ return (*fi).frame;
} else if (dir > 0) {
-
- /* find beat position following frame */
-
- try {
- bbt = bbt_add (bbt, one_beat, metric);
+ if ((*fi).frame <= frame) {
+ DEBUG_TRACE (DEBUG::SnapBBT, "requested frame is on beat, step forward\n");
+ ++fi;
}
- catch (...) {
+ DEBUG_TRACE (DEBUG::SnapBBT, string_compose ("rounded to beat: map iter at %1|%2 %3, return\n",
+ (*fi).bar, (*fi).beat, (*fi).frame));
+ return (*fi).frame;
+ } else {
+ /* find beat nearest to frame */
+ if ((*fi).frame == frame) {
return frame;
}
- } else {
-
- /* "true" rounding */
+ BBTPointList::const_iterator prev = fi;
+ BBTPointList::const_iterator next = fi;
- /* round to nearest beat */
- if (bbt.ticks >= (Meter::ticks_per_beat/2)) {
-
- try {
- bbt = bbt_add (bbt, one_beat, metric);
- }
- catch (...) {
- return frame;
- }
+ /* 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 {
+ return (*next).frame;
}
}
- /* force ticks to the value at the start of a beat */
- bbt.ticks = 0;
break;
-
}
- DEBUG_TRACE(DEBUG::SnapBBT, string_compose ("\tat %1 count frames from %2 to %3 = %4\n", metric.frame(), metric.start(), bbt, count_frames_between (metric.start(), bbt)));
- return metric.frame() + count_frames_between (metric.start(), bbt);
+ /* NOTREACHED */
+ assert (false);
+ return 0;
}
-TempoMap::BBTPointList *
-TempoMap::get_points (nframes64_t lower, nframes64_t upper) const
+void
+TempoMap::get_grid (TempoMap::BBTPointList::const_iterator& begin,
+ TempoMap::BBTPointList::const_iterator& end,
+ framepos_t lower, framepos_t upper)
{
-
- Metrics::const_iterator i;
- BBTPointList *points;
- double current;
- const MeterSection* meter;
- const MeterSection* m;
- const TempoSection* tempo;
- const TempoSection* t;
- uint32_t bar;
- uint32_t beat;
- double beats_per_bar;
- double beat_frame;
- double beat_frames;
- double frames_per_bar;
- double delta_bars;
- double delta_beats;
- double dummy;
- nframes64_t limit;
-
- meter = &first_meter ();
- tempo = &first_tempo ();
-
- /* find the starting point */
-
- for (i = metrics->begin(); i != metrics->end(); ++i) {
-
- if ((*i)->frame() > lower) {
- break;
+ {
+ Glib::Threads::RWLock::WriterLock lm (lock);
+ if (_map.empty() || (_map.back().frame < upper)) {
+ recompute_map (false, upper);
}
-
- if ((t = dynamic_cast<const TempoSection*>(*i)) != 0) {
- tempo = t;
- } else if ((m = dynamic_cast<const MeterSection*>(*i)) != 0) {
- meter = m;
- }
- }
-
- /* We now have:
-
- meter -> the Meter for "lower"
- tempo -> the Tempo for "lower"
- i -> for first new metric after "lower", possibly metrics->end()
-
- Now start generating points.
- */
-
- beats_per_bar = meter->beats_per_bar ();
- frames_per_bar = meter->frames_per_bar (*tempo, _frame_rate);
- beat_frames = tempo->frames_per_beat (_frame_rate, *meter);
-
- if (meter->frame() > tempo->frame()) {
- bar = meter->start().bars;
- beat = meter->start().beats;
- current = meter->frame();
- } else {
- bar = tempo->start().bars;
- beat = tempo->start().beats;
- current = tempo->frame();
}
- /* initialize current to point to the bar/beat just prior to the
- lower frame bound passed in. assumes that current is initialized
- above to be on a beat.
- */
-
- delta_bars = (lower-current) / frames_per_bar;
- delta_beats = modf(delta_bars, &dummy) * beats_per_bar;
- current += (floor(delta_bars) * frames_per_bar) + (floor(delta_beats) * beat_frames);
-
- // adjust bars and beats too
- bar += (uint32_t) (floor(delta_bars));
- beat += (uint32_t) (floor(delta_beats));
-
- points = new BBTPointList;
-
- do {
-
- if (i == metrics->end()) {
- limit = upper;
- // cerr << "== limit set to end of request @ " << limit << endl;
- } else {
- // cerr << "== limit set to next metric @ " << (*i)->frame() << endl;
- limit = (*i)->frame();
- }
-
- limit = min (limit, upper);
-
- while (current < limit) {
-
- /* if we're at the start of a bar, add bar point */
-
- if (beat == 1) {
- if (current >= lower) {
- // cerr << "Add Bar at " << bar << "|1" << " @ " << current << endl;
- points->push_back (BBTPoint (*meter, *tempo,(nframes64_t)rint(current), Bar, bar, 1));
-
- }
- }
-
- /* add some beats if we can */
-
- beat_frame = current;
-
- while (beat <= ceil( beats_per_bar) && beat_frame < limit) {
- if (beat_frame >= lower) {
- // cerr << "Add Beat at " << bar << '|' << beat << " @ " << beat_frame << endl;
- points->push_back (BBTPoint (*meter, *tempo, (nframes64_t) rint(beat_frame), Beat, bar, beat));
- }
- beat_frame += beat_frames;
- current+= beat_frames;
-
- beat++;
- }
-
- // cerr << "out of beats, @ end ? " << (i == metrics->end()) << " out of bpb ? "
- // << (beat > ceil(beats_per_bar))
- // << endl;
-
- if (beat > ceil(beats_per_bar) || i != metrics->end()) {
-
- /* we walked an entire bar. its
- important to move `current' forward
- by the actual frames_per_bar, not move it to
- an integral beat_frame, so that metrics with
- non-integral beats-per-bar have
- their bar positions set
- correctly. consider a metric with
- 9-1/2 beats-per-bar. the bar we
- just filled had 10 beat marks,
- but the bar end is 1/2 beat before
- the last beat mark.
- And it is also possible that a tempo
- change occured in the middle of a bar,
- so we subtract the possible extra fraction from the current
- */
-
- if (beat > ceil (beats_per_bar)) {
- /* next bar goes where the numbers suggest */
- current -= beat_frames * (ceil(beats_per_bar)-beats_per_bar);
- // cerr << "++ next bar from numbers\n";
- } else {
- /* next bar goes where the next metric is */
- current = limit;
- // cerr << "++ next bar at next metric\n";
- }
- bar++;
- beat = 1;
- }
-
- }
-
- /* if we're done, then we're done */
-
- if (current >= upper) {
- break;
- }
-
- /* i is an iterator that refers to the next metric (or none).
- if there is a next metric, move to it, and continue.
- */
-
- if (i != metrics->end()) {
-
- if ((t = dynamic_cast<const TempoSection*>(*i)) != 0) {
- tempo = t;
- } else if ((m = dynamic_cast<const MeterSection*>(*i)) != 0) {
- meter = m;
- /* new MeterSection, beat always returns to 1 */
- beat = 1;
- }
-
- current = (*i)->frame ();
- // cerr << "loop around with current @ " << current << endl;
-
- beats_per_bar = meter->beats_per_bar ();
- frames_per_bar = meter->frames_per_bar (*tempo, _frame_rate);
- beat_frames = tempo->frames_per_beat (_frame_rate, *meter);
-
- ++i;
- }
-
- } while (1);
-
- return points;
+ begin = lower_bound (_map.begin(), _map.end(), lower);
+ end = upper_bound (_map.begin(), _map.end(), upper);
}
const TempoSection&
-TempoMap::tempo_section_at (nframes64_t frame)
+TempoMap::tempo_section_at (framepos_t frame) const
{
- Glib::RWLock::ReaderLock lm (lock);
- Metrics::iterator i;
+ Glib::Threads::RWLock::ReaderLock lm (lock);
+ Metrics::const_iterator i;
TempoSection* prev = 0;
- for (i = metrics->begin(); i != metrics->end(); ++i) {
+ for (i = metrics.begin(); i != metrics.end(); ++i) {
TempoSection* t;
if ((t = dynamic_cast<TempoSection*> (*i)) != 0) {
}
const Tempo&
-TempoMap::tempo_at (nframes64_t frame) const
+TempoMap::tempo_at (framepos_t frame) const
{
TempoMetric m (metric_at (frame));
return m.tempo();
const Meter&
-TempoMap::meter_at (nframes64_t frame) const
+TempoMap::meter_at (framepos_t frame) const
{
TempoMetric m (metric_at (frame));
return m.meter();
XMLNode *root = new XMLNode ("TempoMap");
{
- Glib::RWLock::ReaderLock lm (lock);
- for (i = metrics->begin(); i != metrics->end(); ++i) {
- root->add_child_nocopy ((*i)->get_state());
+ Glib::Threads::RWLock::ReaderLock lm (lock);
+ for (i = metrics.begin(); i != metrics.end(); ++i) {
+ root->add_child_nocopy ((*i)->get_state());
}
}
TempoMap::set_state (const XMLNode& node, int /*version*/)
{
{
- Glib::RWLock::WriterLock lm (lock);
+ Glib::Threads::RWLock::WriterLock lm (lock);
XMLNodeList nlist;
XMLNodeConstIterator niter;
- Metrics old_metrics (*metrics);
-
- metrics->clear();
+ Metrics old_metrics (metrics);
+ MeterSection* last_meter = 0;
+ metrics.clear();
nlist = node.children();
-
+
for (niter = nlist.begin(); niter != nlist.end(); ++niter) {
XMLNode* child = *niter;
if (child->name() == TempoSection::xml_state_node_name) {
try {
- metrics->push_back (new TempoSection (*child));
+ TempoSection* ts = new TempoSection (*child);
+ metrics.push_back (ts);
+
+ if (ts->bar_offset() < 0.0) {
+ if (last_meter) {
+ ts->update_bar_offset_from_bbt (*last_meter);
+ }
+ }
}
catch (failed_constructor& err){
error << _("Tempo map: could not set new state, restoring old one.") << endmsg;
- *metrics = old_metrics;
+ metrics = old_metrics;
break;
}
} else if (child->name() == MeterSection::xml_state_node_name) {
try {
- metrics->push_back (new MeterSection (*child));
+ MeterSection* ms = new MeterSection (*child);
+ metrics.push_back (ms);
+ last_meter = ms;
}
catch (failed_constructor& err) {
error << _("Tempo map: could not set new state, restoring old one.") << endmsg;
- *metrics = old_metrics;
+ metrics = old_metrics;
break;
}
}
}
if (niter == nlist.end()) {
-
MetricSectionSorter cmp;
- metrics->sort (cmp);
- timestamp_metrics (true);
+ metrics.sort (cmp);
}
+
+ /* check for multiple tempo/meters at the same location, which
+ ardour2 somehow allowed.
+ */
+
+ Metrics::iterator prev = metrics.end();
+ for (Metrics::iterator i = metrics.begin(); i != metrics.end(); ++i) {
+ if (prev != metrics.end()) {
+ if (dynamic_cast<MeterSection*>(*prev) && dynamic_cast<MeterSection*>(*i)) {
+ if ((*prev)->start() == (*i)->start()) {
+ error << string_compose (_("Multiple meter definitions found at %1"), (*prev)->start()) << endmsg;
+ return -1;
+ }
+ } else if (dynamic_cast<TempoSection*>(*prev) && dynamic_cast<TempoSection*>(*i)) {
+ if ((*prev)->start() == (*i)->start()) {
+ error << string_compose (_("Multiple tempo definitions found at %1"), (*prev)->start()) << endmsg;
+ return -1;
+ }
+ }
+ }
+ prev = i;
+ }
+
+ recompute_map (true, -1);
}
PropertyChanged (PropertyChange ());
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<const TempoSection*>(*i)) != 0) {
- o << "Tempo @ " << *i << ' ' << t->beats_per_minute() << " BPM (denom = " << t->note_type() << ") at " << t->start() << " frame= " << t->frame() << " (move? "
+ o << "Tempo @ " << *i << " (Bar-offset: " << t->bar_offset() << ") " << t->beats_per_minute() << " BPM (pulse = 1/" << t->note_type() << ") at " << t->start() << " frame= " << t->frame() << " (movable? "
<< t->movable() << ')' << endl;
} else if ((m = dynamic_cast<const MeterSection*>(*i)) != 0) {
- o << "Meter @ " << *i << ' ' << m->beats_per_bar() << '/' << m->note_divisor() << " at " << m->start() << " frame= " << m->frame()
- << " (move? " << m->movable() << ')' << endl;
+ o << "Meter @ " << *i << ' ' << m->divisions_per_bar() << '/' << m->note_divisor() << " at " << m->start() << " frame= " << m->frame()
+ << " (movable? " << m->movable() << ')' << endl;
}
}
}
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<const TempoSection*>(*i) != 0) {
cnt++;
}
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<const MeterSection*>(*i) != 0) {
cnt++;
}
}
void
-TempoMap::insert_time (nframes64_t where, nframes64_t amount)
+TempoMap::insert_time (framepos_t where, framecnt_t amount)
{
- for (Metrics::iterator i = metrics->begin(); i != metrics->end(); ++i) {
- if ((*i)->frame() >= where) {
- (*i)->set_frame ((*i)->frame() + amount);
+ {
+ Glib::Threads::RWLock::WriterLock lm (lock);
+ for (Metrics::iterator i = metrics.begin(); i != metrics.end(); ++i) {
+ if ((*i)->frame() >= where && (*i)->movable ()) {
+ (*i)->set_frame ((*i)->frame() + amount);
+ }
}
+
+ /* now reset the BBT time of all metrics, based on their new
+ * audio time. This is the only place where we do this reverse
+ * timestamp.
+ */
+
+ Metrics::iterator i;
+ const MeterSection* meter;
+ const TempoSection* tempo;
+ MeterSection *m;
+ TempoSection *t;
+
+ meter = &first_meter ();
+ tempo = &first_tempo ();
+
+ BBT_Time start;
+ BBT_Time end;
+
+ // cerr << "\n###################### TIMESTAMP via AUDIO ##############\n" << endl;
+
+ bool first = true;
+ MetricSection* prev = 0;
+
+ for (i = metrics.begin(); i != metrics.end(); ++i) {
+
+ BBT_Time bbt;
+ TempoMetric metric (*meter, *tempo);
+
+ if (prev) {
+ metric.set_start (prev->start());
+ metric.set_frame (prev->frame());
+ } else {
+ // metric will be at frames=0 bbt=1|1|0 by default
+ // which is correct for our purpose
+ }
+
+ BBTPointList::const_iterator bi = bbt_before_or_at ((*i)->frame());
+ bbt_time ((*i)->frame(), bbt, bi);
+
+ // cerr << "timestamp @ " << (*i)->frame() << " with " << bbt.bars << "|" << bbt.beats << "|" << bbt.ticks << " => ";
+
+ if (first) {
+ first = false;
+ } else {
+
+ if (bbt.ticks > BBT_Time::ticks_per_beat/2) {
+ /* round up to next beat */
+ bbt.beats += 1;
+ }
+
+ bbt.ticks = 0;
+
+ if (bbt.beats != 1) {
+ /* round up to next bar */
+ bbt.bars += 1;
+ bbt.beats = 1;
+ }
+ }
+
+ // cerr << bbt << endl;
+
+ (*i)->set_start (bbt);
+
+ if ((t = dynamic_cast<TempoSection*>(*i)) != 0) {
+ tempo = t;
+ // cerr << "NEW TEMPO, frame = " << (*i)->frame() << " start = " << (*i)->start() <<endl;
+ } else if ((m = dynamic_cast<MeterSection*>(*i)) != 0) {
+ meter = m;
+ // cerr << "NEW METER, frame = " << (*i)->frame() << " start = " << (*i)->start() <<endl;
+ } else {
+ fatal << _("programming error: unhandled MetricSection type") << endmsg;
+ /*NOTREACHED*/
+ }
+
+ prev = (*i);
+ }
+
+ recompute_map (true);
}
- timestamp_metrics (false);
PropertyChanged (PropertyChange ());
}
-BBT_Time
-TempoMap::bbt_add (const BBT_Time& start, const BBT_Time& other) const
+/** 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
{
- TempoMetric metric = metric_at (start);
- return bbt_add (start, other, metric);
+ Glib::Threads::RWLock::ReaderLock lm (lock);
+ Metrics::const_iterator next_tempo;
+ const TempoSection* tempo = 0;
+
+ /* Find the starting tempo metric */
+
+ for (next_tempo = metrics.begin(); next_tempo != metrics.end(); ++next_tempo) {
+
+ const TempoSection* t;
+
+ if ((t = dynamic_cast<const TempoSection*>(*next_tempo)) != 0) {
+
+ /* This is a bit of a hack, but pos could be -ve, and if it is,
+ we consider the initial metric changes (at time 0) to actually
+ be in effect at pos.
+ */
+
+ framepos_t f = (*next_tempo)->frame ();
+
+ if (pos < 0 && f == 0) {
+ f = pos;
+ }
+
+ if (f > pos) {
+ break;
+ }
+
+ tempo = t;
+ }
+ }
+
+ /* We now have:
+
+ tempo -> the Tempo for "pos"
+ next_tempo -> first tempo after "pos", possibly metrics.end()
+ */
+
+ DEBUG_TRACE (DEBUG::TempoMath,
+ string_compose ("frame %1 plus %2 beats, start with tempo = %3 @ %4\n",
+ pos, beats, *((const Tempo*)tempo), tempo->frame()));
+
+ while (beats) {
+
+ /* Distance to the end of this section in frames */
+ framecnt_t distance_frames = (next_tempo == metrics.end() ? max_framepos : ((*next_tempo)->frame() - pos));
+
+ /* Distance to the end in beats */
+ Evoral::MusicalTime distance_beats = distance_frames / tempo->frames_per_beat (_frame_rate);
+
+ /* Amount to subtract this time */
+ double const delta = min (distance_beats, beats);
+
+ DEBUG_TRACE (DEBUG::TempoMath, string_compose ("\tdistance to %1 = %2 (%3 beats)\n",
+ (next_tempo == metrics.end() ? max_framepos : (*next_tempo)->frame()),
+ distance_frames, distance_beats));
+
+ /* Update */
+ beats -= delta;
+ pos += delta * tempo->frames_per_beat (_frame_rate);
+
+ DEBUG_TRACE (DEBUG::TempoMath, string_compose ("\tnow at %1, %2 beats left\n", pos, beats));
+
+ /* step forwards to next tempo section */
+
+ if (next_tempo != metrics.end()) {
+
+ tempo = dynamic_cast<const TempoSection*>(*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<const TempoSection*>(*next_tempo)) {
+ break;
+ }
+ }
+ }
+ }
+
+ return pos;
}
-/**
- * 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
+/** 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
{
- BBT_Time result = start;
- BBT_Time op = increment; /* argument is const, but we need to modify it */
- uint32_t ticks = result.ticks + op.ticks;
+ Glib::Threads::RWLock::ReaderLock lm (lock);
+ Metrics::const_reverse_iterator prev_tempo;
+ const TempoSection* tempo = 0;
- if (ticks >= Meter::ticks_per_beat) {
- op.beats++;
- result.ticks = ticks % (uint32_t) Meter::ticks_per_beat;
- }
+ /* Find the starting tempo metric */
- /* now comes the complicated part. we have to add one beat a time,
- checking for a new metric on every beat.
- */
-
- /* grab all meter sections */
-
- list<const MeterSection*> meter_sections;
-
- for (Metrics::const_iterator x = metrics->begin(); x != metrics->end(); ++x) {
- const MeterSection* ms;
- if ((ms = dynamic_cast<const MeterSection*>(*x)) != 0) {
- meter_sections.push_back (ms);
+ for (prev_tempo = metrics.rbegin(); prev_tempo != metrics.rend(); ++prev_tempo) {
+
+ const TempoSection* t;
+
+ if ((t = dynamic_cast<const TempoSection*>(*prev_tempo)) != 0) {
+
+ /* This is a bit of a hack, but pos could be -ve, and if it is,
+ we consider the initial metric changes (at time 0) to actually
+ be in effect at pos.
+ */
+
+ framepos_t f = (*prev_tempo)->frame ();
+
+ if (pos < 0 && f == 0) {
+ f = pos;
+ }
+
+ /* this is slightly more complex than the forward case
+ because we reach the tempo in effect at pos after
+ passing through pos (rather before, as in the
+ forward case). having done that, we then need to
+ keep going to get the previous tempo (or
+ metrics.rend())
+ */
+
+ if (f <= pos) {
+ if (tempo == 0) {
+ /* first tempo with position at or
+ before pos
+ */
+ tempo = t;
+ } else if (f < pos) {
+ /* some other tempo section that
+ is even earlier than 'tempo'
+ */
+ break;
+ }
+ }
}
}
-
- assert (!meter_sections.empty());
-
- list<const MeterSection*>::const_iterator next_meter;
- const Meter* meter = 0;
-
- /* go forwards through the meter sections till we get to the one
- covering the current value of result. this positions i to point to
- the next meter section too, or the end.
+
+ 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()
*/
-
- for (next_meter = meter_sections.begin(); next_meter != meter_sections.end(); ++next_meter) {
+
+ while (beats) {
- if (result < (*next_meter)->start()) {
- /* this metric is past the result time. stop looking, we have what we need */
- break;
+ /* Distance to the start of this section in frames */
+ 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);
+
+ /* Amount to subtract this time */
+ double 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);
+
+ DEBUG_TRACE (DEBUG::TempoMath, string_compose ("\tnow at %1, %2 beats left, prev at end ? %3\n", pos, beats,
+ (prev_tempo == metrics.rend())));
+
+ /* step backwards to prior TempoSection */
+
+ if (prev_tempo != metrics.rend()) {
+
+ tempo = dynamic_cast<const TempoSection*>(*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;
+
+ if (prev_tempo != metrics.rend() && dynamic_cast<const TempoSection*>(*prev_tempo) != 0) {
+ break;
+ }
+ }
+ } else {
+ pos -= llrint (beats * tempo->frames_per_beat (_frame_rate));
+ beats = 0;
}
+ }
- 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;
+ return pos;
+}
+
+/** Add the BBT interval op to pos and return the result */
+framepos_t
+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 ();
+
+ assert (meter);
+ assert (tempo);
+
+ /* find the starting metrics for tempo & meter */
+
+ for (i = metrics.begin(); i != metrics.end(); ++i) {
+
+ if ((*i)->frame() > effective_pos) {
break;
}
-
- meter = *next_meter;
+
+ if ((t = dynamic_cast<const TempoSection*>(*i)) != 0) {
+ tempo = t;
+ } else if ((m = dynamic_cast<const MeterSection*>(*i)) != 0) {
+ meter = m;
+ }
}
-
- assert (meter != 0);
-
- /* OK, now have the meter for the bar start we are on, and i is an iterator
- that points to the metric after the one we are currently dealing with
- (or to metrics->end(), of course)
+
+ /* We now have:
+
+ meter -> the Meter for "pos"
+ tempo -> the Tempo for "pos"
+ i -> for first new metric after "pos", possibly metrics.end()
*/
-
+
+ /* now comes the complicated part. we have to add one beat a time,
+ checking for a new metric on every beat.
+ */
+
+ frames_per_beat = tempo->frames_per_beat (_frame_rate);
+
+ uint64_t bars = 0;
+
+ while (op.bars) {
+
+ bars++;
+ op.bars--;
+
+ /* check if we need to use a new metric section: has adding frames moved us
+ to or after the start of the next metric section? in which case, use it.
+ */
+
+ if (i != metrics.end()) {
+ if ((*i)->frame() <= pos) {
+
+ /* about to change tempo or meter, so add the
+ * number of frames for the bars we've just
+ * traversed before we change the
+ * frames_per_beat value.
+ */
+
+ pos += llrint (frames_per_beat * (bars * meter->divisions_per_bar()));
+ bars = 0;
+
+ if ((t = dynamic_cast<const TempoSection*>(*i)) != 0) {
+ tempo = t;
+ } else if ((m = dynamic_cast<const MeterSection*>(*i)) != 0) {
+ meter = m;
+ }
+ ++i;
+ frames_per_beat = tempo->frames_per_beat (_frame_rate);
+
+ }
+ }
+
+ }
+
+ pos += llrint (frames_per_beat * (bars * meter->divisions_per_bar()));
+
+ uint64_t beats = 0;
+
while (op.beats) {
-
+
/* given the current meter, have we gone past the end of the bar ? */
-
- if (result.beats >= meter->beats_per_bar()) {
- /* move to next bar, first beat */
- result.bars++;
- result.beats = 1;
- } else {
- result.beats++;
- }
-
- /* one down ... */
-
+
+ beats++;
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.
+
+ /* check if we need to use a new metric section: has adding frames moved us
+ to or after the start of the next metric section? in which case, use it.
*/
- if (next_meter != meter_sections.end() && (((*next_meter)->start () < result) || (result == (*next_meter)->start()))) {
- meter = *next_meter;
- ++next_meter;
+ if (i != metrics.end()) {
+ if ((*i)->frame() <= pos) {
+
+ /* about to change tempo or meter, so add the
+ * number of frames for the beats we've just
+ * traversed before we change the
+ * frames_per_beat value.
+ */
+
+ pos += llrint (beats * frames_per_beat);
+ beats = 0;
+
+ if ((t = dynamic_cast<const TempoSection*>(*i)) != 0) {
+ tempo = t;
+ } else if ((m = dynamic_cast<const MeterSection*>(*i)) != 0) {
+ meter = m;
+ }
+ ++i;
+ frames_per_beat = tempo->frames_per_beat (_frame_rate);
+ }
}
}
- /* finally, add bars */
+ pos += llrint (beats * frames_per_beat);
- result.bars += op.bars++;
+ if (op.ticks) {
+ if (op.ticks >= BBT_Time::ticks_per_beat) {
+ pos += llrint (frames_per_beat + /* extra beat */
+ (frames_per_beat * ((op.ticks % (uint32_t) BBT_Time::ticks_per_beat) /
+ (double) BBT_Time::ticks_per_beat)));
+ } else {
+ pos += llrint (frames_per_beat * (op.ticks / (double) BBT_Time::ticks_per_beat));
+ }
+ }
- return result;
+ return pos;
}
-/**
- * subtract the BBT interval @param decrement from @param start and return the result
- */
-BBT_Time
-TempoMap::bbt_subtract (const BBT_Time& start, const BBT_Time& decrement) const
+/** 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) const
{
- BBT_Time result = start;
- BBT_Time op = decrement; /* argument is const, but we need to modify it */
+ Glib::Threads::RWLock::ReaderLock lm (lock);
+ Metrics::const_iterator next_tempo;
+ const TempoSection* tempo = 0;
+ framepos_t effective_pos = max (pos, (framepos_t) 0);
- if (op.ticks > result.ticks) {
- /* subtract an extra beat later; meanwhile set ticks to the right "carry" value */
- op.beats++;
- result.ticks = Meter::ticks_per_beat - (op.ticks - result.ticks);
- } else {
- result.ticks -= op.ticks;
- }
+ /* Find the relevant initial tempo metric */
- /* now comes the complicated part. we have to subtract one beat a time,
- checking for a new metric on every beat.
- */
-
- /* grab all meter sections */
-
- list<const MeterSection*> meter_sections;
-
- for (Metrics::const_iterator x = metrics->begin(); x != metrics->end(); ++x) {
- const MeterSection* ms;
- if ((ms = dynamic_cast<const MeterSection*>(*x)) != 0) {
- meter_sections.push_back (ms);
- }
- }
-
- assert (!meter_sections.empty());
-
- /* go backwards through the meter sections till we get to the one
- covering the current value of result. this positions i to point to
- the next (previous) meter section too, or the end.
- */
-
- const MeterSection* meter = 0;
- list<const MeterSection*>::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;
+ for (next_tempo = metrics.begin(); next_tempo != metrics.end(); ++next_tempo) {
+
+ const TempoSection* t;
+
+ if ((t = dynamic_cast<const TempoSection*>(*next_tempo)) != 0) {
+
+ if ((*next_tempo)->frame() > effective_pos) {
+ break;
+ }
+
+ tempo = t;
}
}
- assert (meter != 0);
-
- /* OK, now have the meter for the bar start we are on, and i is an iterator
- that points to the metric after the one we are currently dealing with
- (or to metrics->end(), of course)
+ /* We now have:
+
+ tempo -> the Tempo for "pos"
+ next_tempo -> 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()));
- while (op.beats) {
+ Evoral::MusicalTime beats = 0;
- /* 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->beats_per_bar();
+ while (distance) {
+
+ /* End of this section */
+ framepos_t end;
+ /* Distance to `end' in frames */
+ framepos_t distance_to_end;
+
+ if (next_tempo == metrics.end ()) {
+ /* We can't do (end - pos) if end is max_framepos, as it will overflow if pos is -ve */
+ end = max_framepos;
+ distance_to_end = max_framepos;
} else {
+ end = (*next_tempo)->frame ();
+ distance_to_end = end - pos;
+ }
- /* back one beat */
+ /* 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;
+ assert (tempo);
+ beats += sub / tempo->frames_per_beat (_frame_rate);
+
+ DEBUG_TRACE (DEBUG::TempoMath, string_compose ("now at %1, beats = %2 distance left %3\n",
+ pos, beats, distance));
+
+ /* Move on if there's anything to move to */
+
+ if (next_tempo != metrics.end()) {
+
+ tempo = dynamic_cast<const TempoSection*>(*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<const TempoSection*>(*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()));
+ }
- result.beats--;
}
-
- /* 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.
+ assert (tempo);
+ }
+
+ return beats;
+}
+
+TempoMap::BBTPointList::const_iterator
+TempoMap::bbt_before_or_at (framepos_t pos)
+{
+ /* CALLER MUST HOLD READ LOCK */
+
+ BBTPointList::const_iterator i;
+
+ if (pos < 0) {
+ /* not really correct, but we should catch pos < 0 at a higher
+ level
*/
+ return _map.begin();
+ }
- if (result < meter->start() && next_meter != meter_sections.rend()) {
- meter = *next_meter;
- ++next_meter;
- }
+ i = lower_bound (_map.begin(), _map.end(), pos);
+ assert (i != _map.end());
+ if ((*i).frame > pos) {
+ assert (i != _map.begin());
+ --i;
+ }
+ return i;
+}
+
+struct bbtcmp {
+ bool operator() (const BBT_Time& a, const BBT_Time& b) {
+ return a < b;
+ }
+};
+
+TempoMap::BBTPointList::const_iterator
+TempoMap::bbt_before_or_at (const BBT_Time& bbt)
+{
+ BBTPointList::const_iterator i;
+ bbtcmp cmp;
+
+ i = lower_bound (_map.begin(), _map.end(), bbt, cmp);
+ assert (i != _map.end());
+ if ((*i).bar > bbt.bars || (*i).beat > bbt.beats) {
+ assert (i != _map.begin());
+ --i;
+ }
+ return i;
+}
+
+TempoMap::BBTPointList::const_iterator
+TempoMap::bbt_after_or_at (framepos_t pos)
+{
+ /* CALLER MUST HOLD READ LOCK */
+
+ BBTPointList::const_iterator i;
+
+ if (_map.back().frame == pos) {
+ i = _map.end();
+ assert (i != _map.begin());
+ --i;
+ return i;
}
- /* finally, subtract bars */
+ i = upper_bound (_map.begin(), _map.end(), pos);
+ assert (i != _map.end());
+ return i;
+}
+
+std::ostream&
+operator<< (std::ostream& o, const Meter& m) {
+ return o << m.divisions_per_bar() << '/' << m.note_divisor();
+}
+
+std::ostream&
+operator<< (std::ostream& o, const Tempo& t) {
+ return o << t.beats_per_minute() << " 1/" << t.note_type() << "'s per minute";
+}
+
+std::ostream&
+operator<< (std::ostream& o, const MetricSection& section) {
+
+ o << "MetricSection @ " << section.frame() << " aka " << section.start() << ' ';
+
+ const TempoSection* ts;
+ const MeterSection* ms;
- if (op.bars >= result.bars) {
- /* i'm sorry dave, i can't do that */
- throw std::out_of_range ("illegal BBT subtraction");
+ if ((ts = dynamic_cast<const TempoSection*> (§ion)) != 0) {
+ o << *((const Tempo*) ts);
+ } else if ((ms = dynamic_cast<const MeterSection*> (§ion)) != 0) {
+ o << *((const Meter*) ms);
}
- result.bars -= op.bars;
- return result;
+ return o;
}