X-Git-Url: https://main.carlh.net/gitweb/?a=blobdiff_plain;f=libs%2Fardour%2Ftempo.cc;h=579b523fa08b475596aeb4ebb6e8bab9374a7068;hb=9756b7569accf82d1c2de87cd632845acd107f9d;hp=a79dc2eb41896c571657d0607a1a38552f0126fd;hpb=68a3144344ab0e103539ee27b9f95324f45dcbe7;p=ardour.git diff --git a/libs/ardour/tempo.cc b/libs/ardour/tempo.cc index a79dc2eb41..579b523fa0 100644 --- a/libs/ardour/tempo.cc +++ b/libs/ardour/tempo.cc @@ -18,11 +18,12 @@ */ #include +#include + #include #include -#include #include #include "pbd/xml++.h" @@ -37,14 +38,14 @@ using namespace std; using namespace ARDOUR; using namespace PBD; +using Timecode::BBT_Time; + /* _default tempo is 4/4 qtr=120 */ Meter TempoMap::_default_meter (4.0, 4.0); Tempo TempoMap::_default_tempo (120.0); -const double Meter::ticks_per_beat = 1920.0; - -double Tempo::frames_per_beat (nframes_t sr, const Meter& meter) const +double Tempo::frames_per_beat (framecnt_t sr, const Meter& meter) const { return ((60.0 * sr) / (_beats_per_minute * meter.note_divisor()/_note_type)); } @@ -52,7 +53,7 @@ double Tempo::frames_per_beat (nframes_t sr, const Meter& meter) const /***********************************************************************/ double -Meter::frames_per_bar (const Tempo& tempo, nframes_t sr) const +Meter::frames_per_bar (const Tempo& tempo, framecnt_t sr) const { return ((60.0 * sr * _beats_per_bar) / (tempo.beats_per_minute() * _note_type/tempo.note_type())); } @@ -217,7 +218,7 @@ struct MetricSectionSorter { } }; -TempoMap::TempoMap (nframes_t fr) +TempoMap::TempoMap (framecnt_t fr) { metrics = new Metrics; _frame_rate = fr; @@ -258,11 +259,11 @@ TempoMap::move_metric_section (MetricSection& section, const BBT_Time& when) /* position by audio frame, then recompute BBT timestamps from the audio ones */ - nframes_t frame = frame_time (when); + framepos_t frame = frame_time (when); // cerr << "nominal frame time = " << frame << endl; - nframes_t prev_frame = round_to_type (frame, -1, Beat); - nframes_t next_frame = round_to_type (frame, 1, Beat); + framepos_t prev_frame = round_to_type (frame, -1, Beat); + framepos_t next_frame = round_to_type (frame, 1, Beat); // cerr << "previous beat at " << prev_frame << " next at " << next_frame << endl; @@ -298,7 +299,7 @@ void TempoMap::move_tempo (TempoSection& tempo, const BBT_Time& when) { if (move_metric_section (tempo, when) == 0) { - StateChanged (Change (0)); + PropertyChanged (PropertyChange ()); } } @@ -306,7 +307,7 @@ void TempoMap::move_meter (MeterSection& meter, const BBT_Time& when) { if (move_metric_section (meter, when) == 0) { - StateChanged (Change (0)); + PropertyChanged (PropertyChange ()); } } @@ -333,7 +334,7 @@ TempoMap::remove_tempo (const TempoSection& tempo) } if (removed) { - StateChanged (Change (0)); + PropertyChanged (PropertyChange ()); } } @@ -360,7 +361,7 @@ TempoMap::remove_meter (const MeterSection& tempo) } if (removed) { - StateChanged (Change (0)); + PropertyChanged (PropertyChange ()); } } @@ -369,16 +370,46 @@ TempoMap::do_insert (MetricSection* section, bool with_bbt) { 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. + */ + + Metrics::iterator to_remove = metrics->end (); + for (i = metrics->begin(); i != metrics->end(); ++i) { - if (with_bbt) { - if ((*i)->start() < section->start()) { - continue; - } - } else { - if ((*i)->frame() < section->frame()) { - continue; - } + int const c = (*i)->compare (section, with_bbt); + + 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; + } + + /* hacky comparison of type */ + bool const a = dynamic_cast (*i) != 0; + bool const b = dynamic_cast (section) != 0; + + if (a == b) { + to_remove = i; + break; + } + } + + 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 */ + + for (i = metrics->begin(); i != metrics->end(); ++i) { + + if ((*i)->compare (section, with_bbt) < 0) { + continue; } metrics->insert (i, section); @@ -399,24 +430,23 @@ TempoMap::add_tempo (const Tempo& tempo, BBT_Time where) Glib::RWLock::WriterLock lm (lock); /* new tempos always start on a beat */ - where.ticks = 0; do_insert (new TempoSection (where, tempo.beats_per_minute(), tempo.note_type()), true); } - StateChanged (Change (0)); + PropertyChanged (PropertyChange ()); } void -TempoMap::add_tempo (const Tempo& tempo, nframes_t where) +TempoMap::add_tempo (const Tempo& tempo, framepos_t where) { { Glib::RWLock::WriterLock lm (lock); do_insert (new TempoSection (where, tempo.beats_per_minute(), tempo.note_type()), false); } - StateChanged (Change (0)); + PropertyChanged (PropertyChange ()); } void @@ -444,7 +474,7 @@ TempoMap::replace_tempo (TempoSection& existing, const Tempo& replacement) } if (replaced) { - StateChanged (Change (0)); + PropertyChanged (PropertyChange ()); } } @@ -467,24 +497,23 @@ TempoMap::add_meter (const Meter& meter, BBT_Time where) } /* new meters *always* start on a beat. */ - where.ticks = 0; do_insert (new MeterSection (where, meter.beats_per_bar(), meter.note_divisor()), true); } - StateChanged (Change (0)); + PropertyChanged (PropertyChange ()); } void -TempoMap::add_meter (const Meter& meter, nframes_t where) +TempoMap::add_meter (const Meter& meter, framepos_t where) { { Glib::RWLock::WriterLock lm (lock); do_insert (new MeterSection (where, meter.beats_per_bar(), meter.note_divisor()), false); } - StateChanged (Change (0)); + PropertyChanged (PropertyChange ()); } void @@ -510,7 +539,7 @@ TempoMap::replace_meter (MeterSection& existing, const Meter& replacement) } if (replaced) { - StateChanged (Change (0)); + PropertyChanged (PropertyChange ()); } } @@ -523,14 +552,14 @@ TempoMap::change_initial_tempo (double beats_per_minute, double note_type) for (Metrics::iterator i = metrics->begin(); i != metrics->end(); ++i) { if ((t = dynamic_cast (*i)) != 0) { *((Tempo*) t) = newtempo; - StateChanged (Change (0)); + PropertyChanged (PropertyChange ()); break; } } } void -TempoMap::change_existing_tempo_at (nframes_t where, double beats_per_minute, double note_type) +TempoMap::change_existing_tempo_at (framepos_t where, double beats_per_minute, double note_type) { Tempo newtempo (beats_per_minute, note_type); @@ -569,7 +598,7 @@ TempoMap::change_existing_tempo_at (nframes_t where, double beats_per_minute, do /* reset */ *((Tempo*)prev) = newtempo; - StateChanged (Change (0)); + PropertyChanged (PropertyChange ()); } const MeterSection& @@ -620,8 +649,8 @@ TempoMap::timestamp_metrics (bool use_bbt) // cerr << "\n\n\n ######################\nTIMESTAMP via BBT ##############\n" << endl; - nframes_t current = 0; - nframes_t section_frames; + framepos_t current = 0; + framepos_t section_frames; BBT_Time start; BBT_Time end; @@ -676,7 +705,7 @@ TempoMap::timestamp_metrics (bool use_bbt) first = false; } else { - if (bbt.ticks > Meter::ticks_per_beat/2) { + if (bbt.ticks > BBT_Time::ticks_per_beat/2) { /* round up to next beat */ bbt.beats += 1; } @@ -715,7 +744,7 @@ TempoMap::timestamp_metrics (bool use_bbt) } TempoMetric -TempoMap::metric_at (nframes_t frame) const +TempoMap::metric_at (framepos_t frame) const { TempoMetric m (first_meter(), first_tempo()); const Meter* meter; @@ -783,7 +812,7 @@ TempoMap::metric_at (BBT_Time bbt) const } void -TempoMap::bbt_time (nframes_t frame, BBT_Time& bbt) const +TempoMap::bbt_time (framepos_t frame, BBT_Time& bbt) const { { Glib::RWLock::ReaderLock lm (lock); @@ -792,86 +821,87 @@ TempoMap::bbt_time (nframes_t frame, BBT_Time& bbt) const } void -TempoMap::bbt_time_unlocked (nframes_t frame, BBT_Time& bbt) const +TempoMap::bbt_time_unlocked (framepos_t frame, BBT_Time& bbt) const { bbt_time_with_metric (frame, bbt, metric_at (frame)); } void -TempoMap::bbt_time_with_metric (nframes_t frame, BBT_Time& bbt, const TempoMetric& metric) const +TempoMap::bbt_time_with_metric (framepos_t frame, BBT_Time& bbt, const TempoMetric& metric) const { - nframes_t frame_diff; + framecnt_t frame_diff; // cerr << "---- BBT time for " << frame << " using metric @ " << metric.frame() << " BBT " << metric.start() << endl; const double beats_per_bar = metric.meter().beats_per_bar(); - const double ticks_per_frame = metric.tempo().frames_per_beat (_frame_rate, metric.meter()) / Meter::ticks_per_beat; + const double ticks_per_frame = metric.tempo().frames_per_beat (_frame_rate, metric.meter()) / BBT_Time::ticks_per_beat; /* now compute how far beyond that point we actually are. */ frame_diff = frame - metric.frame(); - bbt.ticks = metric.start().ticks + (uint32_t)round((double)frame_diff / ticks_per_frame); - uint32_t xtra_beats = bbt.ticks / (uint32_t)Meter::ticks_per_beat; - bbt.ticks %= (uint32_t)Meter::ticks_per_beat; - - bbt.beats = metric.start().beats + xtra_beats - 1; // correction for 1-based counting, see below for matching operation. - bbt.bars = metric.start().bars + (uint32_t)floor((double)bbt.beats / beats_per_bar); - bbt.beats = (uint32_t)fmod((double)bbt.beats, beats_per_bar); - - /* if we have a fractional number of beats per bar, we see if - we're in the last beat (the fractional one). if so, we - round ticks appropriately and bump to the next bar. */ - double beat_fraction = beats_per_bar - floor(beats_per_bar); - /* XXX one problem here is that I'm not sure how to handle - fractional beats that don't evenly divide ticks_per_beat. - If they aren't handled consistently, I would guess we'll - continue to have strange discrepancies occuring. Perhaps - this will also behave badly in the case of meters like - 0.1/4, but I can't be bothered to test that. - */ - uint32_t ticks_on_last_beat = (uint32_t)floor(Meter::ticks_per_beat * beat_fraction); - if(bbt.beats > (uint32_t)floor(beats_per_bar) && - bbt.ticks >= ticks_on_last_beat) { - bbt.ticks -= ticks_on_last_beat; - bbt.beats = 0; - bbt.bars++; - } - - bbt.beats++; // correction for 1-based counting, see above for matching operation. + bbt.ticks = metric.start().ticks + (uint32_t)round((double)frame_diff / ticks_per_frame); + uint32_t xtra_beats = bbt.ticks / (uint32_t)BBT_Time::ticks_per_beat; + bbt.ticks %= (uint32_t)BBT_Time::ticks_per_beat; + + bbt.beats = metric.start().beats + xtra_beats - 1; // correction for 1-based counting, see below for matching operation. + bbt.bars = metric.start().bars + (uint32_t)floor((double)bbt.beats / beats_per_bar); + bbt.beats = (uint32_t)fmod((double)bbt.beats, beats_per_bar); + + /* if we have a fractional number of beats per bar, we see if + we're in the last beat (the fractional one). if so, we + round ticks appropriately and bump to the next bar. */ + double beat_fraction = beats_per_bar - floor(beats_per_bar); + /* XXX one problem here is that I'm not sure how to handle + fractional beats that don't evenly divide ticks_per_beat. + If they aren't handled consistently, I would guess we'll + continue to have strange discrepancies occuring. Perhaps + this will also behave badly in the case of meters like + 0.1/4, but I can't be bothered to test that. + */ + uint32_t ticks_on_last_beat = (uint32_t)floor(BBT_Time::ticks_per_beat * beat_fraction); + + if (bbt.beats > (uint32_t)floor(beats_per_bar) && bbt.ticks >= ticks_on_last_beat) { + bbt.ticks -= ticks_on_last_beat; + bbt.beats = 0; + bbt.bars++; + } + + bbt.beats++; // correction for 1-based counting, see above for matching operation. // cerr << "-----\t RETURN " << bbt << endl; } -nframes_t -TempoMap::count_frames_between ( const BBT_Time& start, const BBT_Time& end) const +framecnt_t +TempoMap::count_frames_between (const BBT_Time& start, const BBT_Time& end) const { - /* for this to work with fractional measure types, start and end have to be "legal" BBT types, - that means that the beats and ticks should be inside a bar + /* for this to work with fractional measure types, start and end have to be + "legal" BBT types, that means that the beats and ticks should be inside + a bar */ - nframes_t frames = 0; - nframes_t start_frame = 0; - nframes_t end_frame = 0; + framecnt_t frames = 0; + framepos_t start_frame = 0; + framepos_t end_frame = 0; TempoMetric m = metric_at (start); uint32_t bar_offset = start.bars - m.start().bars; double beat_offset = bar_offset*m.meter().beats_per_bar() - (m.start().beats-1) + (start.beats -1) - + start.ticks/Meter::ticks_per_beat; + + start.ticks/BBT_Time::ticks_per_beat; - start_frame = m.frame() + (nframes_t) rint( beat_offset * m.tempo().frames_per_beat(_frame_rate, m.meter())); + start_frame = m.frame() + (framepos_t) rint(beat_offset * m.tempo().frames_per_beat(_frame_rate, m.meter())); m = metric_at(end); bar_offset = end.bars - m.start().bars; beat_offset = bar_offset * m.meter().beats_per_bar() - (m.start().beats -1) + (end.beats - 1) - + end.ticks/Meter::ticks_per_beat; + + end.ticks/BBT_Time::ticks_per_beat; - end_frame = m.frame() + (nframes_t) rint(beat_offset * m.tempo().frames_per_beat(_frame_rate, m.meter())); + end_frame = m.frame() + (framepos_t) rint(beat_offset * m.tempo().frames_per_beat(_frame_rate, m.meter())); frames = end_frame - start_frame; @@ -879,12 +909,12 @@ TempoMap::count_frames_between ( const BBT_Time& start, const BBT_Time& end) con } -nframes_t +framecnt_t TempoMap::count_frames_between_metrics (const Meter& meter, const Tempo& tempo, const BBT_Time& start, const BBT_Time& end) const { - /* this is used in timestamping the metrics by actually counting the beats */ + /* this is used in timestamping the metrics by actually counting the beats */ - nframes_t frames = 0; + framecnt_t frames = 0; uint32_t bar = start.bars; double beat = (double) start.beats; double beats_counted = 0; @@ -923,24 +953,24 @@ TempoMap::count_frames_between_metrics (const Meter& meter, const Tempo& tempo, // << " fpb was " << beat_frames // << endl; - frames = (nframes_t) floor (beats_counted * beat_frames); + frames = (framecnt_t) llrint (floor (beats_counted * beat_frames)); return frames; } -nframes_t +framepos_t TempoMap::frame_time (const BBT_Time& bbt) const { BBT_Time start ; /* 1|1|0 */ - return count_frames_between ( start, bbt); + return count_frames_between (start, bbt); } -nframes_t -TempoMap::bbt_duration_at (nframes_t pos, const BBT_Time& bbt, int dir) const +framecnt_t +TempoMap::bbt_duration_at (framepos_t pos, const BBT_Time& bbt, int dir) const { - nframes_t frames = 0; + framecnt_t frames = 0; BBT_Time when; bbt_time(pos, when); @@ -953,11 +983,10 @@ TempoMap::bbt_duration_at (nframes_t pos, const BBT_Time& bbt, int dir) const return frames; } -nframes_t +framecnt_t TempoMap::bbt_duration_at_unlocked (const BBT_Time& when, const BBT_Time& bbt, int dir) const { - - nframes_t frames = 0; + framecnt_t frames = 0; double beats_per_bar; BBT_Time result; @@ -969,15 +998,12 @@ TempoMap::bbt_duration_at_unlocked (const BBT_Time& when, const BBT_Time& bbt, i TempoMetric metric = metric_at(result); beats_per_bar = metric.meter().beats_per_bar(); - - - /*reduce things to legal bbt values - we have to handle possible fractional=shorter beats at the end of measures - and things like 0|11|9000 as a duration in a 4.5/4 measure - the musical decision is that the fractional beat is also a beat , although a shorter one + /* Reduce things to legal bbt values we have to handle possible + fractional=shorter beats at the end of measures and things like 0|11|9000 + as a duration in a 4.5/4 measure the musical decision is that the + fractional beat is also a beat , although a shorter one */ - if (dir >= 0) { result.beats = when.beats + bbt.beats; result.ticks = when.ticks + bbt.ticks; @@ -989,17 +1015,20 @@ TempoMap::bbt_duration_at_unlocked (const BBT_Time& when, const BBT_Time& bbt, i beats_per_bar = metric.meter().beats_per_bar(); } - /*we now counted the beats and landed in the target measure, now deal with ticks - this seems complicated, but we want to deal with the corner case of a sequence of time signatures like 0.2/4-0.7/4 - and with request like bbt = 3|2|9000 ,so we repeat the same loop but add ticks + + /* We now counted the beats and landed in the target measure, now deal + with ticks this seems complicated, but we want to deal with the + corner case of a sequence of time signatures like 0.2/4-0.7/4 and + with request like bbt = 3|2|9000 ,so we repeat the same loop but add + ticks */ /* of course gtk_ardour only allows bar with at least 1.0 beats ..... */ - uint32_t ticks_at_beat = (uint32_t) ( result.beats == ceil(beats_per_bar) ? - (1 - (ceil(beats_per_bar) - beats_per_bar))* Meter::ticks_per_beat - : Meter::ticks_per_beat ); + uint32_t ticks_at_beat = (uint32_t) (result.beats == ceil(beats_per_bar) ? + (1 - (ceil(beats_per_bar) - beats_per_bar))* BBT_Time::ticks_per_beat + : BBT_Time::ticks_per_beat ); while (result.ticks >= ticks_at_beat) { result.beats++; @@ -1010,24 +1039,22 @@ TempoMap::bbt_duration_at_unlocked (const BBT_Time& when, const BBT_Time& bbt, i metric = metric_at(result); // maybe there is a meter change beats_per_bar = metric.meter().beats_per_bar(); } - ticks_at_beat= (uint32_t) ( result.beats == ceil(beats_per_bar) ? - (1 - (ceil(beats_per_bar) - beats_per_bar) ) * Meter::ticks_per_beat - : Meter::ticks_per_beat); - + ticks_at_beat= (uint32_t) (result.beats == ceil(beats_per_bar) ? + (1 - (ceil(beats_per_bar) - beats_per_bar) ) * BBT_Time::ticks_per_beat + : BBT_Time::ticks_per_beat); } } else { uint32_t b = bbt.beats; - /* count beats */ - while( b > when.beats ) { - - result.bars = max(1U,result.bars-- ) ; + /* count beats */ + while (b > when.beats) { + --result.bars; + result.bars = max(1U, result.bars); metric = metric_at(result); // maybe there is a meter change beats_per_bar = metric.meter().beats_per_bar(); if (b >= ceil(beats_per_bar)) { - b -= (uint32_t) ceil(beats_per_bar); } else { b = (uint32_t) ceil(beats_per_bar) - b + when.beats ; @@ -1035,26 +1062,27 @@ TempoMap::bbt_duration_at_unlocked (const BBT_Time& when, const BBT_Time& bbt, i } result.beats = when.beats - b; - /*count ticks */ + /* count ticks */ if (bbt.ticks <= when.ticks) { result.ticks = when.ticks - bbt.ticks; } else { - uint32_t ticks_at_beat= (uint32_t) Meter::ticks_per_beat; + uint32_t ticks_at_beat= (uint32_t) BBT_Time::ticks_per_beat; uint32_t t = bbt.ticks - when.ticks; do { if (result.beats == 1) { - result.bars = max(1U, result.bars-- ) ; + --result.bars; + result.bars = max(1U, result.bars) ; metric = metric_at(result); // maybe there is a meter change beats_per_bar = metric.meter().beats_per_bar(); result.beats = (uint32_t) ceil(beats_per_bar); - ticks_at_beat = (uint32_t) ((1 - (ceil(beats_per_bar) - beats_per_bar)) * Meter::ticks_per_beat) ; + ticks_at_beat = (uint32_t) ((1 - (ceil(beats_per_bar) - beats_per_bar)) * BBT_Time::ticks_per_beat) ; } else { - result.beats --; - ticks_at_beat = (uint32_t) Meter::ticks_per_beat; + --result.beats; + ticks_at_beat = (uint32_t) BBT_Time::ticks_per_beat; } if (t <= ticks_at_beat) { @@ -1069,8 +1097,8 @@ TempoMap::bbt_duration_at_unlocked (const BBT_Time& when, const BBT_Time& bbt, i } - if (dir < 0 ) { - frames = count_frames_between( result,when); + if (dir < 0) { + frames = count_frames_between(result, when); } else { frames = count_frames_between(when,result); } @@ -1080,29 +1108,28 @@ TempoMap::bbt_duration_at_unlocked (const BBT_Time& when, const BBT_Time& bbt, i -nframes_t -TempoMap::round_to_bar (nframes_t fr, int dir) +framepos_t +TempoMap::round_to_bar (framepos_t fr, int dir) { - { - Glib::RWLock::ReaderLock lm (lock); + { + Glib::RWLock::ReaderLock lm (lock); return round_to_type (fr, dir, Bar); } } -nframes_t -TempoMap::round_to_beat (nframes_t fr, int dir) +framepos_t +TempoMap::round_to_beat (framepos_t fr, int dir) { - { - Glib::RWLock::ReaderLock lm (lock); + { + Glib::RWLock::ReaderLock lm (lock); return round_to_type (fr, dir, Beat); } } -nframes_t -TempoMap::round_to_beat_subdivision (nframes_t fr, int sub_num, int dir) +framepos_t +TempoMap::round_to_beat_subdivision (framepos_t fr, int sub_num, int dir) { - BBT_Time the_beat; uint32_t ticks_one_half_subdivisions_worth; uint32_t ticks_one_subdivisions_worth; @@ -1110,7 +1137,7 @@ TempoMap::round_to_beat_subdivision (nframes_t fr, int sub_num, int dir) bbt_time(fr, the_beat); - ticks_one_subdivisions_worth = (uint32_t)Meter::ticks_per_beat / sub_num; + ticks_one_subdivisions_worth = (uint32_t)BBT_Time::ticks_per_beat / sub_num; ticks_one_half_subdivisions_worth = ticks_one_subdivisions_worth / 2; if (dir > 0) { @@ -1129,13 +1156,7 @@ TempoMap::round_to_beat_subdivision (nframes_t fr, int sub_num, int dir) difference = ticks_one_subdivisions_worth - mod; } - if (the_beat.ticks + difference >= (uint32_t)Meter::ticks_per_beat) { - the_beat.beats++; - the_beat.ticks += difference; - the_beat.ticks -= (uint32_t)Meter::ticks_per_beat; - } else { - the_beat.ticks += difference; - } + the_beat = bbt_add (the_beat, BBT_Time (0, 0, difference)); } else if (dir < 0) { @@ -1146,27 +1167,19 @@ TempoMap::round_to_beat_subdivision (nframes_t fr, int sub_num, int dir) if (mod == 0) { /* right on the subdivision, so the difference is just the subdivision ticks */ difference = ticks_one_subdivisions_worth; - cerr << "On the sub, move by 1 sub = " << difference << endl; } else { /* not on subdivision, compute distance to previous subdivision, which is just the modulus. */ difference = mod; - cerr << "off the sub, move by 1 sub = " << difference << endl; } - - cerr << "ticks = " << the_beat.ticks << endl; - - if (the_beat.ticks < difference) { - cerr << "backup beats, set ticks to " - << (uint32_t)Meter::ticks_per_beat - difference << endl; - the_beat.beats--; - the_beat.ticks = (uint32_t)Meter::ticks_per_beat - difference; - } else { - cerr << " reduce ticks\n"; - the_beat.ticks -= difference; + try { + the_beat = bbt_subtract (the_beat, BBT_Time (0, 0, difference)); + } catch (...) { + /* can't go backwards from wherever pos is, so just return it */ + return fr; } } else { @@ -1174,13 +1187,7 @@ TempoMap::round_to_beat_subdivision (nframes_t fr, int sub_num, int dir) if (the_beat.ticks % ticks_one_subdivisions_worth > ticks_one_half_subdivisions_worth) { difference = ticks_one_subdivisions_worth - (the_beat.ticks % ticks_one_subdivisions_worth); - if (the_beat.ticks + difference >= (uint32_t)Meter::ticks_per_beat) { - the_beat.beats++; - the_beat.ticks += difference; - the_beat.ticks -= (uint32_t)Meter::ticks_per_beat; - } else { - the_beat.ticks += difference; - } + the_beat = bbt_add (the_beat, BBT_Time (0, 0, difference)); } else { // difference = ticks_one_subdivisions_worth - (the_beat.ticks % ticks_one_subdivisions_worth); the_beat.ticks -= the_beat.ticks % ticks_one_subdivisions_worth; @@ -1190,59 +1197,117 @@ TempoMap::round_to_beat_subdivision (nframes_t fr, int sub_num, int dir) return frame_time (the_beat); } -nframes_t -TempoMap::round_to_type (nframes_t frame, int dir, BBTPointType type) +framepos_t +TempoMap::round_to_type (framepos_t frame, int dir, BBTPointType type) { TempoMetric metric = metric_at (frame); BBT_Time bbt; BBT_Time start; - bbt_time_with_metric (frame, bbt, metric); + BBT_Time one_bar (1,0,0); + BBT_Time one_beat (0,1,0); + bbt_time_with_metric (frame, bbt, metric); switch (type) { case Bar: - DEBUG_TRACE(DEBUG::SnapBBT, string_compose ("round from %1 (%3) to bars in direction %2\n", frame, (dir < 0 ? "back" : "forward"), bbt)); + DEBUG_TRACE(DEBUG::SnapBBT, string_compose ("round from %1 (%3) to bars in direction %2\n", frame, dir, bbt)); + if (dir < 0) { - if (bbt.bars > 1) { - bbt.bars--; + + /* find bar position preceding frame */ + + try { + bbt = bbt_subtract (bbt, one_bar); + } + + catch (...) { + return frame; } + + } else if (dir > 0) { - if (bbt.beats > 0) { - bbt.bars++; - } else if (metric.frame() < frame) { - bbt.bars++; + + /* find bar position following frame */ + + try { + bbt = bbt_add (bbt, one_bar, metric); + } + catch (...) { + return frame; } + } else { - if (bbt.beats > metric.meter().beats_per_bar()/2) { + + /* "true" rounding */ + + float midbar_beats; + float midbar_ticks; + + midbar_beats = metric.meter().beats_per_bar() / 2 + 1; + midbar_ticks = BBT_Time::ticks_per_beat * fmod (midbar_beats, 1.0f); + midbar_beats = floor (midbar_beats); + + BBT_Time midbar (bbt.bars, lrintf (midbar_beats), lrintf (midbar_ticks)); + + if (bbt < midbar) { + /* round down */ + bbt.beats = 1; + bbt.ticks = 0; + } else { + /* round up */ bbt.bars++; + bbt.beats = 1; + bbt.ticks = 0; } } + /* force beats & ticks to their values at the start of a bar */ bbt.beats = 1; bbt.ticks = 0; break; case Beat: DEBUG_TRACE(DEBUG::SnapBBT, string_compose ("round from %1 (%3) to beat in direction %2\n", frame, (dir < 0 ? "back" : "forward"), bbt)); + if (dir < 0) { - if (bbt.beats > 1) { - bbt.beats--; - } + + /* find beat position preceding frame */ + + try { + bbt = bbt_subtract (bbt, one_beat); + } + + catch (...) { + return frame; + } + } else if (dir > 0) { - if (bbt.ticks > 0) { - bbt.beats++; - } else if (metric.frame() < frame) { - bbt.beats++; + + /* find beat position following frame */ + + try { + bbt = bbt_add (bbt, one_beat, metric); } + catch (...) { + return frame; + } + } else { - if (bbt.ticks >= (Meter::ticks_per_beat/2)) { - bbt.beats++; + + /* "true" rounding */ + + /* round to nearest beat */ + if (bbt.ticks >= (BBT_Time::ticks_per_beat/2)) { + + try { + bbt = bbt_add (bbt, one_beat, metric); + } + catch (...) { + return frame; + } } } - if (bbt.beats > ceil(metric.meter().beats_per_bar()) ) { - bbt.beats = 1; - bbt.bars++; - } + /* force ticks to the value at the start of a beat */ bbt.ticks = 0; break; @@ -1253,7 +1318,7 @@ TempoMap::round_to_type (nframes_t frame, int dir, BBTPointType type) } TempoMap::BBTPointList * -TempoMap::get_points (nframes_t lower, nframes_t upper) const +TempoMap::get_points (framepos_t lower, framepos_t upper) const { Metrics::const_iterator i; @@ -1272,7 +1337,7 @@ TempoMap::get_points (nframes_t lower, nframes_t upper) const double delta_bars; double delta_beats; double dummy; - nframes_t limit; + framepos_t limit; meter = &first_meter (); tempo = &first_tempo (); @@ -1349,7 +1414,7 @@ TempoMap::get_points (nframes_t lower, nframes_t upper) const if (beat == 1) { if (current >= lower) { // cerr << "Add Bar at " << bar << "|1" << " @ " << current << endl; - points->push_back (BBTPoint (*meter, *tempo,(nframes_t)rint(current), Bar, bar, 1)); + points->push_back (BBTPoint (*meter, *tempo,(framepos_t)rint(current), Bar, bar, 1)); } } @@ -1358,10 +1423,10 @@ TempoMap::get_points (nframes_t lower, nframes_t upper) const beat_frame = current; - while (beat <= ceil( beats_per_bar) && beat_frame < limit) { + while (beat <= ceil(beats_per_bar) && beat_frame < limit) { if (beat_frame >= lower) { // cerr << "Add Beat at " << bar << '|' << beat << " @ " << beat_frame << endl; - points->push_back (BBTPoint (*meter, *tempo, (nframes_t) rint(beat_frame), Beat, bar, beat)); + points->push_back (BBTPoint (*meter, *tempo, (framepos_t) rint(beat_frame), Beat, bar, beat)); } beat_frame += beat_frames; current+= beat_frames; @@ -1442,10 +1507,10 @@ TempoMap::get_points (nframes_t lower, nframes_t upper) const } const TempoSection& -TempoMap::tempo_section_at (nframes_t frame) +TempoMap::tempo_section_at (framepos_t frame) const { Glib::RWLock::ReaderLock lm (lock); - Metrics::iterator i; + Metrics::const_iterator i; TempoSection* prev = 0; for (i = metrics->begin(); i != metrics->end(); ++i) { @@ -1469,7 +1534,7 @@ TempoMap::tempo_section_at (nframes_t frame) } const Tempo& -TempoMap::tempo_at (nframes_t frame) const +TempoMap::tempo_at (framepos_t frame) const { TempoMetric m (metric_at (frame)); return m.tempo(); @@ -1477,7 +1542,7 @@ TempoMap::tempo_at (nframes_t frame) const const Meter& -TempoMap::meter_at (nframes_t frame) const +TempoMap::meter_at (framepos_t frame) const { TempoMetric m (metric_at (frame)); return m.meter(); @@ -1490,9 +1555,9 @@ TempoMap::get_state () XMLNode *root = new XMLNode ("TempoMap"); { - Glib::RWLock::ReaderLock lm (lock); + Glib::RWLock::ReaderLock lm (lock); for (i = metrics->begin(); i != metrics->end(); ++i) { - root->add_child_nocopy ((*i)->get_state()); + root->add_child_nocopy ((*i)->get_state()); } } @@ -1550,7 +1615,7 @@ TempoMap::set_state (const XMLNode& node, int /*version*/) } } - StateChanged (Change (0)); + PropertyChanged (PropertyChange ()); return 0; } @@ -1604,15 +1669,466 @@ TempoMap::n_meters() const } void -TempoMap::insert_time (nframes_t where, nframes_t amount) +TempoMap::insert_time (framepos_t where, framecnt_t amount) { for (Metrics::iterator i = metrics->begin(); i != metrics->end(); ++i) { - if ((*i)->frame() >= where) { + if ((*i)->frame() >= where && (*i)->movable ()) { (*i)->set_frame ((*i)->frame() + amount); } } timestamp_metrics (false); - StateChanged (Change (0)); + PropertyChanged (PropertyChange ()); +} + +BBT_Time +TempoMap::bbt_add (const BBT_Time& start, const BBT_Time& other) const +{ + TempoMetric metric = metric_at (start); + return bbt_add (start, other, metric); +} + +/** + * add the BBT interval @param increment to @param start and return the result + */ +BBT_Time +TempoMap::bbt_add (const BBT_Time& start, const BBT_Time& increment, const TempoMetric& /*metric*/) const +{ + BBT_Time result = start; + BBT_Time op = increment; /* argument is const, but we need to modify it */ + uint32_t ticks = result.ticks + op.ticks; + + if (ticks >= BBT_Time::ticks_per_beat) { + op.beats++; + result.ticks = ticks % (uint32_t) BBT_Time::ticks_per_beat; + } else { + result.ticks += op.ticks; + } + + /* now comes the complicated part. we have to add one beat a time, + checking for a new metric on every beat. + */ + + /* grab all meter sections */ + + list meter_sections; + + for (Metrics::const_iterator x = metrics->begin(); x != metrics->end(); ++x) { + const MeterSection* ms; + if ((ms = dynamic_cast(*x)) != 0) { + meter_sections.push_back (ms); + } + } + + assert (!meter_sections.empty()); + + list::const_iterator next_meter; + const Meter* meter = 0; + + /* go forwards through the meter sections till we get to the one + covering the current value of result. this positions i to point to + the next meter section too, or the end. + */ + + for (next_meter = meter_sections.begin(); next_meter != meter_sections.end(); ++next_meter) { + + if (result < (*next_meter)->start()) { + /* this metric is past the result time. stop looking, we have what we need */ + break; + } + + if (result == (*next_meter)->start()) { + /* this meter section starts at result, push i beyond it so that it points + to the NEXT section, opwise we will get stuck later, and use this meter section. + */ + meter = *next_meter; + ++next_meter; + break; + } + + meter = *next_meter; + } + + assert (meter != 0); + + /* OK, now have the meter for the bar start we are on, and i is an iterator + that points to the metric after the one we are currently dealing with + (or to metrics->end(), of course) + */ + + while (op.beats) { + + /* given the current meter, have we gone past the end of the bar ? */ + + if (result.beats >= meter->beats_per_bar()) { + /* move to next bar, first beat */ + result.bars++; + result.beats = 1; + } else { + result.beats++; + } + + /* one down ... */ + + op.beats--; + + /* check if we need to use a new meter section: has adding beats to result taken us + to or after the start of the next meter section? in which case, use it. + */ + + if (next_meter != meter_sections.end() && (((*next_meter)->start () < result) || (result == (*next_meter)->start()))) { + meter = *next_meter; + ++next_meter; + } + } + + /* finally, add bars */ + + result.bars += op.bars++; + + return result; +} + +/** + * subtract the BBT interval @param decrement from @param start and return the result + */ +BBT_Time +TempoMap::bbt_subtract (const BBT_Time& start, const BBT_Time& decrement) const +{ + BBT_Time result = start; + BBT_Time op = decrement; /* argument is const, but we need to modify it */ + + if (op.ticks > result.ticks) { + /* subtract an extra beat later; meanwhile set ticks to the right "carry" value */ + op.beats++; + result.ticks = BBT_Time::ticks_per_beat - (op.ticks - result.ticks); + } else { + result.ticks -= op.ticks; + } + + /* now comes the complicated part. we have to subtract one beat a time, + checking for a new metric on every beat. + */ + + /* grab all meter sections */ + + list meter_sections; + + for (Metrics::const_iterator x = metrics->begin(); x != metrics->end(); ++x) { + const MeterSection* ms; + if ((ms = dynamic_cast(*x)) != 0) { + meter_sections.push_back (ms); + } + } + + assert (!meter_sections.empty()); + + /* go backwards through the meter sections till we get to the one + covering the current value of result. this positions i to point to + the next (previous) meter section too, or the end. + */ + + const MeterSection* meter = 0; + list::reverse_iterator next_meter; // older versions of GCC don't + // support const_reverse_iterator::operator!=() + + for (next_meter = meter_sections.rbegin(); next_meter != meter_sections.rend(); ++next_meter) { + + /* when we find the first meter section that is before or at result, use it, + and set next_meter to the previous one + */ + + if ((*next_meter)->start() < result || (*next_meter)->start() == result) { + meter = *next_meter; + ++next_meter; + break; + } + } + + assert (meter != 0); + + /* OK, now have the meter for the bar start we are on, and i is an iterator + that points to the metric after the one we are currently dealing with + (or to metrics->end(), of course) + */ + + while (op.beats) { + + /* have we reached the start of the bar? if so, move to the last beat of the previous + bar. opwise, just step back 1 beat. + */ + + if (result.beats == 1) { + + /* move to previous bar, last beat */ + + if (result.bars <= 1) { + /* i'm sorry dave, i can't do that */ + throw std::out_of_range ("illegal BBT subtraction"); + } + + result.bars--; + result.beats = meter->beats_per_bar(); + } else { + + /* back one beat */ + + result.beats--; + } + + /* one down ... */ + op.beats--; + + /* check if we need to use a new meter section: has subtracting beats to result taken us + to before the start of the current meter section? in which case, use the prior one. + */ + + if (result < meter->start() && next_meter != meter_sections.rend()) { + meter = *next_meter; + ++next_meter; + } + } + + /* finally, subtract bars */ + + if (op.bars >= result.bars) { + /* i'm sorry dave, i can't do that */ + throw std::out_of_range ("illegal BBT subtraction"); + } + + result.bars -= op.bars; + return result; +} + +/** Add the BBT interval op to pos and return the result */ +framepos_t +TempoMap::framepos_plus_bbt (framepos_t pos, BBT_Time op) const +{ + /* XXX: this is a little inaccurate as small errors are introduced + every time a probably-fractional product of something and + frames_per_beat is rounded. Other errors can be introduced + by op.ticks' integer nature. + */ + + Metrics::const_iterator i; + const MeterSection* meter; + const MeterSection* m; + const TempoSection* tempo; + const TempoSection* t; + framecnt_t frames_per_beat; + + meter = &first_meter (); + tempo = &first_tempo (); + + assert (meter); + assert (tempo); + + /* find the starting metrics for tempo & meter */ + + for (i = metrics->begin(); i != metrics->end(); ++i) { + + if ((*i)->frame() > pos) { + break; + } + + if ((t = dynamic_cast(*i)) != 0) { + tempo = t; + } else if ((m = dynamic_cast(*i)) != 0) { + meter = m; + } + } + + /* We now have: + + meter -> the Meter for "pos" + tempo -> the Tempo for "pos" + i -> for first new metric after "pos", possibly metrics->end() + */ + + /* now comes the complicated part. we have to add one beat a time, + checking for a new metric on every beat. + */ + + frames_per_beat = tempo->frames_per_beat (_frame_rate, *meter); + + while (op.bars) { + + pos += llrint (frames_per_beat * meter->beats_per_bar()); + op.bars--; + + /* check if we need to use a new metric section: has adding frames moved us + to or after the start of the next metric section? in which case, use it. + */ + + if (i != metrics->end()) { + if ((*i)->frame() <= pos) { + + if ((t = dynamic_cast(*i)) != 0) { + tempo = t; + } else if ((m = dynamic_cast(*i)) != 0) { + meter = m; + } + ++i; + frames_per_beat = tempo->frames_per_beat (_frame_rate, *meter); + + } + } + + } + + while (op.beats) { + + /* given the current meter, have we gone past the end of the bar ? */ + + pos += frames_per_beat; + op.beats--; + + /* 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) { + + if ((t = dynamic_cast(*i)) != 0) { + tempo = t; + } else if ((m = dynamic_cast(*i)) != 0) { + meter = m; + } + ++i; + frames_per_beat = tempo->frames_per_beat (_frame_rate, *meter); + } + } + } + + if (op.ticks) { + if (op.ticks >= BBT_Time::ticks_per_beat) { + pos += frames_per_beat; + pos += llrint (frames_per_beat * ((op.ticks % (uint32_t) BBT_Time::ticks_per_beat) / (double) BBT_Time::ticks_per_beat)); + } else { + pos += llrint (frames_per_beat * (op.ticks / (double) BBT_Time::ticks_per_beat)); + } + } + + return pos; +} + +/** Count the number of beats that are equivalent to distance when starting at pos */ +double +TempoMap::framewalk_to_beats (framepos_t pos, framecnt_t distance) const +{ + Metrics::const_iterator i; + double beats = 0; + const MeterSection* meter; + const MeterSection* m; + const TempoSection* tempo; + const TempoSection* t; + double frames_per_beat; + + double ddist = distance; + double dpos = pos; + + meter = &first_meter (); + tempo = &first_tempo (); + + assert (meter); + assert (tempo); + + /* find the starting metrics for tempo & meter */ + + for (i = metrics->begin(); i != metrics->end(); ++i) { + + if ((*i)->frame() > pos) { + break; + } + + if ((t = dynamic_cast(*i)) != 0) { + tempo = t; + } else if ((m = dynamic_cast(*i)) != 0) { + meter = m; + } + } + + /* We now have: + + meter -> the Meter for "pos" + tempo -> the Tempo for "pos" + i -> for first new metric after "pos", possibly metrics->end() + */ + + /* now comes the complicated part. we have to add one beat a time, + checking for a new metric on every beat. + */ + + frames_per_beat = tempo->frames_per_beat (_frame_rate, *meter); + + while (ddist > 0) { + + /* if we're nearly at the end, but have a fractional beat left, + compute the fraction and then its all over + */ + + if (ddist < frames_per_beat) { + beats += ddist / frames_per_beat; + break; + } + + /* walk one beat */ + + ddist -= frames_per_beat; + dpos += frames_per_beat; + beats += 1.0; + + /* 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() <= (framepos_t) dpos) { + + if ((t = dynamic_cast(*i)) != 0) { + tempo = t; + } else if ((m = dynamic_cast(*i)) != 0) { + meter = m; + } + ++i; + frames_per_beat = tempo->frames_per_beat (_frame_rate, *meter); + } + } + + } + + return beats; +} + + +/** 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 (MetricSection* other, bool with_bbt) const +{ + if (with_bbt) { + if (start() == other->start()) { + return 0; + } else if (start() < other->start()) { + return -1; + } else { + return 1; + } + } else { + if (frame() == other->frame()) { + return 0; + } else if (frame() < other->frame()) { + return -1; + } else { + return 1; + } + } + + /* NOTREACHED */ + return 0; }