#include <algorithm>
#include <stdexcept>
+#include <cmath>
#include <unistd.h>
-#include <cmath>
-
#include <glibmm/thread.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>
new_start.bars = start().bars;
double ticks = BBT_Time::ticks_per_beat * meter.divisions_per_bar() * _bar_offset;
- new_start.beats = (uint32_t) floor(ticks/BBT_Time::ticks_per_beat);
- new_start.ticks = (uint32_t) fmod (ticks, BBT_Time::ticks_per_beat);
+ new_start.beats = (uint32_t) floor (ticks/BBT_Time::ticks_per_beat);
+ new_start.ticks = 0; /* (uint32_t) fmod (ticks, BBT_Time::ticks_per_beat); */
/* remember the 1-based counting properties of beats */
new_start.beats += 1;
}
}
- Metrics::iterator i;
+
/* Look for any existing MetricSection that is of the same type and
- at the same time as the new one, and remove it before adding
- the new one.
+ in the same bar as the new one, and remove it before adding
+ the new one. Note that this means that if we find a matching,
+ existing section, we can break out of the loop since we're
+ guaranteed that there is only one such match.
*/
- Metrics::iterator to_remove = metrics.end ();
+ for (Metrics::iterator i = metrics.begin(); i != metrics.end(); ++i) {
- for (i = metrics.begin(); i != metrics.end(); ++i) {
+ bool const iter_is_tempo = dynamic_cast<TempoSection*> (*i) != 0;
+ bool const insert_is_tempo = dynamic_cast<TempoSection*> (section) != 0;
- int const c = (*i)->compare (*section);
+ if (iter_is_tempo && insert_is_tempo) {
- if (c < 0) {
- /* this section is before the one to be added; go back round */
- continue;
- } else if (c > 0) {
- /* this section is after the one to be added; there can't be any at the same time */
- break;
- }
+ /* Tempo sections */
- /* hacky comparison of type */
- bool const iter_is_tempo = dynamic_cast<TempoSection*> (*i) != 0;
- bool const insert_is_tempo = dynamic_cast<TempoSection*> (section) != 0;
+ if ((*i)->start().bars == section->start().bars &&
+ (*i)->start().beats == section->start().beats) {
+
+ 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;
+ }
- if (iter_is_tempo == insert_is_tempo) {
+ } else if (!iter_is_tempo && !insert_is_tempo) {
- if (!(*i)->movable()) {
+ /* Meter Sections */
- /* can't (re)move this section, so overwrite
- * its data content (but not its properties as
- * a section
- */
+ if ((*i)->start().bars == section->start().bars) {
- if (!iter_is_tempo) {
+ 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 {
- *(dynamic_cast<Tempo*>(*i)) = *(dynamic_cast<Tempo*>(section));
+ metrics.erase (i);
+
}
- need_add = false;
+
break;
}
-
- to_remove = i;
- break;
+ } else {
+ /* non-matching types, so we don't care */
}
}
- if (to_remove != metrics.end()) {
- /* remove the MetricSection at the same time as the one we are about to add */
- metrics.erase (to_remove);
- }
-
- /* Add the given MetricSection */
+ /* Add the given MetricSection, if we didn't just reset an existing
+ * one above
+ */
if (need_add) {
+
+ Metrics::iterator i;
+
for (i = metrics.begin(); i != metrics.end(); ++i) {
-
- if ((*i)->compare (*section) < 0) {
- continue;
+ if ((*i)->start() > section->start()) {
+ break;
}
-
- metrics.insert (i, section);
- break;
- }
-
- if (i == metrics.end()) {
- metrics.insert (metrics.end(), section);
}
+
+ metrics.insert (i, section);
}
}
{
const TempoSection& first (first_tempo());
- if (ts != first) {
+ if (ts.start() != first.start()) {
remove_tempo (ts, false);
add_tempo (tempo, where);
} else {
{
const MeterSection& first (first_meter());
- if (ms != first) {
+ if (ms.start() != first.start()) {
remove_meter (ms, false);
add_meter (meter, where);
} else {
if (end < 0) {
- if (_map.empty()) {
- /* compute 1 mins worth */
- end = _frame_rate * 60;
- } else {
- end = _map.back().frame;
- }
+ /* we will actually stop once we hit
+ the last metric.
+ */
+ end = max_framepos;
+
} else {
if (!_map.empty ()) {
/* never allow the map to be shortened */
TempoSection* ts;
MeterSection* ms;
- double divisions_per_bar;
double beat_frames;
+ framepos_t bar_start_frame;
+
+ DEBUG_TRACE (DEBUG::TempoMath, string_compose ("Extend map to %1 from %2 = %3\n", end, current, current_frame));
+
+ if (current.beats == 1) {
+ bar_start_frame = current_frame;
+ } else {
+ bar_start_frame = 0;
+ }
- divisions_per_bar = meter->divisions_per_bar ();
beat_frames = meter->frames_per_grid (*tempo,_frame_rate);
while (current_frame < end) {
-
+
current.beats++;
current_frame += beat_frames;
/* back up to previous beat */
current_frame -= beat_frames;
- /* set tempo section location based on offset from last beat */
-
- double bar_offset_in_beats = 1 + (ts->bar_offset() * meter->divisions_per_bar());
-
- /* we've already advanced
- * current.beats, but we want
- * the previous beat's value
+ /* 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.
*/
- bar_offset_in_beats -= current.beats - 1;
+ double offset_within_old_beat = (tempo->frame() - current_frame) / beat_frames;
- tempo->set_frame (current_frame + (bar_offset_in_beats * beat_frames));
+ current_frame += (offset_within_old_beat * beat_frames) + ((1.0 - offset_within_old_beat) * next_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 ...
meter->set_frame (current_frame);
}
- divisions_per_bar = meter->divisions_per_bar ();
beat_frames = meter->frames_per_grid (*tempo, _frame_rate);
DEBUG_TRACE (DEBUG::TempoMath, string_compose ("New metric with beat frames = %1 dpb %2 meter %3 tempo %4\n",
- beat_frames, divisions_per_bar, *((Meter*)meter), *((Tempo*)tempo)));
+ beat_frames, meter->divisions_per_bar(), *((Meter*)meter), *((Tempo*)tempo)));
++next_metric;
goto set_metrics;
}
}
- }
+ }
if (current.beats == 1) {
DEBUG_TRACE (DEBUG::TempoMath, string_compose ("Add Bar at %1|1 @ %2\n", current.bars, current_frame));
_map.push_back (BBTPoint (*meter, *tempo,(framepos_t) llrint(current_frame), current.bars, 1));
+ bar_start_frame = current_frame;
} else {
DEBUG_TRACE (DEBUG::TempoMath, string_compose ("Add Beat at %1|%2 @ %3\n", current.bars, current.beats, current_frame));
_map.push_back (BBTPoint (*meter, *tempo, (framepos_t) llrint(current_frame), current.bars, current.beats));
}
+
+ 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;
+ }
+ }
}
}
for (Metrics::const_iterator i = metrics.begin(); i != metrics.end(); ++i) {
- // cerr << "Looking at a metric section " << **i << endl;
-
if ((*i)->frame() > frame) {
break;
}
m.set_start ((*i)->start ());
}
- // cerr << "for framepos " << frame << " returning " << m.meter() << " @ " << m.tempo() << " location " << m.frame() << " = " << m.start() << endl;
return m;
}
require_map_to (frame);
Glib::RWLock::ReaderLock lm (lock);
+
+ if (frame < 0) {
+ bbt.bars = 1;
+ bbt.beats = 1;
+ bbt.ticks = 0;
+ warning << string_compose (_("tempo map asked for BBT time at frame %1\n"), frame) << endmsg;
+ return;
+ }
+
return bbt_time (frame, bbt, bbt_before_or_at (frame));
}
framepos_t
TempoMap::frame_time (const BBT_Time& bbt)
{
+ if (bbt.bars < 1) {
+ warning << string_compose (_("tempo map asked for frame time at bar < 1 (%1)\n"), bbt) << endmsg;
+ return 0;
+ }
+
+ if (bbt.beats < 1) {
+ throw std::logic_error ("beats are counted from one");
+ }
+
require_map_to (bbt);
Glib::RWLock::ReaderLock lm (lock);
framecnt_t
TempoMap::bbt_duration_at (framepos_t pos, const BBT_Time& bbt, int dir)
{
- Glib::RWLock::ReaderLock lm (lock);
- framecnt_t frames = 0;
BBT_Time when;
-
bbt_time (pos, when);
- frames = bbt_duration_at_unlocked (when, bbt,dir);
-
- return frames;
+
+ Glib::RWLock::ReaderLock lm (lock);
+ return bbt_duration_at_unlocked (when, bbt, dir);
}
framecnt_t
-TempoMap::bbt_duration_at_unlocked (const BBT_Time& when, const BBT_Time& bbt, int dir)
+TempoMap::bbt_duration_at_unlocked (const BBT_Time& when, const BBT_Time& bbt, int /*dir*/)
{
if (bbt.bars == 0 && bbt.beats == 0 && bbt.ticks == 0) {
return 0;
assert (fi != _map.end());
- DEBUG_TRACE(DEBUG::SnapBBT, string_compose ("round from %1 (%3|%4 @ %5) to bars in direction %2\n", frame, dir, (*fi).bar, (*fi).beat, (*fi).frame));
+ DEBUG_TRACE (DEBUG::SnapBBT, string_compose ("round from %1 (%3|%4 @ %5) to %6 in direction %2\n", frame, dir, (*fi).bar, (*fi).beat, (*fi).frame,
+ (type == Bar ? "bar" : "beat")));
switch (type) {
case Bar:
BBTPointList::const_iterator prev = fi;
BBTPointList::const_iterator next = fi;
- if (prev != _map.begin()) {
- --prev;
- }
+
+ /* fi is already the beat before_or_at frame, and
+ we've just established that its not at frame, so its
+ the beat before frame.
+ */
++next;
if ((next == _map.end()) || (frame - (*prev).frame) < ((*next).frame - frame)) {
XMLNodeConstIterator niter;
Metrics old_metrics (metrics);
MeterSection* last_meter = 0;
-
metrics.clear();
nlist = node.children();
metrics.sort (cmp);
}
- recompute_map (true);
+ recompute_map (true, -1);
}
PropertyChanged (PropertyChange ());
{
Glib::RWLock::ReaderLock lm (lock);
Metrics::const_iterator next_tempo;
- const TempoSection* tempo;
+ const TempoSection* tempo = 0;
/* Find the starting tempo metric */
return pos;
}
-/** Subtract some (fractional) beats to a frame position, and return the result in frames */
+/** Subtract some (fractional) beats from a frame position, and return the result in frames */
framepos_t
TempoMap::framepos_minus_beats (framepos_t pos, Evoral::MusicalTime beats) const
{
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 ();
for (i = metrics.begin(); i != metrics.end(); ++i) {
- if ((*i)->frame() > pos) {
+ if ((*i)->frame() > effective_pos) {
break;
}
{
Glib::RWLock::ReaderLock lm (lock);
Metrics::const_iterator next_tempo;
- const TempoSection* tempo;
-
+ const TempoSection* tempo = 0;
+ framepos_t effective_pos = max (pos, (framepos_t) 0);
+
/* Find the relevant initial tempo metric */
for (next_tempo = metrics.begin(); next_tempo != metrics.end(); ++next_tempo) {
if ((t = dynamic_cast<const TempoSection*>(*next_tempo)) != 0) {
- if ((*next_tempo)->frame() > pos) {
+ if ((*next_tempo)->frame() > effective_pos) {
break;
}
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, *((Tempo*)tempo), tempo->frame()));
+
Evoral::MusicalTime beats = 0;
while (distance) {
/* End of this section */
- framepos_t const end = ((next_tempo == metrics.end()) ? max_framepos : (*next_tempo)->frame ());
-
- /* Distance to the end in frames */
- framecnt_t const distance_to_end = end - pos;
+ framepos_t end;
+ /* Distance to `end' in frames */
+ framepos_t distance_to_end;
+
+ if (next_tempo == metrics.end ()) {
+ /* We can't do (end - pos) if end is max_framepos, as it will overflow if pos is -ve */
+ end = max_framepos;
+ distance_to_end = max_framepos;
+ } else {
+ end = (*next_tempo)->frame ();
+ distance_to_end = end - pos;
+ }
/* Amount to subtract this time */
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()) {
break;
}
}
+
+ if (next_tempo == metrics.end()) {
+ DEBUG_TRACE (DEBUG::TempoMath, "no more tempo sections\n");
+ } else {
+ DEBUG_TRACE (DEBUG::TempoMath, string_compose ("next tempo section is %1 @ %2\n",
+ **next_tempo, (*next_tempo)->frame()));
+ }
+
}
+ assert (tempo);
}
return beats;
BBTPointList::const_iterator i;
+ if (pos < 0) {
+ /* not really correct, but we should catch pos < 0 at a higher
+ level
+ */
+ return _map.begin();
+ }
+
i = lower_bound (_map.begin(), _map.end(), pos);
assert (i != _map.end());
if ((*i).frame > pos) {
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&
operator<< (std::ostream& o, const Meter& m) {
return o << m.divisions_per_bar() << '/' << m.note_divisor();