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 /* we cast away const here because this is the one place where we need
855 * to actually modify the frame time of each metric section.
858 _extend_map (const_cast<TempoSection*> ((*i).tempo),
859 const_cast<MeterSection*> ((*i).meter),
860 next_metric, BBT_Time ((*i).bar, (*i).beat, 0), (*i).frame, end);
864 TempoMap::_extend_map (TempoSection* tempo, MeterSection* meter,
865 Metrics::iterator next_metric,
866 BBT_Time current, framepos_t current_frame, framepos_t end)
868 /* CALLER MUST HOLD WRITE LOCK */
872 double divisions_per_bar;
875 divisions_per_bar = meter->divisions_per_bar ();
876 beat_frames = meter->frames_per_division (*tempo,_frame_rate);
878 while (current_frame < end) {
881 current_frame += beat_frames;
883 if (current.beats > meter->divisions_per_bar()) {
888 if (next_metric != metrics.end()) {
890 /* no operator >= so invert operator < */
892 DEBUG_TRACE (DEBUG::TempoMath, string_compose ("now at %1 next metric @ %2\n", current, (*next_metric)->start()));
894 if (!(current < (*next_metric)->start())) {
897 if (((ts = dynamic_cast<TempoSection*> (*next_metric)) != 0)) {
901 /* new tempo section: if its on a beat,
902 * we don't have to do anything other
903 * than recompute various distances,
904 * done further below as we transition
905 * the next metric section.
907 * if its not on the beat, we have to
908 * compute the duration of the beat it
909 * is within, which will be different
910 * from the preceding following ones
911 * since it takes part of its duration
912 * from the preceding tempo and part
913 * from this new tempo.
916 if (tempo->start().ticks != 0) {
918 double next_beat_frames = meter->frames_per_division (*tempo,_frame_rate);
920 DEBUG_TRACE (DEBUG::TempoMath, string_compose ("bumped into non-beat-aligned tempo metric at %1 = %2, adjust next beat using %3\n",
921 tempo->start(), current_frame, tempo->bar_offset()));
923 /* back up to previous beat */
924 current_frame -= beat_frames;
925 /* set tempo section location based on offset from last beat */
926 tempo->set_frame (current_frame + (ts->bar_offset() * beat_frames));
927 /* advance to the location of the new (adjusted) beat */
928 current_frame += (ts->bar_offset() * beat_frames) + ((1.0 - ts->bar_offset()) * next_beat_frames);
929 /* next metric doesn't have to
930 * match this precisely to
933 DEBUG_TRACE (DEBUG::TempoMath, string_compose ("Adjusted last beat to %1\n", current_frame));
937 DEBUG_TRACE (DEBUG::TempoMath, string_compose ("bumped into beat-aligned tempo metric at %1 = %2\n",
938 tempo->start(), current_frame));
939 tempo->set_frame (current_frame);
942 } else if ((ms = dynamic_cast<MeterSection*>(*next_metric)) != 0) {
946 /* new meter section: always defines the
950 DEBUG_TRACE (DEBUG::TempoMath, string_compose ("bumped into meter section at %1 vs %2 (%3)\n",
951 meter->start(), current, current_frame));
953 assert (current.beats == 1);
955 meter->set_frame (current_frame);
958 divisions_per_bar = meter->divisions_per_bar ();
959 beat_frames = meter->frames_per_division (*tempo, _frame_rate);
961 DEBUG_TRACE (DEBUG::TempoMath, string_compose ("New metric with beat frames = %1 dpb %2 meter %3 tempo %4\n",
962 beat_frames, divisions_per_bar, *((Meter*)meter), *((Tempo*)tempo)));
966 if (next_metric != metrics.end() && ((*next_metric)->start() == current)) {
967 /* same position so go back and set this one up before advancing
974 if (current.beats == 1) {
975 DEBUG_TRACE (DEBUG::TempoMath, string_compose ("Add Bar at %1|1 @ %2\n", current.bars, current_frame));
976 _map.push_back (BBTPoint (*meter, *tempo,(framepos_t) llrint(current_frame), current.bars, 1));
978 DEBUG_TRACE (DEBUG::TempoMath, string_compose ("Add Beat at %1|%2 @ %3\n", current.bars, current.beats, current_frame));
979 _map.push_back (BBTPoint (*meter, *tempo, (framepos_t) llrint(current_frame), current.bars, current.beats));
985 TempoMap::metric_at (framepos_t frame) const
987 Glib::RWLock::ReaderLock lm (lock);
988 TempoMetric m (first_meter(), first_tempo());
992 /* at this point, we are *guaranteed* to have m.meter and m.tempo pointing
993 at something, because we insert the default tempo and meter during
994 TempoMap construction.
996 now see if we can find better candidates.
999 for (Metrics::const_iterator i = metrics.begin(); i != metrics.end(); ++i) {
1001 // cerr << "Looking at a metric section " << **i << endl;
1003 if ((*i)->frame() > frame) {
1007 if ((tempo = dynamic_cast<const TempoSection*>(*i)) != 0) {
1008 m.set_tempo (*tempo);
1009 } else if ((meter = dynamic_cast<const MeterSection*>(*i)) != 0) {
1010 m.set_meter (*meter);
1013 m.set_frame ((*i)->frame ());
1014 m.set_start ((*i)->start ());
1017 // cerr << "for framepos " << frame << " returning " << m.meter() << " @ " << m.tempo() << " location " << m.frame() << " = " << m.start() << endl;
1022 TempoMap::metric_at (BBT_Time bbt) const
1024 Glib::RWLock::ReaderLock lm (lock);
1025 TempoMetric m (first_meter(), first_tempo());
1029 /* at this point, we are *guaranteed* to have m.meter and m.tempo pointing
1030 at something, because we insert the default tempo and meter during
1031 TempoMap construction.
1033 now see if we can find better candidates.
1036 for (Metrics::const_iterator i = metrics.begin(); i != metrics.end(); ++i) {
1038 BBT_Time section_start ((*i)->start());
1040 if (section_start.bars > bbt.bars || (section_start.bars == bbt.bars && section_start.beats > bbt.beats)) {
1044 if ((tempo = dynamic_cast<const TempoSection*>(*i)) != 0) {
1045 m.set_tempo (*tempo);
1046 } else if ((meter = dynamic_cast<const MeterSection*>(*i)) != 0) {
1047 m.set_meter (*meter);
1050 m.set_frame ((*i)->frame ());
1051 m.set_start (section_start);
1058 TempoMap::bbt_time (framepos_t frame, BBT_Time& bbt)
1060 require_map_to (frame);
1062 Glib::RWLock::ReaderLock lm (lock);
1063 return bbt_time (frame, bbt, bbt_before_or_at (frame));
1067 TempoMap::bbt_time_rt (framepos_t frame, BBT_Time& bbt)
1069 Glib::RWLock::ReaderLock lm (lock, Glib::TRY_LOCK);
1072 throw std::logic_error ("TempoMap::bbt_time_rt() could not lock tempo map");
1075 if (_map.empty() || _map.back().frame < frame) {
1076 throw std::logic_error (string_compose ("map not long enough to reach %1", frame));
1079 return bbt_time (frame, bbt, bbt_before_or_at (frame));
1083 TempoMap::bbt_time (framepos_t frame, BBT_Time& bbt, const BBTPointList::const_iterator& i)
1085 /* CALLER MUST HOLD READ LOCK */
1087 bbt.bars = (*i).bar;
1088 bbt.beats = (*i).beat;
1090 if ((*i).frame == frame) {
1093 bbt.ticks = llrint (((frame - (*i).frame) / (*i).meter->frames_per_division(*((*i).tempo), _frame_rate)) *
1094 BBT_Time::ticks_per_bar_division);
1099 TempoMap::frame_time (const BBT_Time& bbt)
1101 require_map_to (bbt);
1103 Glib::RWLock::ReaderLock lm (lock);
1105 BBTPointList::const_iterator s = bbt_before_or_at (BBT_Time (1, 1, 0));
1106 BBTPointList::const_iterator e = bbt_before_or_at (BBT_Time (bbt.bars, bbt.beats, 0));
1108 if (bbt.ticks != 0) {
1109 return ((*e).frame - (*s).frame) +
1110 llrint ((*e).meter->frames_per_division (*(*e).tempo, _frame_rate) * (bbt.ticks/BBT_Time::ticks_per_bar_division));
1112 return ((*e).frame - (*s).frame);
1117 TempoMap::bbt_duration_at (framepos_t pos, const BBT_Time& bbt, int dir)
1119 Glib::RWLock::ReaderLock lm (lock);
1120 framecnt_t frames = 0;
1123 bbt_time (pos, when);
1124 frames = bbt_duration_at_unlocked (when, bbt,dir);
1130 TempoMap::bbt_duration_at_unlocked (const BBT_Time& when, const BBT_Time& bbt, int dir)
1132 if (bbt.bars == 0 && bbt.beats == 0 && bbt.ticks == 0) {
1136 /* round back to the previous precise beat */
1137 BBTPointList::const_iterator wi = bbt_before_or_at (BBT_Time (when.bars, when.beats, 0));
1138 BBTPointList::const_iterator start (wi);
1139 double tick_frames = 0;
1141 assert (wi != _map.end());
1143 /* compute how much rounding we did because of non-zero ticks */
1145 if (when.ticks != 0) {
1146 tick_frames = (*wi).meter->frames_per_division (*(*wi).tempo, _frame_rate) * (when.ticks/BBT_Time::ticks_per_bar_division);
1152 while (wi != _map.end() && bars < bbt.bars) {
1154 if ((*wi).is_bar()) {
1158 assert (wi != _map.end());
1160 while (wi != _map.end() && beats < bbt.beats) {
1164 assert (wi != _map.end());
1166 /* add any additional frames related to ticks in the added value */
1168 if (bbt.ticks != 0) {
1169 tick_frames += (*wi).meter->frames_per_division (*(*wi).tempo, _frame_rate) * (bbt.ticks/BBT_Time::ticks_per_bar_division);
1172 return ((*wi).frame - (*start).frame) + llrint (tick_frames);
1176 TempoMap::round_to_bar (framepos_t fr, int dir)
1178 return round_to_type (fr, dir, Bar);
1182 TempoMap::round_to_beat (framepos_t fr, int dir)
1184 return round_to_type (fr, dir, Beat);
1188 TempoMap::round_to_beat_subdivision (framepos_t fr, int sub_num, int dir)
1190 require_map_to (fr);
1192 Glib::RWLock::ReaderLock lm (lock);
1193 BBTPointList::const_iterator i = bbt_before_or_at (fr);
1195 uint32_t ticks_one_subdivisions_worth;
1196 uint32_t difference;
1198 bbt_time (fr, the_beat, i);
1200 DEBUG_TRACE (DEBUG::SnapBBT, string_compose ("round %1 to nearest 1/%2 beat, before-or-at = %3 @ %4|%5 precise = %6\n",
1201 fr, sub_num, (*i).frame, (*i).bar, (*i).beat, the_beat));
1203 ticks_one_subdivisions_worth = (uint32_t)BBT_Time::ticks_per_bar_division / sub_num;
1207 /* round to next (even if we're on a subdivision */
1209 uint32_t mod = the_beat.ticks % ticks_one_subdivisions_worth;
1212 /* right on the subdivision, so the difference is just the subdivision ticks */
1213 the_beat.ticks += ticks_one_subdivisions_worth;
1216 /* not on subdivision, compute distance to next subdivision */
1218 the_beat.ticks += ticks_one_subdivisions_worth - mod;
1221 if (the_beat.ticks > BBT_Time::ticks_per_bar_division) {
1222 assert (i != _map.end());
1224 assert (i != _map.end());
1225 the_beat.ticks -= BBT_Time::ticks_per_bar_division;
1229 } else if (dir < 0) {
1231 /* round to previous (even if we're on a subdivision) */
1233 uint32_t mod = the_beat.ticks % ticks_one_subdivisions_worth;
1236 /* right on the subdivision, so the difference is just the subdivision ticks */
1237 difference = ticks_one_subdivisions_worth;
1239 /* not on subdivision, compute distance to previous subdivision, which
1240 is just the modulus.
1246 if (the_beat.ticks < difference) {
1247 if (i == _map.begin()) {
1248 /* can't go backwards from wherever pos is, so just return it */
1252 the_beat.ticks = BBT_Time::ticks_per_bar_division - the_beat.ticks;
1254 the_beat.ticks -= difference;
1258 /* round to nearest */
1262 /* compute the distance to the previous and next subdivision */
1264 if ((rem = fmod ((double) the_beat.ticks, (double) ticks_one_subdivisions_worth)) > ticks_one_subdivisions_worth/2.0) {
1266 /* closer to the next subdivision, so shift forward */
1268 the_beat.ticks = lrint (the_beat.ticks + (ticks_one_subdivisions_worth - rem));
1270 DEBUG_TRACE (DEBUG::SnapBBT, string_compose ("moved forward to %1\n", the_beat.ticks));
1272 if (the_beat.ticks > BBT_Time::ticks_per_bar_division) {
1273 assert (i != _map.end());
1275 assert (i != _map.end());
1276 the_beat.ticks -= BBT_Time::ticks_per_bar_division;
1277 DEBUG_TRACE (DEBUG::SnapBBT, string_compose ("fold beat to %1\n", the_beat));
1280 } else if (rem > 0) {
1282 /* closer to previous subdivision, so shift backward */
1284 if (rem > the_beat.ticks) {
1285 if (i == _map.begin()) {
1286 /* can't go backwards past zero, so ... */
1289 /* step back to previous beat */
1291 the_beat.ticks = lrint (BBT_Time::ticks_per_bar_division - rem);
1292 DEBUG_TRACE (DEBUG::SnapBBT, string_compose ("step back beat to %1\n", the_beat));
1294 the_beat.ticks = lrint (the_beat.ticks - rem);
1295 DEBUG_TRACE (DEBUG::SnapBBT, string_compose ("moved backward to %1\n", the_beat.ticks));
1298 /* on the subdivision, do nothing */
1302 return (*i).frame + (the_beat.ticks/BBT_Time::ticks_per_bar_division) *
1303 (*i).meter->frames_per_division (*((*i).tempo), _frame_rate);
1307 TempoMap::round_to_type (framepos_t frame, int dir, BBTPointType type)
1309 require_map_to (frame);
1311 Glib::RWLock::ReaderLock lm (lock);
1312 BBTPointList::const_iterator fi;
1315 fi = bbt_after_or_at (frame);
1317 fi = bbt_before_or_at (frame);
1320 assert (fi != _map.end());
1322 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));
1327 /* find bar previous to 'frame' */
1329 if ((*fi).is_bar() && (*fi).frame == frame) {
1333 while (!(*fi).is_bar()) {
1334 if (fi == _map.begin()) {
1339 DEBUG_TRACE (DEBUG::SnapBBT, string_compose ("rounded to bar: map iter at %1|%2 %3, return\n",
1340 (*fi).bar, (*fi).beat, (*fi).frame));
1343 } else if (dir > 0) {
1345 /* find bar following 'frame' */
1347 if ((*fi).is_bar() && (*fi).frame == frame) {
1351 while (!(*fi).is_bar()) {
1353 if (fi == _map.end()) {
1359 DEBUG_TRACE (DEBUG::SnapBBT, string_compose ("rounded to bar: map iter at %1|%2 %3, return\n",
1360 (*fi).bar, (*fi).beat, (*fi).frame));
1365 /* true rounding: find nearest bar */
1367 BBTPointList::const_iterator prev = fi;
1368 BBTPointList::const_iterator next = fi;
1370 if ((*fi).frame == frame) {
1374 while ((*prev).beat != 1) {
1375 if (prev == _map.begin()) {
1381 while ((*next).beat != 1) {
1383 if (next == _map.end()) {
1389 if ((frame - (*prev).frame) < ((*next).frame - frame)) {
1390 return (*prev).frame;
1392 return (*next).frame;
1401 if ((*fi).frame >= frame) {
1402 DEBUG_TRACE (DEBUG::SnapBBT, "requested frame is on beat, step back\n");
1405 DEBUG_TRACE (DEBUG::SnapBBT, string_compose ("rounded to beat: map iter at %1|%2 %3, return\n",
1406 (*fi).bar, (*fi).beat, (*fi).frame));
1408 } else if (dir > 0) {
1409 if ((*fi).frame <= frame) {
1410 DEBUG_TRACE (DEBUG::SnapBBT, "requested frame is on beat, step forward\n");
1413 DEBUG_TRACE (DEBUG::SnapBBT, string_compose ("rounded to beat: map iter at %1|%2 %3, return\n",
1414 (*fi).bar, (*fi).beat, (*fi).frame));
1417 /* find beat nearest to frame */
1418 if ((*fi).frame == frame) {
1422 BBTPointList::const_iterator prev = fi;
1423 BBTPointList::const_iterator next = fi;
1427 if ((frame - (*prev).frame) < ((*next).frame - frame)) {
1428 return (*prev).frame;
1430 return (*next).frame;
1442 TempoMap::map (TempoMap::BBTPointList::const_iterator& begin,
1443 TempoMap::BBTPointList::const_iterator& end,
1444 framepos_t lower, framepos_t upper)
1447 Glib::RWLock::WriterLock lm (lock);
1448 if (_map.empty() || (_map.back().frame < upper)) {
1449 recompute_map (false, upper);
1453 begin = lower_bound (_map.begin(), _map.end(), lower);
1454 end = upper_bound (_map.begin(), _map.end(), upper);
1458 TempoMap::tempo_section_at (framepos_t frame) const
1460 Glib::RWLock::ReaderLock lm (lock);
1461 Metrics::const_iterator i;
1462 TempoSection* prev = 0;
1464 for (i = metrics.begin(); i != metrics.end(); ++i) {
1467 if ((t = dynamic_cast<TempoSection*> (*i)) != 0) {
1469 if ((*i)->frame() > frame) {
1485 TempoMap::tempo_at (framepos_t frame) const
1487 TempoMetric m (metric_at (frame));
1493 TempoMap::meter_at (framepos_t frame) const
1495 TempoMetric m (metric_at (frame));
1500 TempoMap::get_state ()
1502 Metrics::const_iterator i;
1503 XMLNode *root = new XMLNode ("TempoMap");
1506 Glib::RWLock::ReaderLock lm (lock);
1507 for (i = metrics.begin(); i != metrics.end(); ++i) {
1508 root->add_child_nocopy ((*i)->get_state());
1516 TempoMap::set_state (const XMLNode& node, int /*version*/)
1519 Glib::RWLock::WriterLock lm (lock);
1522 XMLNodeConstIterator niter;
1523 Metrics old_metrics (metrics);
1524 MeterSection* last_meter = 0;
1528 nlist = node.children();
1530 for (niter = nlist.begin(); niter != nlist.end(); ++niter) {
1531 XMLNode* child = *niter;
1533 if (child->name() == TempoSection::xml_state_node_name) {
1536 TempoSection* ts = new TempoSection (*child);
1537 metrics.push_back (ts);
1539 if (ts->bar_offset() < 0.0) {
1541 ts->update_bar_offset_from_bbt (*last_meter);
1546 catch (failed_constructor& err){
1547 error << _("Tempo map: could not set new state, restoring old one.") << endmsg;
1548 metrics = old_metrics;
1552 } else if (child->name() == MeterSection::xml_state_node_name) {
1555 MeterSection* ms = new MeterSection (*child);
1556 metrics.push_back (ms);
1560 catch (failed_constructor& err) {
1561 error << _("Tempo map: could not set new state, restoring old one.") << endmsg;
1562 metrics = old_metrics;
1568 if (niter == nlist.end()) {
1569 MetricSectionSorter cmp;
1573 recompute_map (true);
1576 PropertyChanged (PropertyChange ());
1582 TempoMap::dump (std::ostream& o) const
1584 Glib::RWLock::ReaderLock lm (lock);
1585 const MeterSection* m;
1586 const TempoSection* t;
1588 for (Metrics::const_iterator i = metrics.begin(); i != metrics.end(); ++i) {
1590 if ((t = dynamic_cast<const TempoSection*>(*i)) != 0) {
1591 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? "
1592 << t->movable() << ')' << endl;
1593 } else if ((m = dynamic_cast<const MeterSection*>(*i)) != 0) {
1594 o << "Meter @ " << *i << ' ' << m->divisions_per_bar() << '/' << m->note_divisor() << " at " << m->start() << " frame= " << m->frame()
1595 << " (movable? " << m->movable() << ')' << endl;
1601 TempoMap::n_tempos() const
1603 Glib::RWLock::ReaderLock lm (lock);
1606 for (Metrics::const_iterator i = metrics.begin(); i != metrics.end(); ++i) {
1607 if (dynamic_cast<const TempoSection*>(*i) != 0) {
1616 TempoMap::n_meters() const
1618 Glib::RWLock::ReaderLock lm (lock);
1621 for (Metrics::const_iterator i = metrics.begin(); i != metrics.end(); ++i) {
1622 if (dynamic_cast<const MeterSection*>(*i) != 0) {
1631 TempoMap::insert_time (framepos_t where, framecnt_t amount)
1634 Glib::RWLock::WriterLock lm (lock);
1635 for (Metrics::iterator i = metrics.begin(); i != metrics.end(); ++i) {
1636 if ((*i)->frame() >= where && (*i)->movable ()) {
1637 (*i)->set_frame ((*i)->frame() + amount);
1641 /* now reset the BBT time of all metrics, based on their new
1642 * audio time. This is the only place where we do this reverse
1646 Metrics::iterator i;
1647 const MeterSection* meter;
1648 const TempoSection* tempo;
1652 meter = &first_meter ();
1653 tempo = &first_tempo ();
1658 // cerr << "\n###################### TIMESTAMP via AUDIO ##############\n" << endl;
1661 MetricSection* prev = 0;
1663 for (i = metrics.begin(); i != metrics.end(); ++i) {
1666 TempoMetric metric (*meter, *tempo);
1669 metric.set_start (prev->start());
1670 metric.set_frame (prev->frame());
1672 // metric will be at frames=0 bbt=1|1|0 by default
1673 // which is correct for our purpose
1676 BBTPointList::const_iterator bi = bbt_before_or_at ((*i)->frame());
1677 bbt_time ((*i)->frame(), bbt, bi);
1679 // cerr << "timestamp @ " << (*i)->frame() << " with " << bbt.bars << "|" << bbt.beats << "|" << bbt.ticks << " => ";
1685 if (bbt.ticks > BBT_Time::ticks_per_bar_division/2) {
1686 /* round up to next beat */
1692 if (bbt.beats != 1) {
1693 /* round up to next bar */
1699 // cerr << bbt << endl;
1701 (*i)->set_start (bbt);
1703 if ((t = dynamic_cast<TempoSection*>(*i)) != 0) {
1705 // cerr << "NEW TEMPO, frame = " << (*i)->frame() << " start = " << (*i)->start() <<endl;
1706 } else if ((m = dynamic_cast<MeterSection*>(*i)) != 0) {
1708 // cerr << "NEW METER, frame = " << (*i)->frame() << " start = " << (*i)->start() <<endl;
1710 fatal << _("programming error: unhandled MetricSection type") << endmsg;
1717 recompute_map (true);
1721 PropertyChanged (PropertyChange ());
1724 /** Add some (fractional) beats to a session frame position, and return the result in frames.
1725 * pos can be -ve, if required.
1728 TempoMap::framepos_plus_beats (framepos_t pos, Evoral::MusicalTime beats)
1730 return framepos_plus_bbt (pos, BBT_Time (beats));
1733 /** Subtract some (fractional) beats to a frame position, and return the result in frames */
1735 TempoMap::framepos_minus_beats (framepos_t pos, Evoral::MusicalTime beats)
1737 return framepos_minus_bbt (pos, BBT_Time (beats));
1741 TempoMap::framepos_minus_bbt (framepos_t pos, BBT_Time op)
1743 Glib::RWLock::ReaderLock lm (lock);
1744 BBTPointList::const_iterator i;
1745 framecnt_t extra_frames = 0;
1746 bool had_bars = (op.bars != 0);
1748 /* start from the bar|beat right before (or at) pos */
1750 i = bbt_before_or_at (pos);
1752 /* we know that (*i).frame is less than or equal to pos */
1753 extra_frames = pos - (*i).frame;
1755 /* walk backwards */
1757 while (i != _map.begin() && (op.bars || op.beats)) {
1761 if ((*i).is_bar()) {
1768 if ((had_bars && op.bars == 0) || !had_bars) {
1769 /* finished counting bars, or none to count,
1770 so decrement beat count
1778 /* handle ticks (assumed to be less than
1779 * BBT_Time::ticks_per_bar_division, as always.
1783 frameoffset_t tick_frames = llrint ((*i).meter->frames_per_division (*(*i).tempo, _frame_rate) * (op.ticks/BBT_Time::ticks_per_bar_division));
1784 framepos_t pre_tick_frames = (*i).frame + extra_frames;
1785 if (tick_frames < pre_tick_frames) {
1786 return pre_tick_frames - tick_frames;
1790 return (*i).frame + extra_frames;
1794 /** Add the BBT interval op to pos and return the result */
1796 TempoMap::framepos_plus_bbt (framepos_t pos, BBT_Time op)
1798 Glib::RWLock::ReaderLock lm (lock);
1799 BBT_Time op_copy (op);
1800 int additional_minutes = 1;
1801 BBTPointList::const_iterator i;
1802 framecnt_t backup_frames = 0;
1803 bool had_bars = (op.bars != 0);
1807 i = bbt_before_or_at (pos);
1811 /* we know that (*i).frame is before or equal to pos */
1812 backup_frames = pos - (*i).frame;
1814 while (i != _map.end() && (op.bars || op.beats)) {
1819 if ((*i).is_bar()) {
1826 if ((had_bars && op.bars == 0) || !had_bars) {
1827 /* finished counting bars, or none to count,
1828 so decrement beat count
1837 if (i != _map.end()) {
1841 /* we hit the end of the map before finish the bbt walk.
1844 recompute_map (false, pos + (_frame_rate * 60 * additional_minutes));
1845 additional_minutes *= 2;
1847 /* go back and try again */
1848 warning << "reached end of map with op now at " << op << " end = "
1849 << _map.back().frame << ' ' << _map.back().bar << '|' << _map.back().beat << ", trying to walk "
1850 << op_copy << " ... retry"
1855 return (*i).frame - backup_frames +
1856 llrint ((*i).meter->frames_per_division (*(*i).tempo, _frame_rate) * (op.ticks/BBT_Time::ticks_per_bar_division));
1858 return (*i).frame - backup_frames;
1862 /** Count the number of beats that are equivalent to distance when going forward,
1866 TempoMap::framewalk_to_beats (framepos_t pos, framecnt_t distance)
1868 framepos_t end = pos + distance;
1870 require_map_to (end);
1872 Glib::RWLock::ReaderLock lm (lock);
1873 BBTPointList::const_iterator i = bbt_after_or_at (pos);
1874 Evoral::MusicalTime beats = 0;
1876 /* if our starting BBTPoint is after pos, add a fractional beat
1877 to represent that distance.
1880 if ((*i).frame != pos) {
1881 beats += ((*i).frame - pos) / (*i).meter->frames_per_division (*(*i).tempo, _frame_rate);
1884 while (i != _map.end() && (*i).frame < end) {
1889 assert (i != _map.end());
1891 /* if our ending BBTPoint is after the end, subtract a fractional beat
1892 to represent that distance.
1895 if ((*i).frame > end) {
1896 beats -= ((*i).frame - end) / (*i).meter->frames_per_division (*(*i).tempo, _frame_rate);
1902 TempoMap::BBTPointList::const_iterator
1903 TempoMap::bbt_before_or_at (framepos_t pos)
1905 /* CALLER MUST HOLD READ LOCK */
1907 BBTPointList::const_iterator i;
1909 i = lower_bound (_map.begin(), _map.end(), pos);
1910 assert (i != _map.end());
1911 if ((*i).frame > pos) {
1912 assert (i != _map.begin());
1919 bool operator() (const BBT_Time& a, const BBT_Time& b) {
1924 TempoMap::BBTPointList::const_iterator
1925 TempoMap::bbt_before_or_at (const BBT_Time& bbt)
1927 BBTPointList::const_iterator i;
1930 i = lower_bound (_map.begin(), _map.end(), bbt, cmp);
1931 assert (i != _map.end());
1932 if ((*i).bar > bbt.bars || (*i).beat > bbt.beats) {
1933 assert (i != _map.begin());
1939 TempoMap::BBTPointList::const_iterator
1940 TempoMap::bbt_after_or_at (framepos_t pos)
1942 /* CALLER MUST HOLD READ LOCK */
1944 BBTPointList::const_iterator i;
1946 if (_map.back().frame == pos) {
1948 assert (i != _map.begin());
1953 i = upper_bound (_map.begin(), _map.end(), pos);
1954 assert (i != _map.end());
1958 /** Compare the time of this with that of another MetricSection.
1959 * @param with_bbt True to compare using start(), false to use frame().
1960 * @return -1 for less than, 0 for equal, 1 for greater than.
1964 MetricSection::compare (const MetricSection& other) const
1966 if (start() == other.start()) {
1968 } else if (start() < other.start()) {
1979 MetricSection::operator== (const MetricSection& other) const
1981 return compare (other) == 0;
1985 MetricSection::operator!= (const MetricSection& other) const
1987 return compare (other) != 0;
1991 operator<< (std::ostream& o, const Meter& m) {
1992 return o << m.divisions_per_bar() << '/' << m.note_divisor();
1996 operator<< (std::ostream& o, const Tempo& t) {
1997 return o << t.beats_per_minute() << " 1/" << t.note_type() << "'s per minute";
2001 operator<< (std::ostream& o, const MetricSection& section) {
2003 o << "MetricSection @ " << section.frame() << " aka " << section.start() << ' ';
2005 const TempoSection* ts;
2006 const MeterSection* ms;
2008 if ((ts = dynamic_cast<const TempoSection*> (§ion)) != 0) {
2009 o << *((Tempo*) ts);
2010 } else if ((ms = dynamic_cast<const MeterSection*> (§ion)) != 0) {
2011 o << *((Meter*) ms);