#include <algorithm>
#include <stdexcept>
+#include <cmath>
#include <unistd.h>
-#include <cmath>
-
-#include <glibmm/thread.h>
+#include <glibmm/threads.h>
#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 <locale.h>
Meter TempoMap::_default_meter (4.0, 4.0);
Tempo TempoMap::_default_tempo (120.0);
-double
-Tempo::frames_per_beat (framecnt_t sr) const
-{
- return (60.0 * sr) / _beats_per_minute;
-}
-
/***********************************************************************/
-double
-Meter::frames_per_division (const Tempo& tempo, framecnt_t sr) const
+double
+Meter::frames_per_grid (const Tempo& tempo, framecnt_t sr) const
{
+ /* This is tempo- and meter-sensitive. The number it returns
+ is based on the interval between any two lines in the
+ grid that is constructed from tempo and meter sections.
+
+ The return value IS NOT interpretable in terms of "beats".
+ */
+
return (60.0 * sr) / (tempo.beats_per_minute() * (_note_type/tempo.note_type()));
}
double
Meter::frames_per_bar (const Tempo& tempo, framecnt_t sr) const
{
- return frames_per_division (tempo, sr) * _divisions_per_bar;
+ return frames_per_grid (tempo, sr) * _divisions_per_bar;
}
+
/***********************************************************************/
const string TempoSection::xml_state_node_name = "Tempo";
TempoSection::TempoSection (const XMLNode& node)
- : MetricSection (BBT_Time()), Tempo (TempoMap::default_tempo())
+ : MetricSection (0.0), Tempo (TempoMap::default_tempo())
{
const XMLProperty *prop;
- BBT_Time start;
- LocaleGuard lg (X_("POSIX"));
-
- if ((prop = node.property ("start")) == 0) {
+ LocaleGuard lg;
+ BBT_Time bbt;
+ double beat;
+
+ if ((prop = node.property ("start")) != 0) {
+ if (sscanf (prop->value().c_str(), "%" PRIu32 "|%" PRIu32 "|%" PRIu32,
+ &bbt.bars,
+ &bbt.beats,
+ &bbt.ticks) == 3) {
+ /* legacy session - start used to be in bbt*/
+ _legacy_bbt = bbt;
+ beat = -1.0;
+ set_beat (beat);
+ }
+ } else {
error << _("TempoSection XML node has no \"start\" property") << endmsg;
- throw failed_constructor();
}
- if (sscanf (prop->value().c_str(), "%" PRIu32 "|%" PRIu32 "|%" PRIu32,
- &start.bars,
- &start.beats,
- &start.ticks) < 3) {
- error << _("TempoSection XML node has an illegal \"start\" value") << endmsg;
- throw failed_constructor();
+
+ if ((prop = node.property ("beat")) != 0) {
+ if (sscanf (prop->value().c_str(), "%lf", &beat) != 1 || beat < 0.0) {
+ error << _("TempoSection XML node has an illegal \"beat\" value") << endmsg;
+ } else {
+ set_beat (beat);
+ }
+ } else {
+ error << _("TempoSection XML node has no \"beat\" property") << endmsg;
}
- set_start (start);
if ((prop = node.property ("beats-per-minute")) == 0) {
error << _("TempoSection XML node has no \"beats-per-minute\" property") << endmsg;
throw failed_constructor();
}
}
+
+ if ((prop = node.property ("tempo-type")) == 0) {
+ _type = Type::Constant;
+ } else {
+ if (strstr(prop->value().c_str(),"Constant")) {
+ _type = Type::Constant;
+ } else {
+ _type = Type::Ramp;
+ }
+ }
}
XMLNode&
{
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,
- start().beats,
- start().ticks);
- root->add_property ("start", buf);
+ snprintf (buf, sizeof (buf), "%f", beat());
+ root->add_property ("beat", buf);
snprintf (buf, sizeof (buf), "%f", _beats_per_minute);
root->add_property ("beats-per-minute", buf);
snprintf (buf, sizeof (buf), "%f", _note_type);
snprintf (buf, sizeof (buf), "%s", movable()?"yes":"no");
root->add_property ("movable", buf);
+ snprintf (buf, sizeof (buf), "%s", _type == Constant?"Constant":"Ramp");
+ root->add_property ("tempo-type", buf);
+
return *root;
}
TempoSection::update_bar_offset_from_bbt (const Meter& m)
{
- _bar_offset = ((start().beats - 1) * BBT_Time::ticks_per_bar_division + start().ticks) /
- (m.divisions_per_bar() * BBT_Time::ticks_per_bar_division);
+ _bar_offset = (beat() * BBT_Time::ticks_per_beat) /
+ (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, beat(), m.divisions_per_bar()));
+}
+
+void
+TempoSection::set_type (Type type)
+{
+ _type = type;
+}
+
+/** returns the tempo at the zero-based (relative to tempo section) frame.
+*/
+double
+TempoSection::tempo_at_frame (framepos_t frame, double end_bpm, framepos_t end_frame, framecnt_t frame_rate) const
+{
+
+ if (_type == Constant) {
+ return beats_per_minute();
+ }
+
+ return tick_tempo_at_time (frame_to_minute (frame, frame_rate), end_bpm * BBT_Time::ticks_per_beat, frame_to_minute (end_frame, frame_rate)) / BBT_Time::ticks_per_beat;
+}
+
+/** returns the zero-based frame (relative to tempo section)
+ where the tempo occurs.
+*/
+framepos_t
+TempoSection::frame_at_tempo (double tempo, double end_bpm, framepos_t end_frame, framecnt_t frame_rate) const
+{
+ if (_type == Constant) {
+ return 0;
+ }
+
+ return minute_to_frame (time_at_tick_tempo (tempo * BBT_Time::ticks_per_beat, end_bpm * BBT_Time::ticks_per_beat, frame_to_minute (end_frame, frame_rate)), frame_rate);
+}
+
+/** returns the zero-based tick (relative to tempo section)
+ where the zero-based frame (relative to tempo section)
+ lies.
+*/
+double
+TempoSection::tick_at_frame (framepos_t frame, double end_bpm, framepos_t end_frame, framecnt_t frame_rate) const
+{
+ if (_type == Constant) {
+ return (frame / frames_per_beat (frame_rate)) * BBT_Time::ticks_per_beat;
+ }
+
+ return tick_at_time (frame_to_minute (frame, frame_rate), end_bpm * BBT_Time::ticks_per_beat, frame_to_minute (end_frame, frame_rate));
+}
+
+/** returns the zero-based frame (relative to tempo section)
+ where the zero-based tick (relative to tempo section)
+ falls.
+*/
+framepos_t
+TempoSection::frame_at_tick (double tick, double end_bpm, framepos_t end_frame, framecnt_t frame_rate) const
+{
+ if (_type == Constant) {
+ return (framepos_t) floor ((tick / BBT_Time::ticks_per_beat) * frames_per_beat (frame_rate));
+ }
+
+ return minute_to_frame (time_at_tick (tick, end_bpm * BBT_Time::ticks_per_beat, frame_to_minute (end_frame, frame_rate)), frame_rate);
+}
+
+/** returns the zero-based beat (relative to tempo section)
+ where the zero-based frame (relative to tempo section)
+ lies.
+*/
+double
+TempoSection::beat_at_frame (framepos_t frame, double end_bpm, framepos_t end_frame, framecnt_t frame_rate) const
+{
+ return tick_at_frame (frame, end_bpm, end_frame, frame_rate) / BBT_Time::ticks_per_beat;
+}
+
+/** returns the zero-based frame (relative to tempo section start frame)
+ where the zero-based beat (relative to tempo section start)
+ falls.
+*/
+
+framepos_t
+TempoSection::frame_at_beat (double beat, double end_bpm, framepos_t end_frame, framecnt_t frame_rate) const
+{
+ return frame_at_tick (beat * BBT_Time::ticks_per_beat, end_bpm, end_frame, frame_rate);
+}
+
+framecnt_t
+TempoSection::minute_to_frame (double time, framecnt_t frame_rate) const
+{
+ return time * 60.0 * frame_rate;
+}
+
+double
+TempoSection::frame_to_minute (framecnt_t frame, framecnt_t frame_rate) const
+{
+ return (frame / (double) frame_rate) / 60.0;
+}
+
+/* position function */
+double
+TempoSection::a_func (double end_tpm, double c_func) const
+{
+ return log (end_tpm / ticks_per_minute()) / c_func;
+}
+
+/*function constant*/
+double
+TempoSection::c_func (double end_tpm, double end_time) const
+{
+ return log (end_tpm / ticks_per_minute()) / end_time;
+}
+
+/* tempo in tpm at time in minutes */
+double
+TempoSection::tick_tempo_at_time (double time, double end_tpm, double end_time) const
+{
+ return exp (c_func (end_tpm, end_time) * time) * ticks_per_minute();
+}
+
+/* time in minutes at tempo in tpm */
+double
+TempoSection::time_at_tick_tempo (double tick_tempo, double end_tpm, double end_time) const
+{
+ return log (tick_tempo / ticks_per_minute()) / c_func (end_tpm, end_time);
+}
+
+/* tick at time in minutes */
+double
+TempoSection::tick_at_time (double time, double end_tpm, double end_time) const
+{
+ return ((exp (c_func (end_tpm, end_time) * time)) - 1) * ticks_per_minute() / c_func (end_tpm, end_time);
+}
+
+/* time in minutes at tick */
+double
+TempoSection::time_at_tick (double tick, double end_tpm, double end_time) const
+{
+ return log (((c_func (end_tpm, end_time) * tick) / ticks_per_minute()) + 1) / c_func (end_tpm, end_time);
+}
+
+/* beat at time in minutes */
+double
+TempoSection::beat_at_time (double time, double end_tpm, double end_time) const
+{
+ return tick_at_time (time, end_tpm, end_time) / BBT_Time::ticks_per_beat;
+}
- DEBUG_TRACE (DEBUG::TempoMath, string_compose ("Tempo set bar offset to %1 from %2 w/%3\n", _bar_offset, start(), m.divisions_per_bar()));
+/* time in munutes at beat */
+double
+TempoSection::time_at_beat (double beat, double end_tpm, double end_time) const
+{
+ return time_at_tick (beat * BBT_Time::ticks_per_beat, end_tpm, end_time);
}
void
TempoSection::update_bbt_time_from_bar_offset (const Meter& meter)
{
- BBT_Time new_start;
+ double new_beat;
if (_bar_offset < 0.0) {
/* not set yet */
return;
}
- new_start.bars = start().bars;
-
- double ticks = BBT_Time::ticks_per_bar_division * meter.divisions_per_bar() * _bar_offset;
- new_start.beats = (uint32_t) floor(ticks/BBT_Time::ticks_per_bar_division);
- new_start.ticks = (uint32_t) fmod (ticks, BBT_Time::ticks_per_bar_division);
+ new_beat = beat();
+
+ double ticks = BBT_Time::ticks_per_beat * meter.divisions_per_bar() * _bar_offset;
+ new_beat = 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));
+ 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_beat, new_beat));
- set_start (new_start);
+ set_beat (new_beat);
}
/***********************************************************************/
const string MeterSection::xml_state_node_name = "Meter";
MeterSection::MeterSection (const XMLNode& node)
- : MetricSection (BBT_Time()), Meter (TempoMap::default_meter())
+ : MetricSection (0.0), Meter (TempoMap::default_meter())
{
- const XMLProperty *prop;
+ XMLProperty const * prop;
BBT_Time start;
- LocaleGuard lg (X_("POSIX"));
-
- if ((prop = node.property ("start")) == 0) {
+ LocaleGuard lg;
+ const XMLProperty *prop;
+ BBT_Time bbt;
+ double beat = 0.0;
+ pair<double, BBT_Time> start;
+
+ if ((prop = node.property ("start")) != 0) {
+ if (sscanf (prop->value().c_str(), "%" PRIu32 "|%" PRIu32 "|%" PRIu32,
+ &bbt.bars,
+ &bbt.beats,
+ &bbt.ticks) < 3) {
+ error << _("MeterSection XML node has an illegal \"start\" value") << endmsg;
+ } else {
+ /* legacy session - start used to be in bbt*/
+ beat = -1.0;
+ }
+ } else {
error << _("MeterSection XML node has no \"start\" property") << endmsg;
- throw failed_constructor();
}
- if (sscanf (prop->value().c_str(), "%" PRIu32 "|%" PRIu32 "|%" PRIu32,
- &start.bars,
- &start.beats,
- &start.ticks) < 3) {
- error << _("MeterSection XML node has an illegal \"start\" value") << endmsg;
+ if ((prop = node.property ("beat")) != 0) {
+ if (sscanf (prop->value().c_str(), "%lf", &beat) != 1 || beat < 0.0) {
+ error << _("MeterSection XML node has an illegal \"beat\" value") << endmsg;
+ }
+ } else {
+ error << _("MeterSection XML node has no \"beat\" property") << endmsg;
+ }
+
+ start.first = beat;
+
+ if ((prop = node.property ("bbt")) == 0) {
+ error << _("MeterSection XML node has no \"bbt\" property") << endmsg;
+ } else if (sscanf (prop->value().c_str(), "%" PRIu32 "|%" PRIu32 "|%" PRIu32,
+ &bbt.bars,
+ &bbt.beats,
+ &bbt.ticks) < 3) {
+ error << _("MeterSection XML node has an illegal \"bbt\" value") << endmsg;
throw failed_constructor();
}
- set_start (start);
+ start.second = bbt;
+
+ set_beat (start);
/* beats-per-bar is old; divisions-per-bar is new */
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) {
{
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,
- start().beats,
- start().ticks);
- root->add_property ("start", buf);
+ bbt().bars,
+ bbt().beats,
+ bbt().ticks);
+ root->add_property ("bbt", buf);
+ snprintf (buf, sizeof (buf), "%lf", beat());
+ root->add_property ("beat", buf);
snprintf (buf, sizeof (buf), "%f", _note_type);
root->add_property ("note-type", buf);
snprintf (buf, sizeof (buf), "%f", _divisions_per_bar);
struct MetricSectionSorter {
bool operator() (const MetricSection* a, const MetricSection* b) {
- return a->start() < b->start();
+ return a->beat() < b->beat();
+ }
+};
+
+struct MetricSectionFrameSorter {
+ bool operator() (const MetricSection* a, const MetricSection* b) {
+ return a->frame() < b->frame();
}
};
TempoMap::TempoMap (framecnt_t fr)
{
- metrics = new Metrics;
_frame_rate = fr;
- last_bbt_valid = false;
BBT_Time start;
start.bars = 1;
start.beats = 1;
start.ticks = 0;
- TempoSection *t = new TempoSection (start, _default_tempo.beats_per_minute(), _default_tempo.note_type());
- MeterSection *m = new MeterSection (start, _default_meter.divisions_per_bar(), _default_meter.note_divisor());
+ TempoSection *t = new TempoSection (0.0, _default_tempo.beats_per_minute(), _default_tempo.note_type(), TempoSection::Type::Constant);
+ MeterSection *m = new MeterSection (0.0, 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 ()
bool removed = false;
{
- Glib::RWLock::WriterLock lm (lock);
- Metrics::iterator 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);
- 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) {
}
}
+bool
+TempoMap::remove_tempo_locked (const TempoSection& tempo)
+{
+ Metrics::iterator 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);
+ 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<MeterSection*> (*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) {
}
}
+bool
+TempoMap::remove_meter_locked (const MeterSection& tempo)
+{
+ Metrics::iterator 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);
+ return true;
+ }
+ }
+ }
+ }
+
+ return false;
+}
+
void
TempoMap::do_insert (MetricSection* section)
{
- /* CALLER MUST HOLD WRITE LOCK */
-
- bool reassign_tempo_bbt = false;
bool need_add = true;
- assert (section->start().ticks == 0);
-
/* we only allow new meters to be inserted on beat 1 of an existing
- * measure.
+ * measure.
*/
-
- if (dynamic_cast<MeterSection*>(section)) {
+ MeterSection* m = 0;
+ if ((m = dynamic_cast<MeterSection*>(section)) != 0) {
+ assert (m->bbt().ticks == 0);
/* we need to (potentially) update the BBT times of tempo
sections based on this new meter.
*/
-
- reassign_tempo_bbt = true;
-
- if ((section->start().beats != 1) || (section->start().ticks != 0)) {
-
- BBT_Time corrected = section->start();
- corrected.beats = 1;
- corrected.ticks = 0;
-
+
+ if ((m->bbt().beats != 1) || (m->bbt().ticks != 0)) {
+
+ pair<double, BBT_Time> corrected = make_pair (m->beat(), m->bbt());
+ corrected.second.beats = 1;
+ corrected.second.ticks = 0;
+ corrected.first = bbt_to_beats_unlocked (corrected.second);
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);
+ m->bbt(), corrected.second) << endmsg;
+ m->set_beat (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 (i = metrics->begin(); i != metrics->end(); ++i) {
+ for (Metrics::iterator i = metrics.begin(); i != metrics.end(); ++i) {
- int const c = (*i)->compare (*section);
+ TempoSection* const tempo = dynamic_cast<TempoSection*> (*i);
+ TempoSection* const insert_tempo = dynamic_cast<TempoSection*> (section);
- 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;
- }
+ if (tempo && insert_tempo) {
- /* hacky comparison of type */
- bool const iter_is_tempo = dynamic_cast<TempoSection*> (*i) != 0;
- bool const insert_is_tempo = dynamic_cast<TempoSection*> (section) != 0;
+ /* Tempo sections */
- if (iter_is_tempo == insert_is_tempo) {
+ if (tempo->beat() == insert_tempo->beat()) {
- if (!(*i)->movable()) {
+ if (!tempo->movable()) {
- /* can't (re)move this section, so overwrite it
- */
+ /* can't (re)move this section, so overwrite
+ * its data content (but not its properties as
+ * a section).
+ */
- if (!iter_is_tempo) {
- *(dynamic_cast<MeterSection*>(*i)) = *(dynamic_cast<MeterSection*>(section));
+ *(dynamic_cast<Tempo*>(*i)) = *(dynamic_cast<Tempo*>(insert_tempo));
+ need_add = false;
} else {
- *(dynamic_cast<TempoSection*>(*i)) = *(dynamic_cast<TempoSection*>(section));
+ metrics.erase (i);
}
- need_add = false;
break;
}
- to_remove = i;
- break;
- }
- }
+ } else if (!tempo && !insert_tempo) {
+
+ /* Meter Sections */
+ MeterSection* const meter = dynamic_cast<MeterSection*> (*i);
+ MeterSection* const insert_meter = dynamic_cast<MeterSection*> (section);
+ if (meter->beat() == insert_meter->beat()) {
+
+ if (!meter->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*>(insert_meter));
+ need_add = false;
+ } else {
+ metrics.erase (i);
+
+ }
- if (to_remove != metrics->end()) {
- /* remove the MetricSection at the same time as the one we are about to add */
- metrics->erase (to_remove);
+ break;
+ }
+ } else {
+ /* non-matching types, so we don't care */
+ }
}
- /* Add the given MetricSection */
+ /* Add the given MetricSection, if we didn't just reset an existing
+ * one above
+ */
if (need_add) {
- for (i = metrics->begin(); i != metrics->end(); ++i) {
-
- if ((*i)->compare (*section) < 0) {
- continue;
+ MeterSection* const insert_meter = dynamic_cast<MeterSection*> (section);
+ TempoSection* const insert_tempo = dynamic_cast<TempoSection*> (section);
+
+ Metrics::iterator i;
+ if (insert_meter) {
+ for (i = metrics.begin(); i != metrics.end(); ++i) {
+ MeterSection* const meter = dynamic_cast<MeterSection*> (*i);
+
+ if (meter && meter->beat() > insert_meter->beat()) {
+ break;
+ }
}
-
- metrics->insert (i, section);
- break;
- }
+ } else if (insert_tempo) {
+ for (i = metrics.begin(); i != metrics.end(); ++i) {
+ TempoSection* const tempo = dynamic_cast<TempoSection*> (*i);
- if (i == metrics->end()) {
- metrics->insert (metrics->end(), section);
+ if (tempo) {
+ if (tempo->beat() > insert_tempo->beat()) {
+ break;
+ }
+ }
+ }
}
- }
- recompute_map (reassign_tempo_bbt);
+ metrics.insert (i, section);
+ }
}
void
-TempoMap::replace_tempo (const TempoSection& ts, const Tempo& tempo, const BBT_Time& where)
+TempoMap::replace_tempo (const TempoSection& ts, const Tempo& tempo, const double& where, TempoSection::Type type)
{
- 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.beat() != first.beat()) {
+ remove_tempo_locked (ts);
+ add_tempo_locked (tempo, where, true, type);
+ } else {
+ first.set_type (type);
+ {
+ /* cannot move the first tempo section */
+ *static_cast<Tempo*>(&first) = tempo;
+ recompute_map (false);
+ }
+ }
}
PropertyChanged (PropertyChange ());
}
void
-TempoMap::add_tempo (const Tempo& tempo, BBT_Time where)
+TempoMap::gui_set_tempo_frame (TempoSection& ts, framepos_t frame, double beat_where)
{
{
- Glib::RWLock::WriterLock lm (lock);
+ Glib::Threads::RWLock::WriterLock lm (lock);
+
+ /* currently this is always done in audio time */
+ //if (ts.position_lock_style() == MusicTime) {
+ if (0) {
+ /* MusicTime */
+ ts.set_beat (beat_where);
+ MetricSectionSorter cmp;
+ metrics.sort (cmp);
+ } else {
+ /*AudioTime*/
+ ts.set_frame (frame);
- /* new tempos always start on a beat */
- where.ticks = 0;
+ MetricSectionFrameSorter fcmp;
+ metrics.sort (fcmp);
- 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.
- */
+ Metrics::const_iterator i;
+ TempoSection* prev_ts = 0;
+ TempoSection* next_ts = 0;
- 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;
+ for (i = metrics.begin(); i != metrics.end(); ++i) {
+ TempoSection* t;
+ if ((t = dynamic_cast<TempoSection*> (*i)) != 0) {
+
+ if (t->frame() >= frame) {
+ break;
+ }
+
+ prev_ts = t;
+ }
}
- }
- ts->update_bar_offset_from_bbt (*meter);
+ for (i = metrics.begin(); i != metrics.end(); ++i) {
+ TempoSection* t;
+ if ((t = dynamic_cast<TempoSection*> (*i)) != 0) {
+
+ if (t->frame() > frame) {
+ next_ts = t;
+ break;
+ }
+ }
+ }
- /* and insert it */
+ if (prev_ts) {
+ /* set the start beat */
+ double beats_to_ts = prev_ts->beat_at_frame (frame - prev_ts->frame(), ts.beats_per_minute(), frame - prev_ts->frame(), _frame_rate);
+ double beats = beats_to_ts + prev_ts->beat();
+
+ if (next_ts) {
+ if (next_ts->beat() < beats) {
+ /* with frame-based editing, it is possible to get in a
+ situation where if the tempo was placed at the mouse pointer frame,
+ the following music-based tempo would jump to an earlier frame,
+ changing the beat beat of the moved tempo.
+ in this case, we have to do some beat-based comparison TODO
+ */
+ } else if (prev_ts->beat() > beats) {
+ ts.set_beat (prev_ts->beat());
+ } else {
+ ts.set_beat (beats);
+ }
+ } else {
+ ts.set_beat (beats);
+ }
+ MetricSectionSorter cmp;
+ metrics.sort (cmp);
+ }
+ }
- do_insert (ts);
+ recompute_map (false);
}
- PropertyChanged (PropertyChange ());
+ MetricPositionChanged (); // Emit Signal
}
void
-TempoMap::replace_meter (const MeterSection& ms, const Meter& meter, const BBT_Time& where)
+TempoMap::add_tempo (const Tempo& tempo, double where, ARDOUR::TempoSection::Type type)
{
- const MeterSection& first (first_meter());
-
- if (ms != first) {
- remove_meter (ms, false);
- add_meter (meter, where);
- } else {
- Glib::RWLock::WriterLock lm (lock);
- /* cannot move the first meter section */
- *((Meter*)&first) = meter;
- recompute_map (true);
+ {
+ Glib::Threads::RWLock::WriterLock lm (lock);
+ add_tempo_locked (tempo, where, true, type);
}
+
PropertyChanged (PropertyChange ());
}
void
-TempoMap::add_meter (const Meter& meter, BBT_Time where)
+TempoMap::add_tempo_locked (const Tempo& tempo, double where, bool recompute, ARDOUR::TempoSection::Type type)
{
- {
- Glib::RWLock::WriterLock lm (lock);
+ TempoSection* ts = new TempoSection (where, tempo.beats_per_minute(), tempo.note_type(), type);
- /* 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.
+ do_insert (ts);
- */
+ if (recompute) {
+ recompute_map (false);
+ }
+}
- if (where.beats != 1) {
- where.beats = 1;
- where.bars++;
+void
+TempoMap::replace_meter (const MeterSection& ms, const Meter& meter, const BBT_Time& where)
+{
+ {
+ Glib::Threads::RWLock::WriterLock lm (lock);
+ MeterSection& first (first_meter());
+ if (ms.beat() != first.beat()) {
+ remove_meter_locked (ms);
+ add_meter_locked (meter, bbt_to_beats_unlocked (where), where, true);
+ } else {
+ /* cannot move the first meter section */
+ *static_cast<Meter*>(&first) = meter;
+ recompute_map (true);
}
+ }
- /* new meters *always* start on a beat. */
- where.ticks = 0;
+ PropertyChanged (PropertyChange ());
+}
- do_insert (new MeterSection (where, meter.divisions_per_bar(), meter.note_divisor()));
+void
+TempoMap::add_meter (const Meter& meter, double beat, BBT_Time where)
+{
+ {
+ Glib::Threads::RWLock::WriterLock lm (lock);
+ add_meter_locked (meter, beat, where, true);
}
+
#ifndef NDEBUG
if (DEBUG_ENABLED(DEBUG::TempoMap)) {
dump (std::cerr);
PropertyChanged (PropertyChange ());
}
+void
+TempoMap::add_meter_locked (const Meter& meter, double beat, 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 (beat, where, meter.divisions_per_bar(), meter.note_divisor()));
+
+ if (recompute) {
+ recompute_map (true);
+ }
+
+}
+
void
TempoMap::change_initial_tempo (double beats_per_minute, double note_type)
{
Tempo newtempo (beats_per_minute, note_type);
TempoSection* t;
- for (Metrics::iterator i = metrics->begin(); i != metrics->end(); ++i) {
+ for (Metrics::iterator i = metrics.begin(); i != metrics.end(); ++i) {
if ((t = dynamic_cast<TempoSection*> (*i)) != 0) {
{
- Glib::RWLock::WriterLock lm (lock);
+ Glib::Threads::RWLock::WriterLock lm (lock);
*((Tempo*) t) = newtempo;
recompute_map (false);
}
/* 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 */
{
- Glib::RWLock::WriterLock lm (lock);
+ Glib::Threads::RWLock::WriterLock lm (lock);
/* cannot move the first tempo section */
*((Tempo*)prev) = newtempo;
recompute_map (false);
{
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;
}
}
fatal << _("programming error: no tempo section in tempo map!") << endmsg;
- /*NOTREACHED*/
+ abort(); /*NOTREACHED*/
return *m;
}
-const TempoSection&
-TempoMap::first_tempo () const
+MeterSection&
+TempoMap::first_meter ()
{
- const TempoSection *t = 0;
+ MeterSection *m = 0;
- for (Metrics::const_iterator i = metrics->begin(); i != metrics->end(); ++i) {
- if ((t = dynamic_cast<const TempoSection *> (*i)) != 0) {
- return *t;
+ /* CALLER MUST HOLD LOCK */
+
+ for (Metrics::iterator i = metrics.begin(); i != metrics.end(); ++i) {
+ if ((m = dynamic_cast<MeterSection *> (*i)) != 0) {
+ return *m;
}
}
fatal << _("programming error: no tempo section in tempo map!") << endmsg;
- /*NOTREACHED*/
- return *t;
+ abort(); /*NOTREACHED*/
+ return *m;
}
-void
-TempoMap::timestamp_metrics_from_audio_time ()
+const TempoSection&
+TempoMap::first_tempo () const
{
- 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_unlocked ((*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_bar_division/2) {
- /* round up to next beat */
- bbt.beats += 1;
- }
-
- bbt.ticks = 0;
-
- if (bbt.beats != 1) {
- /* round up to next bar */
- bbt.bars += 1;
- bbt.beats = 1;
- }
- }
+ const TempoSection *t = 0;
- // cerr << bbt << endl;
+ /* CALLER MUST HOLD LOCK */
- (*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*/
+ for (Metrics::const_iterator i = metrics.begin(); i != metrics.end(); ++i) {
+ if ((t = dynamic_cast<const TempoSection *> (*i)) != 0) {
+ return *t;
}
-
- prev = (*i);
- }
-
-#ifndef NDEBUG
- if (DEBUG_ENABLED(DEBUG::TempoMap)) {
- dump (cerr);
}
-#endif
+ fatal << _("programming error: no tempo section in tempo map!") << endmsg;
+ abort(); /*NOTREACHED*/
+ return *t;
}
-void
-TempoMap::require_map_to (framepos_t pos)
+TempoSection&
+TempoMap::first_tempo ()
{
- if (_map.empty() || _map.back().frame < pos) {
- recompute_map (false, pos);
- }
-}
+ TempoSection *t = 0;
-void
-TempoMap::require_map_to (const BBT_Time& bbt)
-{
- if (_map.empty() || _map.back().bbt() < bbt) {
- recompute_map (false, 99);
+ for (Metrics::const_iterator i = metrics.begin(); i != metrics.end(); ++i) {
+ if ((t = dynamic_cast<TempoSection *> (*i)) != 0) {
+ return *t;
+ }
}
+
+ fatal << _("programming error: no tempo section in tempo map!") << endmsg;
+ abort(); /*NOTREACHED*/
+ return *t;
}
void
TempoMap::recompute_map (bool reassign_tempo_bbt, framepos_t end)
{
- MeterSection* meter;
- TempoSection* tempo;
- TempoSection* ts;
- MeterSection* ms;
- double divisions_per_bar;
- double beat_frames;
- double current_frame;
- BBT_Time current;
- Metrics::iterator next_metric;
+ /* CALLER MUST HOLD WRITE LOCK */
if (end < 0) {
- if (_map.empty()) {
- /* compute 1 mins worth */
- end = _frame_rate * 60;
- } else {
- end = _map.back().frame;
- }
- }
- DEBUG_TRACE (DEBUG::TempoMath, string_compose ("recomputing tempo map, zero to %1\n", end));
-
- _map.clear ();
-
- for (Metrics::iterator i = metrics->begin(); i != metrics->end(); ++i) {
- if ((ms = dynamic_cast<MeterSection *> (*i)) != 0) {
- meter = ms;
- break;
- }
- }
+ /* we will actually stop once we hit
+ the last metric.
+ */
+ end = max_framepos;
- for (Metrics::iterator i = metrics->begin(); i != metrics->end(); ++i) {
- if ((ts = dynamic_cast<TempoSection *> (*i)) != 0) {
- tempo = ts;
- break;
- }
}
- /* assumes that the first meter & tempo are at frame zero */
- current_frame = 0;
- meter->set_frame (0);
- tempo->set_frame (0);
-
- /* assumes that the first meter & tempo are at 1|1|0 */
- current.bars = 1;
- current.beats = 1;
- current.ticks = 0;
-
- divisions_per_bar = meter->divisions_per_bar ();
- beat_frames = meter->frames_per_division (*tempo,_frame_rate);
-
- if (reassign_tempo_bbt) {
-
- MeterSection* rmeter = meter;
-
- DEBUG_TRACE (DEBUG::TempoMath, "\tUpdating tempo marks BBT time from bar offset\n");
-
- for (Metrics::iterator i = metrics->begin(); i != metrics->end(); ++i) {
-
- if ((ts = dynamic_cast<TempoSection*>(*i)) != 0) {
-
- /* reassign the BBT time of this tempo section
- * based on its bar offset position.
- */
-
- ts->update_bbt_time_from_bar_offset (*rmeter);
+ DEBUG_TRACE (DEBUG::TempoMath, string_compose ("recomputing tempo map, zero to %1\n", end));
- } else if ((ms = dynamic_cast<MeterSection*>(*i)) != 0) {
- rmeter = ms;
- } else {
- fatal << _("programming error: unhandled MetricSection type") << endmsg;
- /*NOTREACHED*/
- }
- }
+ if (end == 0) {
+ /* silly call from Session::process() during startup
+ */
+ return;
}
- DEBUG_TRACE (DEBUG::TempoMath, string_compose ("start with meter = %1 tempo = %2 dpb %3 fpb %4\n",
- *((Meter*)meter), *((Tempo*)tempo), divisions_per_bar, beat_frames));
-
- next_metric = metrics->begin();
- ++next_metric; // skip meter (or tempo)
- ++next_metric; // skip tempo (or meter)
-
- DEBUG_TRACE (DEBUG::TempoMath, string_compose ("Add first bar at 1|1 @ %2\n", current.bars, current_frame));
- _map.push_back (BBTPoint (*meter, *tempo,(framepos_t) llrint(current_frame), 1, 1));
-
- while (current_frame < end) {
-
- current.beats++;
- current_frame += beat_frames;
-
- if (current.beats > meter->divisions_per_bar()) {
- current.bars++;
- current.beats = 1;
- }
-
- if (next_metric != metrics->end()) {
-
- /* no operator >= so invert operator < */
+ Metrics::const_iterator i;
- DEBUG_TRACE (DEBUG::TempoMath, string_compose ("now at %1 next metric @ %2\n", current, (*next_metric)->start()));
+ TempoSection* prev_ts = 0;
- if (!(current < (*next_metric)->start())) {
+ for (i = metrics.begin(); i != metrics.end(); ++i) {
+ TempoSection* t;
- set_metrics:
- if (((ts = dynamic_cast<TempoSection*> (*next_metric)) != 0)) {
+ if ((t = dynamic_cast<TempoSection*> (*i)) != 0) {
- tempo = ts;
+ if (prev_ts) {
+ double const beats_relative_to_prev_ts = t->beat() - prev_ts->beat();
+ double const ticks_relative_to_prev_ts = beats_relative_to_prev_ts * BBT_Time::ticks_per_beat;
- /* 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.
- */
+ /* assume (falsely) that the target tempo is constant */
+ double const t_fpb = t->frames_per_beat (_frame_rate);
+ double const av_fpb = (prev_ts->frames_per_beat (_frame_rate) + t_fpb) / 2.0;
+ /* this walk shouldn't be needed as given c, time a = log (Ta / T0) / c. what to do? */
+ double length_estimate = beats_relative_to_prev_ts * av_fpb;
- if (tempo->start().ticks != 0) {
-
- double next_beat_frames = meter->frames_per_division (*tempo,_frame_rate);
-
- DEBUG_TRACE (DEBUG::TempoMath, string_compose ("bumped into non-beat-aligned tempo metric at %1 = %2, adjust next beat using %3\n",
- tempo->start(), current_frame, tempo->bar_offset()));
-
- /* back up to previous beat */
- current_frame -= beat_frames;
- /* set tempo section location based on offset from last beat */
- tempo->set_frame (current_frame + (ts->bar_offset() * beat_frames));
- /* advance to the location of the new (adjusted) beat */
- current_frame += (ts->bar_offset() * beat_frames) + ((1.0 - ts->bar_offset()) * next_beat_frames);
- /* 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);
- }
+ if (prev_ts->type() == TempoSection::Type::Constant) {
+ length_estimate = beats_relative_to_prev_ts * prev_ts->frames_per_beat (_frame_rate);
+ }
- } else if ((ms = dynamic_cast<MeterSection*>(*next_metric)) != 0) {
-
- meter = ms;
+ double const system_precision_at_target_tempo = (_frame_rate / t->ticks_per_minute()) * 1.5;
+ double tick_error = system_precision_at_target_tempo + 1.0; // sorry for the wtf
- /* 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);
+ while (fabs (tick_error) > system_precision_at_target_tempo) {
- meter->set_frame (current_frame);
- }
-
- divisions_per_bar = meter->divisions_per_bar ();
- beat_frames = meter->frames_per_division (*tempo, _frame_rate);
-
- DEBUG_TRACE (DEBUG::TempoMath, string_compose ("New metric with beat frames = %1 dpb %2 meter %3 tempo %4\n",
- beat_frames, divisions_per_bar, *((Meter*)meter), *((Tempo*)tempo)));
-
- ++next_metric;
-
- if (next_metric != metrics->end() && ((*next_metric)->start() == current)) {
- /* same position so go back and set this one up before advancing
- */
- goto set_metrics;
+ double const actual_ticks = prev_ts->tick_at_frame (length_estimate, t->beats_per_minute(),
+ (framepos_t) length_estimate, _frame_rate);
+ tick_error = ticks_relative_to_prev_ts - actual_ticks;
+ length_estimate += tick_error * (t->ticks_per_minute() / _frame_rate);
}
+
+ t->set_frame (length_estimate + prev_ts->frame());
}
+ prev_ts = t;
}
+ }
- 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));
- } 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));
+ Metrics::const_iterator mi;
+ MeterSection* meter = 0;
+
+ for (mi = metrics.begin(); mi != metrics.end(); ++mi) {
+ /* we can do this beacuse we have the tempo section frames set */
+ if ((meter = dynamic_cast<MeterSection*> (*mi)) != 0) {
+ meter->set_frame (frame_at_tick (meter->beat() * BBT_Time::ticks_per_beat));
}
}
}
+
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
now see if we can find better candidates.
*/
- for (Metrics::const_iterator i = metrics->begin(); i != metrics->end(); ++i) {
-
- // cerr << "Looking at a metric section " << **i << endl;
+ for (Metrics::const_iterator i = metrics.begin(); i != metrics.end(); ++i) {
if ((*i)->frame() > frame) {
break;
}
- if ((tempo = dynamic_cast<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;
+ }
}
-
- // cerr << "for framepos " << frame << " returning " << m.meter() << " @ " << m.tempo() << " location " << m.frame() << " = " << m.start() << endl;
+
return m;
}
-
+/* XX meters only */
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
now see if we can find better candidates.
*/
- for (Metrics::const_iterator i = metrics->begin(); i != metrics->end(); ++i) {
-
- BBT_Time section_start ((*i)->start());
+ for (Metrics::const_iterator i = metrics.begin(); i != metrics.end(); ++i) {
+ MeterSection* mw;
+ if ((mw = dynamic_cast<MeterSection*> (*i)) != 0) {
+ BBT_Time section_start (mw->bbt());
- if (section_start.bars > bbt.bars || (section_start.bars == bbt.bars && section_start.beats > bbt.beats)) {
- break;
- }
+ if (section_start.bars > bbt.bars || (section_start.bars == bbt.bars && section_start.beats > bbt.beats)) {
+ 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 (section_start);
}
return m;
void
TempoMap::bbt_time (framepos_t frame, BBT_Time& bbt)
{
- {
- Glib::RWLock::ReaderLock lm (lock);
- BBTPointList::const_iterator i = bbt_before_or_at (frame);
- bbt_time_unlocked (frame, bbt, i);
+ Glib::Threads::RWLock::ReaderLock lm (lock);
+
+ if (frame < 0) {
+ bbt.bars = 1;
+ bbt.beats = 1;
+ bbt.ticks = 0;
+ warning << string_compose (_("tempo map asked for BBT time at frame %1\n"), frame) << endmsg;
+ return;
}
+ bbt = beats_to_bbt_unlocked (beat_at_frame (frame));
}
-void
-TempoMap::bbt_time_unlocked (framepos_t frame, BBT_Time& bbt, const BBTPointList::const_iterator& i)
+double
+TempoMap::bbt_to_beats (Timecode::BBT_Time bbt)
{
- bbt.bars = (*i).bar;
- bbt.beats = (*i).beat;
-
- if ((*i).frame == frame) {
- bbt.ticks = 0;
- } else {
- bbt.ticks = llrint (((frame - (*i).frame) / (*i).meter->frames_per_division(*((*i).tempo), _frame_rate)) *
- BBT_Time::ticks_per_bar_division);
- }
+ Glib::Threads::RWLock::ReaderLock lm (lock);
+ return bbt_to_beats_unlocked (bbt);
}
-framepos_t
-TempoMap::frame_time (const BBT_Time& bbt)
+double
+TempoMap::bbt_to_beats_unlocked (Timecode::BBT_Time bbt)
{
- Glib::RWLock::ReaderLock lm (lock);
+ /* CALLER HOLDS READ LOCK */
- BBTPointList::const_iterator s = bbt_point_for (BBT_Time (1, 1, 0));
- BBTPointList::const_iterator e = bbt_point_for (BBT_Time (bbt.bars, bbt.beats, 0));
+ double accumulated_beats = 0.0;
+ double accumulated_bars = 0.0;
+ MeterSection* prev_ms = 0;
- if (bbt.ticks != 0) {
- return ((*e).frame - (*s).frame) +
- llrint ((*e).meter->frames_per_division (*(*e).tempo, _frame_rate) * (bbt.ticks/BBT_Time::ticks_per_bar_division));
- } else {
- return ((*e).frame - (*s).frame);
+ Metrics::const_iterator i;
+
+ for (i = metrics.begin(); i != metrics.end(); ++i) {
+ MeterSection* m;
+ if ((m = dynamic_cast<MeterSection*> (*i)) != 0) {
+ double bars_to_m = 0.0;
+ if (prev_ms) {
+ bars_to_m = (m->beat() - prev_ms->beat()) / prev_ms->divisions_per_bar();
+ }
+ if ((bars_to_m + accumulated_bars) > (bbt.bars - 1)) {
+ break;
+ }
+ if (prev_ms) {
+ accumulated_beats += m->beat() - prev_ms->beat();
+ accumulated_bars += bars_to_m;
+ }
+ prev_ms = m;
+ }
}
+
+ double const remaining_bars = (bbt.bars - 1) - accumulated_bars;
+ double const remaining_bars_in_beats = remaining_bars * prev_ms->divisions_per_bar();
+ double const ret = remaining_bars_in_beats + accumulated_beats + (bbt.beats - 1) + (bbt.ticks / BBT_Time::ticks_per_beat);
+ return ret;
}
-framecnt_t
-TempoMap::bbt_duration_at (framepos_t pos, const BBT_Time& bbt, int dir)
+Timecode::BBT_Time
+TempoMap::beats_to_bbt (double beats)
{
- 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 beats_to_bbt_unlocked (beats);
}
-framecnt_t
-TempoMap::bbt_duration_at_unlocked (const BBT_Time& when, const BBT_Time& bbt, int dir)
+Timecode::BBT_Time
+TempoMap::beats_to_bbt_unlocked (double beats)
{
- if (bbt.bars == 0 && bbt.beats == 0 && bbt.ticks == 0) {
- return 0;
- }
+ /* CALLER HOLDS READ LOCK */
- /* round back to the previous precise beat */
- BBTPointList::const_iterator wi = bbt_point_for (BBT_Time (when.bars, when.beats, 0));
- BBTPointList::const_iterator start (wi);
- double tick_frames = 0;
+ MeterSection* prev_ms = 0;
+ uint32_t accumulated_bars = 0;
- assert (wi != _map.end());
+ Metrics::const_iterator i;
- /* compute how much rounding we did because of non-zero ticks */
+ for (i = metrics.begin(); i != metrics.end(); ++i) {
+ MeterSection* m = 0;
- if (when.ticks != 0) {
- tick_frames = (*wi).meter->frames_per_division (*(*wi).tempo, _frame_rate) * (when.ticks/BBT_Time::ticks_per_bar_division);
- }
-
- uint32_t bars = 0;
- uint32_t beats = 0;
+ if ((m = dynamic_cast<MeterSection*> (*i)) != 0) {
+
+ if (beats < m->beat()) {
+ /* this is the meter after the one our beat is on*/
+ break;
+ }
+
+ if (prev_ms) {
+ /* we need a whole number of bars. */
+ accumulated_bars += ((m->beat() - prev_ms->beat()) + 1) / prev_ms->divisions_per_bar();
+ }
- while (wi != _map.end() && bars < bbt.bars) {
- ++wi;
- if ((*wi).is_bar()) {
- ++bars;
+ prev_ms = m;
}
}
- assert (wi != _map.end());
- while (wi != _map.end() && beats < bbt.beats) {
- ++wi;
- ++beats;
- }
- assert (wi != _map.end());
+ double const beats_in_ms = beats - prev_ms->beat();
+ uint32_t const bars_in_ms = (uint32_t) floor (beats_in_ms / prev_ms->divisions_per_bar());
+ uint32_t const total_bars = bars_in_ms + accumulated_bars;
+ double const remaining_beats = beats_in_ms - (bars_in_ms * prev_ms->divisions_per_bar());
+ double const remaining_ticks = (remaining_beats - floor (remaining_beats)) * BBT_Time::ticks_per_beat;
- /* add any additional frames related to ticks in the added value */
+ BBT_Time ret;
- if (bbt.ticks != 0) {
- tick_frames += (*wi).meter->frames_per_division (*(*wi).tempo, _frame_rate) * (bbt.ticks/BBT_Time::ticks_per_bar_division);
- }
+ ret.ticks = (uint32_t) floor (remaining_ticks + 0.5);
+ ret.beats = (uint32_t) floor (remaining_beats);
+ ret.bars = total_bars;
- return ((*wi).frame - (*start).frame) + llrint (tick_frames);
-}
+ /* 0 0 0 to 1 1 0 - based mapping*/
+ ++ret.bars;
+ ++ret.beats;
-framepos_t
-TempoMap::round_to_bar (framepos_t fr, int dir)
-{
- {
- Glib::RWLock::ReaderLock lm (lock);
- return round_to_type (fr, dir, Bar);
+ if (ret.ticks >= BBT_Time::ticks_per_beat) {
+ ++ret.beats;
+ ret.ticks -= BBT_Time::ticks_per_beat;
}
-}
-framepos_t
-TempoMap::round_to_beat (framepos_t fr, int dir)
-{
- {
- Glib::RWLock::ReaderLock lm (lock);
- return round_to_type (fr, dir, Beat);
+ if (ret.beats > prev_ms->divisions_per_bar()) {
+ ++ret.bars;
+ ret.beats = 1;
}
+
+ return ret;
}
-framepos_t
-TempoMap::round_to_beat_subdivision (framepos_t fr, int sub_num, int dir)
+double
+TempoMap::tick_at_frame (framecnt_t frame) const
{
- Glib::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;
+ /* HOLD (at least) THE READER LOCK */
- bbt_time_unlocked (fr, the_beat, i);
-
- DEBUG_TRACE (DEBUG::SnapBBT, string_compose ("round %1 to nearest 1/%2 beat, before-or-at = %3 @ %4|%5 precise = %6\n",
- fr, sub_num, (*i).frame, (*i).bar, (*i).beat, the_beat));
+ Metrics::const_iterator i;
+ TempoSection* prev_ts = 0;
+ double accumulated_ticks = 0.0;
- ticks_one_subdivisions_worth = (uint32_t)BBT_Time::ticks_per_bar_division / sub_num;
+ for (i = metrics.begin(); i != metrics.end(); ++i) {
+ TempoSection* t;
- if (dir > 0) {
+ if ((t = dynamic_cast<TempoSection*> (*i)) != 0) {
- /* round to next (even if we're on a subdivision */
+ if ((prev_ts) && frame < t->frame()) {
+ /*the previous ts is the one containing the frame */
- uint32_t mod = the_beat.ticks % ticks_one_subdivisions_worth;
+ framepos_t const time = frame - prev_ts->frame();
+ framepos_t const last_frame = t->frame() - prev_ts->frame();
+ double const last_beats_per_minute = t->beats_per_minute();
- if (mod == 0) {
- /* right on the subdivision, so the difference is just the subdivision ticks */
- the_beat.ticks += ticks_one_subdivisions_worth;
+ return prev_ts->tick_at_frame (time, last_beats_per_minute, last_frame, _frame_rate) + accumulated_ticks;
+ }
- } else {
- /* not on subdivision, compute distance to next subdivision */
+ if (prev_ts && t->frame() > prev_ts->frame()) {
+ accumulated_ticks = t->beat() * BBT_Time::ticks_per_beat;
+ }
- the_beat.ticks += ticks_one_subdivisions_worth - mod;
+ prev_ts = t;
}
+ }
- if (the_beat.ticks > BBT_Time::ticks_per_bar_division) {
- assert (i != _map.end());
- ++i;
- assert (i != _map.end());
- the_beat.ticks -= BBT_Time::ticks_per_bar_division;
- }
+ /* treated as constant for this ts */
+ framecnt_t const frames_in_section = frame - prev_ts->frame();
+ double const ticks_in_section = (frames_in_section / prev_ts->frames_per_beat (_frame_rate)) * Timecode::BBT_Time::ticks_per_beat;
+ return ticks_in_section + accumulated_ticks;
- } else if (dir < 0) {
+}
- /* round to previous (even if we're on a subdivision) */
+framecnt_t
+TempoMap::frame_at_tick (double tick) const
+{
+ /* HOLD THE READER LOCK */
- uint32_t mod = the_beat.ticks % ticks_one_subdivisions_worth;
+ double accumulated_ticks = 0.0;
+ double accumulated_ticks_to_prev = 0.0;
+ const TempoSection* prev_ts = 0;
- if (mod == 0) {
- /* right on the subdivision, 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.
- */
+ Metrics::const_iterator i;
- difference = mod;
- }
+ for (i = metrics.begin(); i != metrics.end(); ++i) {
+ TempoSection* t;
+ if ((t = dynamic_cast<TempoSection*> (*i)) != 0) {
- if (the_beat.ticks < difference) {
- if (i == _map.begin()) {
- /* can't go backwards from wherever pos is, so just return it */
- return fr;
+ if (prev_ts && t->frame() > prev_ts->frame()) {
+ accumulated_ticks = t->beat() * BBT_Time::ticks_per_beat;
}
- --i;
- the_beat.ticks = BBT_Time::ticks_per_bar_division - the_beat.ticks;
- } else {
- the_beat.ticks -= difference;
- }
- } else {
- /* round to nearest */
+ if (prev_ts && tick < accumulated_ticks) {
+ /* prev_ts is the one affecting us. */
- double rem;
+ double const ticks_in_section = tick - accumulated_ticks_to_prev;
+ framepos_t const last_time = t->frame() - prev_ts->frame();
+ double const last_beats_per_minute = t->beats_per_minute();
- /* 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 */
+ return prev_ts->frame_at_tick (ticks_in_section, last_beats_per_minute, last_time, _frame_rate) + prev_ts->frame();
+ }
+ accumulated_ticks_to_prev = accumulated_ticks;
+ prev_ts = t;
+ }
+ }
+ /* must be treated as constant, irrespective of _type */
+ double const ticks_in_section = tick - accumulated_ticks_to_prev;
+ double const dtime = (ticks_in_section / BBT_Time::ticks_per_beat) * prev_ts->frames_per_beat (_frame_rate);
- the_beat.ticks = lrint (the_beat.ticks + (ticks_one_subdivisions_worth - rem));
+ framecnt_t const ret = ((framecnt_t) floor (dtime)) + prev_ts->frame();
- DEBUG_TRACE (DEBUG::SnapBBT, string_compose ("moved forward to %1\n", the_beat.ticks));
+ return ret;
+}
- if (the_beat.ticks > BBT_Time::ticks_per_bar_division) {
- assert (i != _map.end());
- ++i;
- assert (i != _map.end());
- the_beat.ticks -= BBT_Time::ticks_per_bar_division;
- DEBUG_TRACE (DEBUG::SnapBBT, string_compose ("fold beat to %1\n", the_beat));
- }
+double
+TempoMap::beat_at_frame (framecnt_t frame) const
+{
+ Glib::Threads::RWLock::ReaderLock lm (lock);
- } else if (rem > 0) {
-
- /* closer to previous subdivision, so shift backward */
+ return tick_at_frame (frame) / BBT_Time::ticks_per_beat;
+}
- 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_bar_division - rem);
- DEBUG_TRACE (DEBUG::SnapBBT, string_compose ("step back beat to %1\n", the_beat));
- } else {
- the_beat.ticks = lrint (the_beat.ticks - rem);
- DEBUG_TRACE (DEBUG::SnapBBT, string_compose ("moved backward to %1\n", the_beat.ticks));
- }
- } else {
- /* on the subdivision, do nothing */
- }
- }
+framecnt_t
+TempoMap::frame_at_beat (double beat) const
+{
+ Glib::Threads::RWLock::ReaderLock lm (lock);
- return (*i).frame + (the_beat.ticks/BBT_Time::ticks_per_bar_division) *
- (*i).meter->frames_per_division (*((*i).tempo), _frame_rate);
+ return frame_at_tick (beat * BBT_Time::ticks_per_beat);
}
framepos_t
-TempoMap::round_to_type (framepos_t frame, int dir, BBTPointType type)
+TempoMap::frame_time (const BBT_Time& bbt)
{
- BBTPointList::const_iterator fi;
+ if (bbt.bars < 1) {
+ warning << string_compose (_("tempo map asked for frame time at bar < 1 (%1)\n"), bbt) << endmsg;
+ return 0;
+ }
- if (dir > 0) {
- fi = bbt_after_or_at (frame);
- } else {
- fi = bbt_before_or_at (frame);
+ if (bbt.beats < 1) {
+ throw std::logic_error ("beats are counted from one");
}
+ Glib::Threads::RWLock::ReaderLock lm (lock);
- assert (fi != _map.end());
+ framepos_t const ret = frame_at_beat (bbt_to_beats_unlocked (bbt));
- DEBUG_TRACE(DEBUG::SnapBBT, string_compose ("round from %1 (%3|%4 @ %5) to bars in direction %2\n", frame, dir, (*fi).bar, (*fi).beat, (*fi).frame));
-
- switch (type) {
- case Bar:
- if (dir < 0) {
- /* find bar previous to 'frame' */
+ return ret;
+}
- 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;
+framecnt_t
+TempoMap::bbt_duration_at (framepos_t pos, const BBT_Time& bbt, int dir)
+{
- } else if (dir > 0) {
+ Glib::Threads::RWLock::ReaderLock lm (lock);
- /* find bar following 'frame' */
+ Metrics::const_iterator i;
+ TempoSection* first = 0;
+ TempoSection* second = 0;
- if ((*fi).is_bar() && (*fi).frame == frame) {
- ++fi;
- }
+ for (i = metrics.begin(); i != metrics.end(); ++i) {
+ TempoSection* t;
- while (!(*fi).is_bar()) {
- fi++;
- if (fi == _map.end()) {
- --fi;
- break;
- }
+ if ((t = dynamic_cast<TempoSection*> (*i)) != 0) {
+
+ if ((*i)->frame() > pos) {
+ second = t;
+ 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;
+ first = t;
+ }
+ }
+ if (first && second) {
+ framepos_t const last_time = second->frame() - first->frame();
+ double const last_beats_per_minute = second->beats_per_minute();
+
+ framepos_t const time = pos - first->frame();
+ double const tick_at_time = first->tick_at_frame (time, last_beats_per_minute, last_time, _frame_rate);
+ double const bbt_ticks = bbt.ticks + (bbt.beats * BBT_Time::ticks_per_beat);
+
+ double const time_at_bbt = first->frame_at_tick (tick_at_time + bbt_ticks, last_beats_per_minute, last_time, _frame_rate);
+
+ return time_at_bbt - time;
+ }
+
+ double const ticks = bbt.ticks + (bbt.beats * BBT_Time::ticks_per_beat);
+ return (framecnt_t) floor ((ticks / BBT_Time::ticks_per_beat) * first->frames_per_beat(_frame_rate));
+}
+
+framepos_t
+TempoMap::round_to_bar (framepos_t fr, RoundMode dir)
+{
+ return round_to_type (fr, dir, Bar);
+}
+
+framepos_t
+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, RoundMode dir)
+{
+ Glib::Threads::RWLock::ReaderLock lm (lock);
+
+ uint32_t ticks = (uint32_t) floor (tick_at_frame (fr) + 0.5);
+ uint32_t beats = (uint32_t) floor (ticks / BBT_Time::ticks_per_beat);
+ uint32_t ticks_one_subdivisions_worth = (uint32_t)BBT_Time::ticks_per_beat / sub_num;
+
+ ticks -= beats * BBT_Time::ticks_per_beat;
+
+ if (dir > 0) {
+ /* round to next (or same iff dir == RoundUpMaybe) */
+
+ uint32_t mod = ticks % ticks_one_subdivisions_worth;
+
+ 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 */
+ ticks += ticks_one_subdivisions_worth;
} else {
-
- /* true rounding: find nearest bar */
+ /* not on subdivision, compute distance to next subdivision */
- BBTPointList::const_iterator prev = fi;
- BBTPointList::const_iterator next = fi;
+ ticks += ticks_one_subdivisions_worth - mod;
+ }
- if ((*fi).frame == frame) {
- return frame;
- }
+ if (ticks >= BBT_Time::ticks_per_beat) {
+ ticks -= BBT_Time::ticks_per_beat;
+ }
+ } else if (dir < 0) {
- while ((*prev).beat != 1) {
- if (prev == _map.begin()) {
- break;
- }
- prev--;
+ /* round to previous (or same iff dir == RoundDownMaybe) */
+
+ uint32_t difference = ticks % ticks_one_subdivisions_worth;
+
+ 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;
+ }
+
+ if (ticks < difference) {
+ ticks = BBT_Time::ticks_per_beat - ticks;
+ } else {
+ ticks -= difference;
+ }
+
+ } else {
+ /* round to nearest */
+ double rem;
+
+ /* compute the distance to the previous and next subdivision */
+
+ if ((rem = fmod ((double) ticks, (double) ticks_one_subdivisions_worth)) > ticks_one_subdivisions_worth/2.0) {
+
+ /* closer to the next subdivision, so shift forward */
+
+ ticks = lrint (ticks + (ticks_one_subdivisions_worth - rem));
+
+ DEBUG_TRACE (DEBUG::SnapBBT, string_compose ("moved forward to %1\n", ticks));
+
+ if (ticks > BBT_Time::ticks_per_beat) {
+ ++beats;
+ ticks -= BBT_Time::ticks_per_beat;
+ DEBUG_TRACE (DEBUG::SnapBBT, string_compose ("fold beat to %1\n", beats));
}
- while ((*next).beat != 1) {
- next++;
- if (next == _map.end()) {
- --next;
- break;
+ } else if (rem > 0) {
+
+ /* closer to previous subdivision, so shift backward */
+
+ if (rem > ticks) {
+ if (beats == 0) {
+ /* can't go backwards past zero, so ... */
+ return 0;
}
+ /* step back to previous beat */
+ --beats;
+ ticks = lrint (BBT_Time::ticks_per_beat - rem);
+ DEBUG_TRACE (DEBUG::SnapBBT, string_compose ("step back beat to %1\n", beats));
+ } else {
+ ticks = lrint (ticks - rem);
+ DEBUG_TRACE (DEBUG::SnapBBT, string_compose ("moved backward to %1\n", ticks));
}
+ } else {
+ /* on the subdivision, do nothing */
+ }
+ }
+ return frame_at_tick ((beats * BBT_Time::ticks_per_beat) + ticks);
+}
- if ((frame - (*prev).frame) < ((*next).frame - frame)) {
- return (*prev).frame;
+framepos_t
+TempoMap::round_to_type (framepos_t frame, RoundMode dir, BBTPointType type)
+{
+ Glib::Threads::RWLock::ReaderLock lm (lock);
+
+ double const beat_at_framepos = beat_at_frame (frame);
+
+ BBT_Time bbt (beats_to_bbt_unlocked (beat_at_framepos));
+
+ switch (type) {
+ case Bar:
+ if (dir < 0) {
+ /* find bar previous to 'frame' */
+ bbt.beats = 1;
+ bbt.ticks = 0;
+ return frame_time (bbt);
+
+ } else if (dir > 0) {
+ /* find bar following 'frame' */
+ ++bbt.bars;
+ bbt.beats = 1;
+ bbt.ticks = 0;
+ return frame_time (bbt);
+ } else {
+ /* true rounding: find nearest bar */
+ framepos_t raw_ft = frame_time (bbt);
+ bbt.beats = 1;
+ bbt.ticks = 0;
+ framepos_t prev_ft = frame_time (bbt);
+ ++bbt.bars;
+ framepos_t next_ft = frame_time (bbt);
+
+ if ((raw_ft - prev_ft) > (next_ft - prev_ft) / 2) {
+ return next_ft;
} else {
- return (*next).frame;
+ return prev_ft;
}
-
}
break;
case Beat:
if (dir < 0) {
- if ((*fi).frame >= frame) {
- DEBUG_TRACE (DEBUG::SnapBBT, "requested frame is on beat, step back\n");
- --fi;
- }
- DEBUG_TRACE (DEBUG::SnapBBT, string_compose ("rounded to beat: map iter at %1|%2 %3, return\n",
- (*fi).bar, (*fi).beat, (*fi).frame));
- return (*fi).frame;
+ return frame_at_beat (floor (beat_at_framepos));
} else if (dir > 0) {
- if ((*fi).frame <= frame) {
- DEBUG_TRACE (DEBUG::SnapBBT, "requested frame is on beat, step forward\n");
- ++fi;
- }
- DEBUG_TRACE (DEBUG::SnapBBT, string_compose ("rounded to beat: map iter at %1|%2 %3, return\n",
- (*fi).bar, (*fi).beat, (*fi).frame));
- return (*fi).frame;
+ return frame_at_beat (ceil (beat_at_framepos));
} else {
- /* find beat nearest to frame */
- if ((*fi).frame == frame) {
- return frame;
- }
-
- BBTPointList::const_iterator prev = fi;
- BBTPointList::const_iterator next = fi;
- --prev;
- ++next;
-
- if ((frame - (*prev).frame) < ((*next).frame - frame)) {
- return (*prev).frame;
- } else {
- return (*next).frame;
- }
+ return frame_at_beat (floor (beat_at_framepos + 0.5));
}
break;
}
- /* NOTREACHED */
- assert (false);
return 0;
}
void
-TempoMap::map (TempoMap::BBTPointList::const_iterator& begin,
- TempoMap::BBTPointList::const_iterator& end,
- framepos_t lower, framepos_t upper)
+TempoMap::get_grid (vector<TempoMap::BBTPoint>& points,
+ framepos_t lower, framepos_t upper)
{
- if (_map.empty() || upper >= _map.back().frame) {
- recompute_map (false, upper);
- }
+ Glib::Threads::RWLock::ReaderLock lm (lock);
+ uint32_t const upper_beat = (uint32_t) floor (beat_at_frame (upper));
+ uint32_t cnt = (uint32_t) ceil (beat_at_frame (lower));
+
+ while (cnt <= upper_beat) {
+ framecnt_t const pos = frame_at_beat (cnt);
+ MeterSection const meter = meter_section_at (pos);
+ Tempo const tempo = tempo_at (pos);
+ BBT_Time const bbt = beats_to_bbt_unlocked ((double) cnt);
- begin = lower_bound (_map.begin(), _map.end(), lower);
- end = upper_bound (_map.begin(), _map.end(), upper);
+ points.push_back (BBTPoint (meter, tempo, pos, bbt.bars, bbt.beats));
+ ++cnt;
+ }
}
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;
- 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) {
if (prev == 0) {
fatal << endmsg;
+ abort(); /*NOTREACHED*/
}
return *prev;
}
-const Tempo&
+/* don't use this to calculate length (the tempo is only correct for this frame).
+ do that stuff based on the beat_at_frame and frame_at_beat api
+*/
+double
+TempoMap::frames_per_beat_at (framepos_t frame, framecnt_t sr) const
+{
+ Glib::Threads::RWLock::ReaderLock lm (lock);
+
+ const TempoSection* ts_at = &tempo_section_at (frame);
+ const TempoSection* ts_after = 0;
+ Metrics::const_iterator i;
+
+ for (i = metrics.begin(); i != metrics.end(); ++i) {
+ TempoSection* t;
+
+ if ((t = dynamic_cast<TempoSection*> (*i)) != 0) {
+
+ if ((*i)->frame() > frame) {
+ ts_after = t;
+ break;
+ }
+ }
+ }
+
+ if (ts_after) {
+ return (60.0 * _frame_rate) / (ts_at->tempo_at_frame (frame - ts_at->frame(), ts_after->beats_per_minute(), ts_after->frame(), _frame_rate));
+ }
+ /* must be treated as constant tempo */
+ return ts_at->frames_per_beat (_frame_rate);
+}
+
+const Tempo
TempoMap::tempo_at (framepos_t frame) const
{
+ Glib::Threads::RWLock::ReaderLock lm (lock);
+
TempoMetric m (metric_at (frame));
+ TempoSection* prev_ts = 0;
+
+ Metrics::const_iterator i;
+
+ for (i = metrics.begin(); i != metrics.end(); ++i) {
+ TempoSection* t;
+ if ((t = dynamic_cast<TempoSection*> (*i)) != 0) {
+ if ((prev_ts) && t->frame() > frame) {
+ /* this is the one past frame */
+ framepos_t const time = frame - prev_ts->frame();
+ framepos_t const last_time = t->frame() - prev_ts->frame();
+ double const last_beats_per_minute = t->beats_per_minute();
+ double const ret = prev_ts->tempo_at_frame (time, last_beats_per_minute, last_time, _frame_rate);
+ Tempo const ret_tempo (ret, m.tempo().note_type ());
+ return ret_tempo;
+ }
+ prev_ts = t;
+ }
+ }
+
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<MeterSection*> (*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
XMLNode *root = new XMLNode ("TempoMap");
{
- Glib::RWLock::ReaderLock lm (lock);
- for (i = metrics->begin(); i != metrics->end(); ++i) {
+ Glib::Threads::RWLock::ReaderLock lm (lock);
+ for (i = metrics.begin(); i != metrics.end(); ++i) {
root->add_child_nocopy ((*i)->get_state());
}
}
TempoMap::set_state (const XMLNode& node, int /*version*/)
{
{
- Glib::RWLock::WriterLock lm (lock);
+ Glib::Threads::RWLock::WriterLock lm (lock);
XMLNodeList nlist;
XMLNodeConstIterator niter;
- Metrics old_metrics (*metrics);
+ Metrics old_metrics (metrics);
MeterSection* last_meter = 0;
-
- metrics->clear();
+ metrics.clear();
nlist = node.children();
-
+
for (niter = nlist.begin(); niter != nlist.end(); ++niter) {
XMLNode* child = *niter;
try {
TempoSection* ts = new TempoSection (*child);
- metrics->push_back (ts);
+ metrics.push_back (ts);
if (ts->bar_offset() < 0.0) {
if (last_meter) {
- ts->update_bar_offset_from_bbt (*last_meter);
- }
+ //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;
}
try {
MeterSection* ms = new MeterSection (*child);
- metrics->push_back (ms);
+ metrics.push_back (ms);
last_meter = ms;
}
catch (failed_constructor& err) {
error << _("Tempo map: could not set new state, restoring old one.") << endmsg;
- *metrics = old_metrics;
+ metrics = old_metrics;
break;
}
}
}
if (niter == nlist.end()) {
-
MetricSectionSorter cmp;
- metrics->sort (cmp);
- recompute_map (true);
+ metrics.sort (cmp);
+ }
+ /* check for legacy sessions where bbt was the base musical unit for tempo */
+ for (Metrics::iterator i = metrics.begin(); i != metrics.end(); ++i) {
+ MeterSection* prev_ms;
+ TempoSection* prev_ts;
+ if ((prev_ms = dynamic_cast<MeterSection*>(*i)) != 0) {
+ if (prev_ms->beat() < 0.0) {
+ /*XX we cannot possibly make this work??. */
+ pair<double, BBT_Time> start = make_pair (((prev_ms->bbt().bars - 1) * 4.0) + (prev_ms->bbt().beats - 1) + (prev_ms->bbt().ticks / BBT_Time::ticks_per_beat), prev_ms->bbt());
+ prev_ms->set_beat (start);
+ }
+ } else if ((prev_ts = dynamic_cast<TempoSection*>(*i)) != 0) {
+ if (prev_ts->beat() < 0.0) {
+ double const start = ((prev_ts->legacy_bbt().bars - 1) * 4.0) + (prev_ts->legacy_bbt().beats - 1) + (prev_ts->legacy_bbt().ticks / BBT_Time::ticks_per_beat);
+ prev_ts->set_beat (start);
+
+ }
+ }
+ }
+ /* 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()) {
+ MeterSection* ms;
+ MeterSection* prev_ms;
+ TempoSection* ts;
+ TempoSection* prev_ts;
+ if ((prev_ms = dynamic_cast<MeterSection*>(*prev)) != 0 && (ms = dynamic_cast<MeterSection*>(*i)) != 0) {
+ if (prev_ms->beat() == ms->beat()) {
+ cerr << string_compose (_("Multiple meter definitions found at %1"), prev_ms->beat()) << endmsg;
+ error << string_compose (_("Multiple meter definitions found at %1"), prev_ms->beat()) << endmsg;
+ return -1;
+ }
+ } else if ((prev_ts = dynamic_cast<TempoSection*>(*prev)) != 0 && (ts = dynamic_cast<TempoSection*>(*i)) != 0) {
+ if (prev_ts->beat() == ts->beat()) {
+ cerr << string_compose (_("Multiple tempo definitions found at %1"), prev_ts->beat()) << endmsg;
+ error << string_compose (_("Multiple tempo definitions found at %1"), prev_ts->beat()) << 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 << " (Bar-offset: " << t->bar_offset() << ") " << t->beats_per_minute() << " BPM (pulse = 1/" << t->note_type() << ") at " << t->start() << " frame= " << t->frame() << " (movable? "
+ o << "Tempo @ " << *i << " (Bar-offset: " << t->bar_offset() << ") " << t->beats_per_minute() << " BPM (pulse = 1/" << t->note_type() << ") at " << t->beat() << " frame= " << t->frame() << " (movable? "
<< t->movable() << ')' << endl;
} else if ((m = dynamic_cast<const MeterSection*>(*i)) != 0) {
- o << "Meter @ " << *i << ' ' << m->divisions_per_bar() << '/' << m->note_divisor() << " at " << m->start() << " frame= " << m->frame()
+ o << "Meter @ " << *i << ' ' << m->divisions_per_bar() << '/' << m->note_divisor() << " at " << m->bbt() << " 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 (framepos_t where, framecnt_t amount)
{
- for (Metrics::iterator i = metrics->begin(); i != metrics->end(); ++i) {
- if ((*i)->frame() >= where && (*i)->movable ()) {
- (*i)->set_frame ((*i)->frame() + amount);
+ {
+ Glib::Threads::RWLock::WriterLock lm (lock);
+ for (Metrics::iterator i = metrics.begin(); i != metrics.end(); ++i) {
+ if ((*i)->frame() >= where && (*i)->movable ()) {
+ (*i)->set_frame ((*i)->frame() + amount);
+ }
}
+
+ /* 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);
+ MeterSection* ms = const_cast<MeterSection*>(meter);
+ TempoSection* ts = const_cast<TempoSection*>(tempo);
+ if (prev) {
+ if (ts){
+ if ((t = dynamic_cast<TempoSection*>(prev)) != 0) {
+ ts->set_beat (t->beat());
+ }
+ if ((m = dynamic_cast<MeterSection*>(prev)) != 0) {
+ ts->set_beat (m->beat());
+ }
+ ts->set_frame (prev->frame());
+
+ }
+ if (ms) {
+ if ((m = dynamic_cast<MeterSection*>(prev)) != 0) {
+ pair<double, BBT_Time> start = make_pair (m->beat(), m->bbt());
+ ms->set_beat (start);
+ }
+ if ((t = dynamic_cast<TempoSection*>(prev)) != 0) {
+ pair<double, BBT_Time> start = make_pair (t->beat(), beats_to_bbt_unlocked (t->beat()));
+ ms->set_beat (start);
+ }
+ ms->set_frame (prev->frame());
+ }
+
+ } else {
+ // metric will be at frames=0 bbt=1|1|0 by default
+ // which is correct for our purpose
+ }
+
+ // cerr << bbt << endl;
+
+ if ((t = dynamic_cast<TempoSection*>(*i)) != 0) {
+ t->set_beat (beat_at_frame (m->frame()));
+ tempo = t;
+ // cerr << "NEW TEMPO, frame = " << (*i)->frame() << " beat = " << (*i)->beat() <<endl;
+ } else if ((m = dynamic_cast<MeterSection*>(*i)) != 0) {
+ bbt_time (m->frame(), bbt);
+
+ // cerr << "timestamp @ " << (*i)->frame() << " with " << bbt.bars << "|" << bbt.beats << "|" << bbt.ticks << " => ";
+
+ if (first) {
+ first = false;
+ } else {
+
+ if (bbt.ticks > BBT_Time::ticks_per_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;
+ }
+ }
+ pair<double, BBT_Time> start = make_pair (beat_at_frame (m->frame()), bbt);
+ m->set_beat (start);
+ meter = m;
+ // cerr << "NEW METER, frame = " << (*i)->frame() << " beat = " << (*i)->beat() <<endl;
+ } else {
+ fatal << _("programming error: unhandled MetricSection type") << endmsg;
+ abort(); /*NOTREACHED*/
+ }
+
+ prev = (*i);
+ }
+
+ recompute_map (true);
}
- timestamp_metrics_from_audio_time ();
PropertyChanged (PropertyChange ());
}
+bool
+TempoMap::remove_time (framepos_t where, framecnt_t amount)
+{
+ bool moved = false;
+
+ std::list<MetricSection*> 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<TempoSection*> (*i);
+ if (lt)
+ last_tempo = lt;
+ MeterSection *lm = dynamic_cast<MeterSection*> (*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<TempoSection*> (*i);
+ meter_after = dynamic_cast<MeterSection*> (*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<MetricSection*>::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)
+TempoMap::framepos_plus_beats (framepos_t pos, Evoral::Beats beats) const
{
- return framepos_plus_bbt (pos, BBT_Time (beats));
+ return frame_at_beat (beat_at_frame (pos) + beats.to_double());
}
-/** Subtract some (fractional) beats to a frame position, and return the result in frames */
+/** Subtract some (fractional) beats from a frame position, and return the result in frames */
framepos_t
-TempoMap::framepos_minus_beats (framepos_t pos, Evoral::MusicalTime beats)
+TempoMap::framepos_minus_beats (framepos_t pos, Evoral::Beats beats) const
{
+ return frame_at_beat (beat_at_frame (pos) - beats.to_double());
+}
+
+/** Add the BBT interval op to pos and return the result */
+framepos_t
+TempoMap::framepos_plus_bbt (framepos_t pos, BBT_Time op) const
+{
+ cerr << "framepos_plus_bbt - untested" << endl;
+ Glib::Threads::RWLock::ReaderLock lm (lock);
+
Metrics::const_iterator i;
- const TempoSection* tempo = 0;
+ const MeterSection* meter;
+ const MeterSection* m;
+ const TempoSection* tempo;
+ const TempoSection* next_tempo = 0;
const TempoSection* t;
-
- /* Find the starting tempo */
+ double frames_per_beat;
+ framepos_t effective_pos = max (pos, (framepos_t) 0);
- for (i = metrics->begin(); i != metrics->end(); ++i) {
+ meter = &first_meter ();
+ tempo = &first_tempo ();
- /* This is a bit of a hack, but pos could be -ve, and if it is,
- we consider the initial metric changes (at time 0) to actually
- be in effect at pos.
- */
- framepos_t f = (*i)->frame ();
- if (pos < 0 && f == 0) {
- f = pos;
- }
+ assert (meter);
+ assert (tempo);
- if ((*i)->frame() > pos) {
+ /* find the starting metrics for tempo & meter */
+
+ for (i = metrics.begin(); i != metrics.end(); ++i) {
+
+ if ((*i)->frame() > effective_pos) {
break;
}
if ((t = dynamic_cast<const TempoSection*>(*i)) != 0) {
tempo = t;
+ } else if ((m = dynamic_cast<const MeterSection*>(*i)) != 0) {
+ meter = m;
}
}
- bool no_more_tempos = false;
-
- /* Move i back to the tempo before "pos" */
- if (i != metrics->begin ()) {
- while (i != metrics->begin ()) {
- --i;
- t = dynamic_cast<TempoSection*> (*i);
- if (t) {
- break;
+ for (i = metrics.begin(); i != metrics.end(); ++i) {
+ if ((*i)->frame() > effective_pos) {
+ if ((t = dynamic_cast<const TempoSection*>(*i)) != 0) {
+ next_tempo = t;
}
+ break;
}
- } else {
- no_more_tempos = true;
}
/* We now have:
+ meter -> the Meter for "pos"
tempo -> the Tempo for "pos"
- i -> the first metric before "pos", unless no_more_tempos is true
+ next_tempo -> the Tempo after "pos" or 0
+ i -> for first new metric after "pos", possibly metrics.end()
*/
- while (beats) {
+ /* now comes the complicated part. we have to add one beat a time,
+ checking for a new metric on every beat.
+ */
+
+ uint64_t bars = 0;
+ /* fpb is used for constant tempo */
+ frames_per_beat = tempo->frames_per_beat (_frame_rate);
+
+ while (op.bars) {
- /* Distance to the end of this section in frames */
- framecnt_t distance_frames = no_more_tempos ? max_framepos : (pos - (*i)->frame());
+ bars++;
+ op.bars--;
- /* Distance to the end in beats */
- Evoral::MusicalTime distance_beats = distance_frames / tempo->frames_per_beat (_frame_rate);
+ /* 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.
+ */
+
+ if ((t = dynamic_cast<const TempoSection*>(*i)) != 0) {
+ next_tempo = t;
+ }
- /* Amount to subtract this time */
- double const sub = min (distance_beats, beats);
+ if (next_tempo) {
+ pos += tempo->frame_at_beat (bars * meter->divisions_per_bar(), next_tempo->beats_per_minute(), next_tempo->frame(), _frame_rate);
+ } else {
+ pos += llrint (frames_per_beat * (bars * meter->divisions_per_bar()));
+ }
- /* Update */
- beats -= sub;
- pos -= sub * tempo->frames_per_beat (_frame_rate);
+ bars = 0;
- /* Move i and tempo back, if there's anything to move to */
- if (i != metrics->begin ()) {
- while (i != metrics->begin ()) {
- --i;
if ((t = dynamic_cast<const TempoSection*>(*i)) != 0) {
tempo = t;
- break;
+ } else if ((m = dynamic_cast<const MeterSection*>(*i)) != 0) {
+ meter = m;
}
+ ++i;
+ frames_per_beat = tempo->frames_per_beat (_frame_rate);
}
- } else {
- no_more_tempos = true;
}
- }
- 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)
-{
- BBT_Time op_copy (op);
- int additional_minutes = 1;
- BBTPointList::const_iterator i;
+ if (next_tempo) {
+ pos += tempo->frame_at_beat (bars * meter->divisions_per_bar(), next_tempo->beats_per_minute(), next_tempo->frame(), _frame_rate);
+ } else {
+ pos += llrint (frames_per_beat * (bars * meter->divisions_per_bar()));
+ }
- while (true) {
+ uint64_t beats = 0;
- i = bbt_before_or_at (pos);
-
- op = op_copy;
+ while (op.beats) {
- if ((*i).frame != pos) {
- /* we know that (*i).frame is before pos */
- cerr << "Need to account for offset of " << (pos - (*i).frame) << endl;
- }
+ /* given the current meter, have we gone past the end of the bar ? */
- while (i != _map.end() && (op.bars || op.beats)) {
- ++i;
- if ((*i).is_bar()) {
- if (op.bars) {
- op.bars--;
- }
- } else {
- if (op.beats) {
- op.beats--;
- }
- }
- }
-
- if (i != _map.end()) {
- break;
- }
+ beats++;
+ op.beats--;
- /* we hit the end of the map before finish the bbt walk.
- */
+ /* 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.
+ */
- require_map_to (pos + (_frame_rate * 60 * additional_minutes));
- additional_minutes *= 2;
+ if (i != metrics.end()) {
+ if ((*i)->frame() <= pos) {
- /* go back and try again */
- cerr << "reached end of map with op now at " << op << " end = " << _map.back().frame << ' ' << _map.back().bar << '|' << _map.back().beat << ", trying to walk " << op_copy << " ... retry\n";
- }
-
- if (op.ticks) {
- return (*i).frame +
- llrint ((*i).meter->frames_per_division (*(*i).tempo, _frame_rate) * (op.ticks/BBT_Time::ticks_per_bar_division));
- } else {
- return (*i).frame;
- }
-}
+ /* 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.
+ */
-/** Count the number of beats that are equivalent to distance when going forward,
- starting at pos.
-*/
-Evoral::MusicalTime
-TempoMap::framewalk_to_beats (framepos_t pos, framecnt_t distance)
-{
- BBTPointList::const_iterator i = bbt_after_or_at (pos);
- Evoral::MusicalTime beats = 0;
- framepos_t end = pos + distance;
+ if ((t = dynamic_cast<const TempoSection*>(*i)) != 0) {
+ next_tempo = t;
+ }
- require_map_to (end);
+ if (next_tempo) {
+ pos += tempo->frame_at_beat (beats, next_tempo->beats_per_minute(), next_tempo->frame(), _frame_rate);
+ } else {
+ pos += llrint (beats * frames_per_beat);
+ }
- /* if our starting BBTPoint is after pos, add a fractional beat
- to represent that distance.
- */
+ beats = 0;
- if ((*i).frame != pos) {
- beats += ((*i).frame - pos) / (*i).meter->frames_per_division (*(*i).tempo, _frame_rate);
+ 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);
+ }
+ }
}
- while (i != _map.end() && (*i).frame < end) {
- ++i;
- beats++;
+ if (next_tempo) {
+ pos += tempo->frame_at_beat (beats, next_tempo->beats_per_minute(), next_tempo->frame(), _frame_rate);
+ } else {
+ pos += llrint (beats * frames_per_beat);
}
- assert (i != _map.end());
-
- /* if our ending BBTPoint is after the end, subtract a fractional beat
- to represent that distance.
- */
- if ((*i).frame > end) {
- beats -= ((*i).frame - end) / (*i).meter->frames_per_division (*(*i).tempo, _frame_rate);
+ if (op.ticks) {
+ pos += tempo->frame_at_tick (op.ticks, next_tempo->beats_per_minute(), next_tempo->frame(), _frame_rate);
}
- return beats;
-}
+ return pos;
-TempoMap::BBTPointList::const_iterator
-TempoMap::bbt_before_or_at (framepos_t pos)
-{
- require_map_to (pos);
- BBTPointList::const_iterator i = lower_bound (_map.begin(), _map.end(), pos);
- assert (i != _map.end());
- if ((*i).frame > pos) {
- assert (i != _map.begin());
- --i;
- }
- return i;
}
-TempoMap::BBTPointList::const_iterator
-TempoMap::bbt_after_or_at (framepos_t pos)
+/** Count the number of beats that are equivalent to distance when going forward,
+ starting at pos.
+*/
+Evoral::Beats
+TempoMap::framewalk_to_beats (framepos_t pos, framecnt_t distance) const
{
- require_map_to (pos);
- BBTPointList::const_iterator i = upper_bound (_map.begin(), _map.end(), pos);
- assert (i != _map.end());
- return i;
+ return Evoral::Beats(beat_at_frame (pos + distance) - beat_at_frame (pos));
}
struct bbtcmp {
}
};
-TempoMap::BBTPointList::const_iterator
-TempoMap::bbt_point_for (const BBT_Time& bbt)
-{
- bbtcmp cmp;
- int additional_minutes = 1;
-
- while (_map.empty() || _map.back().bar < (bbt.bars + 1)) {
- /* add some more distance, using bigger steps each time */
- require_map_to (_map.back().frame + (_frame_rate * 60 * additional_minutes));
- additional_minutes *= 2;
- }
-
- BBTPointList::const_iterator i = lower_bound (_map.begin(), _map.end(), bbt, cmp);
- assert (i != _map.end());
- return i;
-}
-
-
-/** Compare the time of this with that of another MetricSection.
- * @param with_bbt True to compare using start(), false to use frame().
- * @return -1 for less than, 0 for equal, 1 for greater than.
- */
-
-int
-MetricSection::compare (const MetricSection& other) const
-{
- if (start() == other.start()) {
- return 0;
- } else if (start() < other.start()) {
- return -1;
- } else {
- return 1;
- }
-
- /* NOTREACHED */
- return 0;
-}
-
-bool
-MetricSection::operator== (const MetricSection& other) const
-{
- return compare (other) == 0;
-}
-
-bool
-MetricSection::operator!= (const MetricSection& other) const
-{
- return compare (other) != 0;
-}
-
-std::ostream&
+std::ostream&
operator<< (std::ostream& o, const Meter& m) {
return o << m.divisions_per_bar() << '/' << m.note_divisor();
}
-std::ostream&
+std::ostream&
operator<< (std::ostream& o, const Tempo& t) {
return o << t.beats_per_minute() << " 1/" << t.note_type() << "'s per minute";
}
-std::ostream&
+std::ostream&
operator<< (std::ostream& o, const MetricSection& section) {
- o << "MetricSection @ " << section.frame() << " aka " << section.start() << ' ';
+ o << "MetricSection @ " << section.frame() << ' ';
const TempoSection* ts;
const MeterSection* ms;
if ((ts = dynamic_cast<const TempoSection*> (§ion)) != 0) {
- o << *((Tempo*) ts);
+ o << *((const Tempo*) ts);
} else if ((ms = dynamic_cast<const MeterSection*> (§ion)) != 0) {
- o << *((Meter*) ms);
+ //o << *((const Meter*) ms);
}
return o;