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.
1203 lines
33 KiB
1203 lines
33 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 "NotationQuantizer.h"
|
|
#include "BaseProperties.h"
|
|
#include "NotationTypes.h"
|
|
#include "Selection.h"
|
|
#include "Composition.h"
|
|
#include "Sets.h"
|
|
#include "Profiler.h"
|
|
|
|
#include <iostream>
|
|
#include <cmath>
|
|
#include <cstdio> // for sprintf
|
|
#include <ctime>
|
|
|
|
using std::cout;
|
|
using std::cerr;
|
|
using std::endl;
|
|
|
|
//#define DEBUG_NOTATION_QUANTIZER 1
|
|
|
|
namespace Rosegarden {
|
|
|
|
using namespace BaseProperties;
|
|
|
|
class NotationQuantizer::Impl
|
|
{
|
|
public:
|
|
Impl(NotationQuantizer *const q) :
|
|
m_unit(Note(Note::Demisemiquaver).getDuration()),
|
|
m_simplicityFactor(13),
|
|
m_maxTuplet(3),
|
|
m_articulate(true),
|
|
m_q(q),
|
|
m_provisionalBase("notationquantizer-provisionalBase"),
|
|
m_provisionalAbsTime("notationquantizer-provisionalAbsTime"),
|
|
m_provisionalDuration("notationquantizer-provisionalDuration"),
|
|
m_provisionalNoteType("notationquantizer-provisionalNoteType"),
|
|
m_provisionalScore("notationquantizer-provisionalScore")
|
|
{ }
|
|
|
|
Impl(const Impl &i) :
|
|
m_unit(i.m_unit),
|
|
m_simplicityFactor(i.m_simplicityFactor),
|
|
m_maxTuplet(i.m_maxTuplet),
|
|
m_articulate(i.m_articulate),
|
|
m_q(i.m_q),
|
|
m_provisionalBase(i.m_provisionalBase),
|
|
m_provisionalAbsTime(i.m_provisionalAbsTime),
|
|
m_provisionalDuration(i.m_provisionalDuration),
|
|
m_provisionalNoteType(i.m_provisionalNoteType),
|
|
m_provisionalScore(i.m_provisionalScore)
|
|
{ }
|
|
|
|
class ProvisionalQuantizer : public Quantizer {
|
|
// This class exists only to pick out the provisional abstime
|
|
// and duration values from half-quantized events, so that we
|
|
// can treat them using the normal Chord class
|
|
public:
|
|
ProvisionalQuantizer(Impl *i) : Quantizer("blah", "blahblah"), m_impl(i) { }
|
|
virtual timeT getQuantizedDuration(const Event *e) const {
|
|
return m_impl->getProvisional((Event *)e, DurationValue);
|
|
}
|
|
virtual timeT getQuantizedAbsoluteTime(const Event *e) const {
|
|
timeT t = m_impl->getProvisional((Event *)e, AbsoluteTimeValue);
|
|
#ifdef DEBUG_NOTATION_QUANTIZER
|
|
cout << "ProvisionalQuantizer::getQuantizedAbsoluteTime: returning " << t << endl;
|
|
#endif
|
|
return t;
|
|
}
|
|
|
|
private:
|
|
Impl *m_impl;
|
|
};
|
|
|
|
void quantizeRange(Segment *,
|
|
Segment::iterator,
|
|
Segment::iterator) const;
|
|
|
|
void quantizeAbsoluteTime(Segment *, Segment::iterator) const;
|
|
long scoreAbsoluteTimeForBase(Segment *, const Segment::iterator &,
|
|
int depth, timeT base, timeT sigTime,
|
|
timeT t, timeT d, int noteType,
|
|
const Segment::iterator &,
|
|
const Segment::iterator &,
|
|
bool &right) const;
|
|
void quantizeDurationProvisional(Segment *, Segment::iterator) const;
|
|
void quantizeDuration(Segment *, Chord &) const;
|
|
|
|
void scanTupletsInBar(Segment *,
|
|
timeT barStart, timeT barDuration,
|
|
timeT wholeStart, timeT wholeDuration,
|
|
const std::vector<int> &divisions) const;
|
|
void scanTupletsAt(Segment *, Segment::iterator, int depth,
|
|
timeT base, timeT barStart,
|
|
timeT tupletStart, timeT tupletBase) const;
|
|
bool isValidTupletAt(Segment *, const Segment::iterator &,
|
|
int depth, timeT base, timeT sigTime,
|
|
timeT tupletBase) const;
|
|
|
|
void setProvisional(Event *, ValueType value, timeT t) const;
|
|
timeT getProvisional(Event *, ValueType value) const;
|
|
void unsetProvisionalProperties(Event *) const;
|
|
|
|
timeT m_unit;
|
|
int m_simplicityFactor;
|
|
int m_maxTuplet;
|
|
bool m_articulate;
|
|
bool m_contrapuntal;
|
|
|
|
private:
|
|
NotationQuantizer *const m_q;
|
|
|
|
PropertyName m_provisionalBase;
|
|
PropertyName m_provisionalAbsTime;
|
|
PropertyName m_provisionalDuration;
|
|
PropertyName m_provisionalNoteType;
|
|
PropertyName m_provisionalScore;
|
|
};
|
|
|
|
NotationQuantizer::NotationQuantizer() :
|
|
Quantizer(NotationPrefix),
|
|
m_impl(new Impl(this))
|
|
{
|
|
// nothing else
|
|
}
|
|
|
|
NotationQuantizer::NotationQuantizer(std::string source, std::string target) :
|
|
Quantizer(source, target),
|
|
m_impl(new Impl(this))
|
|
{
|
|
// nothing else
|
|
}
|
|
|
|
NotationQuantizer::NotationQuantizer(const NotationQuantizer &q) :
|
|
Quantizer(q.m_target),
|
|
m_impl(new Impl(*q.m_impl))
|
|
{
|
|
// nothing else
|
|
}
|
|
|
|
NotationQuantizer::~NotationQuantizer()
|
|
{
|
|
delete m_impl;
|
|
}
|
|
|
|
void
|
|
NotationQuantizer::setUnit(timeT unit)
|
|
{
|
|
m_impl->m_unit = unit;
|
|
}
|
|
|
|
timeT
|
|
NotationQuantizer::getUnit() const
|
|
{
|
|
return m_impl->m_unit;
|
|
}
|
|
|
|
void
|
|
NotationQuantizer::setMaxTuplet(int m)
|
|
{
|
|
m_impl->m_maxTuplet = m;
|
|
}
|
|
|
|
int
|
|
NotationQuantizer::getMaxTuplet() const
|
|
{
|
|
return m_impl->m_maxTuplet;
|
|
}
|
|
|
|
void
|
|
NotationQuantizer::setSimplicityFactor(int s)
|
|
{
|
|
m_impl->m_simplicityFactor = s;
|
|
}
|
|
|
|
int
|
|
NotationQuantizer::getSimplicityFactor() const
|
|
{
|
|
return m_impl->m_simplicityFactor;
|
|
}
|
|
|
|
void
|
|
NotationQuantizer::setContrapuntal(bool c)
|
|
{
|
|
m_impl->m_contrapuntal = c;
|
|
}
|
|
|
|
bool
|
|
NotationQuantizer::getContrapuntal() const
|
|
{
|
|
return m_impl->m_contrapuntal;
|
|
}
|
|
|
|
void
|
|
NotationQuantizer::setArticulate(bool a)
|
|
{
|
|
m_impl->m_articulate = a;
|
|
}
|
|
|
|
bool
|
|
NotationQuantizer::getArticulate() const
|
|
{
|
|
return m_impl->m_articulate;
|
|
}
|
|
|
|
void
|
|
NotationQuantizer::Impl::setProvisional(Event *e, ValueType v, timeT t) const
|
|
{
|
|
if (v == AbsoluteTimeValue) {
|
|
e->setMaybe<Int>(m_provisionalAbsTime, t);
|
|
} else {
|
|
e->setMaybe<Int>(m_provisionalDuration, t);
|
|
}
|
|
}
|
|
|
|
timeT
|
|
NotationQuantizer::Impl::getProvisional(Event *e, ValueType v) const
|
|
{
|
|
timeT t;
|
|
if (v == AbsoluteTimeValue) {
|
|
t = e->getAbsoluteTime();
|
|
e->get<Int>(m_provisionalAbsTime, t);
|
|
} else {
|
|
t = e->getDuration();
|
|
e->get<Int>(m_provisionalDuration, t);
|
|
}
|
|
return t;
|
|
}
|
|
|
|
void
|
|
NotationQuantizer::Impl::unsetProvisionalProperties(Event *e) const
|
|
{
|
|
e->unset(m_provisionalBase);
|
|
e->unset(m_provisionalAbsTime);
|
|
e->unset(m_provisionalDuration);
|
|
e->unset(m_provisionalNoteType);
|
|
e->unset(m_provisionalScore);
|
|
}
|
|
|
|
void
|
|
NotationQuantizer::Impl::quantizeAbsoluteTime(Segment *s, Segment::iterator i) const
|
|
{
|
|
Profiler profiler("NotationQuantizer::Impl::quantizeAbsoluteTime");
|
|
|
|
Composition *comp = s->getComposition();
|
|
|
|
TimeSignature timeSig;
|
|
timeT t = m_q->getFromSource(*i, AbsoluteTimeValue);
|
|
timeT sigTime = comp->getTimeSignatureAt(t, timeSig);
|
|
|
|
timeT d = getProvisional(*i, DurationValue);
|
|
int noteType = Note::getNearestNote(d).getNoteType();
|
|
(*i)->setMaybe<Int>(m_provisionalNoteType, noteType);
|
|
|
|
int maxDepth = 8 - noteType;
|
|
if (maxDepth < 4) maxDepth = 4;
|
|
std::vector<int> divisions;
|
|
timeSig.getDivisions(maxDepth, divisions);
|
|
if (timeSig == TimeSignature()) // special case for 4/4
|
|
divisions[0] = 2;
|
|
|
|
// At each depth of beat subdivision, we find the closest match
|
|
// and assign it a score according to distance and depth. The
|
|
// calculation for the score should accord "better" scores to
|
|
// shorter distance and lower depth, but it should avoid giving
|
|
// a "perfect" score to any combination of distance and depth
|
|
// except where both are 0. Also, the effective depth is
|
|
// 2 more than the value of our depth counter, which counts
|
|
// from 0 at a point where the effective depth is already 1.
|
|
|
|
timeT base = timeSig.getBarDuration();
|
|
|
|
timeT bestBase = -2;
|
|
long bestScore = 0;
|
|
bool bestRight = false;
|
|
|
|
#ifdef DEBUG_NOTATION_QUANTIZER
|
|
cout << "quantizeAbsoluteTime: t is " << t << ", d is " << d << endl;
|
|
#endif
|
|
|
|
// scoreAbsoluteTimeForBase wants to know the previous starting
|
|
// note (N) and the previous starting note that ends (roughly)
|
|
// before this one starts (N'). Much more efficient to calculate
|
|
// them once now before the loop.
|
|
|
|
static timeT shortTime = Note(Note::Shortest).getDuration();
|
|
|
|
Segment::iterator j(i);
|
|
Segment::iterator n(s->end()), nprime(s->end());
|
|
for (;;) {
|
|
if (j == s->begin()) break;
|
|
--j;
|
|
if ((*j)->isa(Note::EventType)) {
|
|
if (n == s->end()) n = j;
|
|
if ((*j)->getAbsoluteTime() + (*j)->getDuration() + shortTime/2
|
|
<= (*i)->getAbsoluteTime()) {
|
|
nprime = j;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
#ifdef DEBUG_NOTATION_QUANTIZER
|
|
if (n != s->end() && n != nprime) {
|
|
cout << "found n (distinct from nprime) at " << (*n)->getAbsoluteTime() << endl;
|
|
}
|
|
if (nprime != s->end()) {
|
|
cout << "found nprime at " << (*nprime)->getAbsoluteTime()
|
|
<< ", duration " << (*nprime)->getDuration() << endl;
|
|
}
|
|
#endif
|
|
|
|
for (int depth = 0; depth < maxDepth; ++depth) {
|
|
|
|
base /= divisions[depth];
|
|
if (base < m_unit) break;
|
|
bool right = false;
|
|
long score = scoreAbsoluteTimeForBase(s, i, depth, base, sigTime,
|
|
t, d, noteType, n, nprime, right);
|
|
|
|
if (depth == 0 || score < bestScore) {
|
|
#ifdef DEBUG_NOTATION_QUANTIZER
|
|
cout << " [*]";
|
|
#endif
|
|
bestBase = base;
|
|
bestScore = score;
|
|
bestRight = right;
|
|
}
|
|
|
|
#ifdef DEBUG_NOTATION_QUANTIZER
|
|
cout << endl;
|
|
#endif
|
|
}
|
|
|
|
if (bestBase == -2) {
|
|
#ifdef DEBUG_NOTATION_QUANTIZER
|
|
cout << "Quantizer::quantizeAbsoluteTime: weirdness: no snap found" << endl;
|
|
#endif
|
|
} else {
|
|
// we need to snap relative to the time sig, not relative
|
|
// to the start of the whole composition
|
|
t -= sigTime;
|
|
|
|
t = (t / bestBase) * bestBase;
|
|
if (bestRight) t += bestBase;
|
|
|
|
/*
|
|
timeT low = (t / bestBase) * bestBase;
|
|
timeT high = low + bestBase;
|
|
t = ((high - t > t - low) ? low : high);
|
|
*/
|
|
|
|
t += sigTime;
|
|
|
|
#ifdef DEBUG_NOTATION_QUANTIZER
|
|
cout << "snap base is " << bestBase << ", snapped to " << t << endl;
|
|
#endif
|
|
}
|
|
|
|
setProvisional(*i, AbsoluteTimeValue, t);
|
|
(*i)->setMaybe<Int>(m_provisionalBase, bestBase);
|
|
(*i)->setMaybe<Int>(m_provisionalScore, bestScore);
|
|
}
|
|
|
|
long
|
|
NotationQuantizer::Impl::scoreAbsoluteTimeForBase(Segment *s,
|
|
const Segment::iterator & /* i */,
|
|
int depth,
|
|
timeT base,
|
|
timeT sigTime,
|
|
timeT t,
|
|
timeT d,
|
|
int noteType,
|
|
const Segment::iterator &n,
|
|
const Segment::iterator &nprime,
|
|
bool &wantRight)
|
|
const
|
|
{
|
|
Profiler profiler("NotationQuantizer::Impl::scoreAbsoluteTimeForBase");
|
|
|
|
// Lower score is better.
|
|
|
|
static timeT shortTime = Note(Note::Shortest).getDuration();
|
|
|
|
double simplicityFactor(m_simplicityFactor);
|
|
simplicityFactor -= Note::Crotchet - noteType;
|
|
if (simplicityFactor < 10) simplicityFactor = 10;
|
|
|
|
double effectiveDepth = pow(depth + 2, simplicityFactor / 10);
|
|
|
|
//!!! use velocity to adjust the effective depth as well? -- louder
|
|
// notes are more likely to be on big boundaries. Actually, perhaps
|
|
// introduce a generally-useful "salience" score a la Dixon et al
|
|
|
|
long leftScore = 0;
|
|
|
|
for (int ri = 0; ri < 2; ++ri) {
|
|
|
|
bool right = (ri == 1);
|
|
|
|
long distance = (t - sigTime) % base;
|
|
if (right) distance = base - distance;
|
|
long score = long((distance + shortTime / 2) * effectiveDepth);
|
|
|
|
double penalty1 = 1.0;
|
|
|
|
// seriously penalise moving a note beyond its own end time
|
|
if (d > 0 && right && distance >= d * 0.9) {
|
|
penalty1 = double(distance) / d + 0.5;
|
|
}
|
|
|
|
double penalty2 = 1.0;
|
|
|
|
// Examine the previous starting note (N), and the previous
|
|
// starting note that ends before this one starts (N').
|
|
|
|
// We should penalise moving this note to before the performed end
|
|
// of N' and seriously penalise moving it to the same quantized
|
|
// start time as N' -- but we should encourage moving it to the
|
|
// same time as the provisional end of N', or to the same start
|
|
// time as N if N != N'.
|
|
|
|
if (!right) {
|
|
if (n != s->end()) {
|
|
if (n != nprime) {
|
|
timeT nt = getProvisional(*n, AbsoluteTimeValue);
|
|
if (t - distance == nt) penalty2 = penalty2 * 2 / 3;
|
|
}
|
|
if (nprime != s->end()) {
|
|
timeT npt = getProvisional(*nprime, AbsoluteTimeValue);
|
|
timeT npd = getProvisional(*nprime, DurationValue);
|
|
if (t - distance <= npt) penalty2 *= 4;
|
|
else if (t - distance < npt + npd) penalty2 *= 2;
|
|
else if (t - distance == npt + npd) penalty2 = penalty2 * 2 / 3;
|
|
}
|
|
}
|
|
}
|
|
|
|
#ifdef DEBUG_NOTATION_QUANTIZER
|
|
cout << " depth/eff/dist/t/score/pen1/pen2/res: " << depth << "/" << effectiveDepth << "/" << distance << "/" << (right ? t + distance : t - distance) << "/" << score << "/" << penalty1 << "/" << penalty2 << "/" << (score * penalty1 * penalty2);
|
|
if (right) cout << " -> ";
|
|
else cout << " <- ";
|
|
if (ri == 0) cout << endl;
|
|
#endif
|
|
|
|
score = long(score * penalty1);
|
|
score = long(score * penalty2);
|
|
|
|
if (ri == 0) {
|
|
leftScore = score;
|
|
} else {
|
|
if (score < leftScore) {
|
|
wantRight = true;
|
|
return score;
|
|
} else {
|
|
wantRight = false;
|
|
return leftScore;
|
|
}
|
|
}
|
|
}
|
|
|
|
return leftScore;
|
|
}
|
|
|
|
void
|
|
NotationQuantizer::Impl::quantizeDurationProvisional(Segment *, Segment::iterator i)
|
|
const
|
|
{
|
|
Profiler profiler("NotationQuantizer::Impl::quantizeDurationProvisional");
|
|
|
|
// Calculate a first guess at the likely notation duration based
|
|
// only on its performed duration, without considering start time.
|
|
|
|
timeT d = m_q->getFromSource(*i, DurationValue);
|
|
if (d == 0) {
|
|
setProvisional(*i, DurationValue, d);
|
|
return;
|
|
}
|
|
|
|
Note shortNote = Note::getNearestNote(d, 2);
|
|
|
|
timeT shortTime = shortNote.getDuration();
|
|
timeT time = shortTime;
|
|
|
|
if (shortTime != d) {
|
|
|
|
Note longNote(shortNote);
|
|
|
|
if ((shortNote.getDots() > 0 ||
|
|
shortNote.getNoteType() == Note::Shortest)) { // can't dot that
|
|
|
|
if (shortNote.getNoteType() < Note::Longest) {
|
|
longNote = Note(shortNote.getNoteType() + 1, 0);
|
|
}
|
|
|
|
} else {
|
|
longNote = Note(shortNote.getNoteType(), 1);
|
|
}
|
|
|
|
timeT longTime = longNote.getDuration();
|
|
|
|
// we should prefer to round up to a note with fewer dots rather
|
|
// than down to one with more
|
|
|
|
//!!! except in dotted time, etc -- we really want this to work on a
|
|
// similar attraction-to-grid basis to the abstime quantization
|
|
|
|
if ((longNote.getDots() + 1) * (longTime - d) <
|
|
(shortNote.getDots() + 1) * (d - shortTime)) {
|
|
time = longTime;
|
|
}
|
|
}
|
|
|
|
setProvisional(*i, DurationValue, time);
|
|
|
|
if ((*i)->has(BEAMED_GROUP_TUPLET_BASE)) {
|
|
// We're going to recalculate these, and use our own results
|
|
(*i)->unset(BEAMED_GROUP_ID);
|
|
(*i)->unset(BEAMED_GROUP_TYPE);
|
|
(*i)->unset(BEAMED_GROUP_TUPLET_BASE);
|
|
(*i)->unset(BEAMED_GROUP_TUPLED_COUNT);
|
|
(*i)->unset(BEAMED_GROUP_UNTUPLED_COUNT);
|
|
//!!! (*i)->unset(TUPLET_NOMINAL_DURATION);
|
|
}
|
|
}
|
|
|
|
void
|
|
NotationQuantizer::Impl::quantizeDuration(Segment *s, Chord &c) const
|
|
{
|
|
static int totalFracCount = 0;
|
|
static float totalFrac = 0;
|
|
|
|
Profiler profiler("NotationQuantizer::Impl::quantizeDuration");
|
|
|
|
#ifdef DEBUG_NOTATION_QUANTIZER
|
|
cout << "quantizeDuration: chord has " << c.size() << " notes" << endl;
|
|
#endif
|
|
|
|
Composition *comp = s->getComposition();
|
|
|
|
TimeSignature timeSig;
|
|
// timeT t = m_q->getFromSource(*c.getInitialElement(), AbsoluteTimeValue);
|
|
// timeT sigTime = comp->getTimeSignatureAt(t, timeSig);
|
|
|
|
timeT d = getProvisional(*c.getInitialElement(), DurationValue);
|
|
int noteType = Note::getNearestNote(d).getNoteType();
|
|
int maxDepth = 8 - noteType;
|
|
if (maxDepth < 4) maxDepth = 4;
|
|
std::vector<int> divisions;
|
|
timeSig.getDivisions(maxDepth, divisions);
|
|
|
|
Segment::iterator nextNote = c.getNextNote();
|
|
timeT nextNoteTime =
|
|
(s->isBeforeEndMarker(nextNote) ?
|
|
getProvisional(*nextNote, AbsoluteTimeValue) :
|
|
s->getEndMarkerTime());
|
|
|
|
timeT nonContrapuntalDuration = 0;
|
|
|
|
for (Chord::iterator ci = c.begin(); ci != c.end(); ++ci) {
|
|
|
|
if (!(**ci)->isa(Note::EventType)) continue;
|
|
if ((**ci)->has(m_provisionalDuration) &&
|
|
(**ci)->has(BEAMED_GROUP_TUPLET_BASE)) {
|
|
// dealt with already in tuplet code, we'd only mess it up here
|
|
#ifdef DEBUG_NOTATION_QUANTIZER
|
|
cout << "not recalculating duration for tuplet" << endl;
|
|
#endif
|
|
continue;
|
|
}
|
|
|
|
timeT ud = 0;
|
|
|
|
if (!m_contrapuntal) {
|
|
// if not contrapuntal, give all notes in chord equal duration
|
|
if (nonContrapuntalDuration > 0) {
|
|
#ifdef DEBUG_NOTATION_QUANTIZER
|
|
cout << "setting duration trivially to " << nonContrapuntalDuration << endl;
|
|
#endif
|
|
setProvisional(**ci, DurationValue, nonContrapuntalDuration);
|
|
continue;
|
|
} else {
|
|
// establish whose duration to use, then set it at the
|
|
// bottom after it's been quantized
|
|
Segment::iterator li = c.getLongestElement();
|
|
if (li != s->end()) ud = m_q->getFromSource(*li, DurationValue);
|
|
else ud = m_q->getFromSource(**ci, DurationValue);
|
|
}
|
|
} else {
|
|
ud = m_q->getFromSource(**ci, DurationValue);
|
|
}
|
|
|
|
timeT qt = getProvisional(**ci, AbsoluteTimeValue);
|
|
|
|
#ifdef DEBUG_NOTATION_QUANTIZER
|
|
cout << "note at time " << (**ci)->getAbsoluteTime() << " (provisional time " << qt << ")" << endl;
|
|
#endif
|
|
|
|
timeT base = timeSig.getBarDuration();
|
|
std::pair<timeT, timeT> bases;
|
|
for (int depth = 0; depth < maxDepth; ++depth) {
|
|
if (base >= ud) {
|
|
bases = std::pair<timeT, timeT>(base / divisions[depth], base);
|
|
}
|
|
base /= divisions[depth];
|
|
}
|
|
|
|
#ifdef DEBUG_NOTATION_QUANTIZER
|
|
cout << "duration is " << ud << ", probably between "
|
|
<< bases.first << " and " << bases.second << endl;
|
|
#endif
|
|
|
|
timeT qd = getProvisional(**ci, DurationValue);
|
|
|
|
timeT spaceAvailable = nextNoteTime - qt;
|
|
|
|
if (spaceAvailable > 0) {
|
|
float frac = float(ud) / float(spaceAvailable);
|
|
totalFrac += frac;
|
|
totalFracCount += 1;
|
|
}
|
|
|
|
if (!m_contrapuntal && qd > spaceAvailable) {
|
|
|
|
qd = Note::getNearestNote(spaceAvailable).getDuration();
|
|
|
|
#ifdef DEBUG_NOTATION_QUANTIZER
|
|
cout << "non-contrapuntal segment, rounded duration down to "
|
|
<< qd << " (as only " << spaceAvailable << " available)"
|
|
<< endl;
|
|
#endif
|
|
|
|
} else {
|
|
|
|
//!!! Note longer than the longest note we have. Deal with
|
|
//this -- how? Quantize the end time? Split the note?
|
|
//(Prefer to do that in a separate phase later if requested.)
|
|
//Leave it as it is? (Yes, for now.)
|
|
if (bases.first == 0) return;
|
|
|
|
timeT absTimeBase = bases.first;
|
|
(**ci)->get<Int>(m_provisionalBase, absTimeBase);
|
|
|
|
spaceAvailable = std::min(spaceAvailable,
|
|
comp->getBarEndForTime(qt) - qt);
|
|
|
|
// We have a really good possibility of staccato if we have a
|
|
// note on a boundary whose base is double the note duration
|
|
// and there's nothing else until the next boundary and we're
|
|
// shorter than about a quaver (i.e. the base is a quaver or
|
|
// less)
|
|
|
|
if (qd*2 <= absTimeBase && (qd*8/3) >= absTimeBase &&
|
|
bases.second == absTimeBase) {
|
|
|
|
if (nextNoteTime >= qt + bases.second) {
|
|
#ifdef DEBUG_NOTATION_QUANTIZER
|
|
cout << "We rounded to " << qd
|
|
<< " but we're on " << absTimeBase << " absTimeBase"
|
|
<< " and the next base is " << bases.second
|
|
<< " and we have room for it, so"
|
|
<< " rounding up again" << endl;
|
|
#endif
|
|
qd = bases.second;
|
|
}
|
|
|
|
} else {
|
|
|
|
// Alternatively, if we rounded down but there's space to
|
|
// round up, consider doing so
|
|
|
|
//!!! mark staccato if necessary, and take existing marks into account
|
|
|
|
Note note(Note::getNearestNote(qd));
|
|
|
|
if (qd < ud || (qd == ud && note.getDots() == 2)) {
|
|
|
|
if (note.getNoteType() < Note::Longest) {
|
|
|
|
if (bases.second <= spaceAvailable) {
|
|
#ifdef DEBUG_NOTATION_QUANTIZER
|
|
cout << "We rounded down to " << qd
|
|
<< " but have room for " << bases.second
|
|
<< ", rounding up again" << endl;
|
|
#endif
|
|
qd = bases.second;
|
|
} else {
|
|
#ifdef DEBUG_NOTATION_QUANTIZER
|
|
cout << "We rounded down to " << qd
|
|
<< "; can't fit " << bases.second << endl;
|
|
#endif
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
setProvisional(**ci, DurationValue, qd);
|
|
if (!m_contrapuntal) nonContrapuntalDuration = qd;
|
|
}
|
|
|
|
#ifdef DEBUG_NOTATION_QUANTIZER
|
|
cout << "totalFrac " << totalFrac << ", totalFracCount " << totalFracCount << ", avg " << (totalFracCount > 0 ? (totalFrac / totalFracCount) : 0) << endl;
|
|
#endif
|
|
}
|
|
|
|
|
|
void
|
|
NotationQuantizer::Impl::scanTupletsInBar(Segment *s,
|
|
timeT barStart,
|
|
timeT barDuration,
|
|
timeT wholeStart,
|
|
timeT wholeEnd,
|
|
const std::vector<int> &divisions) const
|
|
{
|
|
Profiler profiler("NotationQuantizer::Impl::scanTupletsInBar");
|
|
|
|
//!!! need to further constrain the area scanned so as to cope with
|
|
// partial bars
|
|
|
|
timeT base = barDuration;
|
|
|
|
for (int depth = -1; depth < int(divisions.size()) - 2; ++depth) {
|
|
|
|
if (depth >= 0) base /= divisions[depth];
|
|
if (base <= Note(Note::Semiquaver).getDuration()) break;
|
|
|
|
#ifdef DEBUG_NOTATION_QUANTIZER
|
|
cout << "\nscanTupletsInBar: trying at depth " << depth << " (base " << base << ")" << endl;
|
|
#endif
|
|
|
|
// check for triplets if our next divisor is 2 and the following
|
|
// one is not 3
|
|
|
|
if (divisions[depth+1] != 2 || divisions[depth+2] == 3) continue;
|
|
|
|
timeT tupletBase = base / 3;
|
|
timeT tupletStart = barStart;
|
|
|
|
while (tupletStart < barStart + barDuration) {
|
|
|
|
timeT tupletEnd = tupletStart + base;
|
|
if (tupletStart < wholeStart || tupletEnd > wholeEnd) {
|
|
tupletStart = tupletEnd;
|
|
continue;
|
|
}
|
|
|
|
#ifdef DEBUG_NOTATION_QUANTIZER
|
|
cout << "scanTupletsInBar: testing " << tupletStart << "," << base << " at tuplet base " << tupletBase << endl;
|
|
#endif
|
|
|
|
// find first note within a certain distance whose start time
|
|
// quantized to tupletStart or greater
|
|
Segment::iterator j = s->findTime(tupletStart - tupletBase / 3);
|
|
timeT jTime = tupletEnd;
|
|
|
|
while (s->isBeforeEndMarker(j) &&
|
|
(!(*j)->isa(Note::EventType) ||
|
|
!(*j)->get<Int>(m_provisionalAbsTime, jTime) ||
|
|
jTime < tupletStart)) {
|
|
if ((*j)->getAbsoluteTime() > tupletEnd + tupletBase / 3) {
|
|
break;
|
|
}
|
|
++j;
|
|
}
|
|
|
|
if (jTime >= tupletEnd) { // nothing to make tuplets of
|
|
#ifdef DEBUG_NOTATION_QUANTIZER
|
|
cout << "scanTupletsInBar: nothing here" << endl;
|
|
#endif
|
|
tupletStart = tupletEnd;
|
|
continue;
|
|
}
|
|
|
|
scanTupletsAt(s, j, depth+1, base, barStart,
|
|
tupletStart, tupletBase);
|
|
|
|
tupletStart = tupletEnd;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
void
|
|
NotationQuantizer::Impl::scanTupletsAt(Segment *s,
|
|
Segment::iterator i,
|
|
int depth,
|
|
timeT base,
|
|
timeT sigTime,
|
|
timeT tupletStart,
|
|
timeT tupletBase) const
|
|
{
|
|
Profiler profiler("NotationQuantizer::Impl::scanTupletsAt");
|
|
|
|
Segment::iterator j = i;
|
|
timeT tupletEnd = tupletStart + base;
|
|
timeT jTime = tupletEnd;
|
|
|
|
std::vector<Event *> candidates;
|
|
int count = 0;
|
|
|
|
while (s->isBeforeEndMarker(j) &&
|
|
((*j)->isa(Note::EventRestType) ||
|
|
((*j)->get<Int>(m_provisionalAbsTime, jTime) &&
|
|
jTime < tupletEnd))) {
|
|
|
|
if (!(*j)->isa(Note::EventType)) { ++j; continue; }
|
|
|
|
#ifdef DEBUG_NOTATION_QUANTIZER
|
|
cout << "scanTupletsAt time " << jTime << " (unquantized "
|
|
<< (*j)->getAbsoluteTime() << "), found note" << endl;
|
|
#endif
|
|
|
|
// reject any group containing anything already a tuplet
|
|
if ((*j)->has(BEAMED_GROUP_TUPLET_BASE)) {
|
|
#ifdef DEBUG_NOTATION_QUANTIZER
|
|
cout << "already made tuplet here" << endl;
|
|
#endif
|
|
return;
|
|
}
|
|
|
|
timeT originalBase;
|
|
|
|
if (!(*j)->get<Int>(m_provisionalBase, originalBase)) {
|
|
#ifdef DEBUG_NOTATION_QUANTIZER
|
|
cout << "some notes not provisionally quantized, no good" << endl;
|
|
#endif
|
|
return;
|
|
}
|
|
|
|
if (originalBase == base) {
|
|
#ifdef DEBUG_NOTATION_QUANTIZER
|
|
cout << "accepting note at original base" << endl;
|
|
#endif
|
|
candidates.push_back(*j);
|
|
} else if (((jTime - sigTime) % base) == 0) {
|
|
#ifdef DEBUG_NOTATION_QUANTIZER
|
|
cout << "accepting note that happens to lie on original base" << endl;
|
|
#endif
|
|
candidates.push_back(*j);
|
|
} else {
|
|
|
|
// This is a note that did not quantize to the original base
|
|
// (the first note in the tuplet would have, but we can't tell
|
|
// anything from that). Reject the entire group if it fails
|
|
// any of the likelihood tests for tuplets.
|
|
|
|
if (!isValidTupletAt(s, j, depth, base, sigTime, tupletBase)) {
|
|
#ifdef DEBUG_NOTATION_QUANTIZER
|
|
cout << "no good" << endl;
|
|
#endif
|
|
return;
|
|
}
|
|
|
|
candidates.push_back(*j);
|
|
++count;
|
|
}
|
|
|
|
++j;
|
|
}
|
|
|
|
// must have at least one note that is not already quantized to the
|
|
// original base
|
|
if (count < 1) {
|
|
#ifdef DEBUG_NOTATION_QUANTIZER
|
|
cout << "scanTupletsAt: found no note not already quantized to " << base << endl;
|
|
#endif
|
|
return;
|
|
}
|
|
|
|
#ifdef DEBUG_NOTATION_QUANTIZER
|
|
cout << "scanTupletsAt: Tuplet group of duration " << base << " starting at " << tupletStart << endl;
|
|
#endif
|
|
|
|
// Woo-hoo! It looks good.
|
|
|
|
int groupId = s->getNextId();
|
|
std::map<int, bool> multiples;
|
|
|
|
for (std::vector<Event *>::iterator ei = candidates.begin();
|
|
ei != candidates.end(); ++ei) {
|
|
|
|
//!!! Interesting -- we can't modify rests here, but Segment's
|
|
// normalizeRests won't insert the correct sort of rest for us...
|
|
// what to do?
|
|
//!!! insert a tupleted rest, and prevent Segment::normalizeRests
|
|
// from messing about with it
|
|
if (!(*ei)->isa(Note::EventType)) continue;
|
|
(*ei)->set<String>(BEAMED_GROUP_TYPE, GROUP_TYPE_TUPLED);
|
|
|
|
//!!! This is too easy, because we rejected any notes of
|
|
//durations not conforming to a single multiple of the
|
|
//tupletBase in isValidTupletAt
|
|
|
|
(*ei)->set<Int>(BEAMED_GROUP_ID, groupId);
|
|
(*ei)->set<Int>(BEAMED_GROUP_TUPLET_BASE, base/2); //!!! wrong if tuplet count != 3
|
|
(*ei)->set<Int>(BEAMED_GROUP_TUPLED_COUNT, 2); //!!! as above
|
|
(*ei)->set<Int>(BEAMED_GROUP_UNTUPLED_COUNT, base/tupletBase);
|
|
|
|
timeT t = (*ei)->getAbsoluteTime();
|
|
t -= tupletStart;
|
|
timeT low = (t / tupletBase) * tupletBase;
|
|
timeT high = low + tupletBase;
|
|
t = ((high - t > t - low) ? low : high);
|
|
|
|
multiples[t / tupletBase] = true;
|
|
|
|
t += tupletStart;
|
|
|
|
setProvisional(*ei, AbsoluteTimeValue, t);
|
|
setProvisional(*ei, DurationValue, tupletBase);
|
|
}
|
|
|
|
// fill in with tupleted rests
|
|
|
|
for (int m = 0; m < base / tupletBase; ++m) {
|
|
|
|
if (multiples[m]) continue;
|
|
|
|
timeT absTime = tupletStart + m * tupletBase;
|
|
timeT duration = tupletBase;
|
|
//!!! while (multiples[++m]) duration += tupletBase;
|
|
|
|
Event *rest = new Event(Note::EventRestType, absTime, duration);
|
|
|
|
rest->set<String>(BEAMED_GROUP_TYPE, GROUP_TYPE_TUPLED);
|
|
rest->set<Int>(BEAMED_GROUP_ID, groupId);
|
|
rest->set<Int>(BEAMED_GROUP_TUPLET_BASE, base/2); //!!! wrong if tuplet count != 3
|
|
rest->set<Int>(BEAMED_GROUP_TUPLED_COUNT, 2); //!!! as above
|
|
rest->set<Int>(BEAMED_GROUP_UNTUPLED_COUNT, base/tupletBase);
|
|
|
|
m_q->m_toInsert.push_back(rest);
|
|
}
|
|
}
|
|
|
|
bool
|
|
NotationQuantizer::Impl::isValidTupletAt(Segment *s,
|
|
const Segment::iterator &i,
|
|
int depth,
|
|
timeT /* base */,
|
|
timeT sigTime,
|
|
timeT tupletBase) const
|
|
{
|
|
Profiler profiler("NotationQuantizer::Impl::isValidTupletAt");
|
|
|
|
//!!! This is basically wrong; we need to be able to deal with groups
|
|
// that contain e.g. a crotchet and a quaver, tripleted.
|
|
|
|
timeT ud = m_q->getFromSource(*i, DurationValue);
|
|
|
|
if (ud > (tupletBase * 5 / 4)) {
|
|
#ifdef DEBUG_NOTATION_QUANTIZER
|
|
cout << "\nNotationQuantizer::isValidTupletAt: note too long at "
|
|
<< (*i)->getDuration() << " (tupletBase is " << tupletBase << ")"
|
|
<< endl;
|
|
#endif
|
|
return false; // too long
|
|
}
|
|
|
|
//!!! This bit is a cop-out. It means we reject anything that looks
|
|
// like it's going to have rests in it. Bah.
|
|
if (ud <= (tupletBase * 3 / 8)) {
|
|
#ifdef DEBUG_NOTATION_QUANTIZER
|
|
cout << "\nNotationQuantizer::isValidTupletAt: note too short at "
|
|
<< (*i)->getDuration() << " (tupletBase is " << tupletBase << ")"
|
|
<< endl;
|
|
#endif
|
|
return false;
|
|
}
|
|
|
|
long score = 0;
|
|
if (!(*i)->get<Int>(m_provisionalScore, score)) return false;
|
|
|
|
timeT t = m_q->getFromSource(*i, AbsoluteTimeValue);
|
|
timeT d = getProvisional(*i, DurationValue);
|
|
int noteType = (*i)->get<Int>(m_provisionalNoteType);
|
|
|
|
//!!! not as complete as the calculation we do in the original scoring
|
|
bool dummy;
|
|
long tupletScore = scoreAbsoluteTimeForBase
|
|
(s, i, depth, tupletBase, sigTime, t, d, noteType, s->end(), s->end(), dummy);
|
|
#ifdef DEBUG_NOTATION_QUANTIZER
|
|
cout << "\nNotationQuantizer::isValidTupletAt: score " << score
|
|
<< " vs tupletScore " << tupletScore << endl;
|
|
#endif
|
|
return (tupletScore < score);
|
|
}
|
|
|
|
|
|
void
|
|
NotationQuantizer::quantizeRange(Segment *s,
|
|
Segment::iterator from,
|
|
Segment::iterator to) const
|
|
{
|
|
m_impl->quantizeRange(s, from, to);
|
|
}
|
|
|
|
void
|
|
NotationQuantizer::Impl::quantizeRange(Segment *s,
|
|
Segment::iterator from,
|
|
Segment::iterator to) const
|
|
{
|
|
Profiler *profiler = new Profiler("NotationQuantizer::Impl::quantizeRange");
|
|
|
|
clock_t start = clock();
|
|
int events = 0, notes = 0, passes = 0;
|
|
int setGood = 0, setBad = 0;
|
|
|
|
#ifdef DEBUG_NOTATION_QUANTIZER
|
|
cout << "NotationQuantizer::Impl::quantizeRange: from time "
|
|
<< (from == s->end() ? -1 : (*from)->getAbsoluteTime())
|
|
<< " to "
|
|
<< (to == s->end() ? -1 : (*to)->getAbsoluteTime())
|
|
<< endl;
|
|
#endif
|
|
|
|
timeT segmentEndTime = s->getEndMarkerTime();
|
|
|
|
// This process does several passes over the data. It's assumed
|
|
// that this is not going to be invoked in any really time-critical
|
|
// place.
|
|
|
|
// Calculate absolute times on the first pass, so that we know
|
|
// which things are chords. We need to assign absolute times to
|
|
// all events, but we only need do durations for notes.
|
|
|
|
PropertyName provisionalBase("notationquantizer-provisionalBase");
|
|
|
|
// We don't use setToTarget until we have our final values ready,
|
|
// as it erases and replaces the events. Just set the properties.
|
|
|
|
// Set a provisional duration to each note first
|
|
|
|
for (Segment::iterator i = from; i != to; ++i) {
|
|
|
|
++events;
|
|
if ((*i)->isa(Note::EventRestType)) continue;
|
|
if ((*i)->isa(Note::EventType)) ++notes;
|
|
quantizeDurationProvisional(s, i);
|
|
}
|
|
++passes;
|
|
|
|
// now do the absolute-time calculation
|
|
|
|
timeT wholeStart = 0, wholeEnd = 0;
|
|
|
|
Segment::iterator i = from;
|
|
|
|
for (Segment::iterator nexti = i; i != to; i = nexti) {
|
|
|
|
++nexti;
|
|
|
|
if ((*i)->isa(Note::EventRestType)) {
|
|
if (i == from) ++from;
|
|
s->erase(i);
|
|
continue;
|
|
}
|
|
|
|
quantizeAbsoluteTime(s, i);
|
|
|
|
timeT t0 = (*i)->get<Int>(m_provisionalAbsTime);
|
|
timeT t1 = (*i)->get<Int>(m_provisionalDuration) + t0;
|
|
if (wholeStart == wholeEnd) {
|
|
wholeStart = t0;
|
|
wholeEnd = t1;
|
|
} else if (t1 > wholeEnd) {
|
|
wholeEnd = t1;
|
|
}
|
|
}
|
|
++passes;
|
|
|
|
// now we've grouped into chords, look for tuplets next
|
|
|
|
Composition *comp = s->getComposition();
|
|
|
|
if (m_maxTuplet >= 2) {
|
|
|
|
std::vector<int> divisions;
|
|
comp->getTimeSignatureAt(wholeStart).getDivisions(7, divisions);
|
|
|
|
for (int barNo = comp->getBarNumber(wholeStart);
|
|
barNo <= comp->getBarNumber(wholeEnd); ++barNo) {
|
|
|
|
bool isNew = false;
|
|
TimeSignature timeSig = comp->getTimeSignatureInBar(barNo, isNew);
|
|
if (isNew) timeSig.getDivisions(7, divisions);
|
|
scanTupletsInBar(s, comp->getBarStart(barNo),
|
|
timeSig.getBarDuration(),
|
|
wholeStart, wholeEnd, divisions);
|
|
}
|
|
++passes;
|
|
}
|
|
|
|
ProvisionalQuantizer provisionalQuantizer((Impl *)this);
|
|
|
|
for (i = from; i != to; ++i) {
|
|
|
|
if (!(*i)->isa(Note::EventType)) continue;
|
|
|
|
// could potentially supply clef and key here, but at the
|
|
// moment Chord doesn't do anything with them (unlike
|
|
// NotationChord) and we don't have any really clever
|
|
// ideas for how to use them here anyway
|
|
// Chord c(*s, i, m_q);
|
|
Chord c(*s, i, &provisionalQuantizer);
|
|
|
|
quantizeDuration(s, c);
|
|
|
|
bool ended = false;
|
|
for (Segment::iterator ci = c.getInitialElement();
|
|
s->isBeforeEndMarker(ci); ++ci) {
|
|
if (ci == to) ended = true;
|
|
if (ci == c.getFinalElement()) break;
|
|
}
|
|
if (ended) break;
|
|
|
|
i = c.getFinalElement();
|
|
}
|
|
++passes;
|
|
|
|
// staccato (we now do slurs separately, in SegmentNotationHelper::autoSlur)
|
|
|
|
if (m_articulate) {
|
|
|
|
for (i = from; i != to; ++i) {
|
|
|
|
if (!(*i)->isa(Note::EventType)) continue;
|
|
|
|
timeT qd = getProvisional(*i, DurationValue);
|
|
timeT ud = m_q->getFromSource(*i, DurationValue);
|
|
|
|
if (ud < (qd * 3 / 4) &&
|
|
qd <= Note(Note::Crotchet).getDuration()) {
|
|
Marks::addMark(**i, Marks::Staccato, true);
|
|
} else if (ud > qd) {
|
|
Marks::addMark(**i, Marks::Tenuto, true);
|
|
}
|
|
}
|
|
++passes;
|
|
}
|
|
|
|
i = from;
|
|
|
|
for (Segment::iterator nexti = i; i != to; i = nexti) {
|
|
|
|
++nexti;
|
|
|
|
if ((*i)->isa(Note::EventRestType)) continue;
|
|
|
|
timeT t = getProvisional(*i, AbsoluteTimeValue);
|
|
timeT d = getProvisional(*i, DurationValue);
|
|
|
|
unsetProvisionalProperties(*i);
|
|
|
|
if ((*i)->getAbsoluteTime() == t &&
|
|
(*i)->getDuration() == d) ++setBad;
|
|
else ++setGood;
|
|
|
|
#ifdef DEBUG_NOTATION_QUANTIZER
|
|
cout << "Setting to target at " << t << "," << d << endl;
|
|
#endif
|
|
|
|
m_q->setToTarget(s, i, t, d);
|
|
}
|
|
++passes;
|
|
/*
|
|
cerr << "NotationQuantizer: " << events << " events ("
|
|
<< notes << " notes), " << passes << " passes, "
|
|
<< setGood << " good sets, " << setBad << " bad sets, "
|
|
<< ((clock() - start) * 1000 / CLOCKS_PER_SEC) << "ms elapsed"
|
|
<< endl;
|
|
*/
|
|
if (s->getEndTime() < segmentEndTime) {
|
|
s->setEndMarkerTime(segmentEndTime);
|
|
}
|
|
|
|
delete profiler; // on heap so it updates before the next line:
|
|
Profiles::getInstance()->dump();
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|