// -*- c-basic-offset: 4 -*- /* Rosegarden A sequencer and musical notation editor. This program is Copyright 2000-2008 Guillaume Laurent , Chris Cannam , Richard Bown The moral right of the authors to claim authorship of this work has been asserted. 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 the Free Software Foundation; either version 2 of the License, or (at your option) any later version. See the file COPYING included with this distribution for more information. */ #include "Composition.h" #include "misc/Debug.h" #include "Segment.h" #include "FastVector.h" #include "BaseProperties.h" #include "BasicQuantizer.h" #include "NotationQuantizer.h" #include #include #include #include #include #if (__GNUC__ < 3) #include #define stringstream strstream #else #include #endif using std::cerr; using std::endl; //#define DEBUG_BAR_STUFF 1 //#define DEBUG_TEMPO_STUFF 1 namespace Rosegarden { const PropertyName Composition::NoAbsoluteTimeProperty = "NoAbsoluteTime"; const PropertyName Composition::BarNumberProperty = "BarNumber"; const std::string Composition::TempoEventType = "tempo"; const PropertyName Composition::TempoProperty = "Tempo"; const PropertyName Composition::TargetTempoProperty = "TargetTempo"; const PropertyName Composition::TempoTimestampProperty = "TimestampSec"; bool Composition::ReferenceSegmentEventCmp::operator()(const Event &e1, const Event &e2) const { if (e1.has(NoAbsoluteTimeProperty) || e2.has(NoAbsoluteTimeProperty)) { RealTime r1 = getTempoTimestamp(&e1); RealTime r2 = getTempoTimestamp(&e2); return r1 < r2; } else { return e1 < e2; } } Composition::ReferenceSegment::ReferenceSegment(std::string eventType) : m_eventType(eventType) { // nothing } Composition::ReferenceSegment::~ReferenceSegment() { clear(); } void Composition::ReferenceSegment::clear() { for (iterator it = begin(); it != end(); ++it) delete (*it); Impl::erase(begin(), end()); } timeT Composition::ReferenceSegment::getDuration() const { const_iterator i = end(); if (i == begin()) return 0; --i; return (*i)->getAbsoluteTime() + (*i)->getDuration(); } Composition::ReferenceSegment::iterator Composition::ReferenceSegment::tqfind(Event *e) { return std::lower_bound (begin(), end(), e, ReferenceSegmentEventCmp()); } Composition::ReferenceSegment::iterator Composition::ReferenceSegment::insert(Event *e) { if (!e->isa(m_eventType)) { throw Event::BadType(std::string("event in ReferenceSegment"), m_eventType, e->getType(), __FILE__, __LINE__); } iterator i = tqfind(e); if (i != end() && (*i)->getAbsoluteTime() == e->getAbsoluteTime()) { Event *old = (*i); (*i) = e; delete old; return i; } else { return Impl::insert(i, e); } } void Composition::ReferenceSegment::erase(Event *e) { iterator i = tqfind(e); if (i != end()) Impl::erase(i); } Composition::ReferenceSegment::iterator Composition::ReferenceSegment::findTime(timeT t) { Event dummy("dummy", t, 0, MIN_SUBORDERING); return tqfind(&dummy); } Composition::ReferenceSegment::iterator Composition::ReferenceSegment::findRealTime(RealTime t) { Event dummy("dummy", 0, 0, MIN_SUBORDERING); dummy.set(NoAbsoluteTimeProperty, true); setTempoTimestamp(&dummy, t); return tqfind(&dummy); } Composition::ReferenceSegment::iterator Composition::ReferenceSegment::findNearestTime(timeT t) { iterator i = findTime(t); if (i == end() || (*i)->getAbsoluteTime() > t) { if (i == begin()) return end(); else --i; } return i; } Composition::ReferenceSegment::iterator Composition::ReferenceSegment::findNearestRealTime(RealTime t) { iterator i = findRealTime(t); if (i == end() || (getTempoTimestamp(*i) > t)) { if (i == begin()) return end(); else --i; } return i; } int Composition::m_defaultNbBars = 100; Composition::Composition() : m_solo(false), // default is not soloing m_selectedTrack(0), m_timeSigSegment(TimeSignature::EventType), m_tempoSegment(TempoEventType), m_barPositionsNeedCalculating(true), m_tempoTimestampsNeedCalculating(true), m_basicQuantizer(new BasicQuantizer()), m_notationQuantizer(new NotationQuantizer()), m_position(0), m_defaultTempo(getTempoForQpm(120.0)), m_minTempo(0), m_maxTempo(0), m_startMarker(0), m_endMarker(getBarRange(m_defaultNbBars).first), m_loopStart(0), m_loopEnd(0), m_playMetronome(false), m_recordMetronome(true), m_nextTriggerSegmentId(0) { // nothing else } Composition::~Composition() { if (!m_observers.empty()) { cerr << "Warning: Composition::~Composition() with " << m_observers.size() << " observers still extant" << endl; cerr << "Observers are:"; for (ObserverSet::const_iterator i = m_observers.begin(); i != m_observers.end(); ++i) { cerr << " " << (void *)(*i); cerr << " [" << typeid(**i).name() << "]"; } cerr << endl; } notifySourceDeletion(); clear(); delete m_basicQuantizer; delete m_notationQuantizer; } Composition::iterator Composition::addSegment(Segment *segment) { iterator res = weakAddSegment(segment); if (res != end()) { updateRefreshStatuses(); notifySegmentAdded(segment); } return res; } Composition::iterator Composition::weakAddSegment(Segment *segment) { if (!segment) return end(); iterator res = m_segments.insert(segment); segment->setComposition(this); return res; } void Composition::deleteSegment(Composition::iterator i) { if (i == end()) return; Segment *p = (*i); p->setComposition(0); m_segments.erase(i); notifySegmentRemoved(p); delete p; updateRefreshStatuses(); } bool Composition::deleteSegment(Segment *segment) { iterator i = findSegment(segment); if (i == end()) return false; deleteSegment(i); return true; } bool Composition::detachSegment(Segment *segment) { bool res = weakDetachSegment(segment); if (res) { notifySegmentRemoved(segment); updateRefreshStatuses(); } return res; } bool Composition::weakDetachSegment(Segment *segment) { iterator i = findSegment(segment); if (i == end()) return false; segment->setComposition(0); m_segments.erase(i); return true; } bool Composition::tqcontains(const Segment *s) { iterator i = findSegment(s); return (i != end()); } Composition::iterator Composition::findSegment(const Segment *s) { iterator i = m_segments.lower_bound(const_cast(s)); while (i != end()) { if (*i == s) break; if ((*i)->getStartTime() > s->getStartTime()) return end(); ++i; } return i; } void Composition::setSegmentStartTime(Segment *segment, timeT startTime) { // remove the segment from the multiset iterator i = findSegment(segment); if (i == end()) return; m_segments.erase(i); segment->setStartTimeDataMember(startTime); // re-add it m_segments.insert(segment); } int Composition::getMaxContemporaneousSegmentsOnTrack(TrackId track) const { // Could be made faster, but only if it needs to be. // This is similar to the polyphony calculation in // DocumentMetaConfigurationPage ctor. std::set simultaneous; std::multimap ends; int maximum = 0; for (const_iterator i = begin(); i != end(); ++i) { if ((*i)->getTrack() != track) continue; timeT t0 = (*i)->getStartTime(); timeT t1 = (*i)->getRepeatEndTime(); // std::cerr << "getMaxContemporaneousSegmentsOnTrack(" << track << "): segment " << *i << " from " << t0 << " to " << t1 << std::endl; while (!ends.empty() && t0 >= ends.begin()->first) { simultaneous.erase(ends.begin()->second); ends.erase(ends.begin()); } simultaneous.insert(*i); ends.insert(std::multimap::value_type(t1, *i)); int current = simultaneous.size(); if (current > maximum) maximum = current; } return maximum; } int Composition::getSegmentVoiceIndex(const Segment *segment) const { TrackId track = segment->getTrack(); // See function above std::map indices; std::set used; std::multimap ends; int maximum = 0; for (const_iterator i = begin(); i != end(); ++i) { if ((*i)->getTrack() != track) continue; timeT t0 = (*i)->getStartTime(); timeT t1 = (*i)->getRepeatEndTime(); int index; while (!ends.empty() && t0 >= ends.begin()->first) { index = indices[ends.begin()->second]; used.erase(index); indices.erase(ends.begin()->second); ends.erase(ends.begin()); } for (index = 0; ; ++index) { if (used.find(index) == used.end()) break; } if (*i == segment) return index; indices[*i] = index; used.insert(index); ends.insert(std::multimap::value_type(t1, *i)); } std::cerr << "WARNING: Composition::getSegmentVoiceIndex: segment " << segment << " not found in composition" << std::endl; return 0; } TriggerSegmentRec * Composition::addTriggerSegment(Segment *s, int pitch, int velocity) { TriggerSegmentId id = m_nextTriggerSegmentId; return addTriggerSegment(s, id, pitch, velocity); } TriggerSegmentRec * Composition::addTriggerSegment(Segment *s, TriggerSegmentId id, int pitch, int velocity) { TriggerSegmentRec *rec = getTriggerSegmentRec(id); if (rec) return 0; rec = new TriggerSegmentRec(id, s, pitch, velocity); m_triggerSegments.insert(rec); s->setComposition(this); if (m_nextTriggerSegmentId <= id) m_nextTriggerSegmentId = id + 1; return rec; } void Composition::deleteTriggerSegment(TriggerSegmentId id) { TriggerSegmentRec dummyRec(id, 0); triggersegmentcontaineriterator i = m_triggerSegments.find(&dummyRec); if (i == m_triggerSegments.end()) return; (*i)->getSegment()->setComposition(0); delete (*i)->getSegment(); delete *i; m_triggerSegments.erase(i); } void Composition::detachTriggerSegment(TriggerSegmentId id) { TriggerSegmentRec dummyRec(id, 0); triggersegmentcontaineriterator i = m_triggerSegments.find(&dummyRec); if (i == m_triggerSegments.end()) return; (*i)->getSegment()->setComposition(0); delete *i; m_triggerSegments.erase(i); } void Composition::clearTriggerSegments() { for (triggersegmentcontaineriterator i = m_triggerSegments.begin(); i != m_triggerSegments.end(); ++i) { delete (*i)->getSegment(); delete *i; } m_triggerSegments.clear(); } int Composition::getTriggerSegmentId(Segment *s) { for (triggersegmentcontaineriterator i = m_triggerSegments.begin(); i != m_triggerSegments.end(); ++i) { if ((*i)->getSegment() == s) return (*i)->getId(); } return -1; } Segment * Composition::getTriggerSegment(TriggerSegmentId id) { TriggerSegmentRec *rec = getTriggerSegmentRec(id); if (!rec) return 0; return rec->getSegment(); } TriggerSegmentRec * Composition::getTriggerSegmentRec(TriggerSegmentId id) { TriggerSegmentRec dummyRec(id, 0); triggersegmentcontaineriterator i = m_triggerSegments.find(&dummyRec); if (i == m_triggerSegments.end()) return 0; return *i; } TriggerSegmentId Composition::getNextTriggerSegmentId() const { return m_nextTriggerSegmentId; } void Composition::setNextTriggerSegmentId(TriggerSegmentId id) { m_nextTriggerSegmentId = id; } void Composition::updateTriggerSegmentReferences() { std::map refs; for (iterator i = begin(); i != end(); ++i) { for (Segment::iterator j = (*i)->begin(); j != (*i)->end(); ++j) { if ((*j)->has(BaseProperties::TRIGGER_SEGMENT_ID)) { TriggerSegmentId id = (*j)->get(BaseProperties::TRIGGER_SEGMENT_ID); refs[id].insert((*i)->getRuntimeId()); } } } for (std::map::iterator i = refs.begin(); i != refs.end(); ++i) { TriggerSegmentRec *rec = getTriggerSegmentRec(i->first); if (rec) rec->setReferences(i->second); } } timeT Composition::getDuration() const { timeT maxDuration = 0; for (segmentcontainer::const_iterator i = m_segments.begin(); i != m_segments.end(); ++i) { timeT segmentTotal = (*i)->getEndTime(); if (segmentTotal > maxDuration) { maxDuration = segmentTotal; } } return maxDuration; } void Composition::setStartMarker(const timeT &sM) { m_startMarker = sM; updateRefreshStatuses(); } void Composition::setEndMarker(const timeT &eM) { bool shorten = (eM < m_endMarker); m_endMarker = eM; updateRefreshStatuses(); notifyEndMarkerChange(shorten); } void Composition::clear() { while (m_segments.size() > 0) { deleteSegment(begin()); } clearTracks(); clearMarkers(); clearTriggerSegments(); m_timeSigSegment.clear(); m_tempoSegment.clear(); m_defaultTempo = getTempoForQpm(120.0); m_minTempo = 0; m_maxTempo = 0; m_loopStart = 0; m_loopEnd = 0; m_position = 0; m_startMarker = 0; m_endMarker = getBarRange(m_defaultNbBars).first; m_solo = false; m_selectedTrack = 0; updateRefreshStatuses(); } void Composition::calculateBarPositions() const { if (!m_barPositionsNeedCalculating) return; #ifdef DEBUG_BAR_STUFF cerr << "Composition::calculateBarPositions" << endl; #endif ReferenceSegment &t = m_timeSigSegment; ReferenceSegment::iterator i; timeT lastBarNo = 0; timeT lastSigTime = 0; timeT barDuration = TimeSignature().getBarDuration(); if (getStartMarker() < 0) { if (!t.empty() && (*t.begin())->getAbsoluteTime() <= 0) { barDuration = TimeSignature(**t.begin()).getBarDuration(); } lastBarNo = getStartMarker() / barDuration; lastSigTime = getStartMarker(); #ifdef DEBUG_BAR_STUFF cerr << "Composition::calculateBarPositions: start marker = " << getStartMarker() << ", so initial bar number = " << lastBarNo << endl; #endif } for (i = t.begin(); i != t.end(); ++i) { timeT myTime = (*i)->getAbsoluteTime(); int n = (myTime - lastSigTime) / barDuration; // should only happen for first time sig, when it's at time < 0: if (myTime < lastSigTime) --n; // would there be a new bar here anyway? if (barDuration * n + lastSigTime == myTime) { // yes n += lastBarNo; } else { // no n += lastBarNo + 1; } #ifdef DEBUG_BAR_STUFF cerr << "Composition::calculateBarPositions: bar " << n << " at " << myTime << endl; #endif (*i)->set(BarNumberProperty, n); lastBarNo = n; lastSigTime = myTime; barDuration = TimeSignature(**i).getBarDuration(); } m_barPositionsNeedCalculating = false; } int Composition::getNbBars() const { calculateBarPositions(); // the "-1" is a small kludge to deal with the case where the // composition has a duration that's an exact number of bars int bars = getBarNumber(getDuration() - 1) + 1; #ifdef DEBUG_BAR_STUFF cerr << "Composition::getNbBars: returning " << bars << endl; #endif return bars; } int Composition::getBarNumber(timeT t) const { calculateBarPositions(); ReferenceSegment::iterator i = m_timeSigSegment.findNearestTime(t); int n; if (i == m_timeSigSegment.end()) { // precedes any time signatures timeT bd = TimeSignature().getBarDuration(); if (t < 0) { // see comment in getTimeSignatureAtAux i = m_timeSigSegment.begin(); if (i != m_timeSigSegment.end() && (*i)->getAbsoluteTime() <= 0) { bd = TimeSignature(**i).getBarDuration(); } } n = t / bd; if (t < 0) { // negative bars should be rounded down, except where // the time is on a barline in which case we already // have the right value (i.e. time -1920 is bar -1, // but time -3840 is also bar -1, in 4/4) if (n * bd != t) --n; } } else { n = (*i)->get(BarNumberProperty); timeT offset = t - (*i)->getAbsoluteTime(); n += offset / TimeSignature(**i).getBarDuration(); } #ifdef DEBUG_BAR_STUFF cerr << "Composition::getBarNumber(" << t << "): returning " << n << endl; #endif return n; } std::pair Composition::getBarRangeForTime(timeT t) const { return getBarRange(getBarNumber(t)); } std::pair Composition::getBarRange(int n) const { calculateBarPositions(); Event dummy("dummy", 0); dummy.set(BarNumberProperty, n); ReferenceSegment::iterator j = std::lower_bound (m_timeSigSegment.begin(), m_timeSigSegment.end(), &dummy, BarNumberComparator()); ReferenceSegment::iterator i = j; if (i == m_timeSigSegment.end() || (*i)->get(BarNumberProperty) > n) { if (i == m_timeSigSegment.begin()) i = m_timeSigSegment.end(); else --i; } else ++j; // j needs to point to following barline timeT start, finish; if (i == m_timeSigSegment.end()) { // precedes any time sig changes timeT barDuration = TimeSignature().getBarDuration(); if (n < 0) { // see comment in getTimeSignatureAtAux i = m_timeSigSegment.begin(); if (i != m_timeSigSegment.end() && (*i)->getAbsoluteTime() <= 0) { barDuration = TimeSignature(**i).getBarDuration(); } } start = n * barDuration; finish = start + barDuration; #ifdef DEBUG_BAR_STUFF cerr << "Composition::getBarRange[1]: bar " << n << ": (" << start << " -> " << finish << ")" << endl; #endif } else { timeT barDuration = TimeSignature(**i).getBarDuration(); start = (*i)->getAbsoluteTime() + (n - (*i)->get(BarNumberProperty)) * barDuration; finish = start + barDuration; #ifdef DEBUG_BAR_STUFF cerr << "Composition::getBarRange[2]: bar " << n << ": (" << start << " -> " << finish << ")" << endl; #endif } // partial bar if (j != m_timeSigSegment.end() && finish > (*j)->getAbsoluteTime()) { finish = (*j)->getAbsoluteTime(); #ifdef DEBUG_BAR_STUFF cerr << "Composition::getBarRange[3]: bar " << n << ": (" << start << " -> " << finish << ")" << endl; #endif } return std::pair(start, finish); } int Composition::addTimeSignature(timeT t, TimeSignature timeSig) { #ifdef DEBUG_BAR_STUFF cerr << "Composition::addTimeSignature(" << t << ", " << timeSig.getNumerator() << "/" << timeSig.getDenominator() << ")" << endl; #endif ReferenceSegment::iterator i = m_timeSigSegment.insert(timeSig.getAsEvent(t)); m_barPositionsNeedCalculating = true; updateRefreshStatuses(); notifyTimeSignatureChanged(); return std::distance(m_timeSigSegment.begin(), i); } TimeSignature Composition::getTimeSignatureAt(timeT t) const { TimeSignature timeSig; (void)getTimeSignatureAt(t, timeSig); return timeSig; } timeT Composition::getTimeSignatureAt(timeT t, TimeSignature &timeSig) const { ReferenceSegment::iterator i = getTimeSignatureAtAux(t); if (i == m_timeSigSegment.end()) { timeSig = TimeSignature(); return 0; } else { timeSig = TimeSignature(**i); return (*i)->getAbsoluteTime(); } } TimeSignature Composition::getTimeSignatureInBar(int barNo, bool &isNew) const { isNew = false; timeT t = getBarRange(barNo).first; ReferenceSegment::iterator i = getTimeSignatureAtAux(t); if (i == m_timeSigSegment.end()) return TimeSignature(); if (t == (*i)->getAbsoluteTime()) isNew = true; return TimeSignature(**i); } Composition::ReferenceSegment::iterator Composition::getTimeSignatureAtAux(timeT t) const { ReferenceSegment::iterator i = m_timeSigSegment.findNearestTime(t); // In negative time, if there's no time signature actually defined // prior to the point of interest then we use the next time // signature after it, so long as it's no later than time zero. // This is the only rational way to deal with count-in bars where // the correct time signature otherwise won't appear until we hit // bar zero. if (t < 0 && i == m_timeSigSegment.end()) { i = m_timeSigSegment.begin(); if (i != m_timeSigSegment.end() && (*i)->getAbsoluteTime() > 0) { i = m_timeSigSegment.end(); } } return i; } int Composition::getTimeSignatureCount() const { return m_timeSigSegment.size(); } int Composition::getTimeSignatureNumberAt(timeT t) const { ReferenceSegment::iterator i = getTimeSignatureAtAux(t); if (i == m_timeSigSegment.end()) return -1; else return std::distance(m_timeSigSegment.begin(), i); } std::pair Composition::getTimeSignatureChange(int n) const { return std::pair (m_timeSigSegment[n]->getAbsoluteTime(), TimeSignature(*m_timeSigSegment[n])); } void Composition::removeTimeSignature(int n) { m_timeSigSegment.erase(m_timeSigSegment[n]); m_barPositionsNeedCalculating = true; updateRefreshStatuses(); notifyTimeSignatureChanged(); } tempoT Composition::getTempoAtTime(timeT t) const { ReferenceSegment::iterator i = m_tempoSegment.findNearestTime(t); // In negative time, if there's no tempo event actually defined // prior to the point of interest then we use the next one after // it, so long as it's no later than time zero. This is the only // rational way to deal with count-in bars where the correct // tempo otherwise won't appear until we hit bar zero. See also // getTimeSignatureAt if (i == m_tempoSegment.end()) { if (t < 0) { #ifdef DEBUG_TEMPO_STUFF cerr << "Composition: Negative time " << t << " for tempo, using 0" << endl; #endif return getTempoAtTime(0); } else return m_defaultTempo; } tempoT tempo = (tempoT)((*i)->get(TempoProperty)); if ((*i)->has(TargetTempoProperty)) { tempoT target = (tempoT)((*i)->get(TargetTempoProperty)); ReferenceSegment::iterator j = i; ++j; if (target > 0 || (target == 0 && j != m_tempoSegment.end())) { timeT t0 = (*i)->getAbsoluteTime(); timeT t1 = (j != m_tempoSegment.end() ? (*j)->getAbsoluteTime() : getEndMarker()); if (t1 < t0) return tempo; if (target == 0) { target = (tempoT)((*j)->get(TempoProperty)); } // tempo ramps are linear in 1/tempo double s0 = 1.0 / double(tempo); double s1 = 1.0 / double(target); double s = s0 + (t - t0) * ((s1 - s0) / (t1 - t0)); tempoT result = tempoT((1.0 / s) + 0.01); #ifdef DEBUG_TEMPO_STUFF cerr << "Composition: Calculated tempo " << result << " at " << t << endl; #endif return result; } } #ifdef DEBUG_TEMPO_STUFF cerr << "Composition: Found tempo " << tempo << " at " << t << endl; #endif return tempo; } int Composition::addTempoAtTime(timeT time, tempoT tempo, tempoT targetTempo) { // If there's an existing tempo at this time, the ReferenceSegment // object will remove the duplicate, but we have to ensure that // the minimum and maximum tempos are updated if necessary. bool fullTempoUpdate = false; int n = getTempoChangeNumberAt(time); if (n >= 0) { std::pair tc = getTempoChange(n); if (tc.first == time) { if (tc.second == m_minTempo || tc.second == m_maxTempo) { fullTempoUpdate = true; } else { std::pair tr = getTempoRamping(n); if (tr.first && (tr.second == m_minTempo || tr.second == m_maxTempo)) { fullTempoUpdate = true; } } } } Event *tempoEvent = new Event(TempoEventType, time); tempoEvent->set(TempoProperty, tempo); if (targetTempo >= 0) { tempoEvent->set(TargetTempoProperty, targetTempo); } ReferenceSegment::iterator i = m_tempoSegment.insert(tempoEvent); if (fullTempoUpdate) { updateExtremeTempos(); } else { if (tempo < m_minTempo || m_minTempo == 0) m_minTempo = tempo; if (targetTempo > 0 && targetTempo < m_minTempo) m_minTempo = targetTempo; if (tempo > m_maxTempo || m_maxTempo == 0) m_maxTempo = tempo; if (targetTempo > 0 && targetTempo > m_maxTempo) m_maxTempo = targetTempo; } m_tempoTimestampsNeedCalculating = true; updateRefreshStatuses(); #ifdef DEBUG_TEMPO_STUFF cerr << "Composition: Added tempo " << tempo << " at " << time << endl; #endif notifyTempoChanged(); return std::distance(m_tempoSegment.begin(), i); } int Composition::getTempoChangeCount() const { return m_tempoSegment.size(); } int Composition::getTempoChangeNumberAt(timeT t) const { ReferenceSegment::iterator i = m_tempoSegment.findNearestTime(t); if (i == m_tempoSegment.end()) return -1; else return std::distance(m_tempoSegment.begin(), i); } std::pair Composition::getTempoChange(int n) const { return std::pair (m_tempoSegment[n]->getAbsoluteTime(), tempoT(m_tempoSegment[n]->get(TempoProperty))); } std::pair Composition::getTempoRamping(int n, bool calculate) const { tempoT target = -1; if (m_tempoSegment[n]->has(TargetTempoProperty)) { target = m_tempoSegment[n]->get(TargetTempoProperty); } bool ramped = (target >= 0); if (target == 0) { if (calculate) { if (m_tempoSegment.size() > n+1) { target = m_tempoSegment[n+1]->get(TempoProperty); } } } if (target < 0 || (calculate && (target == 0))) { target = m_tempoSegment[n]->get(TempoProperty); } return std::pair(ramped, target); } void Composition::removeTempoChange(int n) { tempoT oldTempo = m_tempoSegment[n]->get(TempoProperty); tempoT oldTarget = -1; if (m_tempoSegment[n]->has(TargetTempoProperty)) { oldTarget = m_tempoSegment[n]->get(TargetTempoProperty); } m_tempoSegment.erase(m_tempoSegment[n]); m_tempoTimestampsNeedCalculating = true; if (oldTempo == m_minTempo || oldTempo == m_maxTempo || (oldTarget > 0 && oldTarget == m_minTempo) || (oldTarget > 0 && oldTarget == m_maxTempo)) { updateExtremeTempos(); } updateRefreshStatuses(); notifyTempoChanged(); } void Composition::updateExtremeTempos() { m_minTempo = 0; m_maxTempo = 0; for (ReferenceSegment::iterator i = m_tempoSegment.begin(); i != m_tempoSegment.end(); ++i) { tempoT tempo = (*i)->get(TempoProperty); tempoT target = -1; if ((*i)->has(TargetTempoProperty)) { target = (*i)->get(TargetTempoProperty); } if (tempo < m_minTempo || m_minTempo == 0) m_minTempo = tempo; if (target > 0 && target < m_minTempo) m_minTempo = target; if (tempo > m_maxTempo || m_maxTempo == 0) m_maxTempo = tempo; if (target > 0 && target > m_maxTempo) m_maxTempo = target; } if (m_minTempo == 0) { m_minTempo = m_defaultTempo; m_maxTempo = m_defaultTempo; } } RealTime Composition::getElapsedRealTime(timeT t) const { calculateTempoTimestamps(); ReferenceSegment::iterator i = m_tempoSegment.findNearestTime(t); if (i == m_tempoSegment.end()) { i = m_tempoSegment.begin(); if (t >= 0 || (i == m_tempoSegment.end() || (*i)->getAbsoluteTime() > 0)) { return time2RealTime(t, m_defaultTempo); } } RealTime elapsed; tempoT target = -1; timeT nextTempoTime = t; if (!getTempoTarget(i, target, nextTempoTime)) target = -1; if (target > 0) { elapsed = getTempoTimestamp(*i) + time2RealTime(t - (*i)->getAbsoluteTime(), tempoT((*i)->get(TempoProperty)), nextTempoTime - (*i)->getAbsoluteTime(), target); } else { elapsed = getTempoTimestamp(*i) + time2RealTime(t - (*i)->getAbsoluteTime(), tempoT((*i)->get(TempoProperty))); } #ifdef DEBUG_TEMPO_STUFF cerr << "Composition::getElapsedRealTime: " << t << " -> " << elapsed << " (last tempo change at " << (*i)->getAbsoluteTime() << ")" << endl; #endif return elapsed; } timeT Composition::getElapsedTimeForRealTime(RealTime t) const { calculateTempoTimestamps(); ReferenceSegment::iterator i = m_tempoSegment.findNearestRealTime(t); if (i == m_tempoSegment.end()) { i = m_tempoSegment.begin(); if (t >= RealTime::zeroTime || (i == m_tempoSegment.end() || (*i)->getAbsoluteTime() > 0)) { return realTime2Time(t, m_defaultTempo); } } timeT elapsed; tempoT target = -1; timeT nextTempoTime = 0; if (!getTempoTarget(i, target, nextTempoTime)) target = -1; if (target > 0) { elapsed = (*i)->getAbsoluteTime() + realTime2Time(t - getTempoTimestamp(*i), (tempoT)((*i)->get(TempoProperty)), nextTempoTime - (*i)->getAbsoluteTime(), target); } else { elapsed = (*i)->getAbsoluteTime() + realTime2Time(t - getTempoTimestamp(*i), (tempoT)((*i)->get(TempoProperty))); } #ifdef DEBUG_TEMPO_STUFF static int doError = true; if (doError) { doError = false; RealTime cfReal = getElapsedRealTime(elapsed); timeT cfTimeT = getElapsedTimeForRealTime(cfReal); doError = true; cerr << "getElapsedTimeForRealTime: " << t << " -> " << elapsed << " (error " << (cfReal - t) << " or " << (cfTimeT - elapsed) << ", tempo " << (*i)->getAbsoluteTime() << ":" << (tempoT)((*i)->get(TempoProperty)) << ")" << endl; } #endif return elapsed; } void Composition::calculateTempoTimestamps() const { if (!m_tempoTimestampsNeedCalculating) return; timeT lastTimeT = 0; RealTime lastRealTime; tempoT tempo = m_defaultTempo; tempoT target = -1; #ifdef DEBUG_TEMPO_STUFF cerr << "Composition::calculateTempoTimestamps: Tempo events are:" << endl; #endif for (ReferenceSegment::iterator i = m_tempoSegment.begin(); i != m_tempoSegment.end(); ++i) { RealTime myTime; if (target > 0) { myTime = lastRealTime + time2RealTime((*i)->getAbsoluteTime() - lastTimeT, tempo, (*i)->getAbsoluteTime() - lastTimeT, target); } else { myTime = lastRealTime + time2RealTime((*i)->getAbsoluteTime() - lastTimeT, tempo); } setTempoTimestamp(*i, myTime); #ifdef DEBUG_TEMPO_STUFF (*i)->dump(cerr); #endif lastRealTime = myTime; lastTimeT = (*i)->getAbsoluteTime(); tempo = tempoT((*i)->get(TempoProperty)); target = -1; timeT nextTempoTime = 0; if (!getTempoTarget(i, target, nextTempoTime)) target = -1; } m_tempoTimestampsNeedCalculating = false; } #ifdef DEBUG_TEMPO_STUFF static int DEBUG_silence_recursive_tempo_printout = 0; #endif RealTime Composition::time2RealTime(timeT t, tempoT tempo) const { static timeT cdur = Note(Note::Crotchet).getDuration(); double dt = (double(t) * 100000 * 60) / (double(tempo) * cdur); int sec = int(dt); int nsec = int((dt - sec) * 1000000000); RealTime rt(sec, nsec); #ifdef DEBUG_TEMPO_STUFF if (!DEBUG_silence_recursive_tempo_printout) { cerr << "Composition::time2RealTime: t " << t << ", sec " << sec << ", nsec " << nsec << ", tempo " << tempo << ", cdur " << cdur << ", dt " << dt << ", rt " << rt << endl; DEBUG_silence_recursive_tempo_printout = 1; timeT ct = realTime2Time(rt, tempo); timeT et = t - ct; RealTime ert = time2RealTime(et, tempo); cerr << "cf. realTime2Time(" << rt << ") -> " << ct << " [err " << et << " (" << ert << "?)]" << endl; DEBUG_silence_recursive_tempo_printout=0; } #endif return rt; } RealTime Composition::time2RealTime(timeT time, tempoT tempo, timeT targetTime, tempoT targetTempo) const { static timeT cdur = Note(Note::Crotchet).getDuration(); // The real time elapsed at musical time t, in seconds, during a // smooth tempo change from "tempo" at musical time zero to // "targetTempo" at musical time "targetTime", is // // 2 // at + t (b - a) // --------- // 2n // where // // a is the initial tempo in seconds per tick // b is the target tempo in seconds per tick // n is targetTime in ticks if (targetTime == 0 || targetTempo == tempo) { return time2RealTime(time, targetTempo); } double a = (100000 * 60) / (double(tempo) * cdur); double b = (100000 * 60) / (double(targetTempo) * cdur); double t = time; double n = targetTime; double result = (a * t) + (t * t * (b - a)) / (2 * n); int sec = int(result); int nsec = int((result - sec) * 1000000000); RealTime rt(sec, nsec); #ifdef DEBUG_TEMPO_STUFF if (!DEBUG_silence_recursive_tempo_printout) { cerr << "Composition::time2RealTime[2]: time " << time << ", tempo " << tempo << ", targetTime " << targetTime << ", targetTempo " << targetTempo << ": rt " << rt << endl; DEBUG_silence_recursive_tempo_printout = 1; // RealTime nextRt = time2RealTime(targetTime, tempo, targetTime, targetTempo); timeT ct = realTime2Time(rt, tempo, targetTime, targetTempo); cerr << "cf. realTime2Time: rt " << rt << " -> " << ct << endl; DEBUG_silence_recursive_tempo_printout=0; } #endif return rt; } timeT Composition::realTime2Time(RealTime rt, tempoT tempo) const { static timeT cdur = Note(Note::Crotchet).getDuration(); double tsec = (double(rt.sec) * cdur) * (tempo / (60.0 * 100000.0)); double tnsec = (double(rt.nsec) * cdur) * (tempo / 100000.0); double dt = tsec + (tnsec / 60000000000.0); timeT t = (timeT)(dt + (dt < 0 ? -1e-6 : 1e-6)); #ifdef DEBUG_TEMPO_STUFF if (!DEBUG_silence_recursive_tempo_printout) { cerr << "Composition::realTime2Time: rt.sec " << rt.sec << ", rt.nsec " << rt.nsec << ", tempo " << tempo << ", cdur " << cdur << ", tsec " << tsec << ", tnsec " << tnsec << ", dt " << dt << ", t " << t << endl; DEBUG_silence_recursive_tempo_printout = 1; RealTime crt = time2RealTime(t, tempo); RealTime ert = rt - crt; timeT et = realTime2Time(ert, tempo); cerr << "cf. time2RealTime(" << t << ") -> " << crt << " [err " << ert << " (" << et << "?)]" << endl; DEBUG_silence_recursive_tempo_printout = 0; } #endif return t; } timeT Composition::realTime2Time(RealTime rt, tempoT tempo, timeT targetTime, tempoT targetTempo) const { static timeT cdur = Note(Note::Crotchet).getDuration(); // Inverse of the expression in time2RealTime above. // // The musical time elapsed at real time t, in ticks, during a // smooth tempo change from "tempo" at real time zero to // "targetTempo" at real time "targetTime", is // // 2na (+/-) sqrt((2nb)^2 + 8(b-a)tn) // - ---------------------------------- // 2(b-a) // where // // a is the initial tempo in seconds per tick // b is the target tempo in seconds per tick // n is target real time in ticks if (targetTempo == tempo) return realTime2Time(rt, tempo); double a = (100000 * 60) / (double(tempo) * cdur); double b = (100000 * 60) / (double(targetTempo) * cdur); double t = double(rt.sec) + double(rt.nsec) / 1e9; double n = targetTime; double term1 = 2.0 * n * a; double term2 = (2.0 * n * a) * (2.0 * n * a) + 8 * (b - a) * t * n; if (term2 < 0) { // We're screwed, but at least let's not crash std::cerr << "ERROR: Composition::realTime2Time: term2 < 0 (it's " << term2 << ")" << std::endl; #ifdef DEBUG_TEMPO_STUFF std::cerr << "rt = " << rt << ", tempo = " << tempo << ", targetTime = " << targetTime << ", targetTempo = " << targetTempo << std::endl; std::cerr << "n = " << n << ", b = " << b << ", a = " << a << ", t = " << t < 0) term3 = -term3; double result = - (term1 + term3) / (2 * (b - a)); #ifdef DEBUG_TEMPO_STUFF std::cerr << "Composition::realTime2Time:" <getId() << std::endl; } return 0; } // Move a track object to a new id and position in the container - // used when deleting and undoing deletion of tracks. // // void Composition::resetTrackIdAndPosition(TrackId oldId, TrackId newId, int position) { trackiterator titerator = m_tracks.find(oldId); if (titerator != m_tracks.end()) { // detach old track Track *track = (*titerator).second; m_tracks.erase(titerator); // set new position and track->setId(newId); track->setPosition(position); m_tracks[newId] = track; // modify segment mappings // for (segmentcontainer::const_iterator i = m_segments.begin(); i != m_segments.end(); ++i) { if ((*i)->getTrack() == oldId) (*i)->setTrack(newId); } checkSelectedAndRecordTracks(); updateRefreshStatuses(); notifyTrackChanged(getTrackById(newId)); } else std::cerr << "Composition::resetTrackIdAndPosition - " << "can't move track " << oldId << " to " << newId << std::endl; } void Composition::setSelectedTrack(TrackId track) { m_selectedTrack = track; notifySoloChanged(); } void Composition::setSolo(bool value) { m_solo = value; notifySoloChanged(); } // Insert a Track representation into the Composition // void Composition::addTrack(Track *track) { // make sure a track with the same id isn't already there // if (m_tracks.find(track->getId()) == m_tracks.end()) { m_tracks[track->getId()] = track; track->setOwningComposition(this); updateRefreshStatuses(); notifyTrackChanged(track); } else { std::cerr << "Composition::addTrack(" << track << "), id = " << track->getId() << " - WARNING - track id already present " << __FILE__ << ":" << __LINE__ << std::endl; // throw Exception("track id already present"); } } void Composition::deleteTrack(Rosegarden::TrackId track) { trackiterator titerator = m_tracks.find(track); if (titerator == m_tracks.end()) { std::cerr << "Composition::deleteTrack : no track of id " << track << std::endl; throw Exception("track id not found"); } else { delete ((*titerator).second); m_tracks.erase(titerator); checkSelectedAndRecordTracks(); updateRefreshStatuses(); notifyTrackDeleted(track); } } bool Composition::detachTrack(Rosegarden::Track *track) { trackiterator it = m_tracks.begin(); for (; it != m_tracks.end(); ++it) { if ((*it).second == track) break; } if (it == m_tracks.end()) { std::cerr << "Composition::detachTrack() : no such track " << track << std::endl; throw Exception("track id not found"); return false; } ((*it).second)->setOwningComposition(0); m_tracks.erase(it); updateRefreshStatuses(); checkSelectedAndRecordTracks(); return true; } void Composition::checkSelectedAndRecordTracks() { // reset m_selectedTrack and m_recordTrack to the next valid track id // if the track they point to has been deleted if (m_tracks.find(m_selectedTrack) == m_tracks.end()) { m_selectedTrack = getClosestValidTrackId(m_selectedTrack); notifySoloChanged(); } for (recordtrackcontainer::iterator i = m_recordTracks.begin(); i != m_recordTracks.end(); ++i) { if (m_tracks.find(*i) == m_tracks.end()) { m_recordTracks.erase(i); } } } TrackId Composition::getClosestValidTrackId(TrackId id) const { long diff = LONG_MAX; TrackId closestValidTrackId = 0; for (trackcontainer::const_iterator i = getTracks().begin(); i != getTracks().end(); ++i) { long cdiff = labs(i->second->getId() - id); if (cdiff < diff) { diff = cdiff; closestValidTrackId = i->second->getId(); } else break; // std::map is sorted, so if the diff increases, we're passed closest valid id } return closestValidTrackId; } TrackId Composition::getMinTrackId() const { if (getTracks().size() == 0) return 0; trackcontainer::const_iterator i = getTracks().begin(); return i->first; } TrackId Composition::getMaxTrackId() const { if (getTracks().size() == 0) return 0; trackcontainer::const_iterator i = getTracks().end(); --i; return i->first; } void Composition::setTrackRecording(TrackId track, bool recording) { if (recording) { m_recordTracks.insert(track); } else { m_recordTracks.erase(track); } Track *t = getTrackById(track); if (t) { t->setArmed(recording); } } bool Composition::isTrackRecording(TrackId track) const { return m_recordTracks.find(track) != m_recordTracks.end(); } // Export the Composition as XML, also iterates through // Tracks and any further sub-objects // // std::string Composition::toXmlString() { std::stringstream composition; composition << "" << endl << endl; composition << endl; for (trackiterator tit = getTracks().begin(); tit != getTracks().end(); ++tit) { if ((*tit).second) composition << " " << (*tit).second->toXmlString() << endl; } composition << endl; for (ReferenceSegment::iterator i = m_timeSigSegment.begin(); i != m_timeSigSegment.end(); ++i) { // Might be nice just to stream the events, but that's // normally done by XmlStorableEvent in gui/ at the // moment. Still, this isn't too much of a hardship composition << " getAbsoluteTime() << "\" numerator=\"" << (*i)->get(TimeSignature::NumeratorPropertyName) << "\" denominator=\"" << (*i)->get(TimeSignature::DenominatorPropertyName) << "\""; bool common = false; (*i)->get(TimeSignature::ShowAsCommonTimePropertyName, common); if (common) composition << " common=\"true\""; bool hidden = false; (*i)->get(TimeSignature::IsHiddenPropertyName, hidden); if (hidden) composition << " hidden=\"true\""; bool hiddenBars = false; (*i)->get(TimeSignature::HasHiddenBarsPropertyName, hiddenBars); if (hiddenBars) composition << " hiddenbars=\"true\""; composition << "/>" << endl; } composition << endl; for (ReferenceSegment::iterator i = m_tempoSegment.begin(); i != m_tempoSegment.end(); ++i) { tempoT tempo = tempoT((*i)->get(TempoProperty)); tempoT target = -1; if ((*i)->has(TargetTempoProperty)) { target = tempoT((*i)->get(TargetTempoProperty)); } composition << " getAbsoluteTime() << "\" bph=\"" << ((tempo * 6) / 10000) << "\" tempo=\"" << tempo; if (target >= 0) { composition << "\" target=\"" << target; } composition << "\"/>" << endl; } composition << endl; composition << "" << endl << m_metadata.toXmlString() << endl << "" << endl << endl; composition << "" << endl; for (markerconstiterator mIt = m_markers.begin(); mIt != m_markers.end(); ++mIt) { composition << (*mIt)->toXmlString(); } composition << "" << endl; #if (__GNUC__ < 3) composition << "" << std::ends; #else composition << ""; #endif return composition.str(); } void Composition::clearTracks() { trackiterator it = m_tracks.begin(); for (; it != m_tracks.end(); it++) delete ((*it).second); m_tracks.erase(m_tracks.begin(), m_tracks.end()); } Track* Composition::getTrackByPosition(int position) const { trackconstiterator it = m_tracks.begin(); for (; it != m_tracks.end(); it++) { if ((*it).second->getPosition() == position) return (*it).second; } return 0; } int Composition::getTrackPositionById(TrackId id) const { Track *track = getTrackById(id); if (!track) return -1; return track->getPosition(); } Rosegarden::TrackId Composition::getNewTrackId() const { // Re BR #1070325: another track deletion problem // Formerly this was returning the count of tracks currently in // existence -- returning a duplicate ID if some had been deleted // from the middle. Let's find one that's really available instead. TrackId highWater = 0; trackconstiterator it = m_tracks.begin(); for (; it != m_tracks.end(); it++) { if ((*it).second->getId() >= highWater) highWater = (*it).second->getId() + 1; } return highWater; } void Composition::notifySegmentAdded(Segment *s) const { // If there is an earlier repeating segment on the same track, we // need to notify the change of its repeat end time for (const_iterator i = begin(); i != end(); ++i) { if (((*i)->getTrack() == s->getTrack()) && ((*i)->isRepeating()) && ((*i)->getStartTime() < s->getStartTime())) { notifySegmentRepeatEndChanged(*i, (*i)->getRepeatEndTime()); } } for (ObserverSet::const_iterator i = m_observers.begin(); i != m_observers.end(); ++i) { (*i)->segmentAdded(this, s); } } void Composition::notifySegmentRemoved(Segment *s) const { // If there is an earlier repeating segment on the same track, we // need to notify the change of its repeat end time for (const_iterator i = begin(); i != end(); ++i) { if (((*i)->getTrack() == s->getTrack()) && ((*i)->isRepeating()) && ((*i)->getStartTime() < s->getStartTime())) { notifySegmentRepeatEndChanged(*i, (*i)->getRepeatEndTime()); } } for (ObserverSet::const_iterator i = m_observers.begin(); i != m_observers.end(); ++i) { (*i)->segmentRemoved(this, s); } } void Composition::notifySegmentRepeatChanged(Segment *s, bool repeat) const { for (ObserverSet::const_iterator i = m_observers.begin(); i != m_observers.end(); ++i) { (*i)->segmentRepeatChanged(this, s, repeat); } } void Composition::notifySegmentRepeatEndChanged(Segment *s, timeT t) const { for (ObserverSet::const_iterator i = m_observers.begin(); i != m_observers.end(); ++i) { (*i)->segmentRepeatEndChanged(this, s, t); } } void Composition::notifySegmentEventsTimingChanged(Segment *s, timeT delay, RealTime rtDelay) const { for (ObserverSet::const_iterator i = m_observers.begin(); i != m_observers.end(); ++i) { (*i)->segmentEventsTimingChanged(this, s, delay, rtDelay); } } void Composition::notifySegmentTransposeChanged(Segment *s, int transpose) const { for (ObserverSet::const_iterator i = m_observers.begin(); i != m_observers.end(); ++i) { (*i)->segmentTransposeChanged(this, s, transpose); } } void Composition::notifySegmentTrackChanged(Segment *s, TrackId oldId, TrackId newId) const { // If there is an earlier repeating segment on either the // origin or destination track, we need to notify the change // of its repeat end time for (const_iterator i = begin(); i != end(); ++i) { if (((*i)->getTrack() == oldId || (*i)->getTrack() == newId) && ((*i)->isRepeating()) && ((*i)->getStartTime() < s->getStartTime())) { notifySegmentRepeatEndChanged(*i, (*i)->getRepeatEndTime()); } } for (ObserverSet::const_iterator i = m_observers.begin(); i != m_observers.end(); ++i) { (*i)->segmentTrackChanged(this, s, newId); } } void Composition::notifySegmentStartChanged(Segment *s, timeT t) { updateRefreshStatuses(); // not ideal, but best way to ensure track heights are recomputed for (ObserverSet::const_iterator i = m_observers.begin(); i != m_observers.end(); ++i) { (*i)->segmentStartChanged(this, s, t); } } void Composition::notifySegmentEndMarkerChange(Segment *s, bool shorten) { for (ObserverSet::const_iterator i = m_observers.begin(); i != m_observers.end(); ++i) { (*i)->segmentEndMarkerChanged(this, s, shorten); } } void Composition::notifyEndMarkerChange(bool shorten) const { for (ObserverSet::const_iterator i = m_observers.begin(); i != m_observers.end(); ++i) { (*i)->endMarkerTimeChanged(this, shorten); } } void Composition::notifyTrackChanged(Track *t) const { for (ObserverSet::const_iterator i = m_observers.begin(); i != m_observers.end(); ++i) { (*i)->trackChanged(this, t); } } void Composition::notifyTrackDeleted(TrackId t) const { for (ObserverSet::const_iterator i = m_observers.begin(); i != m_observers.end(); ++i) { (*i)->trackDeleted(this, t); } } void Composition::notifyMetronomeChanged() const { for (ObserverSet::const_iterator i = m_observers.begin(); i != m_observers.end(); ++i) { (*i)->metronomeChanged(this); } } void Composition::notifyTimeSignatureChanged() const { for (ObserverSet::const_iterator i = m_observers.begin(); i != m_observers.end(); ++i) { (*i)->timeSignatureChanged(this); } } void Composition::notifySoloChanged() const { for (ObserverSet::const_iterator i = m_observers.begin(); i != m_observers.end(); ++i) { (*i)->soloChanged(this, isSolo(), getSelectedTrack()); } } void Composition::notifyTempoChanged() const { for (ObserverSet::const_iterator i = m_observers.begin(); i != m_observers.end(); ++i) { (*i)->tempoChanged(this); } } void Composition::notifySourceDeletion() const { for (ObserverSet::const_iterator i = m_observers.begin(); i != m_observers.end(); ++i) { (*i)->compositionDeleted(this); } } void breakpoint() { //std::cerr << "breakpoint()\n"; } // Just empty out the markers void Composition::clearMarkers() { markerconstiterator it = m_markers.begin(); for (; it != m_markers.end(); ++it) { delete *it; } m_markers.clear(); } void Composition::addMarker(Rosegarden::Marker *marker) { m_markers.push_back(marker); updateRefreshStatuses(); } bool Composition::detachMarker(Rosegarden::Marker *marker) { markeriterator it = m_markers.begin(); for (; it != m_markers.end(); ++it) { if (*it == marker) { m_markers.erase(it); updateRefreshStatuses(); return true; } } return false; } bool Composition::isMarkerAtPosition(Rosegarden::timeT time) const { markerconstiterator it = m_markers.begin(); for (; it != m_markers.end(); ++it) if ((*it)->getTime() == time) return true; return false; } void Composition::setSegmentColourMap(Rosegarden::ColourMap &newmap) { m_segmentColourMap = newmap; updateRefreshStatuses(); } void Composition::setGeneralColourMap(Rosegarden::ColourMap &newmap) { m_generalColourMap = newmap; updateRefreshStatuses(); } void Composition::dump(std::ostream& out, bool) const { out << "Composition segments : " << endl; for(iterator i = begin(); i != end(); ++i) { Segment* s = *i; out << "Segment start : " << s->getStartTime() << " - end : " << s->getEndMarkerTime() << " - repeating : " << s->isRepeating() << " - track id : " << s->getTrack() << " - label : " << s->getLabel() << endl; } } }