X-Git-Url: https://main.carlh.net/gitweb/?a=blobdiff_plain;f=libs%2Fardour%2Ftempo.cc;h=9dcce637e63d839481003c359ffe034e91047fb3;hb=d9c9acaa80d1701b210eae82f5a365cb3e63c56a;hp=780f5c6a5df6513ff903cdd8e6706595ad5ecf5b;hpb=bb457bb960c5bd7ed538f9d31477293415739f68;p=ardour.git diff --git a/libs/ardour/tempo.cc b/libs/ardour/tempo.cc index 780f5c6a5d..9dcce637e6 100644 --- a/libs/ardour/tempo.cc +++ b/libs/ardour/tempo.cc @@ -1,5 +1,5 @@ /* - Copyright (C) 2000-2002 Paul Davis + Copyright (C) 2000-2002 Paul Davis This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -18,16 +18,18 @@ */ #include +#include + #include #include -#include #include -#include -#include -#include +#include "pbd/xml++.h" +#include "ardour/debug.h" +#include "ardour/tempo.h" +#include "ardour/utils.h" #include "i18n.h" #include @@ -91,7 +93,7 @@ TempoSection::TempoSection (const XMLNode& node) error << _("TempoSection XML node has an illegal \"beats_per_minute\" value") << endmsg; throw failed_constructor(); } - + if ((prop = node.property ("note-type")) == 0) { /* older session, make note type be quarter by default */ _note_type = 4.0; @@ -107,7 +109,7 @@ TempoSection::TempoSection (const XMLNode& node) throw failed_constructor(); } - set_movable (prop->value() == "yes"); + set_movable (string_is_affirmative (prop->value())); } XMLNode& @@ -117,7 +119,7 @@ TempoSection::get_state() const char buf[256]; LocaleGuard lg (X_("POSIX")); - snprintf (buf, sizeof (buf), "%" PRIu32 "|%" PRIu32 "|%" PRIu32, + snprintf (buf, sizeof (buf), "%" PRIu32 "|%" PRIu32 "|%" PRIu32, start().bars, start().beats, start().ticks); @@ -172,7 +174,7 @@ MeterSection::MeterSection (const XMLNode& node) error << _("MeterSection XML node has no \"note-type\" property") << endmsg; throw failed_constructor(); } - + if (sscanf (prop->value().c_str(), "%lf", &_note_type) != 1 || _note_type < 0.0) { error << _("MeterSection XML node has an illegal \"note-type\" value") << endmsg; throw failed_constructor(); @@ -183,7 +185,7 @@ MeterSection::MeterSection (const XMLNode& node) throw failed_constructor(); } - set_movable (prop->value() == "yes"); + set_movable (string_is_affirmative (prop->value())); } XMLNode& @@ -193,7 +195,7 @@ MeterSection::get_state() const char buf[256]; LocaleGuard lg (X_("POSIX")); - snprintf (buf, sizeof (buf), "%" PRIu32 "|%" PRIu32 "|%" PRIu32, + snprintf (buf, sizeof (buf), "%" PRIu32 "|%" PRIu32 "|%" PRIu32, start().bars, start().beats, start().ticks); @@ -216,13 +218,13 @@ struct MetricSectionSorter { } }; -TempoMap::TempoMap (nframes_t fr) +TempoMap::TempoMap (nframes64_t fr) { metrics = new Metrics; _frame_rate = fr; last_bbt_valid = false; BBT_Time start; - + start.bars = 1; start.beats = 1; start.ticks = 0; @@ -234,7 +236,7 @@ TempoMap::TempoMap (nframes_t fr) m->set_movable (false); /* note: frame time is correct (zero) for both of these */ - + metrics->push_back (t); metrics->push_back (m); } @@ -246,29 +248,49 @@ TempoMap::~TempoMap () int TempoMap::move_metric_section (MetricSection& section, const BBT_Time& when) { - if (when == section.start()) { + if (when == section.start() || !section.movable()) { return -1; } - if (!section.movable()) { - return 1; - } - Glib::RWLock::WriterLock lm (lock); MetricSectionSorter cmp; - BBT_Time corrected (when); - - if (dynamic_cast(§ion) != 0) { - if (corrected.beats > 1) { - corrected.beats = 1; - corrected.bars++; + + if (when.beats != 1) { + + /* position by audio frame, then recompute BBT timestamps from the audio ones */ + + nframes64_t frame = frame_time (when); + // cerr << "nominal frame time = " << frame << endl; + + nframes64_t prev_frame = round_to_type (frame, -1, Beat); + nframes64_t next_frame = round_to_type (frame, 1, Beat); + + // cerr << "previous beat at " << prev_frame << " next at " << next_frame << endl; + + /* use the closest beat */ + + if ((frame - prev_frame) < (next_frame - frame)) { + frame = prev_frame; + } else { + frame = next_frame; } + + // cerr << "actual frame time = " << frame << endl; + section.set_frame (frame); + // cerr << "frame time = " << section.frame() << endl; + timestamp_metrics (false); + // cerr << "new BBT time = " << section.start() << endl; + metrics->sort (cmp); + + } else { + + /* positioned at bar start already, so just put it there */ + + section.set_start (when); + metrics->sort (cmp); + timestamp_metrics (true); } - corrected.ticks = 0; - section.set_start (corrected); - metrics->sort (cmp); - timestamp_metrics (); return 0; } @@ -288,7 +310,6 @@ TempoMap::move_meter (MeterSection& meter, const BBT_Time& when) StateChanged (Change (0)); } } - void TempoMap::remove_tempo (const TempoSection& tempo) @@ -345,26 +366,32 @@ TempoMap::remove_meter (const MeterSection& tempo) } void -TempoMap::do_insert (MetricSection* section) +TempoMap::do_insert (MetricSection* section, bool with_bbt) { Metrics::iterator i; for (i = metrics->begin(); i != metrics->end(); ++i) { - - if ((*i)->start() < section->start()) { - continue; + + if (with_bbt) { + if ((*i)->start() < section->start()) { + continue; + } + } else { + if ((*i)->frame() < section->frame()) { + continue; + } } - + metrics->insert (i, section); break; } - + if (i == metrics->end()) { metrics->insert (metrics->end(), section); } - - timestamp_metrics (); -} + + timestamp_metrics (with_bbt); +} void TempoMap::add_tempo (const Tempo& tempo, BBT_Time where) @@ -373,10 +400,21 @@ 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())); + + do_insert (new TempoSection (where, tempo.beats_per_minute(), tempo.note_type()), true); + } + + StateChanged (Change (0)); +} + +void +TempoMap::add_tempo (const Tempo& tempo, nframes64_t where) +{ + { + Glib::RWLock::WriterLock lm (lock); + do_insert (new TempoSection (where, tempo.beats_per_minute(), tempo.note_type()), false); } StateChanged (Change (0)); @@ -387,24 +425,25 @@ TempoMap::replace_tempo (TempoSection& existing, const Tempo& replacement) { bool replaced = false; - { + { Glib::RWLock::WriterLock lm (lock); Metrics::iterator i; - + for (i = metrics->begin(); i != metrics->end(); ++i) { TempoSection *ts; if ((ts = dynamic_cast(*i)) != 0 && ts == &existing) { - - *((Tempo *) ts) = replacement; + + *((Tempo *) ts) = replacement; replaced = true; - timestamp_metrics (); + timestamp_metrics (true); + break; } } } - + if (replaced) { StateChanged (Change (0)); } @@ -429,10 +468,21 @@ 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())); + do_insert (new MeterSection (where, meter.beats_per_bar(), meter.note_divisor()), true); + } + + StateChanged (Change (0)); +} + +void +TempoMap::add_meter (const Meter& meter, nframes64_t where) +{ + { + Glib::RWLock::WriterLock lm (lock); + do_insert (new MeterSection (where, meter.beats_per_bar(), meter.note_divisor()), false); } StateChanged (Change (0)); @@ -443,28 +493,86 @@ TempoMap::replace_meter (MeterSection& existing, const Meter& replacement) { bool replaced = false; - { + { Glib::RWLock::WriterLock lm (lock); Metrics::iterator i; - + for (i = metrics->begin(); i != metrics->end(); ++i) { MeterSection *ms; if ((ms = dynamic_cast(*i)) != 0 && ms == &existing) { - + *((Meter*) ms) = replacement; replaced = true; - timestamp_metrics (); + timestamp_metrics (true); break; } } } - + if (replaced) { StateChanged (Change (0)); } } +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) { + if ((t = dynamic_cast (*i)) != 0) { + *((Tempo*) t) = newtempo; + StateChanged (Change (0)); + break; + } + } +} + +void +TempoMap::change_existing_tempo_at (nframes64_t where, double beats_per_minute, double note_type) +{ + Tempo newtempo (beats_per_minute, note_type); + + TempoSection* prev; + TempoSection* first; + Metrics::iterator i; + + /* find the TempoSection immediately preceding "where" + */ + + for (first = 0, i = metrics->begin(), prev = 0; i != metrics->end(); ++i) { + + if ((*i)->frame() > where) { + break; + } + + TempoSection* t; + + if ((t = dynamic_cast(*i)) != 0) { + if (!first) { + first = t; + } + prev = t; + } + } + + if (!prev) { + if (!first) { + error << string_compose (_("no tempo sections defined in tempo map - cannot change tempo @ %1"), where) << endmsg; + return; + } + + prev = first; + } + + /* reset */ + + *((Tempo*)prev) = newtempo; + StateChanged (Change (0)); +} + const MeterSection& TempoMap::first_meter () const { @@ -498,49 +606,119 @@ TempoMap::first_tempo () const } void -TempoMap::timestamp_metrics () +TempoMap::timestamp_metrics (bool use_bbt) { Metrics::iterator i; const Meter* meter; const Tempo* tempo; Meter *m; Tempo *t; - nframes_t current; - nframes_t section_frames; - BBT_Time start; - BBT_Time end; meter = &first_meter (); tempo = &first_tempo (); - current = 0; - for (i = metrics->begin(); i != metrics->end(); ++i) { - - end = (*i)->start(); + if (use_bbt) { - section_frames = count_frames_between_metrics (*meter, *tempo, start, end); + // cerr << "\n\n\n ######################\nTIMESTAMP via BBT ##############\n" << endl; - current += section_frames; + nframes64_t current = 0; + nframes64_t section_frames; + BBT_Time start; + BBT_Time end; - start = end; + for (i = metrics->begin(); i != metrics->end(); ++i) { - (*i)->set_frame (current); + end = (*i)->start(); - if ((t = dynamic_cast(*i)) != 0) { - tempo = t; - } else if ((m = dynamic_cast(*i)) != 0) { - meter = m; - } else { - fatal << _("programming error: unhandled MetricSection type") << endmsg; - /*NOTREACHED*/ + section_frames = count_frames_between_metrics (*meter, *tempo, start, end); + + current += section_frames; + + start = end; + + (*i)->set_frame (current); + + if ((t = dynamic_cast(*i)) != 0) { + tempo = t; + } else if ((m = dynamic_cast(*i)) != 0) { + meter = m; + } else { + fatal << _("programming error: unhandled MetricSection type") << endmsg; + /*NOTREACHED*/ + } + } + + } else { + + // cerr << "\n\n\n ######################\nTIMESTAMP 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 + } + + bbt_time_with_metric ((*i)->frame(), bbt, metric); + + // cerr << "timestamp @ " << (*i)->frame() << " with " << bbt.bars << "|" << bbt.beats << "|" << bbt.ticks << " => "; + + + if (first) { + first = false; + } else { + + if (bbt.ticks > Meter::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; + } + } + + //s cerr << bbt.bars << "|" << bbt.beats << "|" << bbt.ticks << endl; + + (*i)->set_start (bbt); + + if ((t = dynamic_cast(*i)) != 0) { + tempo = t; + // cerr << "NEW TEMPO, frame = " << (*i)->frame() << " start = " << (*i)->start() <(*i)) != 0) { + meter = m; + // cerr << "NEW METER, frame = " << (*i)->frame() << " start = " << (*i)->start() <frame ()); m.set_start ((*i)->start ()); } - + return m; } -TempoMap::Metric +TempoMetric TempoMap::metric_at (BBT_Time bbt) const { - Metric m (first_meter(), first_tempo()); + TempoMetric m (first_meter(), first_tempo()); const Meter* meter; const Tempo* tempo; @@ -597,7 +775,7 @@ TempoMap::metric_at (BBT_Time bbt) const } else if ((meter = dynamic_cast(*i)) != 0) { m.set_meter (*meter); } - + m.set_frame ((*i)->frame ()); m.set_start (section_start); } @@ -606,107 +784,108 @@ TempoMap::metric_at (BBT_Time bbt) const } void -TempoMap::bbt_time (nframes_t frame, BBT_Time& bbt) const +TempoMap::bbt_time (nframes64_t frame, BBT_Time& bbt) const { - { - Glib::RWLock::ReaderLock lm (lock); + { + Glib::RWLock::ReaderLock lm (lock); bbt_time_unlocked (frame, bbt); } } void -TempoMap::bbt_time_unlocked (nframes_t frame, BBT_Time& bbt) const +TempoMap::bbt_time_unlocked (nframes64_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 Metric& metric) const +TempoMap::bbt_time_with_metric (nframes64_t frame, BBT_Time& bbt, const TempoMetric& metric) const { - nframes_t frame_diff; + nframes64_t frame_diff; - uint32_t xtra_bars = 0; - double xtra_beats = 0; - double beats = 0; + // 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 frames_per_bar = metric.meter().frames_per_bar (metric.tempo(), _frame_rate); - const double beat_frames = metric.tempo().frames_per_beat (_frame_rate, metric.meter()); + const double ticks_per_frame = metric.tempo().frames_per_beat (_frame_rate, metric.meter()) / Meter::ticks_per_beat; /* now compute how far beyond that point we actually are. */ frame_diff = frame - metric.frame(); - xtra_bars = (uint32_t) floor (frame_diff / frames_per_bar); - frame_diff -= (uint32_t) floor (xtra_bars * frames_per_bar); - xtra_beats = (double) frame_diff / beat_frames; - - - /* and set the returned value */ - - /* and correct beat/bar shifts to match the meter. - remember: beat and bar counting is 1-based, - not zero-based - also the meter may contain a fraction - */ - - bbt.bars = metric.start().bars + xtra_bars; - - beats = (double) metric.start().beats + xtra_beats; - - bbt.bars += (uint32_t) floor(beats/ (beats_per_bar+1) ); - - beats = fmod(beats - 1, beats_per_bar )+ 1.0; - bbt.ticks = (uint32_t)( round((beats - floor(beats)) *(double) Meter::ticks_per_beat)); - bbt.beats = (uint32_t) floor(beats); - + 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. + + // cerr << "-----\t RETURN " << bbt << endl; } - -nframes_t +nframes64_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 "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 */ + nframes64_t frames = 0; + nframes64_t start_frame = 0; + nframes64_t end_frame = 0; - nframes_t frames = 0; - nframes_t start_frame = 0; - nframes_t end_frame = 0; - - Metric m = metric_at(start); + 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) + double beat_offset = bar_offset*m.meter().beats_per_bar() - (m.start().beats-1) + (start.beats -1) + start.ticks/Meter::ticks_per_beat; - start_frame = m.frame() + (nframes_t) rint( beat_offset * m.tempo().frames_per_beat(_frame_rate, m.meter())); + start_frame = m.frame() + (nframes64_t) rint( beat_offset * m.tempo().frames_per_beat(_frame_rate, m.meter())); - m = metric_at(end); + 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) + beat_offset = bar_offset * m.meter().beats_per_bar() - (m.start().beats -1) + (end.beats - 1) + end.ticks/Meter::ticks_per_beat; - end_frame = m.frame() + (nframes_t) rint(beat_offset * m.tempo().frames_per_beat(_frame_rate, m.meter())); + end_frame = m.frame() + (nframes64_t) rint(beat_offset * m.tempo().frames_per_beat(_frame_rate, m.meter())); frames = end_frame - start_frame; return frames; - -} -nframes_t +} + +nframes64_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; + nframes64_t frames = 0; uint32_t bar = start.bars; double beat = (double) start.beats; double beats_counted = 0; @@ -719,29 +898,39 @@ TempoMap::count_frames_between_metrics (const Meter& meter, const Tempo& tempo, frames = 0; while (bar < end.bars || (bar == end.bars && beat < end.beats)) { - + if (beat >= beats_per_bar) { beat = 1; ++bar; ++beats_counted; - } else { - ++beat; - ++beats_counted; + if (beat > beats_per_bar) { + /* this is a fractional beat at the end of a fractional bar - so it should only count for the fraction */ + so it should only count for the fraction + */ + beats_counted -= (ceil(beats_per_bar) - beats_per_bar); } + + } else { + ++beat; + ++beats_counted; } } - - frames = (nframes_t) floor (beats_counted * beat_frames); + + // cerr << "Counted " << beats_counted << " from " << start << " to " << end + // << " bpb were " << beats_per_bar + // << " fpb was " << beat_frames + // << endl; + + frames = (nframes64_t) floor (beats_counted * beat_frames); return frames; - -} -nframes_t +} + +nframes64_t TempoMap::frame_time (const BBT_Time& bbt) const { BBT_Time start ; /* 1|1|0 */ @@ -749,13 +938,13 @@ TempoMap::frame_time (const BBT_Time& bbt) const return count_frames_between ( start, bbt); } -nframes_t -TempoMap::bbt_duration_at (nframes_t pos, const BBT_Time& bbt, int dir) const +nframes64_t +TempoMap::bbt_duration_at (nframes64_t pos, const BBT_Time& bbt, int dir) const { - nframes_t frames = 0; + nframes64_t frames = 0; BBT_Time when; - bbt_time(pos,when); + bbt_time(pos, when); { Glib::RWLock::ReaderLock lm (lock); @@ -765,43 +954,43 @@ TempoMap::bbt_duration_at (nframes_t pos, const BBT_Time& bbt, int dir) const return frames; } -nframes_t +nframes64_t TempoMap::bbt_duration_at_unlocked (const BBT_Time& when, const BBT_Time& bbt, int dir) const { - nframes_t frames = 0; + nframes64_t frames = 0; double beats_per_bar; BBT_Time result; - - result.bars = max(1U,when.bars + dir * bbt.bars) ; + + result.bars = max(1U, when.bars + dir * bbt.bars) ; result.beats = 1; result.ticks = 0; - Metric metric = metric_at(result); + TempoMetric metric = metric_at(result); beats_per_bar = metric.meter().beats_per_bar(); - /*reduce things to legal bbt values + /*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 + the musical decision is that the fractional beat is also a beat , although a shorter one */ - + if (dir >= 0) { result.beats = when.beats + bbt.beats; result.ticks = when.ticks + bbt.ticks; - while (result.beats >= (beats_per_bar+1)) { + while (result.beats >= (beats_per_bar + 1)) { result.bars++; result.beats -= (uint32_t) ceil(beats_per_bar); metric = metric_at(result); // maybe there is a meter change beats_per_bar = metric.meter().beats_per_bar(); - + } - /*we now counted the beats and landed in the target measure, now deal with ticks + /*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 */ @@ -810,43 +999,43 @@ TempoMap::bbt_duration_at_unlocked (const BBT_Time& when, const BBT_Time& bbt, i */ 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 + (1 - (ceil(beats_per_bar) - beats_per_bar))* Meter::ticks_per_beat : Meter::ticks_per_beat ); while (result.ticks >= ticks_at_beat) { result.beats++; result.ticks -= ticks_at_beat; - if (result.beats >= (beats_per_bar+1)) { + if (result.beats >= (beats_per_bar + 1)) { result.bars++; result.beats = 1; metric = metric_at(result); // maybe there is a meter change beats_per_bar = metric.meter().beats_per_bar(); } ticks_at_beat= (uint32_t) ( result.beats == ceil(beats_per_bar) ? - (1 - (ceil(beats_per_bar) - beats_per_bar) )* Meter::ticks_per_beat + (1 - (ceil(beats_per_bar) - beats_per_bar) ) * Meter::ticks_per_beat : Meter::ticks_per_beat); } - + } else { uint32_t b = bbt.beats; /* count beats */ while( b > when.beats ) { - + result.bars = max(1U,result.bars-- ) ; metric = metric_at(result); // maybe there is a meter change beats_per_bar = metric.meter().beats_per_bar(); if (b >= ceil(beats_per_bar)) { - + b -= (uint32_t) ceil(beats_per_bar); } else { - b = (uint32_t) ceil(beats_per_bar)- b + when.beats ; + b = (uint32_t) ceil(beats_per_bar) - b + when.beats ; } } result.beats = when.beats - b; - + /*count ticks */ if (bbt.ticks <= when.ticks) { @@ -859,18 +1048,18 @@ TempoMap::bbt_duration_at_unlocked (const BBT_Time& when, const BBT_Time& bbt, i do { if (result.beats == 1) { - result.bars = max(1U,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)) * Meter::ticks_per_beat) ; } else { result.beats --; ticks_at_beat = (uint32_t) Meter::ticks_per_beat; } - + if (t <= ticks_at_beat) { - result.ticks = ticks_at_beat - t; + result.ticks = ticks_at_beat - t; } else { t-= ticks_at_beat; } @@ -892,8 +1081,8 @@ 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) +nframes64_t +TempoMap::round_to_bar (nframes64_t fr, int dir) { { Glib::RWLock::ReaderLock lm (lock); @@ -902,8 +1091,8 @@ TempoMap::round_to_bar (nframes_t fr, int dir) } -nframes_t -TempoMap::round_to_beat (nframes_t fr, int dir) +nframes64_t +TempoMap::round_to_beat (nframes64_t fr, int dir) { { Glib::RWLock::ReaderLock lm (lock); @@ -911,151 +1100,219 @@ TempoMap::round_to_beat (nframes_t fr, int dir) } } -nframes_t - -TempoMap::round_to_beat_subdivision (nframes_t fr, int sub_num) +nframes64_t +TempoMap::round_to_beat_subdivision (nframes64_t fr, int sub_num, int dir) { BBT_Time the_beat; uint32_t ticks_one_half_subdivisions_worth; uint32_t ticks_one_subdivisions_worth; + uint32_t difference; bbt_time(fr, the_beat); ticks_one_subdivisions_worth = (uint32_t)Meter::ticks_per_beat / sub_num; ticks_one_half_subdivisions_worth = ticks_one_subdivisions_worth / 2; - if (the_beat.ticks % ticks_one_subdivisions_worth > ticks_one_half_subdivisions_worth) { - uint32_t 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; - } - } else { - the_beat.ticks -= the_beat.ticks % ticks_one_subdivisions_worth; - } + if (dir > 0) { - return frame_time (the_beat); + /* round to next */ + uint32_t mod = the_beat.ticks % ticks_one_subdivisions_worth; + + if (mod == 0) { + /* right on the subdivision, so the difference is just the subdivision ticks */ + difference = ticks_one_subdivisions_worth; + + } else { + /* not on subdivision, compute distance to next subdivision */ + difference = ticks_one_subdivisions_worth - mod; + } - /***************************** - XXX just keeping this for reference + 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; + } - TempoMap::BBTPointList::iterator i; - TempoMap::BBTPointList *more_zoomed_bbt_points; - nframes_t frame_one_beats_worth; - nframes_t pos = 0; - nframes_t next_pos = 0 ; - double tempo = 1; - double frames_one_subdivisions_worth; - bool fr_has_changed = false; + } else if (dir < 0) { - int n; + /* round to previous */ - frame_one_beats_worth = (nframes_t) ::floor ((double) _frame_rate * 60 / 20 ); //one beat @ 20 bpm - { - Glib::RWLock::ReaderLock lm (lock); - more_zoomed_bbt_points = get_points((fr >= frame_one_beats_worth) ? - fr - frame_one_beats_worth : 0, fr+frame_one_beats_worth ); - } - if (more_zoomed_bbt_points == 0 || more_zoomed_bbt_points->empty()) { - return fr; - } + uint32_t mod = the_beat.ticks % ticks_one_subdivisions_worth; - for (i = more_zoomed_bbt_points->begin(); i != more_zoomed_bbt_points->end(); i++) { - if ((*i).frame <= fr) { - pos = (*i).frame; - tempo = (*i).tempo->beats_per_minute(); - + 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 { - i++; - next_pos = (*i).frame; - break; - } - } - frames_one_subdivisions_worth = ((double) _frame_rate * 60 / (sub_num * tempo)); + /* not on subdivision, compute distance to previous subdivision, which + is just the modulus. + */ - for (n = sub_num; n > 0; n--) { - if (fr >= (pos + ((n - 0.5) * frames_one_subdivisions_worth))) { - fr = (nframes_t) round(pos + (n * frames_one_subdivisions_worth)); - if (fr > next_pos) { - fr = next_pos; //take care of fractional beats that don't match the subdivision asked - } - fr_has_changed = true; - break; + difference = mod; + cerr << "off the sub, move by 1 sub = " << difference << endl; } - } - if (!fr_has_changed) { - fr = pos; - } - delete more_zoomed_bbt_points; - return fr ; + 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; + } + } else { + /* round to nearest */ + + if (the_beat.ticks % ticks_one_subdivisions_worth > ticks_one_half_subdivisions_worth) { + difference = ticks_one_subdivisions_worth - (the_beat.ticks % ticks_one_subdivisions_worth); + if (the_beat.ticks + difference >= (uint32_t)Meter::ticks_per_beat) { + the_beat.beats++; + the_beat.ticks += difference; + the_beat.ticks -= (uint32_t)Meter::ticks_per_beat; + } else { + the_beat.ticks += difference; + } + } else { + // difference = ticks_one_subdivisions_worth - (the_beat.ticks % ticks_one_subdivisions_worth); + the_beat.ticks -= the_beat.ticks % ticks_one_subdivisions_worth; + } + } + return frame_time (the_beat); } -nframes_t - -TempoMap::round_to_type (nframes_t frame, int dir, BBTPointType type) +nframes64_t +TempoMap::round_to_type (nframes64_t frame, int dir, BBTPointType type) { - Metric metric = metric_at (frame); + TempoMetric metric = metric_at (frame); BBT_Time bbt; BBT_Time start; + BBT_Time one_bar (1,0,0); + BBT_Time one_beat (0,1,0); + 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)); + if (dir < 0) { - /* relax */ + + /* 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++; + + /* 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; + midbar_ticks = Meter::ticks_per_beat * fmod (midbar_beats, 1.0f); + midbar_beats = floor (midbar_beats); + + BBT_Time midbar (bbt.bars, lrintf (midbar_beats), lrintf (midbar_ticks)); + + 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) { - /* relax */ + + /* 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++; + + /* find beat position following frame */ + + try { + bbt = bbt_add (bbt, one_beat, metric); } + catch (...) { + return frame; + } + } else { + + /* "true" rounding */ + + /* round to nearest beat */ if (bbt.ticks >= (Meter::ticks_per_beat/2)) { - bbt.beats++; + + 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; - + } + DEBUG_TRACE(DEBUG::SnapBBT, string_compose ("\tat %1 count frames from %2 to %3 = %4\n", metric.frame(), metric.start(), bbt, count_frames_between (metric.start(), bbt))); return metric.frame() + count_frames_between (metric.start(), bbt); } TempoMap::BBTPointList * -TempoMap::get_points (nframes_t lower, nframes_t upper) const +TempoMap::get_points (nframes64_t lower, nframes64_t upper) const { Metrics::const_iterator i; @@ -1074,7 +1331,7 @@ TempoMap::get_points (nframes_t lower, nframes_t upper) const double delta_bars; double delta_beats; double dummy; - nframes_t limit; + nframes64_t limit; meter = &first_meter (); tempo = &first_tempo (); @@ -1095,7 +1352,7 @@ TempoMap::get_points (nframes_t lower, nframes_t upper) const } /* We now have: - + meter -> the Meter for "lower" tempo -> the Tempo for "lower" i -> for first new metric after "lower", possibly metrics->end() @@ -1106,7 +1363,7 @@ TempoMap::get_points (nframes_t lower, nframes_t upper) const beats_per_bar = meter->beats_per_bar (); frames_per_bar = meter->frames_per_bar (*tempo, _frame_rate); beat_frames = tempo->frames_per_beat (_frame_rate, *meter); - + if (meter->frame() > tempo->frame()) { bar = meter->start().bars; beat = meter->start().beats; @@ -1121,7 +1378,7 @@ TempoMap::get_points (nframes_t lower, nframes_t upper) const lower frame bound passed in. assumes that current is initialized above to be on a beat. */ - + delta_bars = (lower-current) / frames_per_bar; delta_beats = modf(delta_bars, &dummy) * beats_per_bar; current += (floor(delta_bars) * frames_per_bar) + (floor(delta_beats) * beat_frames); @@ -1131,24 +1388,27 @@ TempoMap::get_points (nframes_t lower, nframes_t upper) const beat += (uint32_t) (floor(delta_beats)); points = new BBTPointList; - + do { if (i == metrics->end()) { limit = upper; + // cerr << "== limit set to end of request @ " << limit << endl; } else { + // cerr << "== limit set to next metric @ " << (*i)->frame() << endl; limit = (*i)->frame(); } limit = min (limit, upper); while (current < limit) { - + /* if we're at the start of a bar, add bar point */ if (beat == 1) { if (current >= lower) { - points->push_back (BBTPoint (*meter, *tempo,(nframes_t)rint(current), Bar, bar, 1)); + // cerr << "Add Bar at " << bar << "|1" << " @ " << current << endl; + points->push_back (BBTPoint (*meter, *tempo,(nframes64_t)rint(current), Bar, bar, 1)); } } @@ -1159,15 +1419,20 @@ TempoMap::get_points (nframes_t lower, nframes_t upper) const while (beat <= ceil( beats_per_bar) && beat_frame < limit) { if (beat_frame >= lower) { - points->push_back (BBTPoint (*meter, *tempo, (nframes_t) rint(beat_frame), Beat, bar, beat)); + // cerr << "Add Beat at " << bar << '|' << beat << " @ " << beat_frame << endl; + points->push_back (BBTPoint (*meter, *tempo, (nframes64_t) rint(beat_frame), Beat, bar, beat)); } beat_frame += beat_frames; current+= beat_frames; - + beat++; } - if (beat > ceil(beats_per_bar) ) { + // cerr << "out of beats, @ end ? " << (i == metrics->end()) << " out of bpb ? " + // << (beat > ceil(beats_per_bar)) + // << endl; + + if (beat > ceil(beats_per_bar) || i != metrics->end()) { /* we walked an entire bar. its important to move `current' forward @@ -1180,17 +1445,24 @@ TempoMap::get_points (nframes_t lower, nframes_t upper) const just filled had 10 beat marks, but the bar end is 1/2 beat before the last beat mark. - And it is also possible that a tempo - change occured in the middle of a bar, + And it is also possible that a tempo + change occured in the middle of a bar, so we subtract the possible extra fraction from the current */ - current -= beat_frames * (ceil(beats_per_bar)-beats_per_bar); + if (beat > ceil (beats_per_bar)) { + /* next bar goes where the numbers suggest */ + current -= beat_frames * (ceil(beats_per_bar)-beats_per_bar); + // cerr << "++ next bar from numbers\n"; + } else { + /* next bar goes where the next metric is */ + current = limit; + // cerr << "++ next bar at next metric\n"; + } bar++; beat = 1; + } - } - } /* if we're done, then we're done */ @@ -1213,30 +1485,60 @@ TempoMap::get_points (nframes_t lower, nframes_t upper) const beat = 1; } + current = (*i)->frame (); + // cerr << "loop around with current @ " << current << endl; + beats_per_bar = meter->beats_per_bar (); frames_per_bar = meter->frames_per_bar (*tempo, _frame_rate); beat_frames = tempo->frames_per_beat (_frame_rate, *meter); - + ++i; } } while (1); return points; -} +} + +const TempoSection& +TempoMap::tempo_section_at (nframes64_t frame) +{ + Glib::RWLock::ReaderLock lm (lock); + Metrics::iterator i; + TempoSection* prev = 0; + + for (i = metrics->begin(); i != metrics->end(); ++i) { + TempoSection* t; + + if ((t = dynamic_cast (*i)) != 0) { + + if ((*i)->frame() > frame) { + break; + } + + prev = t; + } + } + + if (prev == 0) { + fatal << endmsg; + } + + return *prev; +} const Tempo& -TempoMap::tempo_at (nframes_t frame) +TempoMap::tempo_at (nframes64_t frame) const { - Metric m (metric_at (frame)); + TempoMetric m (metric_at (frame)); return m.tempo(); } const Meter& -TempoMap::meter_at (nframes_t frame) +TempoMap::meter_at (nframes64_t frame) const { - Metric m (metric_at (frame)); + TempoMetric m (metric_at (frame)); return m.meter(); } @@ -1257,7 +1559,7 @@ TempoMap::get_state () } int -TempoMap::set_state (const XMLNode& node) +TempoMap::set_state (const XMLNode& node, int /*version*/) { { Glib::RWLock::WriterLock lm (lock); @@ -1265,32 +1567,32 @@ TempoMap::set_state (const XMLNode& node) XMLNodeList nlist; XMLNodeConstIterator niter; Metrics old_metrics (*metrics); - + metrics->clear(); nlist = node.children(); - + for (niter = nlist.begin(); niter != nlist.end(); ++niter) { XMLNode* child = *niter; - + if (child->name() == TempoSection::xml_state_node_name) { - + try { metrics->push_back (new TempoSection (*child)); } - + catch (failed_constructor& err){ error << _("Tempo map: could not set new state, restoring old one.") << endmsg; *metrics = old_metrics; break; } - + } else if (child->name() == MeterSection::xml_state_node_name) { - + try { metrics->push_back (new MeterSection (*child)); } - + catch (failed_constructor& err) { error << _("Tempo map: could not set new state, restoring old one.") << endmsg; *metrics = old_metrics; @@ -1298,15 +1600,15 @@ TempoMap::set_state (const XMLNode& node) } } } - + if (niter == nlist.end()) { - + MetricSectionSorter cmp; metrics->sort (cmp); - timestamp_metrics (); + timestamp_metrics (true); } } - + StateChanged (Change (0)); return 0; @@ -1317,16 +1619,276 @@ TempoMap::dump (std::ostream& o) const { const MeterSection* m; const TempoSection* t; - + for (Metrics::const_iterator i = metrics->begin(); i != metrics->end(); ++i) { if ((t = dynamic_cast(*i)) != 0) { o << "Tempo @ " << *i << ' ' << t->beats_per_minute() << " BPM (denom = " << t->note_type() << ") at " << t->start() << " frame= " << t->frame() << " (move? " << t->movable() << ')' << endl; } else if ((m = dynamic_cast(*i)) != 0) { - o << "Meter @ " << *i << ' ' << m->beats_per_bar() << '/' << m->note_divisor() << " at " << m->start() << " frame= " << m->frame() + o << "Meter @ " << *i << ' ' << m->beats_per_bar() << '/' << m->note_divisor() << " at " << m->start() << " frame= " << m->frame() << " (move? " << m->movable() << ')' << endl; } } } +int +TempoMap::n_tempos() const +{ + Glib::RWLock::ReaderLock lm (lock); + int cnt = 0; + + for (Metrics::const_iterator i = metrics->begin(); i != metrics->end(); ++i) { + if (dynamic_cast(*i) != 0) { + cnt++; + } + } + + return cnt; +} + +int +TempoMap::n_meters() const +{ + Glib::RWLock::ReaderLock lm (lock); + int cnt = 0; + + for (Metrics::const_iterator i = metrics->begin(); i != metrics->end(); ++i) { + if (dynamic_cast(*i) != 0) { + cnt++; + } + } + + return cnt; +} + +void +TempoMap::insert_time (nframes64_t where, nframes64_t amount) +{ + for (Metrics::iterator i = metrics->begin(); i != metrics->end(); ++i) { + if ((*i)->frame() >= where) { + (*i)->set_frame ((*i)->frame() + amount); + } + } + + timestamp_metrics (false); + + StateChanged (Change (0)); +} + +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 >= Meter::ticks_per_beat) { + op.beats++; + result.ticks = ticks % (uint32_t) Meter::ticks_per_beat; + } + + /* 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 = Meter::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; +}