You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
rosegarden/src/base/Composition.cpp

2218 lines
56 KiB

/*
Rosegarden
A sequencer and musical notation editor.
This program is Copyright 2000-2008
Guillaume Laurent <glaurent@telegraph-road.org>,
Chris Cannam <cannam@all-day-breakfast.com>,
Richard Bown <bownie@bownie.com>
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>
#include <sstream>
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;
}
}
}