#include "pbd/xml++.h"
#include "evoral/types.hpp"
#include "ardour/debug.h"
+#include "ardour/lmath.h"
#include "ardour/tempo.h"
#include "i18n.h"
{
const XMLProperty *prop;
BBT_Time start;
- LocaleGuard lg (X_("POSIX"));
+ LocaleGuard lg (X_("C"));
if ((prop = node.property ("start")) == 0) {
error << _("TempoSection XML node has no \"start\" property") << endmsg;
{
XMLNode *root = new XMLNode (xml_state_node_name);
char buf[256];
- LocaleGuard lg (X_("POSIX"));
+ LocaleGuard lg (X_("C"));
snprintf (buf, sizeof (buf), "%" PRIu32 "|%" PRIu32 "|%" PRIu32,
start().bars,
{
const XMLProperty *prop;
BBT_Time start;
- LocaleGuard lg (X_("POSIX"));
+ LocaleGuard lg (X_("C"));
if ((prop = node.property ("start")) == 0) {
error << _("MeterSection XML node has no \"start\" property") << endmsg;
{
XMLNode *root = new XMLNode (xml_state_node_name);
char buf[256];
- LocaleGuard lg (X_("POSIX"));
+ LocaleGuard lg (X_("C"));
snprintf (buf, sizeof (buf), "%" PRIu32 "|%" PRIu32 "|%" PRIu32,
start().bars,
{
Glib::Threads::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;
- }
- }
+ 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)
{
{
Glib::Threads::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;
- }
- }
+ 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)
{
void
TempoMap::replace_tempo (const TempoSection& ts, const Tempo& tempo, const BBT_Time& where)
{
- const TempoSection& first (first_tempo());
-
- if (ts.start() != first.start()) {
- remove_tempo (ts, false);
- add_tempo (tempo, where);
- } else {
- {
- Glib::Threads::RWLock::WriterLock lm (lock);
- /* cannot move the first tempo section */
- *((Tempo*)&first) = tempo;
- recompute_map (false);
+ {
+ Glib::Threads::RWLock::WriterLock lm (lock);
+ TempoSection& first (first_tempo());
+
+ if (ts.start() != first.start()) {
+ remove_tempo_locked (ts);
+ add_tempo_locked (tempo, where, true);
+ } else {
+ {
+ /* cannot move the first tempo section */
+ *static_cast<Tempo*>(&first) = tempo;
+ recompute_map (false);
+ }
}
}
{
{
Glib::Threads::RWLock::WriterLock lm (lock);
+ add_tempo_locked (tempo, where, true);
+ }
- /* new tempos always start on a beat */
- where.ticks = 0;
- TempoSection* ts = new TempoSection (where, tempo.beats_per_minute(), tempo.note_type());
-
- /* find the meter to use to set the bar offset of this
- * tempo section.
- */
+ PropertyChanged (PropertyChange ());
+}
- const Meter* meter = &first_meter();
+void
+TempoMap::add_tempo_locked (const Tempo& tempo, BBT_Time where, bool recompute)
+{
+ /* new tempos always start on a beat */
+ where.ticks = 0;
+
+ TempoSection* ts = new TempoSection (where, tempo.beats_per_minute(), tempo.note_type());
+
+ /* find the meter to use to set the bar offset of this
+ * tempo section.
+ */
+
+ const Meter* meter = &first_meter();
+
+ /* as we start, we are *guaranteed* to have m.meter and m.tempo pointing
+ at something, because we insert the default tempo and meter during
+ TempoMap construction.
+
+ now see if we can find better candidates.
+ */
+
+ for (Metrics::const_iterator i = metrics.begin(); i != metrics.end(); ++i) {
- /* 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.
- */
+ const MeterSection* m;
- 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;
- }
+ if (where < (*i)->start()) {
+ break;
}
-
- ts->update_bar_offset_from_bbt (*meter);
-
- /* and insert it */
- do_insert (ts);
+ if ((m = dynamic_cast<const MeterSection*>(*i)) != 0) {
+ meter = m;
+ }
+ }
+
+ ts->update_bar_offset_from_bbt (*meter);
+
+ /* and insert it */
+
+ do_insert (ts);
+ if (recompute) {
recompute_map (false);
}
-
-
- PropertyChanged (PropertyChange ());
-}
+}
void
TempoMap::replace_meter (const MeterSection& ms, const Meter& meter, const BBT_Time& where)
{
- const MeterSection& first (first_meter());
-
- if (ms.start() != first.start()) {
- remove_meter (ms, false);
- add_meter (meter, where);
- } else {
- {
- Glib::Threads::RWLock::WriterLock lm (lock);
+ {
+ Glib::Threads::RWLock::WriterLock lm (lock);
+ MeterSection& first (first_meter());
+
+ if (ms.start() != first.start()) {
+ remove_meter_locked (ms);
+ add_meter_locked (meter, where, true);
+ } else {
/* cannot move the first meter section */
- *((Meter*)&first) = meter;
+ *static_cast<Meter*>(&first) = meter;
recompute_map (true);
}
}
{
{
Glib::Threads::RWLock::WriterLock lm (lock);
-
- /* a new meter always starts a new bar on the first beat. so
- round the start time appropriately. remember that
- `where' is based on the existing tempo map, not
- the result after we insert the new meter.
-
- */
-
- if (where.beats != 1) {
- where.beats = 1;
- where.bars++;
- }
-
- /* new meters *always* start on a beat. */
- where.ticks = 0;
-
- do_insert (new MeterSection (where, meter.divisions_per_bar(), meter.note_divisor()));
- recompute_map (true);
+ add_meter_locked (meter, where, true);
}
PropertyChanged (PropertyChange ());
}
+void
+TempoMap::add_meter_locked (const Meter& meter, BBT_Time where, bool recompute)
+{
+ /* a new meter always starts a new bar on the first beat. so
+ round the start time appropriately. remember that
+ `where' is based on the existing tempo map, not
+ the result after we insert the new meter.
+
+ */
+
+ if (where.beats != 1) {
+ where.beats = 1;
+ where.bars++;
+ }
+
+ /* new meters *always* start on a beat. */
+ where.ticks = 0;
+
+ do_insert (new MeterSection (where, meter.divisions_per_bar(), meter.note_divisor()));
+
+ if (recompute) {
+ recompute_map (true);
+ }
+
+}
+
void
TempoMap::change_initial_tempo (double beats_per_minute, double note_type)
{
return *m;
}
+MeterSection&
+TempoMap::first_meter ()
+{
+ MeterSection *m = 0;
+
+ /* CALLER MUST HOLD LOCK */
+
+ for (Metrics::iterator i = metrics.begin(); i != metrics.end(); ++i) {
+ if ((m = dynamic_cast<MeterSection *> (*i)) != 0) {
+ return *m;
+ }
+ }
+
+ fatal << _("programming error: no tempo section in tempo map!") << endmsg;
+ abort(); /*NOTREACHED*/
+ return *m;
+}
+
const TempoSection&
TempoMap::first_tempo () const
{
const TempoSection *t = 0;
+ /* CALLER MUST HOLD LOCK */
+
for (Metrics::const_iterator i = metrics.begin(); i != metrics.end(); ++i) {
if ((t = dynamic_cast<const TempoSection *> (*i)) != 0) {
return *t;
return *t;
}
+TempoSection&
+TempoMap::first_tempo ()
+{
+ TempoSection *t = 0;
+
+ 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::require_map_to (framepos_t pos)
{
}
framepos_t
-TempoMap::round_to_bar (framepos_t fr, int dir)
+TempoMap::round_to_bar (framepos_t fr, RoundMode dir)
{
return round_to_type (fr, dir, Bar);
}
framepos_t
-TempoMap::round_to_beat (framepos_t fr, int dir)
+TempoMap::round_to_beat (framepos_t fr, RoundMode dir)
{
return round_to_type (fr, dir, Beat);
}
framepos_t
-TempoMap::round_to_beat_subdivision (framepos_t fr, int sub_num, int dir)
+TempoMap::round_to_beat_subdivision (framepos_t fr, int sub_num, RoundMode dir)
{
require_map_to (fr);
BBTPointList::const_iterator i = bbt_before_or_at (fr);
BBT_Time the_beat;
uint32_t ticks_one_subdivisions_worth;
- uint32_t difference;
bbt_time (fr, the_beat, i);
if (dir > 0) {
- /* round to next (even if we're on a subdivision */
+ /* round to next (or same iff dir == RoundUpMaybe) */
uint32_t mod = the_beat.ticks % ticks_one_subdivisions_worth;
- if (mod == 0) {
+ if (mod == 0 && dir == RoundUpMaybe) {
+ /* right on the subdivision, which is fine, so do nothing */
+
+ } else if (mod == 0) {
/* right on the subdivision, so the difference is just the subdivision ticks */
the_beat.ticks += ticks_one_subdivisions_worth;
} else if (dir < 0) {
- /* round to previous (even if we're on a subdivision) */
+ /* round to previous (or same iff dir == RoundDownMaybe) */
- uint32_t mod = the_beat.ticks % ticks_one_subdivisions_worth;
+ uint32_t difference = the_beat.ticks % ticks_one_subdivisions_worth;
- if (mod == 0) {
- /* right on the subdivision, so the difference is just the subdivision ticks */
+ if (difference == 0 && dir == RoundDownAlways) {
+ /* right on the subdivision, but force-rounding down,
+ so the difference is just the subdivision ticks */
difference = ticks_one_subdivisions_worth;
- } else {
- /* not on subdivision, compute distance to previous subdivision, which
- is just the modulus.
- */
-
- difference = mod;
}
if (the_beat.ticks < difference) {
}
framepos_t
-TempoMap::round_to_type (framepos_t frame, int dir, BBTPointType type)
+TempoMap::round_to_type (framepos_t frame, RoundMode dir, BBTPointType type)
{
require_map_to (frame);
}
if ((*fi).is_bar() && (*fi).frame == frame) {
+ if (dir == RoundDownMaybe) {
+ return frame;
+ }
--fi;
}
/* find bar following 'frame' */
if ((*fi).is_bar() && (*fi).frame == frame) {
+ if (dir == RoundUpMaybe) {
+ return frame;
+ }
++fi;
}
return 0;
}
- if ((*fi).frame >= frame) {
+ if ((*fi).frame > frame || ((*fi).frame == frame && dir == RoundDownAlways)) {
DEBUG_TRACE (DEBUG::SnapBBT, "requested frame is on beat, step back\n");
--fi;
}
(*fi).bar, (*fi).beat, (*fi).frame));
return (*fi).frame;
} else if (dir > 0) {
- if ((*fi).frame <= frame) {
+ if ((*fi).frame < frame || ((*fi).frame == frame && dir == RoundUpAlways)) {
DEBUG_TRACE (DEBUG::SnapBBT, "requested frame is on beat, step forward\n");
++fi;
}
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
if (prev != metrics.end()) {
if (dynamic_cast<MeterSection*>(*prev) && dynamic_cast<MeterSection*>(*i)) {
if ((*prev)->start() == (*i)->start()) {
+ cerr << string_compose (_("Multiple meter definitions found at %1"), (*prev)->start()) << endmsg;
error << string_compose (_("Multiple meter definitions found at %1"), (*prev)->start()) << endmsg;
return -1;
}
} else if (dynamic_cast<TempoSection*>(*prev) && dynamic_cast<TempoSection*>(*i)) {
if ((*prev)->start() == (*i)->start()) {
+ cerr << string_compose (_("Multiple tempo definitions found at %1"), (*prev)->start()) << endmsg;
error << string_compose (_("Multiple tempo definitions found at %1"), (*prev)->start()) << endmsg;
return -1;
}
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) const
+TempoMap::framepos_plus_beats (framepos_t pos, Evoral::Beats beats) const
{
Glib::Threads::RWLock::ReaderLock lm (lock);
Metrics::const_iterator next_tempo;
string_compose ("frame %1 plus %2 beats, start with tempo = %3 @ %4\n",
pos, beats, *((const Tempo*)tempo), tempo->frame()));
- while (beats) {
+ while (!!beats) {
/* Distance to the end of this section in frames */
framecnt_t distance_frames = (next_tempo == metrics.end() ? max_framepos : ((*next_tempo)->frame() - pos));
/* Distance to the end in beats */
- Evoral::MusicalTime distance_beats = distance_frames / tempo->frames_per_beat (_frame_rate);
+ Evoral::Beats distance_beats = Evoral::Beats::ticks_at_rate(
+ distance_frames, tempo->frames_per_beat (_frame_rate));
/* Amount to subtract this time */
- double const delta = min (distance_beats, beats);
+ Evoral::Beats const delta = min (distance_beats, beats);
DEBUG_TRACE (DEBUG::TempoMath, string_compose ("\tdistance to %1 = %2 (%3 beats)\n",
(next_tempo == metrics.end() ? max_framepos : (*next_tempo)->frame()),
/* Update */
beats -= delta;
- pos += delta * tempo->frames_per_beat (_frame_rate);
+ pos += delta.to_ticks(tempo->frames_per_beat (_frame_rate));
DEBUG_TRACE (DEBUG::TempoMath, string_compose ("\tnow at %1, %2 beats left\n", pos, beats));
/** Subtract some (fractional) beats from a frame position, and return the result in frames */
framepos_t
-TempoMap::framepos_minus_beats (framepos_t pos, Evoral::MusicalTime beats) const
+TempoMap::framepos_minus_beats (framepos_t pos, Evoral::Beats beats) const
{
Glib::Threads::RWLock::ReaderLock lm (lock);
Metrics::const_reverse_iterator prev_tempo;
prev_tempo -> the first metric before "pos", possibly metrics.rend()
*/
- while (beats) {
+ while (!!beats) {
/* Distance to the start of this section in frames */
framecnt_t distance_frames = (pos - tempo->frame());
/* Distance to the start in beats */
- Evoral::MusicalTime distance_beats = distance_frames / tempo->frames_per_beat (_frame_rate);
+ Evoral::Beats distance_beats = Evoral::Beats::ticks_at_rate(
+ distance_frames, tempo->frames_per_beat (_frame_rate));
/* Amount to subtract this time */
- double const sub = min (distance_beats, beats);
+ Evoral::Beats const sub = min (distance_beats, beats);
DEBUG_TRACE (DEBUG::TempoMath, string_compose ("\tdistance to %1 = %2 (%3 beats)\n",
tempo->frame(), distance_frames, distance_beats));
/* Update */
beats -= sub;
- pos -= sub * tempo->frames_per_beat (_frame_rate);
+ pos -= sub.to_double() * tempo->frames_per_beat (_frame_rate);
DEBUG_TRACE (DEBUG::TempoMath, string_compose ("\tnow at %1, %2 beats left, prev at end ? %3\n", pos, beats,
(prev_tempo == metrics.rend())));
}
}
} else {
- pos -= llrint (beats * tempo->frames_per_beat (_frame_rate));
- beats = 0;
+ pos -= llrint (beats.to_double() * tempo->frames_per_beat (_frame_rate));
+ beats = Evoral::Beats();
}
}
/** Count the number of beats that are equivalent to distance when going forward,
starting at pos.
*/
-Evoral::MusicalTime
+Evoral::Beats
TempoMap::framewalk_to_beats (framepos_t pos, framecnt_t distance) const
{
Glib::Threads::RWLock::ReaderLock lm (lock);
string_compose ("frame %1 walk by %2 frames, start with tempo = %3 @ %4\n",
pos, distance, *((const Tempo*)tempo), tempo->frame()));
- Evoral::MusicalTime beats = 0;
+ Evoral::Beats beats = Evoral::Beats();
while (distance) {
distance_to_end = end - pos;
}
- /* Amount to subtract this time */
- double const sub = min (distance, distance_to_end);
+ /* Amount to subtract this time in frames */
+ framecnt_t 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));
pos += sub;
distance -= sub;
assert (tempo);
- beats += sub / tempo->frames_per_beat (_frame_rate);
+ beats += Evoral::Beats::ticks_at_rate(sub, tempo->frames_per_beat (_frame_rate));
DEBUG_TRACE (DEBUG::TempoMath, string_compose ("now at %1, beats = %2 distance left %3\n",
pos, beats, distance));