diff options
author | tpearson <tpearson@283d02a7-25f6-0310-bc7c-ecb5cbfe19da> | 2010-03-01 18:37:05 +0000 |
---|---|---|
committer | tpearson <tpearson@283d02a7-25f6-0310-bc7c-ecb5cbfe19da> | 2010-03-01 18:37:05 +0000 |
commit | 145364a8af6a1fec06556221e66d4b724a62fc9a (patch) | |
tree | 53bd71a544008c518034f208d64c932dc2883f50 /src/base/Composition.cpp | |
download | rosegarden-145364a8af6a1fec06556221e66d4b724a62fc9a.tar.gz rosegarden-145364a8af6a1fec06556221e66d4b724a62fc9a.zip |
Added old abandoned KDE3 version of the RoseGarden MIDI tool
git-svn-id: svn://anonsvn.kde.org/home/kde/branches/trinity/applications/rosegarden@1097595 283d02a7-25f6-0310-bc7c-ecb5cbfe19da
Diffstat (limited to 'src/base/Composition.cpp')
-rw-r--r-- | src/base/Composition.cpp | 2225 |
1 files changed, 2225 insertions, 0 deletions
diff --git a/src/base/Composition.cpp b/src/base/Composition.cpp new file mode 100644 index 0000000..cde3a8b --- /dev/null +++ b/src/base/Composition.cpp @@ -0,0 +1,2225 @@ +// -*- c-basic-offset: 4 -*- + +/* + Rosegarden + A sequencer and musical notation editor. + + This program is Copyright 2000-2008 + Guillaume Laurent <[email protected]>, + Chris Cannam <[email protected]>, + Richard Bown <[email protected]> + + 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 <iostream> +#include <iomanip> +#include <algorithm> +#include <cmath> +#include <typeinfo> + +#if (__GNUC__ < 3) +#include <strstream> +#define stringstream strstream +#else +#include <sstream> +#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::find(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 = find(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 = find(e); + if (i != end()) Impl::erase(i); +} + +Composition::ReferenceSegment::iterator +Composition::ReferenceSegment::findTime(timeT t) +{ + Event dummy("dummy", t, 0, MIN_SUBORDERING); + return find(&dummy); +} + +Composition::ReferenceSegment::iterator +Composition::ReferenceSegment::findRealTime(RealTime t) +{ + Event dummy("dummy", 0, 0, MIN_SUBORDERING); + dummy.set<Bool>(NoAbsoluteTimeProperty, true); + setTempoTimestamp(&dummy, t); + return find(&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::contains(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<Segment*>(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<Segment *> simultaneous; + std::multimap<timeT, Segment *> 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<timeT, Segment *>::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<Segment *, int> indices; + std::set<int> used; + std::multimap<timeT, Segment *> 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<timeT, Segment *>::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<TriggerSegmentId, TriggerSegmentRec::SegmentRuntimeIdSet> 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<Int>(BaseProperties::TRIGGER_SEGMENT_ID); + refs[id].insert((*i)->getRuntimeId()); + } + } + } + + for (std::map<TriggerSegmentId, + TriggerSegmentRec::SegmentRuntimeIdSet>::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<Int>(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<Int>(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<timeT, timeT> +Composition::getBarRangeForTime(timeT t) const +{ + return getBarRange(getBarNumber(t)); +} + + +std::pair<timeT, timeT> +Composition::getBarRange(int n) const +{ + calculateBarPositions(); + + Event dummy("dummy", 0); + dummy.set<Int>(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<Int>(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<Int>(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<timeT, timeT>(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<timeT, TimeSignature> +Composition::getTimeSignatureChange(int n) const +{ + return std::pair<timeT, TimeSignature> + (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<Int>(TempoProperty)); + + if ((*i)->has(TargetTempoProperty)) { + + tempoT target = (tempoT)((*i)->get<Int>(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<Int>(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<timeT, tempoT> tc = getTempoChange(n); + if (tc.first == time) { + if (tc.second == m_minTempo || tc.second == m_maxTempo) { + fullTempoUpdate = true; + } else { + std::pair<bool, tempoT> tr = getTempoRamping(n); + if (tr.first && + (tr.second == m_minTempo || tr.second == m_maxTempo)) { + fullTempoUpdate = true; + } + } + } + } + + Event *tempoEvent = new Event(TempoEventType, time); + tempoEvent->set<Int>(TempoProperty, tempo); + + if (targetTempo >= 0) { + tempoEvent->set<Int>(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<timeT, tempoT> +Composition::getTempoChange(int n) const +{ + return std::pair<timeT, tempoT> + (m_tempoSegment[n]->getAbsoluteTime(), + tempoT(m_tempoSegment[n]->get<Int>(TempoProperty))); +} + +std::pair<bool, tempoT> +Composition::getTempoRamping(int n, bool calculate) const +{ + tempoT target = -1; + if (m_tempoSegment[n]->has(TargetTempoProperty)) { + target = m_tempoSegment[n]->get<Int>(TargetTempoProperty); + } + bool ramped = (target >= 0); + if (target == 0) { + if (calculate) { + if (m_tempoSegment.size() > n+1) { + target = m_tempoSegment[n+1]->get<Int>(TempoProperty); + } + } + } + if (target < 0 || (calculate && (target == 0))) { + target = m_tempoSegment[n]->get<Int>(TempoProperty); + } + return std::pair<bool, tempoT>(ramped, target); +} + +void +Composition::removeTempoChange(int n) +{ + tempoT oldTempo = m_tempoSegment[n]->get<Int>(TempoProperty); + tempoT oldTarget = -1; + + if (m_tempoSegment[n]->has(TargetTempoProperty)) { + oldTarget = m_tempoSegment[n]->get<Int>(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<Int>(TempoProperty); + tempoT target = -1; + if ((*i)->has(TargetTempoProperty)) { + target = (*i)->get<Int>(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<Int>(TempoProperty)), + nextTempoTime - (*i)->getAbsoluteTime(), + target); + } else { + elapsed = getTempoTimestamp(*i) + + time2RealTime(t - (*i)->getAbsoluteTime(), + tempoT((*i)->get<Int>(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<Int>(TempoProperty)), + nextTempoTime - (*i)->getAbsoluteTime(), + target); + } else { + elapsed = (*i)->getAbsoluteTime() + + realTime2Time(t - getTempoTimestamp(*i), + (tempoT)((*i)->get<Int>(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<Int>(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<Int>(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 <<std::endl; + std::cerr << "that's sqrt( (" << ((2.0*n*a*2.0*n*a)) << ") + " + << (8*(b-a)*t*n) << " )" << endl; + + std::cerr << "so our original expression was " << rt << " = " + << a << "t + (t^2 * (" << b << " - " << a << ")) / " << 2*n << std::endl; +#endif + + return realTime2Time(rt, tempo); + } + + double term3 = sqrt(term2); + + // We only want the positive root + if (term3 > 0) term3 = -term3; + + double result = - (term1 + term3) / (2 * (b - a)); + +#ifdef DEBUG_TEMPO_STUFF + std::cerr << "Composition::realTime2Time:" <<endl; + std::cerr << "n = " << n << ", b = " << b << ", a = " << a << ", t = " << t <<std::endl; + std::cerr << "+/-sqrt(term2) = " << term3 << std::endl; + std::cerr << "result = " << result << endl; +#endif + + return long(result + 0.1); +} + +bool +Composition::getTempoTarget(ReferenceSegment::const_iterator i, + tempoT &target, + timeT &targetTime) const +{ + target = -1; + targetTime = 0; + bool have = false; + + if ((*i)->has(TargetTempoProperty)) { + target = (*i)->get<Int>(TargetTempoProperty); + if (target >= 0) { + ReferenceSegment::const_iterator j(i); + if (++j != m_tempoSegment.end()) { + if (target == 0) target = (*j)->get<Int>(TempoProperty); + targetTime = (*j)->getAbsoluteTime(); + } else { + targetTime = getEndMarker(); + if (targetTime < (*i)->getAbsoluteTime()) { + target = -1; + } + } + if (target > 0) have = true; + } + } + + return have; +} + +RealTime +Composition::getTempoTimestamp(const Event *e) +{ + RealTime res; + e->get<RealTimeT>(TempoTimestampProperty, res); + return res; +} + +void +Composition::setTempoTimestamp(Event *e, RealTime t) +{ + e->setMaybe<RealTimeT>(TempoTimestampProperty, t); +} + +void +Composition::getMusicalTimeForAbsoluteTime(timeT absTime, + int &bar, int &beat, + int &fraction, int &remainder) +{ + bar = getBarNumber(absTime); + + TimeSignature timeSig = getTimeSignatureAt(absTime); + timeT barStart = getBarStart(bar); + timeT beatDuration = timeSig.getBeatDuration(); + beat = (absTime - barStart) / beatDuration + 1; + + remainder = (absTime - barStart) % beatDuration; + timeT fractionDuration = Note(Note::Shortest).getDuration(); + fraction = remainder / fractionDuration; + remainder = remainder % fractionDuration; +} + +void +Composition::getMusicalTimeForDuration(timeT absTime, timeT duration, + int &bars, int &beats, + int &fractions, int &remainder) +{ + TimeSignature timeSig = getTimeSignatureAt(absTime); + timeT barDuration = timeSig.getBarDuration(); + timeT beatDuration = timeSig.getBeatDuration(); + + bars = duration / barDuration; + beats = (duration % barDuration) / beatDuration; + remainder = (duration % barDuration) % beatDuration; + timeT fractionDuration = Note(Note::Shortest).getDuration(); + fractions = remainder / fractionDuration; + remainder = remainder % fractionDuration; +} + +timeT +Composition::getAbsoluteTimeForMusicalTime(int bar, int beat, + int fraction, int remainder) +{ + timeT t = getBarStart(bar - 1); + TimeSignature timesig = getTimeSignatureAt(t); + t += (beat-1) * timesig.getBeatDuration(); + t += Note(Note::Shortest).getDuration() * fraction; + t += remainder; + return t; +} + +timeT +Composition::getDurationForMusicalTime(timeT absTime, + int bars, int beats, + int fractions, int remainder) +{ + TimeSignature timeSig = getTimeSignatureAt(absTime); + timeT barDuration = timeSig.getBarDuration(); + timeT beatDuration = timeSig.getBeatDuration(); + timeT t = bars * barDuration + beats * beatDuration + fractions * + Note(Note::Shortest).getDuration() + remainder; + return t; +} + +void +Composition::setPosition(timeT position) +{ + m_position = position; +} + +void Composition::setPlayMetronome(bool value) +{ + m_playMetronome = value; + notifyMetronomeChanged(); +} + +void Composition::setRecordMetronome(bool value) +{ + m_recordMetronome = value; + notifyMetronomeChanged(); +} + + + +#ifdef TRACK_DEBUG +// track debug convenience function +// +static void dumpTracks(Composition::trackcontainer& tracks) +{ + Composition::trackiterator it = tracks.begin(); + for (; it != tracks.end(); ++it) { + std::cerr << "tracks[" << (*it).first << "] = " + << (*it).second << std::endl; + } +} +#endif + +Track* Composition::getTrackById(TrackId track) const +{ + trackconstiterator i = m_tracks.find(track); + + if (i != m_tracks.end()) + return (*i).second; + + std::cerr << "Composition::getTrackById(" + << track << ") - WARNING - track id not found, this is probably a BUG " + << __FILE__ << ":" << __LINE__ << std::endl; + std::cerr << "Available track ids are: " << std::endl; + for (trackconstiterator i = m_tracks.begin(); i != m_tracks.end(); ++i) { + std::cerr << (*i).second->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 << "<composition recordtracks=\""; + for (recordtrackiterator i = m_recordTracks.begin(); + i != m_recordTracks.end(); ) { + composition << *i; + if (++i != m_recordTracks.end()) { + composition << ","; + } + } + composition << "\" pointer=\"" << m_position; + composition << "\" defaultTempo=\""; + composition << std::setiosflags(std::ios::fixed) + << std::setprecision(4) + << getTempoQpm(m_defaultTempo); + composition << "\" compositionDefaultTempo=\""; + composition << m_defaultTempo; + + if (m_loopStart != m_loopEnd) + { + composition << "\" loopstart=\"" << m_loopStart; + composition << "\" loopend=\"" << m_loopEnd; + } + + composition << "\" startMarker=\"" << m_startMarker; + composition << "\" endMarker=\"" << m_endMarker; + + // Add the Solo if set + // + if (m_solo) + composition << "\" solo=\"" << m_solo; + + composition << "\" selected=\"" << m_selectedTrack; + composition << "\" playmetronome=\"" << m_playMetronome; + composition << "\" recordmetronome=\"" << m_recordMetronome; + composition << "\" nexttriggerid=\"" << m_nextTriggerSegmentId; + 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 << " <timesignature time=\"" << (*i)->getAbsoluteTime() + << "\" numerator=\"" + << (*i)->get<Int>(TimeSignature::NumeratorPropertyName) + << "\" denominator=\"" + << (*i)->get<Int>(TimeSignature::DenominatorPropertyName) + << "\""; + + bool common = false; + (*i)->get<Bool>(TimeSignature::ShowAsCommonTimePropertyName, common); + if (common) composition << " common=\"true\""; + + bool hidden = false; + (*i)->get<Bool>(TimeSignature::IsHiddenPropertyName, hidden); + if (hidden) composition << " hidden=\"true\""; + + bool hiddenBars = false; + (*i)->get<Bool>(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<Int>(TempoProperty)); + tempoT target = -1; + if ((*i)->has(TargetTempoProperty)) { + target = tempoT((*i)->get<Int>(TargetTempoProperty)); + } + composition << " <tempo time=\"" << (*i)->getAbsoluteTime() + << "\" bph=\"" << ((tempo * 6) / 10000) + << "\" tempo=\"" << tempo; + if (target >= 0) { + composition << "\" target=\"" << target; + } + composition << "\"/>" << endl; + } + + composition << endl; + + composition << "<metadata>" << endl + << m_metadata.toXmlString() << endl + << "</metadata>" << endl << endl; + + composition << "<markers>" << endl; + for (markerconstiterator mIt = m_markers.begin(); + mIt != m_markers.end(); ++mIt) + { + composition << (*mIt)->toXmlString(); + } + composition << "</markers>" << endl; + + +#if (__GNUC__ < 3) + composition << "</composition>" << std::ends; +#else + composition << "</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; + + } + +} + + + +} + + |