2 Copyright (C) 2000-2002 Paul Davis
4 This program is free software; you can redistribute it and/or modify
5 it under the terms of the GNU General Public License as published by
6 the Free Software Foundation; either version 2 of the License, or
7 (at your option) any later version.
9 This program is distributed in the hope that it will be useful,
10 but WITHOUT ANY WARRANTY; without even the implied warranty of
11 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 GNU General Public License for more details.
14 You should have received a copy of the GNU General Public License
15 along with this program; if not, write to the Free Software
16 Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
27 #include <glibmm/thread.h>
28 #include "pbd/xml++.h"
29 #include "evoral/types.hpp"
30 #include "ardour/debug.h"
31 #include "ardour/tempo.h"
32 #include "ardour/utils.h"
38 using namespace ARDOUR;
41 using Timecode::BBT_Time;
43 /* _default tempo is 4/4 qtr=120 */
45 Meter TempoMap::_default_meter (4.0, 4.0);
46 Tempo TempoMap::_default_tempo (120.0);
49 Tempo::frames_per_beat (framecnt_t sr) const
51 return (60.0 * sr) / _beats_per_minute;
54 /***********************************************************************/
57 Meter::frames_per_division (const Tempo& tempo, framecnt_t sr) const
59 return (60.0 * sr) / (tempo.beats_per_minute() * (_note_type/tempo.note_type()));
63 Meter::frames_per_bar (const Tempo& tempo, framecnt_t sr) const
65 return frames_per_division (tempo, sr) * _divisions_per_bar;
68 /***********************************************************************/
70 const string TempoSection::xml_state_node_name = "Tempo";
72 TempoSection::TempoSection (const XMLNode& node)
73 : MetricSection (BBT_Time()), Tempo (TempoMap::default_tempo())
75 const XMLProperty *prop;
77 LocaleGuard lg (X_("POSIX"));
79 if ((prop = node.property ("start")) == 0) {
80 error << _("TempoSection XML node has no \"start\" property") << endmsg;
81 throw failed_constructor();
84 if (sscanf (prop->value().c_str(), "%" PRIu32 "|%" PRIu32 "|%" PRIu32,
88 error << _("TempoSection XML node has an illegal \"start\" value") << endmsg;
89 throw failed_constructor();
94 if ((prop = node.property ("beats-per-minute")) == 0) {
95 error << _("TempoSection XML node has no \"beats-per-minute\" property") << endmsg;
96 throw failed_constructor();
99 if (sscanf (prop->value().c_str(), "%lf", &_beats_per_minute) != 1 || _beats_per_minute < 0.0) {
100 error << _("TempoSection XML node has an illegal \"beats_per_minute\" value") << endmsg;
101 throw failed_constructor();
104 if ((prop = node.property ("note-type")) == 0) {
105 /* older session, make note type be quarter by default */
108 if (sscanf (prop->value().c_str(), "%lf", &_note_type) != 1 || _note_type < 1.0) {
109 error << _("TempoSection XML node has an illegal \"note-type\" value") << endmsg;
110 throw failed_constructor();
114 if ((prop = node.property ("movable")) == 0) {
115 error << _("TempoSection XML node has no \"movable\" property") << endmsg;
116 throw failed_constructor();
119 set_movable (string_is_affirmative (prop->value()));
121 if ((prop = node.property ("bar-offset")) == 0) {
124 if (sscanf (prop->value().c_str(), "%lf", &_bar_offset) != 1 || _bar_offset < 0.0) {
125 error << _("TempoSection XML node has an illegal \"bar-offset\" value") << endmsg;
126 throw failed_constructor();
132 TempoSection::get_state() const
134 XMLNode *root = new XMLNode (xml_state_node_name);
136 LocaleGuard lg (X_("POSIX"));
138 snprintf (buf, sizeof (buf), "%" PRIu32 "|%" PRIu32 "|%" PRIu32,
142 root->add_property ("start", buf);
143 snprintf (buf, sizeof (buf), "%f", _beats_per_minute);
144 root->add_property ("beats-per-minute", buf);
145 snprintf (buf, sizeof (buf), "%f", _note_type);
146 root->add_property ("note-type", buf);
147 // snprintf (buf, sizeof (buf), "%f", _bar_offset);
148 // root->add_property ("bar-offset", buf);
149 snprintf (buf, sizeof (buf), "%s", movable()?"yes":"no");
150 root->add_property ("movable", buf);
157 TempoSection::update_bar_offset_from_bbt (const Meter& m)
159 _bar_offset = ((start().beats - 1) * BBT_Time::ticks_per_bar_division + start().ticks) /
160 (m.divisions_per_bar() * BBT_Time::ticks_per_bar_division);
162 DEBUG_TRACE (DEBUG::TempoMath, string_compose ("Tempo set bar offset to %1 from %2 w/%3\n", _bar_offset, start(), m.divisions_per_bar()));
166 TempoSection::update_bbt_time_from_bar_offset (const Meter& meter)
170 if (_bar_offset < 0.0) {
175 new_start.bars = start().bars;
177 double ticks = BBT_Time::ticks_per_bar_division * meter.divisions_per_bar() * _bar_offset;
178 new_start.beats = (uint32_t) floor(ticks/BBT_Time::ticks_per_bar_division);
179 new_start.ticks = (uint32_t) fmod (ticks, BBT_Time::ticks_per_bar_division);
181 /* remember the 1-based counting properties of beats */
182 new_start.beats += 1;
184 DEBUG_TRACE (DEBUG::TempoMath, string_compose ("from bar offset %1 and dpb %2, ticks = %3->%4 beats = %5\n",
185 _bar_offset, meter.divisions_per_bar(), ticks, new_start.ticks, new_start.beats));
187 set_start (new_start);
190 /***********************************************************************/
192 const string MeterSection::xml_state_node_name = "Meter";
194 MeterSection::MeterSection (const XMLNode& node)
195 : MetricSection (BBT_Time()), Meter (TempoMap::default_meter())
197 const XMLProperty *prop;
199 LocaleGuard lg (X_("POSIX"));
201 if ((prop = node.property ("start")) == 0) {
202 error << _("MeterSection XML node has no \"start\" property") << endmsg;
203 throw failed_constructor();
206 if (sscanf (prop->value().c_str(), "%" PRIu32 "|%" PRIu32 "|%" PRIu32,
210 error << _("MeterSection XML node has an illegal \"start\" value") << endmsg;
211 throw failed_constructor();
216 /* beats-per-bar is old; divisions-per-bar is new */
218 if ((prop = node.property ("divisions-per-bar")) == 0) {
219 if ((prop = node.property ("beats-per-bar")) == 0) {
220 error << _("MeterSection XML node has no \"beats-per-bar\" or \"divisions-per-bar\" property") << endmsg;
221 throw failed_constructor();
225 if (sscanf (prop->value().c_str(), "%lf", &_divisions_per_bar) != 1 || _divisions_per_bar < 0.0) {
226 error << _("MeterSection XML node has an illegal \"beats-per-bar\" or \"divisions-per-bar\" value") << endmsg;
227 throw failed_constructor();
230 if ((prop = node.property ("note-type")) == 0) {
231 error << _("MeterSection XML node has no \"note-type\" property") << endmsg;
232 throw failed_constructor();
235 if (sscanf (prop->value().c_str(), "%lf", &_note_type) != 1 || _note_type < 0.0) {
236 error << _("MeterSection XML node has an illegal \"note-type\" value") << endmsg;
237 throw failed_constructor();
240 if ((prop = node.property ("movable")) == 0) {
241 error << _("MeterSection XML node has no \"movable\" property") << endmsg;
242 throw failed_constructor();
245 set_movable (string_is_affirmative (prop->value()));
249 MeterSection::get_state() const
251 XMLNode *root = new XMLNode (xml_state_node_name);
253 LocaleGuard lg (X_("POSIX"));
255 snprintf (buf, sizeof (buf), "%" PRIu32 "|%" PRIu32 "|%" PRIu32,
259 root->add_property ("start", buf);
260 snprintf (buf, sizeof (buf), "%f", _note_type);
261 root->add_property ("note-type", buf);
262 snprintf (buf, sizeof (buf), "%f", _divisions_per_bar);
263 root->add_property ("divisions-per-bar", buf);
264 snprintf (buf, sizeof (buf), "%s", movable()?"yes":"no");
265 root->add_property ("movable", buf);
270 /***********************************************************************/
272 struct MetricSectionSorter {
273 bool operator() (const MetricSection* a, const MetricSection* b) {
274 return a->start() < b->start();
278 TempoMap::TempoMap (framecnt_t fr)
287 TempoSection *t = new TempoSection (start, _default_tempo.beats_per_minute(), _default_tempo.note_type());
288 MeterSection *m = new MeterSection (start, _default_meter.divisions_per_bar(), _default_meter.note_divisor());
290 t->set_movable (false);
291 m->set_movable (false);
293 /* note: frame time is correct (zero) for both of these */
295 metrics.push_back (t);
296 metrics.push_back (m);
299 TempoMap::~TempoMap ()
304 TempoMap::remove_tempo (const TempoSection& tempo, bool complete_operation)
306 bool removed = false;
309 Glib::RWLock::WriterLock lm (lock);
312 for (i = metrics.begin(); i != metrics.end(); ++i) {
313 if (dynamic_cast<TempoSection*> (*i) != 0) {
314 if (tempo.frame() == (*i)->frame()) {
315 if ((*i)->movable()) {
324 if (removed && complete_operation) {
325 recompute_map (false);
329 if (removed && complete_operation) {
330 PropertyChanged (PropertyChange ());
335 TempoMap::remove_meter (const MeterSection& tempo, bool complete_operation)
337 bool removed = false;
340 Glib::RWLock::WriterLock lm (lock);
343 for (i = metrics.begin(); i != metrics.end(); ++i) {
344 if (dynamic_cast<MeterSection*> (*i) != 0) {
345 if (tempo.frame() == (*i)->frame()) {
346 if ((*i)->movable()) {
355 if (removed && complete_operation) {
356 recompute_map (true);
360 if (removed && complete_operation) {
361 PropertyChanged (PropertyChange ());
366 TempoMap::do_insert (MetricSection* section)
368 bool need_add = true;
370 assert (section->start().ticks == 0);
372 /* we only allow new meters to be inserted on beat 1 of an existing
376 if (dynamic_cast<MeterSection*>(section)) {
378 /* we need to (potentially) update the BBT times of tempo
379 sections based on this new meter.
382 if ((section->start().beats != 1) || (section->start().ticks != 0)) {
384 BBT_Time corrected = section->start();
388 warning << string_compose (_("Meter changes can only be positioned on the first beat of a bar. Moving from %1 to %2"),
389 section->start(), corrected) << endmsg;
391 section->set_start (corrected);
397 /* Look for any existing MetricSection that is of the same type and
398 at the same time as the new one, and remove it before adding
402 Metrics::iterator to_remove = metrics.end ();
404 for (i = metrics.begin(); i != metrics.end(); ++i) {
406 int const c = (*i)->compare (*section);
409 /* this section is before the one to be added; go back round */
412 /* this section is after the one to be added; there can't be any at the same time */
416 /* hacky comparison of type */
417 bool const iter_is_tempo = dynamic_cast<TempoSection*> (*i) != 0;
418 bool const insert_is_tempo = dynamic_cast<TempoSection*> (section) != 0;
420 if (iter_is_tempo == insert_is_tempo) {
422 if (!(*i)->movable()) {
424 /* can't (re)move this section, so overwrite it
427 if (!iter_is_tempo) {
428 *(dynamic_cast<MeterSection*>(*i)) = *(dynamic_cast<MeterSection*>(section));
430 *(dynamic_cast<TempoSection*>(*i)) = *(dynamic_cast<TempoSection*>(section));
441 if (to_remove != metrics.end()) {
442 /* remove the MetricSection at the same time as the one we are about to add */
443 metrics.erase (to_remove);
446 /* Add the given MetricSection */
449 for (i = metrics.begin(); i != metrics.end(); ++i) {
451 if ((*i)->compare (*section) < 0) {
455 metrics.insert (i, section);
459 if (i == metrics.end()) {
460 metrics.insert (metrics.end(), section);
466 TempoMap::replace_tempo (const TempoSection& ts, const Tempo& tempo, const BBT_Time& where)
468 const TempoSection& first (first_tempo());
471 remove_tempo (ts, false);
472 add_tempo (tempo, where);
475 Glib::RWLock::WriterLock lm (lock);
476 /* cannot move the first tempo section */
477 *((Tempo*)&first) = tempo;
478 recompute_map (false);
482 PropertyChanged (PropertyChange ());
486 TempoMap::add_tempo (const Tempo& tempo, BBT_Time where)
489 Glib::RWLock::WriterLock lm (lock);
491 /* new tempos always start on a beat */
494 TempoSection* ts = new TempoSection (where, tempo.beats_per_minute(), tempo.note_type());
496 /* find the meter to use to set the bar offset of this
500 const Meter* meter = &first_meter();
502 /* as we start, we are *guaranteed* to have m.meter and m.tempo pointing
503 at something, because we insert the default tempo and meter during
504 TempoMap construction.
506 now see if we can find better candidates.
509 for (Metrics::const_iterator i = metrics.begin(); i != metrics.end(); ++i) {
511 const MeterSection* m;
513 if (where < (*i)->start()) {
517 if ((m = dynamic_cast<const MeterSection*>(*i)) != 0) {
522 ts->update_bar_offset_from_bbt (*meter);
528 recompute_map (false);
532 PropertyChanged (PropertyChange ());
536 TempoMap::replace_meter (const MeterSection& ms, const Meter& meter, const BBT_Time& where)
538 const MeterSection& first (first_meter());
541 remove_meter (ms, false);
542 add_meter (meter, where);
545 Glib::RWLock::WriterLock lm (lock);
546 /* cannot move the first meter section */
547 *((Meter*)&first) = meter;
548 recompute_map (true);
552 PropertyChanged (PropertyChange ());
556 TempoMap::add_meter (const Meter& meter, BBT_Time where)
559 Glib::RWLock::WriterLock lm (lock);
561 /* a new meter always starts a new bar on the first beat. so
562 round the start time appropriately. remember that
563 `where' is based on the existing tempo map, not
564 the result after we insert the new meter.
568 if (where.beats != 1) {
573 /* new meters *always* start on a beat. */
576 do_insert (new MeterSection (where, meter.divisions_per_bar(), meter.note_divisor()));
577 recompute_map (true);
582 if (DEBUG_ENABLED(DEBUG::TempoMap)) {
587 PropertyChanged (PropertyChange ());
591 TempoMap::change_initial_tempo (double beats_per_minute, double note_type)
593 Tempo newtempo (beats_per_minute, note_type);
596 for (Metrics::iterator i = metrics.begin(); i != metrics.end(); ++i) {
597 if ((t = dynamic_cast<TempoSection*> (*i)) != 0) {
599 Glib::RWLock::WriterLock lm (lock);
600 *((Tempo*) t) = newtempo;
601 recompute_map (false);
603 PropertyChanged (PropertyChange ());
610 TempoMap::change_existing_tempo_at (framepos_t where, double beats_per_minute, double note_type)
612 Tempo newtempo (beats_per_minute, note_type);
618 /* find the TempoSection immediately preceding "where"
621 for (first = 0, i = metrics.begin(), prev = 0; i != metrics.end(); ++i) {
623 if ((*i)->frame() > where) {
629 if ((t = dynamic_cast<TempoSection*>(*i)) != 0) {
639 error << string_compose (_("no tempo sections defined in tempo map - cannot change tempo @ %1"), where) << endmsg;
649 Glib::RWLock::WriterLock lm (lock);
650 /* cannot move the first tempo section */
651 *((Tempo*)prev) = newtempo;
652 recompute_map (false);
655 PropertyChanged (PropertyChange ());
659 TempoMap::first_meter () const
661 const MeterSection *m = 0;
663 for (Metrics::const_iterator i = metrics.begin(); i != metrics.end(); ++i) {
664 if ((m = dynamic_cast<const MeterSection *> (*i)) != 0) {
669 fatal << _("programming error: no tempo section in tempo map!") << endmsg;
675 TempoMap::first_tempo () const
677 const TempoSection *t = 0;
679 for (Metrics::const_iterator i = metrics.begin(); i != metrics.end(); ++i) {
680 if ((t = dynamic_cast<const TempoSection *> (*i)) != 0) {
685 fatal << _("programming error: no tempo section in tempo map!") << endmsg;
691 TempoMap::require_map_to (framepos_t pos)
693 Glib::RWLock::WriterLock lm (lock);
695 if (_map.empty() || _map.back().frame < pos) {
701 TempoMap::require_map_to (const BBT_Time& bbt)
703 Glib::RWLock::WriterLock lm (lock);
705 /* since we have no idea where BBT is if its off the map, see the last
706 * point in the map is past BBT, and if not add an arbitrary amount of
710 int additional_minutes = 1;
713 if (!_map.empty() && _map.back().bar >= (bbt.bars + 1)) {
716 /* add some more distance, using bigger steps each time */
717 extend_map (_map.back().frame + (_frame_rate * 60 * additional_minutes));
718 additional_minutes *= 2;
723 TempoMap::recompute_map (bool reassign_tempo_bbt, framepos_t end)
725 /* CALLER MUST HOLD WRITE LOCK */
731 double current_frame;
733 Metrics::iterator next_metric;
736 /* silly call from Session::process() during startup
744 /* compute 1 mins worth */
745 end = _frame_rate * 60;
747 end = _map.back().frame;
750 if (!_map.empty ()) {
751 /* never allow the map to be shortened */
752 end = max (end, _map.back().frame);
756 DEBUG_TRACE (DEBUG::TempoMath, string_compose ("recomputing tempo map, zero to %1\n", end));
758 for (Metrics::iterator i = metrics.begin(); i != metrics.end(); ++i) {
759 if ((ms = dynamic_cast<MeterSection *> (*i)) != 0) {
765 for (Metrics::iterator i = metrics.begin(); i != metrics.end(); ++i) {
766 if ((ts = dynamic_cast<TempoSection *> (*i)) != 0) {
772 /* assumes that the first meter & tempo are at frame zero */
774 meter->set_frame (0);
775 tempo->set_frame (0);
777 /* assumes that the first meter & tempo are at 1|1|0 */
782 if (reassign_tempo_bbt) {
784 MeterSection* rmeter = meter;
786 DEBUG_TRACE (DEBUG::TempoMath, "\tUpdating tempo marks BBT time from bar offset\n");
788 for (Metrics::iterator i = metrics.begin(); i != metrics.end(); ++i) {
790 if ((ts = dynamic_cast<TempoSection*>(*i)) != 0) {
792 /* reassign the BBT time of this tempo section
793 * based on its bar offset position.
796 ts->update_bbt_time_from_bar_offset (*rmeter);
798 } else if ((ms = dynamic_cast<MeterSection*>(*i)) != 0) {
801 fatal << _("programming error: unhandled MetricSection type") << endmsg;
807 DEBUG_TRACE (DEBUG::TempoMath, string_compose ("start with meter = %1 tempo = %2\n", *((Meter*)meter), *((Tempo*)tempo)));
809 next_metric = metrics.begin();
810 ++next_metric; // skip meter (or tempo)
811 ++next_metric; // skip tempo (or meter)
815 DEBUG_TRACE (DEBUG::TempoMath, string_compose ("Add first bar at 1|1 @ %2\n", current.bars, current_frame));
816 _map.push_back (BBTPoint (*meter, *tempo,(framepos_t) llrint(current_frame), 1, 1));
818 _extend_map (tempo, meter, next_metric, current, current_frame, end);
822 TempoMap::extend_map (framepos_t end)
824 /* CALLER MUST HOLD WRITE LOCK */
827 recompute_map (false, end);
831 BBTPointList::const_iterator i = _map.end();
832 Metrics::iterator next_metric;
836 BBT_Time last_metric_start;
838 if ((*i).tempo->frame() > (*i).meter->frame()) {
839 last_metric_start = (*i).tempo->start();
841 last_metric_start = (*i).meter->start();
844 /* find the metric immediately after the tempo + meter sections for the
845 * last point in the map
848 for (next_metric = metrics.begin(); next_metric != metrics.end(); ++next_metric) {
849 if ((*next_metric)->start() > last_metric_start) {
854 cerr << "extend map to " << end << " next_metric @ end ? " << (next_metric == metrics.end());
855 if (next_metric != metrics.end()) {
856 cerr << *next_metric;
860 /* we cast away const here because this is the one place where we need
861 * to actually modify the frame time of each metric section.
864 _extend_map (const_cast<TempoSection*> ((*i).tempo),
865 const_cast<MeterSection*> ((*i).meter),
866 next_metric, BBT_Time ((*i).bar, (*i).beat, 0), (*i).frame, end);
870 TempoMap::_extend_map (TempoSection* tempo, MeterSection* meter,
871 Metrics::iterator next_metric,
872 BBT_Time current, framepos_t current_frame, framepos_t end)
874 /* CALLER MUST HOLD WRITE LOCK */
878 double divisions_per_bar;
881 divisions_per_bar = meter->divisions_per_bar ();
882 beat_frames = meter->frames_per_division (*tempo,_frame_rate);
884 while (current_frame < end) {
887 current_frame += beat_frames;
889 if (current.beats > meter->divisions_per_bar()) {
894 if (next_metric != metrics.end()) {
896 /* no operator >= so invert operator < */
898 DEBUG_TRACE (DEBUG::TempoMath, string_compose ("now at %1 next metric @ %2\n", current, (*next_metric)->start()));
900 if (!(current < (*next_metric)->start())) {
903 if (((ts = dynamic_cast<TempoSection*> (*next_metric)) != 0)) {
907 /* new tempo section: if its on a beat,
908 * we don't have to do anything other
909 * than recompute various distances,
910 * done further below as we transition
911 * the next metric section.
913 * if its not on the beat, we have to
914 * compute the duration of the beat it
915 * is within, which will be different
916 * from the preceding following ones
917 * since it takes part of its duration
918 * from the preceding tempo and part
919 * from this new tempo.
922 if (tempo->start().ticks != 0) {
924 double next_beat_frames = meter->frames_per_division (*tempo,_frame_rate);
926 DEBUG_TRACE (DEBUG::TempoMath, string_compose ("bumped into non-beat-aligned tempo metric at %1 = %2, adjust next beat using %3\n",
927 tempo->start(), current_frame, tempo->bar_offset()));
929 /* back up to previous beat */
930 current_frame -= beat_frames;
931 /* set tempo section location based on offset from last beat */
932 tempo->set_frame (current_frame + (ts->bar_offset() * beat_frames));
933 /* advance to the location of the new (adjusted) beat */
934 current_frame += (ts->bar_offset() * beat_frames) + ((1.0 - ts->bar_offset()) * next_beat_frames);
935 /* next metric doesn't have to
936 * match this precisely to
939 DEBUG_TRACE (DEBUG::TempoMath, string_compose ("Adjusted last beat to %1\n", current_frame));
943 DEBUG_TRACE (DEBUG::TempoMath, string_compose ("bumped into beat-aligned tempo metric at %1 = %2\n",
944 tempo->start(), current_frame));
945 tempo->set_frame (current_frame);
948 } else if ((ms = dynamic_cast<MeterSection*>(*next_metric)) != 0) {
952 /* new meter section: always defines the
956 DEBUG_TRACE (DEBUG::TempoMath, string_compose ("bumped into meter section at %1 vs %2 (%3)\n",
957 meter->start(), current, current_frame));
959 assert (current.beats == 1);
961 meter->set_frame (current_frame);
964 divisions_per_bar = meter->divisions_per_bar ();
965 beat_frames = meter->frames_per_division (*tempo, _frame_rate);
967 DEBUG_TRACE (DEBUG::TempoMath, string_compose ("New metric with beat frames = %1 dpb %2 meter %3 tempo %4\n",
968 beat_frames, divisions_per_bar, *((Meter*)meter), *((Tempo*)tempo)));
972 if (next_metric != metrics.end() && ((*next_metric)->start() == current)) {
973 /* same position so go back and set this one up before advancing
980 if (current.beats == 1) {
981 DEBUG_TRACE (DEBUG::TempoMath, string_compose ("Add Bar at %1|1 @ %2\n", current.bars, current_frame));
982 _map.push_back (BBTPoint (*meter, *tempo,(framepos_t) llrint(current_frame), current.bars, 1));
984 DEBUG_TRACE (DEBUG::TempoMath, string_compose ("Add Beat at %1|%2 @ %3\n", current.bars, current.beats, current_frame));
985 _map.push_back (BBTPoint (*meter, *tempo, (framepos_t) llrint(current_frame), current.bars, current.beats));
991 TempoMap::metric_at (framepos_t frame) const
993 Glib::RWLock::ReaderLock lm (lock);
994 TempoMetric m (first_meter(), first_tempo());
998 /* at this point, we are *guaranteed* to have m.meter and m.tempo pointing
999 at something, because we insert the default tempo and meter during
1000 TempoMap construction.
1002 now see if we can find better candidates.
1005 for (Metrics::const_iterator i = metrics.begin(); i != metrics.end(); ++i) {
1007 // cerr << "Looking at a metric section " << **i << endl;
1009 if ((*i)->frame() > frame) {
1013 if ((tempo = dynamic_cast<const TempoSection*>(*i)) != 0) {
1014 m.set_tempo (*tempo);
1015 } else if ((meter = dynamic_cast<const MeterSection*>(*i)) != 0) {
1016 m.set_meter (*meter);
1019 m.set_frame ((*i)->frame ());
1020 m.set_start ((*i)->start ());
1023 // cerr << "for framepos " << frame << " returning " << m.meter() << " @ " << m.tempo() << " location " << m.frame() << " = " << m.start() << endl;
1028 TempoMap::metric_at (BBT_Time bbt) const
1030 Glib::RWLock::ReaderLock lm (lock);
1031 TempoMetric m (first_meter(), first_tempo());
1035 /* at this point, we are *guaranteed* to have m.meter and m.tempo pointing
1036 at something, because we insert the default tempo and meter during
1037 TempoMap construction.
1039 now see if we can find better candidates.
1042 for (Metrics::const_iterator i = metrics.begin(); i != metrics.end(); ++i) {
1044 BBT_Time section_start ((*i)->start());
1046 if (section_start.bars > bbt.bars || (section_start.bars == bbt.bars && section_start.beats > bbt.beats)) {
1050 if ((tempo = dynamic_cast<const TempoSection*>(*i)) != 0) {
1051 m.set_tempo (*tempo);
1052 } else if ((meter = dynamic_cast<const MeterSection*>(*i)) != 0) {
1053 m.set_meter (*meter);
1056 m.set_frame ((*i)->frame ());
1057 m.set_start (section_start);
1064 TempoMap::bbt_time (framepos_t frame, BBT_Time& bbt)
1066 require_map_to (frame);
1068 Glib::RWLock::ReaderLock lm (lock);
1069 return bbt_time (frame, bbt, bbt_before_or_at (frame));
1073 TempoMap::bbt_time_rt (framepos_t frame, BBT_Time& bbt)
1075 Glib::RWLock::ReaderLock lm (lock, Glib::TRY_LOCK);
1078 throw std::logic_error ("TempoMap::bbt_time_rt() could not lock tempo map");
1081 if (_map.empty() || _map.back().frame < frame) {
1082 throw std::logic_error (string_compose ("map not long enough to reach %1", frame));
1085 return bbt_time (frame, bbt, bbt_before_or_at (frame));
1089 TempoMap::bbt_time (framepos_t frame, BBT_Time& bbt, const BBTPointList::const_iterator& i)
1091 /* CALLER MUST HOLD READ LOCK */
1093 bbt.bars = (*i).bar;
1094 bbt.beats = (*i).beat;
1096 if ((*i).frame == frame) {
1099 bbt.ticks = llrint (((frame - (*i).frame) / (*i).meter->frames_per_division(*((*i).tempo), _frame_rate)) *
1100 BBT_Time::ticks_per_bar_division);
1105 TempoMap::frame_time (const BBT_Time& bbt)
1107 require_map_to (bbt);
1109 Glib::RWLock::ReaderLock lm (lock);
1111 BBTPointList::const_iterator s = bbt_before_or_at (BBT_Time (1, 1, 0));
1112 BBTPointList::const_iterator e = bbt_before_or_at (BBT_Time (bbt.bars, bbt.beats, 0));
1114 if (bbt.ticks != 0) {
1115 return ((*e).frame - (*s).frame) +
1116 llrint ((*e).meter->frames_per_division (*(*e).tempo, _frame_rate) * (bbt.ticks/BBT_Time::ticks_per_bar_division));
1118 return ((*e).frame - (*s).frame);
1123 TempoMap::bbt_duration_at (framepos_t pos, const BBT_Time& bbt, int dir)
1125 Glib::RWLock::ReaderLock lm (lock);
1126 framecnt_t frames = 0;
1129 bbt_time (pos, when);
1130 frames = bbt_duration_at_unlocked (when, bbt,dir);
1136 TempoMap::bbt_duration_at_unlocked (const BBT_Time& when, const BBT_Time& bbt, int dir)
1138 if (bbt.bars == 0 && bbt.beats == 0 && bbt.ticks == 0) {
1142 /* round back to the previous precise beat */
1143 BBTPointList::const_iterator wi = bbt_before_or_at (BBT_Time (when.bars, when.beats, 0));
1144 BBTPointList::const_iterator start (wi);
1145 double tick_frames = 0;
1147 assert (wi != _map.end());
1149 /* compute how much rounding we did because of non-zero ticks */
1151 if (when.ticks != 0) {
1152 tick_frames = (*wi).meter->frames_per_division (*(*wi).tempo, _frame_rate) * (when.ticks/BBT_Time::ticks_per_bar_division);
1158 while (wi != _map.end() && bars < bbt.bars) {
1160 if ((*wi).is_bar()) {
1164 assert (wi != _map.end());
1166 while (wi != _map.end() && beats < bbt.beats) {
1170 assert (wi != _map.end());
1172 /* add any additional frames related to ticks in the added value */
1174 if (bbt.ticks != 0) {
1175 tick_frames += (*wi).meter->frames_per_division (*(*wi).tempo, _frame_rate) * (bbt.ticks/BBT_Time::ticks_per_bar_division);
1178 return ((*wi).frame - (*start).frame) + llrint (tick_frames);
1182 TempoMap::round_to_bar (framepos_t fr, int dir)
1184 return round_to_type (fr, dir, Bar);
1188 TempoMap::round_to_beat (framepos_t fr, int dir)
1190 return round_to_type (fr, dir, Beat);
1194 TempoMap::round_to_beat_subdivision (framepos_t fr, int sub_num, int dir)
1196 require_map_to (fr);
1198 Glib::RWLock::ReaderLock lm (lock);
1199 BBTPointList::const_iterator i = bbt_before_or_at (fr);
1201 uint32_t ticks_one_subdivisions_worth;
1202 uint32_t difference;
1204 bbt_time (fr, the_beat, i);
1206 DEBUG_TRACE (DEBUG::SnapBBT, string_compose ("round %1 to nearest 1/%2 beat, before-or-at = %3 @ %4|%5 precise = %6\n",
1207 fr, sub_num, (*i).frame, (*i).bar, (*i).beat, the_beat));
1209 ticks_one_subdivisions_worth = (uint32_t)BBT_Time::ticks_per_bar_division / sub_num;
1213 /* round to next (even if we're on a subdivision */
1215 uint32_t mod = the_beat.ticks % ticks_one_subdivisions_worth;
1218 /* right on the subdivision, so the difference is just the subdivision ticks */
1219 the_beat.ticks += ticks_one_subdivisions_worth;
1222 /* not on subdivision, compute distance to next subdivision */
1224 the_beat.ticks += ticks_one_subdivisions_worth - mod;
1227 if (the_beat.ticks > BBT_Time::ticks_per_bar_division) {
1228 assert (i != _map.end());
1230 assert (i != _map.end());
1231 the_beat.ticks -= BBT_Time::ticks_per_bar_division;
1235 } else if (dir < 0) {
1237 /* round to previous (even if we're on a subdivision) */
1239 uint32_t mod = the_beat.ticks % ticks_one_subdivisions_worth;
1242 /* right on the subdivision, so the difference is just the subdivision ticks */
1243 difference = ticks_one_subdivisions_worth;
1245 /* not on subdivision, compute distance to previous subdivision, which
1246 is just the modulus.
1252 if (the_beat.ticks < difference) {
1253 if (i == _map.begin()) {
1254 /* can't go backwards from wherever pos is, so just return it */
1258 the_beat.ticks = BBT_Time::ticks_per_bar_division - the_beat.ticks;
1260 the_beat.ticks -= difference;
1264 /* round to nearest */
1268 /* compute the distance to the previous and next subdivision */
1270 if ((rem = fmod ((double) the_beat.ticks, (double) ticks_one_subdivisions_worth)) > ticks_one_subdivisions_worth/2.0) {
1272 /* closer to the next subdivision, so shift forward */
1274 the_beat.ticks = lrint (the_beat.ticks + (ticks_one_subdivisions_worth - rem));
1276 DEBUG_TRACE (DEBUG::SnapBBT, string_compose ("moved forward to %1\n", the_beat.ticks));
1278 if (the_beat.ticks > BBT_Time::ticks_per_bar_division) {
1279 assert (i != _map.end());
1281 assert (i != _map.end());
1282 the_beat.ticks -= BBT_Time::ticks_per_bar_division;
1283 DEBUG_TRACE (DEBUG::SnapBBT, string_compose ("fold beat to %1\n", the_beat));
1286 } else if (rem > 0) {
1288 /* closer to previous subdivision, so shift backward */
1290 if (rem > the_beat.ticks) {
1291 if (i == _map.begin()) {
1292 /* can't go backwards past zero, so ... */
1295 /* step back to previous beat */
1297 the_beat.ticks = lrint (BBT_Time::ticks_per_bar_division - rem);
1298 DEBUG_TRACE (DEBUG::SnapBBT, string_compose ("step back beat to %1\n", the_beat));
1300 the_beat.ticks = lrint (the_beat.ticks - rem);
1301 DEBUG_TRACE (DEBUG::SnapBBT, string_compose ("moved backward to %1\n", the_beat.ticks));
1304 /* on the subdivision, do nothing */
1308 return (*i).frame + (the_beat.ticks/BBT_Time::ticks_per_bar_division) *
1309 (*i).meter->frames_per_division (*((*i).tempo), _frame_rate);
1313 TempoMap::round_to_type (framepos_t frame, int dir, BBTPointType type)
1315 require_map_to (frame);
1317 Glib::RWLock::ReaderLock lm (lock);
1318 BBTPointList::const_iterator fi;
1321 fi = bbt_after_or_at (frame);
1323 fi = bbt_before_or_at (frame);
1326 assert (fi != _map.end());
1328 DEBUG_TRACE(DEBUG::SnapBBT, string_compose ("round from %1 (%3|%4 @ %5) to bars in direction %2\n", frame, dir, (*fi).bar, (*fi).beat, (*fi).frame));
1333 /* find bar previous to 'frame' */
1335 if ((*fi).is_bar() && (*fi).frame == frame) {
1339 while (!(*fi).is_bar()) {
1340 if (fi == _map.begin()) {
1345 DEBUG_TRACE (DEBUG::SnapBBT, string_compose ("rounded to bar: map iter at %1|%2 %3, return\n",
1346 (*fi).bar, (*fi).beat, (*fi).frame));
1349 } else if (dir > 0) {
1351 /* find bar following 'frame' */
1353 if ((*fi).is_bar() && (*fi).frame == frame) {
1357 while (!(*fi).is_bar()) {
1359 if (fi == _map.end()) {
1365 DEBUG_TRACE (DEBUG::SnapBBT, string_compose ("rounded to bar: map iter at %1|%2 %3, return\n",
1366 (*fi).bar, (*fi).beat, (*fi).frame));
1371 /* true rounding: find nearest bar */
1373 BBTPointList::const_iterator prev = fi;
1374 BBTPointList::const_iterator next = fi;
1376 if ((*fi).frame == frame) {
1380 while ((*prev).beat != 1) {
1381 if (prev == _map.begin()) {
1387 while ((*next).beat != 1) {
1389 if (next == _map.end()) {
1395 if ((frame - (*prev).frame) < ((*next).frame - frame)) {
1396 return (*prev).frame;
1398 return (*next).frame;
1407 if ((*fi).frame >= frame) {
1408 DEBUG_TRACE (DEBUG::SnapBBT, "requested frame is on beat, step back\n");
1411 DEBUG_TRACE (DEBUG::SnapBBT, string_compose ("rounded to beat: map iter at %1|%2 %3, return\n",
1412 (*fi).bar, (*fi).beat, (*fi).frame));
1414 } else if (dir > 0) {
1415 if ((*fi).frame <= frame) {
1416 DEBUG_TRACE (DEBUG::SnapBBT, "requested frame is on beat, step forward\n");
1419 DEBUG_TRACE (DEBUG::SnapBBT, string_compose ("rounded to beat: map iter at %1|%2 %3, return\n",
1420 (*fi).bar, (*fi).beat, (*fi).frame));
1423 /* find beat nearest to frame */
1424 if ((*fi).frame == frame) {
1428 BBTPointList::const_iterator prev = fi;
1429 BBTPointList::const_iterator next = fi;
1433 if ((frame - (*prev).frame) < ((*next).frame - frame)) {
1434 return (*prev).frame;
1436 return (*next).frame;
1448 TempoMap::map (TempoMap::BBTPointList::const_iterator& begin,
1449 TempoMap::BBTPointList::const_iterator& end,
1450 framepos_t lower, framepos_t upper)
1453 Glib::RWLock::WriterLock lm (lock);
1454 if (_map.empty() || (_map.back().frame < upper)) {
1455 recompute_map (false, upper);
1459 begin = lower_bound (_map.begin(), _map.end(), lower);
1460 end = upper_bound (_map.begin(), _map.end(), upper);
1464 TempoMap::tempo_section_at (framepos_t frame) const
1466 Glib::RWLock::ReaderLock lm (lock);
1467 Metrics::const_iterator i;
1468 TempoSection* prev = 0;
1470 for (i = metrics.begin(); i != metrics.end(); ++i) {
1473 if ((t = dynamic_cast<TempoSection*> (*i)) != 0) {
1475 if ((*i)->frame() > frame) {
1491 TempoMap::tempo_at (framepos_t frame) const
1493 TempoMetric m (metric_at (frame));
1499 TempoMap::meter_at (framepos_t frame) const
1501 TempoMetric m (metric_at (frame));
1506 TempoMap::get_state ()
1508 Metrics::const_iterator i;
1509 XMLNode *root = new XMLNode ("TempoMap");
1512 Glib::RWLock::ReaderLock lm (lock);
1513 for (i = metrics.begin(); i != metrics.end(); ++i) {
1514 root->add_child_nocopy ((*i)->get_state());
1522 TempoMap::set_state (const XMLNode& node, int /*version*/)
1525 Glib::RWLock::WriterLock lm (lock);
1528 XMLNodeConstIterator niter;
1529 Metrics old_metrics (metrics);
1530 MeterSection* last_meter = 0;
1534 nlist = node.children();
1536 for (niter = nlist.begin(); niter != nlist.end(); ++niter) {
1537 XMLNode* child = *niter;
1539 if (child->name() == TempoSection::xml_state_node_name) {
1542 TempoSection* ts = new TempoSection (*child);
1543 metrics.push_back (ts);
1545 if (ts->bar_offset() < 0.0) {
1547 ts->update_bar_offset_from_bbt (*last_meter);
1552 catch (failed_constructor& err){
1553 error << _("Tempo map: could not set new state, restoring old one.") << endmsg;
1554 metrics = old_metrics;
1558 } else if (child->name() == MeterSection::xml_state_node_name) {
1561 MeterSection* ms = new MeterSection (*child);
1562 metrics.push_back (ms);
1566 catch (failed_constructor& err) {
1567 error << _("Tempo map: could not set new state, restoring old one.") << endmsg;
1568 metrics = old_metrics;
1574 if (niter == nlist.end()) {
1575 MetricSectionSorter cmp;
1579 recompute_map (true);
1582 PropertyChanged (PropertyChange ());
1588 TempoMap::dump (std::ostream& o) const
1590 Glib::RWLock::ReaderLock lm (lock);
1591 const MeterSection* m;
1592 const TempoSection* t;
1594 for (Metrics::const_iterator i = metrics.begin(); i != metrics.end(); ++i) {
1596 if ((t = dynamic_cast<const TempoSection*>(*i)) != 0) {
1597 o << "Tempo @ " << *i << " (Bar-offset: " << t->bar_offset() << ") " << t->beats_per_minute() << " BPM (pulse = 1/" << t->note_type() << ") at " << t->start() << " frame= " << t->frame() << " (movable? "
1598 << t->movable() << ')' << endl;
1599 } else if ((m = dynamic_cast<const MeterSection*>(*i)) != 0) {
1600 o << "Meter @ " << *i << ' ' << m->divisions_per_bar() << '/' << m->note_divisor() << " at " << m->start() << " frame= " << m->frame()
1601 << " (movable? " << m->movable() << ')' << endl;
1607 TempoMap::n_tempos() const
1609 Glib::RWLock::ReaderLock lm (lock);
1612 for (Metrics::const_iterator i = metrics.begin(); i != metrics.end(); ++i) {
1613 if (dynamic_cast<const TempoSection*>(*i) != 0) {
1622 TempoMap::n_meters() const
1624 Glib::RWLock::ReaderLock lm (lock);
1627 for (Metrics::const_iterator i = metrics.begin(); i != metrics.end(); ++i) {
1628 if (dynamic_cast<const MeterSection*>(*i) != 0) {
1637 TempoMap::insert_time (framepos_t where, framecnt_t amount)
1640 Glib::RWLock::WriterLock lm (lock);
1641 for (Metrics::iterator i = metrics.begin(); i != metrics.end(); ++i) {
1642 if ((*i)->frame() >= where && (*i)->movable ()) {
1643 (*i)->set_frame ((*i)->frame() + amount);
1647 /* now reset the BBT time of all metrics, based on their new
1648 * audio time. This is the only place where we do this reverse
1652 Metrics::iterator i;
1653 const MeterSection* meter;
1654 const TempoSection* tempo;
1658 meter = &first_meter ();
1659 tempo = &first_tempo ();
1664 // cerr << "\n###################### TIMESTAMP via AUDIO ##############\n" << endl;
1667 MetricSection* prev = 0;
1669 for (i = metrics.begin(); i != metrics.end(); ++i) {
1672 TempoMetric metric (*meter, *tempo);
1675 metric.set_start (prev->start());
1676 metric.set_frame (prev->frame());
1678 // metric will be at frames=0 bbt=1|1|0 by default
1679 // which is correct for our purpose
1682 BBTPointList::const_iterator bi = bbt_before_or_at ((*i)->frame());
1683 bbt_time ((*i)->frame(), bbt, bi);
1685 // cerr << "timestamp @ " << (*i)->frame() << " with " << bbt.bars << "|" << bbt.beats << "|" << bbt.ticks << " => ";
1691 if (bbt.ticks > BBT_Time::ticks_per_bar_division/2) {
1692 /* round up to next beat */
1698 if (bbt.beats != 1) {
1699 /* round up to next bar */
1705 // cerr << bbt << endl;
1707 (*i)->set_start (bbt);
1709 if ((t = dynamic_cast<TempoSection*>(*i)) != 0) {
1711 // cerr << "NEW TEMPO, frame = " << (*i)->frame() << " start = " << (*i)->start() <<endl;
1712 } else if ((m = dynamic_cast<MeterSection*>(*i)) != 0) {
1714 // cerr << "NEW METER, frame = " << (*i)->frame() << " start = " << (*i)->start() <<endl;
1716 fatal << _("programming error: unhandled MetricSection type") << endmsg;
1723 recompute_map (true);
1727 PropertyChanged (PropertyChange ());
1730 /** Add some (fractional) beats to a session frame position, and return the result in frames.
1731 * pos can be -ve, if required.
1734 TempoMap::framepos_plus_beats (framepos_t pos, Evoral::MusicalTime beats)
1736 return framepos_plus_bbt (pos, BBT_Time (beats));
1739 /** Subtract some (fractional) beats to a frame position, and return the result in frames */
1741 TempoMap::framepos_minus_beats (framepos_t pos, Evoral::MusicalTime beats)
1743 return framepos_minus_bbt (pos, BBT_Time (beats));
1747 TempoMap::framepos_minus_bbt (framepos_t pos, BBT_Time op)
1749 Glib::RWLock::ReaderLock lm (lock);
1750 BBTPointList::const_iterator i;
1751 framecnt_t extra_frames = 0;
1752 bool had_bars = (op.bars != 0);
1754 /* start from the bar|beat right before (or at) pos */
1756 i = bbt_before_or_at (pos);
1758 /* we know that (*i).frame is less than or equal to pos */
1759 extra_frames = pos - (*i).frame;
1761 /* walk backwards */
1763 while (i != _map.begin() && (op.bars || op.beats)) {
1767 if ((*i).is_bar()) {
1774 if ((had_bars && op.bars == 0) || !had_bars) {
1775 /* finished counting bars, or none to count,
1776 so decrement beat count
1784 /* handle ticks (assumed to be less than
1785 * BBT_Time::ticks_per_bar_division, as always.
1789 frameoffset_t tick_frames = llrint ((*i).meter->frames_per_division (*(*i).tempo, _frame_rate) * (op.ticks/BBT_Time::ticks_per_bar_division));
1790 framepos_t pre_tick_frames = (*i).frame + extra_frames;
1791 if (tick_frames < pre_tick_frames) {
1792 return pre_tick_frames - tick_frames;
1796 return (*i).frame + extra_frames;
1800 /** Add the BBT interval op to pos and return the result */
1802 TempoMap::framepos_plus_bbt (framepos_t pos, BBT_Time op)
1804 Glib::RWLock::ReaderLock lm (lock);
1805 BBT_Time op_copy (op);
1806 int additional_minutes = 1;
1807 BBTPointList::const_iterator i;
1808 framecnt_t backup_frames = 0;
1809 bool had_bars = (op.bars != 0);
1813 i = bbt_before_or_at (pos);
1817 /* we know that (*i).frame is before or equal to pos */
1818 backup_frames = pos - (*i).frame;
1820 while (i != _map.end() && (op.bars || op.beats)) {
1825 if ((*i).is_bar()) {
1832 if ((had_bars && op.bars == 0) || !had_bars) {
1833 /* finished counting bars, or none to count,
1834 so decrement beat count
1843 if (i != _map.end()) {
1847 /* we hit the end of the map before finish the bbt walk.
1850 recompute_map (false, pos + (_frame_rate * 60 * additional_minutes));
1851 additional_minutes *= 2;
1853 /* go back and try again */
1854 warning << "reached end of map with op now at " << op << " end = "
1855 << _map.back().frame << ' ' << _map.back().bar << '|' << _map.back().beat << ", trying to walk "
1856 << op_copy << " ... retry"
1861 return (*i).frame - backup_frames +
1862 llrint ((*i).meter->frames_per_division (*(*i).tempo, _frame_rate) * (op.ticks/BBT_Time::ticks_per_bar_division));
1864 return (*i).frame - backup_frames;
1868 /** Count the number of beats that are equivalent to distance when going forward,
1872 TempoMap::framewalk_to_beats (framepos_t pos, framecnt_t distance)
1874 framepos_t end = pos + distance;
1876 require_map_to (end);
1878 Glib::RWLock::ReaderLock lm (lock);
1879 BBTPointList::const_iterator i = bbt_after_or_at (pos);
1880 Evoral::MusicalTime beats = 0;
1882 /* if our starting BBTPoint is after pos, add a fractional beat
1883 to represent that distance.
1886 if ((*i).frame != pos) {
1887 beats += ((*i).frame - pos) / (*i).meter->frames_per_division (*(*i).tempo, _frame_rate);
1890 while (i != _map.end() && (*i).frame < end) {
1895 assert (i != _map.end());
1897 /* if our ending BBTPoint is after the end, subtract a fractional beat
1898 to represent that distance.
1901 if ((*i).frame > end) {
1902 beats -= ((*i).frame - end) / (*i).meter->frames_per_division (*(*i).tempo, _frame_rate);
1908 TempoMap::BBTPointList::const_iterator
1909 TempoMap::bbt_before_or_at (framepos_t pos)
1911 /* CALLER MUST HOLD READ LOCK */
1913 BBTPointList::const_iterator i;
1915 i = lower_bound (_map.begin(), _map.end(), pos);
1916 assert (i != _map.end());
1917 if ((*i).frame > pos) {
1918 assert (i != _map.begin());
1925 bool operator() (const BBT_Time& a, const BBT_Time& b) {
1930 TempoMap::BBTPointList::const_iterator
1931 TempoMap::bbt_before_or_at (const BBT_Time& bbt)
1933 BBTPointList::const_iterator i;
1936 i = lower_bound (_map.begin(), _map.end(), bbt, cmp);
1937 assert (i != _map.end());
1938 if ((*i).bar > bbt.bars || (*i).beat > bbt.beats) {
1939 assert (i != _map.begin());
1945 TempoMap::BBTPointList::const_iterator
1946 TempoMap::bbt_after_or_at (framepos_t pos)
1948 /* CALLER MUST HOLD READ LOCK */
1950 BBTPointList::const_iterator i;
1952 if (_map.back().frame == pos) {
1954 assert (i != _map.begin());
1959 i = upper_bound (_map.begin(), _map.end(), pos);
1960 assert (i != _map.end());
1964 /** Compare the time of this with that of another MetricSection.
1965 * @param with_bbt True to compare using start(), false to use frame().
1966 * @return -1 for less than, 0 for equal, 1 for greater than.
1970 MetricSection::compare (const MetricSection& other) const
1972 if (start() == other.start()) {
1974 } else if (start() < other.start()) {
1985 MetricSection::operator== (const MetricSection& other) const
1987 return compare (other) == 0;
1991 MetricSection::operator!= (const MetricSection& other) const
1993 return compare (other) != 0;
1997 operator<< (std::ostream& o, const Meter& m) {
1998 return o << m.divisions_per_bar() << '/' << m.note_divisor();
2002 operator<< (std::ostream& o, const Tempo& t) {
2003 return o << t.beats_per_minute() << " 1/" << t.note_type() << "'s per minute";
2007 operator<< (std::ostream& o, const MetricSection& section) {
2009 o << "MetricSection @ " << section.frame() << " aka " << section.start() << ' ';
2011 const TempoSection* ts;
2012 const MeterSection* ms;
2014 if ((ts = dynamic_cast<const TempoSection*> (§ion)) != 0) {
2015 o << *((Tempo*) ts);
2016 } else if ((ms = dynamic_cast<const MeterSection*> (§ion)) != 0) {
2017 o << *((Meter*) ms);