summaryrefslogtreecommitdiffstats
path: root/src/base/SegmentPerformanceHelper.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/base/SegmentPerformanceHelper.cpp')
-rw-r--r--src/base/SegmentPerformanceHelper.cpp472
1 files changed, 472 insertions, 0 deletions
diff --git a/src/base/SegmentPerformanceHelper.cpp b/src/base/SegmentPerformanceHelper.cpp
new file mode 100644
index 0000000..930a794
--- /dev/null
+++ b/src/base/SegmentPerformanceHelper.cpp
@@ -0,0 +1,472 @@
+// -*- c-basic-offset: 4 -*-
+
+/*
+ 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 "SegmentPerformanceHelper.h"
+#include "BaseProperties.h"
+#include <iostream>
+
+namespace Rosegarden
+{
+using std::endl;
+using std::string;
+
+using namespace BaseProperties;
+
+SegmentPerformanceHelper::~SegmentPerformanceHelper() { }
+
+
+SegmentPerformanceHelper::iteratorcontainer
+SegmentPerformanceHelper::getTiedNotes(iterator i)
+{
+ iteratorcontainer c;
+ c.push_back(i);
+
+ Event *e = *i;
+ if (!e->isa(Note::EventType)) return c;
+ Segment::iterator j(i);
+
+ bool tiedBack = false, tiedForward = false;
+ e->get<Bool>(TIED_BACKWARD, tiedBack);
+ e->get<Bool>(TIED_FORWARD, tiedForward);
+
+ timeT d = e->getNotationDuration();
+ timeT t = e->getNotationAbsoluteTime();
+
+ if (!e->has(PITCH)) return c;
+ int pitch = e->get<Int>(PITCH);
+
+ bool valid = false;
+
+ if (tiedBack) {
+ // #1171463: If we can find no preceding TIED_FORWARD event,
+ // then we remove this property
+
+ while (j != begin()) {
+
+ --j;
+ if (!(*j)->isa(Note::EventType)) continue;
+ e = *j; // can reuse e because this branch always returns
+
+ timeT t2 = e->getNotationAbsoluteTime() + e->getNotationDuration();
+ if (t2 < t) break;
+
+ if (t2 > t || !e->has(PITCH) ||
+ e->get<Int>(PITCH) != pitch) continue;
+
+ bool prevTiedForward = false;
+ if (!e->get<Bool>(TIED_FORWARD, prevTiedForward) ||
+ !prevTiedForward) break;
+
+ valid = true;
+ break;
+ }
+
+ if (valid) {
+ return iteratorcontainer();
+ } else {
+ (*i)->unset(TIED_BACKWARD);
+ return c;
+ }
+ }
+ else if (!tiedForward) return c;
+
+ for (;;) {
+ while (++j != end() && !(*j)->isa(Note::EventType));
+ if (j == end()) return c;
+
+ e = *j;
+
+ timeT t2 = e->getNotationAbsoluteTime();
+
+ if (t2 > t + d) break;
+ else if (t2 < t + d || !e->has(PITCH) ||
+ e->get<Int>(PITCH) != pitch) continue;
+
+ if (!e->get<Bool>(TIED_BACKWARD, tiedBack) ||
+ !tiedBack) break;
+
+ d += e->getNotationDuration();
+ c.push_back(j);
+ valid = true;
+
+ if (!e->get<Bool>(TIED_FORWARD, tiedForward) ||
+ !tiedForward) return c;
+ }
+
+ if (!valid) {
+ // Related to #1171463: If we can find no following
+ // TIED_BACKWARD event, then we remove this property
+ (*i)->unset(TIED_FORWARD);
+ }
+
+ return c;
+}
+
+
+bool
+SegmentPerformanceHelper::getGraceAndHostNotes(iterator i,
+ iteratorcontainer &graceNotes,
+ iteratorcontainer &hostNotes,
+ bool &isHostNote)
+{
+ if (i == end() || !(*i)->isa(Note::EventType)) return false;
+
+ Segment::iterator j = i;
+ Segment::iterator firstGraceNote = i;
+ Segment::iterator firstHostNote = i;
+
+ if ((*i)->has(IS_GRACE_NOTE) && (*i)->get<Bool>(IS_GRACE_NOTE)) {
+
+ // i is a grace note. Find the first host note following it
+
+ j = i;
+ while (++j != end()) {
+ if ((*j)->getNotationAbsoluteTime() >
+ (*i)->getNotationAbsoluteTime()) break;
+ if ((*j)->getSubOrdering() < 0) continue;
+ if ((*j)->isa(Note::EventType)) {
+ firstHostNote = j;
+ break;
+ }
+ }
+
+ if (firstHostNote == i) {
+ std::cerr << "SegmentPerformanceHelper::getGraceAndHostNotes: REMARK: Grace note at " << (*i)->getAbsoluteTime() << " has no host note" << std::endl;
+ return false;
+ }
+ } else {
+
+ // i is a host note, but we need to ensure we have the first
+ // one, not just any one
+
+ j = i;
+
+ while (j != begin()) {
+ --j;
+ if ((*j)->getNotationAbsoluteTime() <
+ (*i)->getNotationAbsoluteTime()) break;
+ if ((*j)->getSubOrdering() <
+ (*i)->getSubOrdering()) break;
+ if ((*j)->isa(Note::EventType)) {
+ firstHostNote = j;
+ break;
+ }
+ }
+ }
+
+ // firstHostNote now points to the first host note, which is
+ // either the first non-grace note after i (if i was a grace note)
+ // or the first note with the same time and subordering as i (if i
+ // was not a grace note).
+
+ if ((*firstHostNote)->getSubOrdering() < 0) {
+ std::cerr << "SegmentPerformanceHelper::getGraceAndHostNotes: WARNING: Note at " << (*firstHostNote)->getAbsoluteTime() << " has subordering " << (*i)->getSubOrdering() << " but is not a grace note" << std::endl;
+ return false;
+ }
+
+ j = firstHostNote;
+
+ while (j != begin()) {
+ --j;
+ if ((*j)->getNotationAbsoluteTime() <
+ (*firstHostNote)->getNotationAbsoluteTime()) break;
+ if ((*j)->getSubOrdering() >= 0) continue;
+ if (!(*j)->isa(Note::EventType)) continue;
+ if (!(*j)->has(IS_GRACE_NOTE) || !(*j)->get<Bool>(IS_GRACE_NOTE)) {
+ std::cerr << "SegmentPerformanceHelper::getGraceAndHostNotes: WARNING: Note at " << (*j)->getAbsoluteTime() << " (in trackback) has subordering " << (*j)->getSubOrdering() << " but is not a grace note" << std::endl;
+ break;
+ }
+ firstGraceNote = j;
+ }
+
+ if (firstGraceNote == firstHostNote) {
+ std::cerr << "SegmentPerformanceHelper::getGraceAndHostNotes: REMARK: Note at " << (*firstHostNote)->getAbsoluteTime() << " has no grace notes" << std::endl;
+ return false;
+ }
+
+ j = firstGraceNote;
+
+ // push all of the grace notes, and notes with the same time as
+ // the first host note, onto the container
+
+ isHostNote = false;
+
+ while (j != end()) {
+ if ((*j)->isa(Note::EventType)) {
+ if ((*j)->getSubOrdering() < 0) {
+ if ((*j)->has(IS_GRACE_NOTE) && (*j)->get<Bool>(IS_GRACE_NOTE)) {
+ graceNotes.push_back(j);
+ }
+ } else {
+ hostNotes.push_back(j);
+ if (j == i) isHostNote = true;
+ }
+ }
+ if ((*j)->getNotationAbsoluteTime() >
+ (*firstHostNote)->getNotationAbsoluteTime()) break;
+ ++j;
+ }
+
+ return true;
+}
+
+
+timeT
+SegmentPerformanceHelper::getSoundingAbsoluteTime(iterator i)
+{
+ timeT t = 0;
+
+ timeT discard;
+
+// std::cerr << "SegmentPerformanceHelper::getSoundingAbsoluteTime at " << (*i)->getAbsoluteTime() << std::endl;
+
+ if ((*i)->has(IS_GRACE_NOTE)) {
+// std::cerr << "it's a grace note" << std::endl;
+ if (getGraceNoteTimeAndDuration(false, i, t, discard)) return t;
+ }
+ if ((*i)->has(MAY_HAVE_GRACE_NOTES)) {
+// std::cerr << "it's a candidate host note" << std::endl;
+ if (getGraceNoteTimeAndDuration(true, i, t, discard)) return t;
+ }
+
+ return (*i)->getAbsoluteTime();
+}
+
+timeT
+SegmentPerformanceHelper::getSoundingDuration(iterator i)
+{
+ timeT d = 0;
+
+ timeT discard;
+
+// std::cerr << "SegmentPerformanceHelper::getSoundingDuration at " << (*i)->getAbsoluteTime() << std::endl;
+
+ if ((*i)->has(IS_GRACE_NOTE)) {
+// std::cerr << "it's a grace note" << std::endl;
+ if (getGraceNoteTimeAndDuration(false, i, discard, d)) return d;
+ }
+ if ((*i)->has(MAY_HAVE_GRACE_NOTES)) {
+// std::cerr << "it's a candidate host note" << std::endl;
+ if (getGraceNoteTimeAndDuration(true, i, discard, d)) return d;
+ }
+
+ if ((*i)->has(TIED_BACKWARD)) {
+
+ // Formerly we just returned d in this case, but now we check
+ // with getTiedNotes so as to remove any bogus backward ties
+ // that have no corresponding forward tie. Unfortunately this
+ // is quite a bit slower.
+
+ //!!! optimize. at least we should add a marker property to
+ //anything we've already processed from this helper this time
+ //around.
+
+ iteratorcontainer c(getTiedNotes(i));
+
+ if (c.empty()) { // the tie back is valid
+ return 0;
+ }
+ }
+
+ if (!(*i)->has(TIED_FORWARD) || !(*i)->isa(Note::EventType)) {
+
+ d = (*i)->getDuration();
+
+ } else {
+
+ // tied forward but not back
+
+ iteratorcontainer c(getTiedNotes(i));
+
+ for (iteratorcontainer::iterator ci = c.begin();
+ ci != c.end(); ++ci) {
+ d += (**ci)->getDuration();
+ }
+ }
+
+ return d;
+}
+
+
+// In theory we can do better with tuplets, because real time has
+// finer precision than timeT time. With a timeT resolution of 960ppq
+// however the difference is probably not audible
+
+RealTime
+SegmentPerformanceHelper::getRealAbsoluteTime(iterator i)
+{
+ return segment().getComposition()->getElapsedRealTime
+ (getSoundingAbsoluteTime(i));
+}
+
+
+// In theory we can do better with tuplets, because real time has
+// finer precision than timeT time. With a timeT resolution of 960ppq
+// however the difference is probably not audible
+//
+// (If we did want to do this, it'd help to have abstime->realtime
+// conversion methods that accept double args in Composition)
+
+RealTime
+SegmentPerformanceHelper::getRealSoundingDuration(iterator i)
+{
+ timeT t0 = getSoundingAbsoluteTime(i);
+ timeT t1 = t0 + getSoundingDuration(i);
+
+ if (t1 > segment().getEndMarkerTime()) {
+ t1 = segment().getEndMarkerTime();
+ }
+
+ return segment().getComposition()->getRealTimeDifference(t0, t1);
+}
+
+
+bool
+SegmentPerformanceHelper::getGraceNoteTimeAndDuration(bool host, iterator i,
+ timeT &t, timeT &d)
+{
+ // [This code currently assumes appoggiatura. Acciaccatura later.]
+
+ // For our present purposes, we will assume that grace notes start
+ // at the same time as their host note was intended to, and
+ // "steal" a proportion of the duration of their host note. This
+ // causes the host note to start later, and be shorter, by that
+ // same proportion.
+
+ // If a host note has more than one (consecutive) grace note, they
+ // should take a single cut from the grace note and divide it
+ // amongst themselves.
+
+ // To begin with we will set the proportion to 1/4, but we will
+ // probably want it to be (a) something different [because I don't
+ // really know what I'm doing], (b) adaptive [e.g. shorter host
+ // note or more grace notes = longer proportion], (c)
+ // configurable, or (d) all of the above.
+
+ // Of course we also ought to be taking into account the notated
+ // duration of the grace notes -- though in my working examples it
+ // generally doesn't seem to be the case that we can always just
+ // follow those. I wonder if we can always use the grace notes'
+ // notated duration if the ratio of grace note duration to host
+ // note duration is less than some value? Whatever we do, we
+ // should be dividing the grace note duration up in proportion to
+ // the durations of the grace notes, in situations where we have
+ // more than one grace note consecutively of different durations;
+ // that isn't handled at all here.
+
+ if (i == end()) return false;
+
+ iteratorcontainer graceNotes, hostNotes;
+ bool isHostNote;
+
+ if (!getGraceAndHostNotes(i, graceNotes, hostNotes, isHostNote)) {
+ std::cerr << "SegmentPerformanceHelper::getGraceNoteTimeAndDuration: REMARK: Note at " << (*i)->getAbsoluteTime() << " is not a grace note, or has no grace notes" << std::endl;
+ return false;
+ }
+
+ if (!isHostNote) {
+
+ if (!(*i)->has(IS_GRACE_NOTE) || !(*i)->get<Bool>(IS_GRACE_NOTE)) {
+ std::cerr << "SegmentPerformanceHelper::getGraceNoteTimeAndDuration: WARNING: Note at " << (*i)->getAbsoluteTime() << " is neither grace nor host note, but was reported as suitable by getGraceAndHostNotes" << std::endl;
+ return false;
+ }
+ }
+
+ if (hostNotes.empty()) {
+ std::cerr << "SegmentPerformanceHelper::getGraceNoteTimeAndDuration: REMARK: Grace note at " << (*i)->getAbsoluteTime() << " has no host note" << std::endl;
+ return false;
+ }
+
+ if (graceNotes.empty()) {
+ std::cerr << "SegmentPerformanceHelper::getGraceNoteTimeAndDuration: REMARK: Note at " << (*i)->getAbsoluteTime() << " has no grace notes" << std::endl;
+ return false;
+ }
+
+ timeT hostNoteEarliestTime = 0;
+ timeT hostNoteShortestDuration = 0;
+ timeT hostNoteNotationDuration = 0;
+
+ for (iteratorcontainer::iterator j = hostNotes.begin();
+ j != hostNotes.end(); ++j) {
+
+ if (j == hostNotes.begin() ||
+ (**j)->getAbsoluteTime() < hostNoteEarliestTime) {
+ hostNoteEarliestTime = (**j)->getAbsoluteTime();
+ }
+ if (j == hostNotes.begin() ||
+ (**j)->getDuration() < hostNoteShortestDuration) {
+ hostNoteShortestDuration = (**j)->getDuration();
+ }
+ if (j == hostNotes.begin() ||
+ (**j)->getNotationDuration() > hostNoteNotationDuration) {
+ hostNoteNotationDuration = (**j)->getNotationDuration();
+ }
+ (**j)->set<Bool>(MAY_HAVE_GRACE_NOTES, true);
+ }
+
+ timeT graceNoteTime = hostNoteEarliestTime;
+ timeT graceNoteDuration = hostNoteNotationDuration / 4;
+ if (graceNoteDuration > hostNoteShortestDuration / 2) {
+ graceNoteDuration = hostNoteShortestDuration / 2;
+ }
+
+ if (isHostNote) {
+ t = (*i)->getAbsoluteTime() + graceNoteDuration;
+ d = (*i)->getDuration() - graceNoteDuration;
+ } else {
+
+ int count = 0, index = 0;
+ bool found = false;
+ int prevSubOrdering = 0;
+
+ for (iteratorcontainer::iterator j = graceNotes.begin();
+ j != graceNotes.end(); ++j) {
+
+ bool newChord = false;
+
+ if ((**j)->getSubOrdering() != prevSubOrdering) {
+ newChord = true;
+ prevSubOrdering = (**j)->getSubOrdering();
+ }
+
+ if (newChord) ++count;
+
+ if (*j == i) found = true;
+
+ if (!found) {
+ if (newChord) ++index;
+ }
+ }
+
+ if (index == count) index = 0;
+ if (count == 0) count = 1; // should not happen
+
+ d = graceNoteDuration / count;
+ t = hostNoteEarliestTime + d * index;
+ }
+
+ return true;
+
+
+}
+
+
+}