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.
2218 lines
56 KiB
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;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|