Rhythmic groupings for subbeats

This commit is contained in:
Peter Jonas 2016-08-19 21:58:41 +01:00
parent 65b5be2ae8
commit bfd6cd5c0d
No known key found for this signature in database
GPG key ID: 0B81879C782D49F1
3 changed files with 172 additions and 31 deletions

View file

@ -520,56 +520,69 @@ void populateRhythmicList(std::vector<TDuration>* dList, const Fraction& l, bool
{
int rtickEnd = rtickStart + l.ticks();
BeatType startBeat = nominal.rtick2beatType(rtickStart);
BeatType endBeat = nominal.rtick2beatType(rtickEnd);
bool needToSplit = false; // do we need to split?
int rtickSplit = 0; // tick to split on if we need to
int dUnitsCrossed = 0; // number of timeSig denominator units the note/rest crosses
BeatType strongestBeatCrossed = BeatType::SUBBEAT; // split on the strongest beat or subbeat crossed
int rtickSplit1 = 0; // tick of the strongest beat
int rtickSplit2 = 0; // alternative tick if there is a choice (crossed more than one of strongest type).
// CHECK AT SUBBEAT LEVEL
for (int rtick = rtickStart + nominal.ticksToNextDUnit(rtickStart); rtick < rtickEnd; rtick += nominal.dUnitTicks()) {
dUnitsCrossed++;
BeatType type = nominal.rtick2beatType(rtick);
if (type == strongestBeatCrossed)
rtickSplit2 = rtick;
else if (type < strongestBeatCrossed) {
strongestBeatCrossed = type;
rtickSplit1 = rtick;
rtickSplit2 = 0;
}
}
int startLevel = nominal.rtick2subbeatLevel(rtickStart);
int endLevel = nominal.rtick2subbeatLevel(rtickEnd);
int strongestLevelCrossed = nominal.strongestSubbeatLevelInRange(rtickStart, rtickEnd, &rtickSplit); // sets rtickSplit
if (!dUnitsCrossed) {
// we didn't cross any beats so just split into largest possible durations.
if ((startLevel < 0) || (endLevel < 0) || (strongestLevelCrossed < 0)) {
// Beyond maximum subbeat level so just split into largest possible durations.
std::vector<TDuration> dList2 = toDurationList(l, maxDots > 0, maxDots, false);
dList->insert(dList->end(), dList2.begin(), dList2.end());
return;
}
// we crossed beats, but were they rhythmically important?
if (!forceRhythmicSplit(isRest, startBeat, endBeat, dUnitsCrossed, strongestBeatCrossed, nominal)) {
// crossed beats were not important so try to avoid splitting.
// split if we cross something stronger than where we start and end
if ((strongestLevelCrossed < startLevel) && (strongestLevelCrossed < endLevel))
needToSplit = true;
// but don't split for level 1 syncopation (allow eight-note, quarter, quarter... to cross unstressed beats)
if (startLevel == endLevel && strongestLevelCrossed == startLevel - 1)
needToSplit = false;
// nor for the next simplest case of level 2 syncopation (allow sixteenth-note, eighth, eighth... to cross unstressed beats)
if (startLevel == endLevel && strongestLevelCrossed == startLevel - 2) {
// but disallow sixteenth-note, quarter, quarter...
int ticksToNext = nominal.ticksToNextSubbeat(rtickStart, startLevel - 1);
int ticksPastPrev = nominal.ticksPastSubbeat(rtickStart, startLevel - 1);
needToSplit = ticksToNext != ticksPastPrev;
}
if (!needToSplit && strongestLevelCrossed == 0) {
// NOW CHECK AT DENOMINATOR UNIT LEVEL AND BEAT LEVEL
BeatType startBeat = nominal.rtick2beatType(rtickStart);
BeatType endBeat = nominal.rtick2beatType(rtickEnd);
int dUnitsCrossed = 0; // number of timeSig denominator units the note/rest crosses
// if there is a choice of which beat to split on, should we use the first or last?
bool useLast = startBeat <= BeatType::SIMPLE_UNSTRESSED; // split on the later beat if starting on a beat
BeatType strongestBeatCrossed = nominal.strongestBeatInRange(rtickStart, rtickEnd, &dUnitsCrossed, &rtickSplit, useLast);
needToSplit = forceRhythmicSplit(isRest, startBeat, endBeat, dUnitsCrossed, strongestBeatCrossed, nominal);
}
if (!needToSplit) {
// CHECK THERE IS A DURATION THAT FITS
// crossed beats/subbeats were not important so try to avoid splitting
TDuration d = TDuration(l, true, maxDots);
if (d.fraction() == l) {
// we can use a single duration - no need to split!
dList->push_back(l);
return;
}
// no single TDuration fits so must split anyway, and best to split on a beat.
// no single TDuration fits so must split anyway
}
// prepare to split on strongest beat. Do we have a choice of beat to split on?
if (rtickSplit2 && startBeat <= BeatType::SIMPLE_UNSTRESSED)
rtickSplit1 = rtickSplit2; // use later choice when note starts on a beat, otherwise use early choice.
// Split on the beat
Fraction leftSplit = Fraction::fromTicks(rtickSplit1 - rtickStart);
// Split on the strongest beat or subbeat crossed
Fraction leftSplit = Fraction::fromTicks(rtickSplit - rtickStart);
Fraction rightSplit = l - leftSplit;
// Recurse to see if we need to split further
// Recurse to see if we need to split further before adding to list
populateRhythmicList(dList, leftSplit, isRest, rtickStart, nominal, maxDots);
populateRhythmicList(dList, rightSplit, isRest, rtickSplit1 , nominal, maxDots);
populateRhythmicList(dList, rightSplit, isRest, rtickSplit , nominal, maxDots);
}
//---------------------------------------------------------

View file

@ -73,6 +73,125 @@ BeatType TimeSigFrac::rtick2beatType(int rtick) const
return isCompound() ? BeatType::COMPOUND_UNSTRESSED : BeatType::SIMPLE_UNSTRESSED;
}
//---------------------------------------------------------
// strongestBeatInRange
// dUnitsCrossed - pointer to store number crossed
// subbeatTick - pointer to store tick of strongest beat
// saveLast - which tick to store if strongest type is
// crossed more than once
//
// caller must adjust rticks as appropriate if the measure's
// actual timeSig is different from the nominal timeSig.
//---------------------------------------------------------
BeatType TimeSigFrac::strongestBeatInRange(int rtick1, int rtick2, int* dUnitsCrossed, int* subbeatTick, bool saveLast) const
{
Q_ASSERT(rtick2 > rtick1);
BeatType strongest = BeatType::SUBBEAT;
for (int rtick = rtick1 + ticksToNextDUnit(rtick1); rtick < rtick2; rtick += dUnitTicks()) {
if (dUnitsCrossed)
(*dUnitsCrossed)++;
BeatType type = rtick2beatType(rtick);
if (static_cast<int>(type) < static_cast<int>(strongest) + saveLast) { // "<" behaves like "<=" if saveLast is true
strongest = type;
if (subbeatTick)
(*subbeatTick) = rtick;
}
}
return strongest;
}
//---------------------------------------------------------
// subbeatTicks
// divides dUnitTicks() by 2 once for each level.
//---------------------------------------------------------
int TimeSigFrac::subbeatTicks(int level) const
{
Q_ASSERT(level <= maxSubbeatLevel());
int subbeatTicks = dUnitTicks();
while (level > 0) {
subbeatTicks /= 2;
level--;
}
return subbeatTicks;
}
//---------------------------------------------------------
// maxSubbeatLevel
// subdivision beyond this level would result in rounding errors
//---------------------------------------------------------
int TimeSigFrac::maxSubbeatLevel() const
{
int level = 0;
int subbeatTicks = dUnitTicks();
while (subbeatTicks % 2 == 0) {
subbeatTicks /= 2;
level++;
}
return level;
}
//---------------------------------------------------------
// rtick2subbeatLevel
// returns 0 if rtick is on a beat or denominator unit.
// returns 1 if rtick lies halfway between dUnits
// returns 2 if rtick lies on a multiple of 1/4 of dUnit
// 3 1/8
// 4 1/16
// n 1/(2**n)
// returns -(n+1) if max n is reached and rtick still not found.
//
// Caller must adjust rtick as appropriate if the measure's
// actual timeSig is different from the nominal timeSig.
//---------------------------------------------------------
int TimeSigFrac::rtick2subbeatLevel(int rtick) const
{
int level = 0;
int subbeatTicks = dUnitTicks();
int remainder = rtick % subbeatTicks;
while (remainder != 0) {
level++;
if (subbeatTicks % 2 != 0)
return -level; // further sub-division would split measure into chunks of unequal length.
subbeatTicks /= 2;
remainder %= subbeatTicks;
}
return level;
}
//---------------------------------------------------------
// strongestSubbeatLevelInRange
// Return value is negative if none are found.
//
// Caller must adjust rtick as appropriate if the measure's
// actual timeSig is different from the nominal timeSig.
//---------------------------------------------------------
int TimeSigFrac::strongestSubbeatLevelInRange(int rtick1, int rtick2, int* subbeatTick) const
{
Q_ASSERT(rtick2 > rtick1);
for (int level = 0, subbeatTicks = dUnitTicks();;) {
int n = rtick1 / subbeatTicks;
int m = (rtick2 - 1) / subbeatTicks; // -1 to make the range exclusive
if (m > n) {
if (subbeatTick)
(*subbeatTick) = m * subbeatTicks;
return level;
}
level++;
if (subbeatTicks % 2 != 0)
return -level; // further sub-division would split measure into chunks of unequal length.
subbeatTicks /= 2;
}
}
//---------------------------------------------------------
// operator==
//---------------------------------------------------------

View file

@ -61,6 +61,9 @@ class TimeSigFrac : public Fraction {
int beatTicks() const { return dUnitTicks() * dUnitsPerBeat(); }
int beatsPerMeasure() const { return numerator() / dUnitsPerBeat(); }
int subbeatTicks(int level) const;
int maxSubbeatLevel() const;
bool isTriple() const { return beatsPerMeasure() % 3 == 0; }
bool isDuple() const { Q_ASSERT(!isTriple()); return beatsPerMeasure() % 2 == 0; } // note: always test isTriple() first
@ -69,6 +72,10 @@ class TimeSigFrac : public Fraction {
qreal beatsPerMinute2tempo(qreal bpm) const { return bpm * dUnitsPerBeat() / (15.0 * denominator()); }
BeatType rtick2beatType(int rtick) const;
int rtick2subbeatLevel(int rtick) const; // returns negative value if not on a well-defined subbeat
BeatType strongestBeatInRange(int rtick1, int rtick2, int* dUnitsCrossed = 0, int* subbeatTick = 0, bool saveLast = false) const; // range is exclusive
int strongestSubbeatLevelInRange(int rtick1, int rtick2, int* subbeatTick = 0) const; // range is exclusive
int ticksPastDUnit(int rtick) const { return rtick % dUnitTicks(); } // returns 0 if rtick is exactly on a dUnit
int ticksToNextDUnit(int rtick) const { return dUnitTicks() - ticksPastDUnit(rtick); } // returns dUnitTicks() if rtick is on a dUnit
@ -76,6 +83,8 @@ class TimeSigFrac : public Fraction {
int ticksPastBeat(int rtick) const { return rtick % beatTicks(); } // returns 0 if rtick is exactly on a beat
int ticksToNextBeat(int rtick) const { return beatTicks() - ticksPastBeat(rtick); } // returns beatTicks() if rtick is on a beat
int ticksPastSubbeat(int rtick, int level) const { return rtick % subbeatTicks(level); }
int ticksToNextSubbeat(int rtick, int level) const { return subbeatTicks(level) - ticksPastSubbeat(rtick, level); }
};
//-------------------------------------------------------------------