mirror of
https://github.com/microtherion/VocalEasel.git
synced 2024-12-22 11:14:00 +00:00
2550 lines
66 KiB
C++
2550 lines
66 KiB
C++
//
|
|
// File: VLModel.cpp - Represent song music data
|
|
//
|
|
// Author(s):
|
|
//
|
|
// (MN) Matthias Neeracher
|
|
//
|
|
// Copyright © 2005-2018 Matthias Neeracher
|
|
//
|
|
|
|
#include "VLModel.h"
|
|
#include "VLPitchName.h"
|
|
#include "VLPitchGrid.h"
|
|
|
|
#pragma mark class VLFraction
|
|
|
|
VLFraction & VLFraction::Normalize()
|
|
{
|
|
//
|
|
// Divide GCD
|
|
//
|
|
if (fNum) {
|
|
unsigned a = fNum;
|
|
unsigned b = fDenom;
|
|
|
|
while (b) {
|
|
unsigned c = a % b;
|
|
a = b;
|
|
b = c;
|
|
}
|
|
|
|
fNum /= a;
|
|
fDenom /= a;
|
|
} else
|
|
fDenom = 1;
|
|
|
|
return *this;
|
|
}
|
|
|
|
VLFraction & VLFraction::operator+=(VLFraction other)
|
|
{
|
|
fNum = fNum*other.fDenom + other.fNum*fDenom;
|
|
fDenom *= other.fDenom;
|
|
|
|
return Normalize();
|
|
}
|
|
|
|
VLFraction & VLFraction::operator-=(VLFraction other)
|
|
{
|
|
fNum = fNum*other.fDenom - other.fNum*fDenom;
|
|
fDenom *= other.fDenom;
|
|
|
|
return Normalize();
|
|
}
|
|
|
|
VLFraction & VLFraction::operator*=(VLFraction other)
|
|
{
|
|
fNum *= other.fNum;
|
|
fDenom *= other.fDenom;
|
|
|
|
return Normalize();
|
|
}
|
|
|
|
VLFraction & VLFraction::operator/=(VLFraction other)
|
|
{
|
|
fNum *= other.fDenom;
|
|
fDenom *= other.fNum;
|
|
|
|
return Normalize();
|
|
}
|
|
|
|
VLFraction & VLFraction::operator%=(VLFraction other)
|
|
{
|
|
fNum *= other.fDenom;
|
|
fDenom *= other.fNum;
|
|
fNum %= fDenom;
|
|
|
|
return *this *= other;
|
|
}
|
|
|
|
bool VLFraction::IsPowerOfTwo() const
|
|
{
|
|
//
|
|
// !(x & (x-1)) is true if x had only a single bit set and thus
|
|
// is a power of two. We're not really interested in the case >1
|
|
//
|
|
return fNum == 1 && !(fDenom & (fDenom-1));
|
|
}
|
|
|
|
#pragma mark -
|
|
#pragma mark class VLNote
|
|
|
|
VLNote::VLNote(std::string name)
|
|
{
|
|
fPitch = VLParsePitch(name, 0, &fVisual);
|
|
|
|
if (!name.empty()) { // Failed to parse completely
|
|
fPitch = kNoPitch;
|
|
fVisual= 0;
|
|
}
|
|
}
|
|
|
|
VLNote::VLNote(VLFraction dur, int pitch, uint16_t visual)
|
|
: fDuration(dur), fPitch(pitch), fTied(0), fVisual(visual)
|
|
{
|
|
}
|
|
|
|
std::string VLNote::Name(uint16_t accidental) const
|
|
{
|
|
if (uint16_t acc = (fVisual & kAccidentalsMask)) {
|
|
if (acc == kWantNatural) {
|
|
accidental |= acc;
|
|
} else {
|
|
accidental = acc;
|
|
}
|
|
}
|
|
|
|
return VLPitchName(fPitch, accidental);
|
|
}
|
|
|
|
void VLNote::MakeRepresentable()
|
|
{
|
|
if (fDuration > VLFraction(1))
|
|
fDuration = 1;
|
|
fVisual = kWhole | (fVisual & kAccidentalsMask);
|
|
VLFraction part(1,1);
|
|
VLFraction triplet(2,3);
|
|
bool nonTriplet = fDuration.IsPowerOfTwo();
|
|
while (part.fDenom < 64) {
|
|
if (fDuration >= part) {
|
|
fDuration = part;
|
|
return;
|
|
} else if (fVisual > kWhole && !nonTriplet && fDuration >= triplet) {
|
|
if (fDuration == triplet || (fDuration-triplet) >= VLFraction(1,16)) {
|
|
fDuration = triplet;
|
|
fVisual |= kTriplet;
|
|
return;
|
|
}
|
|
}
|
|
part /= 2;
|
|
triplet /= 2;
|
|
++fVisual;
|
|
}
|
|
fprintf(stderr, "Encountered preposterously brief note: %d/%d\n",
|
|
fDuration.fNum, fDuration.fDenom);
|
|
abort();
|
|
}
|
|
|
|
void VLNote::AlignToGrid(VLFraction at, VLFraction grid)
|
|
{
|
|
if (at+fDuration > grid) {
|
|
fDuration = grid-at;
|
|
MakeRepresentable();
|
|
}
|
|
}
|
|
|
|
VLLyricsNote::VLLyricsNote(const VLNote & note)
|
|
: VLNote(note)
|
|
{
|
|
}
|
|
|
|
VLLyricsNote::VLLyricsNote(VLFraction dur, int pitch, uint16_t visual)
|
|
: VLNote(dur, pitch, visual)
|
|
{
|
|
}
|
|
|
|
#pragma mark -
|
|
#pragma mark class VLChord
|
|
|
|
VLChord::VLChord(VLFraction dur, int pitch, int rootPitch)
|
|
: VLNote(dur, pitch), fSteps(0), fRootPitch(kNoPitch)
|
|
{
|
|
}
|
|
|
|
VLChord::VLChord(std::string name)
|
|
{
|
|
fPitch = VLParseChord(name, &fVisual, &fSteps, &fRootPitch, &fRootAccidental);
|
|
|
|
if (fPitch < 0)
|
|
fPitch = kNoPitch;
|
|
}
|
|
|
|
void VLChord::Name(std::string & base, std::string & ext, std::string & root, uint16_t accidental) const
|
|
{
|
|
uint16_t pitchAccidental;
|
|
uint16_t rootAccidental;
|
|
uint16_t acc;
|
|
|
|
if ((acc = (fVisual & kAccidentalsMask)))
|
|
if (acc == kWantNatural)
|
|
pitchAccidental = accidental | acc;
|
|
else
|
|
pitchAccidental = acc;
|
|
else
|
|
pitchAccidental = accidental;
|
|
if ((acc = fRootAccidental))
|
|
if (acc == kWantNatural)
|
|
rootAccidental = accidental | acc;
|
|
else
|
|
rootAccidental = acc;
|
|
else
|
|
rootAccidental = accidental;
|
|
|
|
VLChordName(fPitch, pitchAccidental, fSteps, fRootPitch, rootAccidental,
|
|
base, ext, root);
|
|
}
|
|
|
|
#pragma mark -
|
|
#pragma mark class VLMeasure
|
|
|
|
VLMeasure::VLMeasure()
|
|
: fBreak(0), fPropIdx(0)
|
|
{
|
|
}
|
|
|
|
bool VLMeasure::IsEmpty() const
|
|
{
|
|
return fChords.size() == 1 && fMelody.size() == 1
|
|
&& fChords.front().fPitch == VLNote::kNoPitch
|
|
&& fMelody.front().fPitch == VLNote::kNoPitch;
|
|
}
|
|
|
|
bool VLMeasure::NoChords() const
|
|
{
|
|
return fChords.size() == 1
|
|
&& fChords.front().fPitch == VLNote::kNoPitch;
|
|
}
|
|
|
|
bool VLMeasure::CanSkipRests() const
|
|
{
|
|
VLFraction restDur(0);
|
|
VLFraction totalDur(0);
|
|
bool initialRest = true;
|
|
|
|
for (VLNoteList::const_iterator n = fMelody.begin(); n != fMelody.end(); ++n) {
|
|
if (n->fPitch != VLNote::kNoPitch)
|
|
initialRest = false;
|
|
totalDur += n->fDuration;
|
|
if (initialRest)
|
|
restDur += n->fDuration;
|
|
}
|
|
return 2*restDur >= totalDur;
|
|
}
|
|
|
|
void VLMeasure::DecomposeNotes(const VLProperties & prop, VLNoteList & decomposed) const
|
|
{
|
|
decomposed.clear();
|
|
|
|
const VLFraction kQuarterDur(1,4);
|
|
const VLFraction kEighthLoc(1,8);
|
|
const VLFraction kQuarTripLoc(1,6);
|
|
const VLFraction kMinDuration(1,4*prop.fDivisions);
|
|
|
|
VLFraction at(0);
|
|
VLNoteList::const_iterator i = fMelody.begin();
|
|
VLNoteList::const_iterator e = fMelody.end();
|
|
int prevTriplets = 0;
|
|
int prevVisual;
|
|
VLFraction prevTripDur;
|
|
|
|
while (i!=e) {
|
|
VLNoteList::const_iterator n = i;
|
|
++n;
|
|
VLNoteList::const_iterator n2 = n;
|
|
if (n2 != e)
|
|
++n2;
|
|
|
|
VLLyricsNote c = *i; // Current note, remaining duration
|
|
VLLyricsNote p = c; // Next partial note
|
|
VLFraction r(0);
|
|
do {
|
|
//
|
|
// Start with longest possible note
|
|
//
|
|
p.MakeRepresentable();
|
|
//
|
|
// Don't paint ourselves into a corner by leaving a non-representable
|
|
// remainder.
|
|
//
|
|
if (p.fDuration != c.fDuration && (c.fDuration-p.fDuration) % kMinDuration != VLFraction(0)) {
|
|
r += kMinDuration;
|
|
p.fDuration = c.fDuration-r;
|
|
continue;
|
|
}
|
|
//
|
|
// Don't try to further shrink a minimal note
|
|
//
|
|
if (p.fDuration == kMinDuration)
|
|
goto checkTriplets;
|
|
//
|
|
// Prefer further triplets
|
|
//
|
|
if (prevTriplets) {
|
|
prevVisual = (prevVisual & ~VLNote::kAccidentalsMask)
|
|
| (p.fVisual & VLNote::kAccidentalsMask);
|
|
if (p.fDuration >= 2*prevTripDur) {
|
|
p.fDuration = 2*prevTripDur;
|
|
if (prevTriplets == 1) {
|
|
p.fVisual = prevVisual-1;
|
|
prevTriplets = 2; // 1/8th, 1/4th triplet or similar
|
|
} else {
|
|
p.fDuration = prevTripDur; // 1/8th, 1/8th, 1/4th
|
|
p.fVisual = prevVisual;
|
|
}
|
|
goto haveDuration;
|
|
} else if (p.fDuration >= prevTripDur) {
|
|
p.fDuration = prevTripDur;
|
|
p.fVisual = prevVisual;
|
|
goto haveDuration;
|
|
} else if (p.fDuration >= prevTripDur/2) {
|
|
p.fDuration = prevTripDur/2;
|
|
p.fVisual = prevVisual+1;
|
|
prevTripDur /= 2;
|
|
if (prevTriplets == 1)
|
|
prevTriplets = 2; // 1/4th, 1/8th
|
|
else
|
|
prevTriplets = 1; // 1/4th, 1/4th, 1/8th
|
|
goto haveDuration;
|
|
}
|
|
prevTriplets = 0;
|
|
}
|
|
if (at.fDenom == 3 || at.fDenom > 4) {
|
|
//
|
|
// Break up notes not starting on quarter beat
|
|
// - Never cross middle of at.fMeasure
|
|
//
|
|
VLFraction middle;
|
|
if (prop.fTime.fNum & 1) // Treat 5/4 as 3+2, not 2+3
|
|
middle = VLFraction((prop.fTime.fNum+1)/2, prop.fTime.fDenom);
|
|
else
|
|
middle = prop.fTime / 2;
|
|
if (at < middle)
|
|
p.AlignToGrid(at, middle);
|
|
VLFraction inBeat = at % kQuarterDur;
|
|
if ((inBeat == kEighthLoc || inBeat == kQuarTripLoc)
|
|
&& p.fDuration == kQuarterDur
|
|
)
|
|
; // Allow syncopated quarters
|
|
else
|
|
p.AlignToGrid(inBeat, kQuarterDur); // Align all others
|
|
}
|
|
checkTriplets:
|
|
if ((p.fVisual & VLNote::kTupletMask) == VLNote::kTriplet) {
|
|
//
|
|
// Distinguish swing 8ths/16ths from triplets
|
|
//
|
|
bool swing16 = prop.fDivisions >= 6;
|
|
VLFraction sw6(1,6);
|
|
VLFraction sw12(1,12);
|
|
VLFraction sw24(1,24);
|
|
VLFraction grid4(1, 4);
|
|
VLFraction grid8(1, 8);
|
|
if ((p.fDuration == sw6 && !(at % grid4))
|
|
|| (swing16 && p.fDuration == sw12 && !(at % grid8))
|
|
) {
|
|
if (p.fDuration == c.fDuration && n2!=e
|
|
&& n->fDuration == p.fDuration && n2->fDuration >= p.fDuration
|
|
) {
|
|
; // Triplet, not swing note
|
|
} else {
|
|
//
|
|
// First swing note (4th triplet -> 8th)
|
|
//
|
|
p.fVisual = (p.fVisual+1) & ~VLNote::kTupletMask;
|
|
}
|
|
} else if ((p.fDuration == sw12 && !((at+p.fDuration) % grid4))
|
|
|| (swing16 && p.fDuration == sw24 && !((at+p.fDuration) % grid8))
|
|
) {
|
|
//
|
|
// Second swing note (8th triplet -> 8th)
|
|
//
|
|
if (!prevTriplets)
|
|
p.fVisual &= ~VLNote::kTupletMask;
|
|
} else if (swing16 && p.fDuration == sw24 && !((at+sw12) % grid4)) {
|
|
//
|
|
// Swing 8th *16th* 16th (16th triplet -> 16th)
|
|
//
|
|
if (!prevTriplets)
|
|
p.fVisual &= ~VLNote::kTupletMask;
|
|
} else if ((p.fDuration > kMinDuration) &&
|
|
((at % p.fDuration != VLFraction(0))
|
|
|| (p.fDuration != c.fDuration
|
|
&& 2*p.fDuration != c.fDuration)
|
|
|| (n!=e && n->fDuration != c.fDuration
|
|
&& n->fDuration != 2*c.fDuration
|
|
&& 2*n->fDuration != c.fDuration))
|
|
) {
|
|
//
|
|
// Get rid of awkward triplets
|
|
//
|
|
p.fDuration *= VLFraction(3,4);
|
|
p.fVisual = (p.fVisual+1) & ~VLNote::kTupletMask;
|
|
}
|
|
}
|
|
haveDuration:
|
|
if ((p.fVisual & VLNote::kTupletMask) == VLNote::kTriplet)
|
|
if ((prevTriplets = (prevTriplets+1)%3)) {
|
|
prevTripDur = p.fDuration;
|
|
prevVisual = p.fVisual;
|
|
}
|
|
p.fTied &= VLNote::kTiedWithPrev;
|
|
if (p.fDuration == c.fDuration)
|
|
p.fTied |= c.fTied & VLNote::kTiedWithNext;
|
|
else
|
|
p.fTied |= VLNote::kTiedWithNext;
|
|
if (p.fPitch == VLNote::kNoPitch)
|
|
p.fTied = VLNote::kNotTied;
|
|
decomposed.push_back(p);
|
|
at += p.fDuration;
|
|
c.fDuration -= p.fDuration;
|
|
p.fDuration = c.fDuration;
|
|
p.fTied |= VLNote::kTiedWithPrev;
|
|
p.fLyrics.clear();
|
|
} while (c.fDuration > VLFraction(0));
|
|
i = n;
|
|
}
|
|
}
|
|
|
|
#pragma mark -
|
|
#pragma mark class VLSong
|
|
|
|
VLSong::VLSong(bool initialize)
|
|
{
|
|
if (!initialize)
|
|
return;
|
|
|
|
const VLFraction fourFour(4,4);
|
|
VLProperties defaultProperties = {fourFour, 0, 1, 3, "Swing"};
|
|
|
|
fProperties.push_back(defaultProperties);
|
|
|
|
AddMeasure();
|
|
|
|
fGoToCoda = -1;
|
|
fCoda = -1;
|
|
}
|
|
|
|
void VLSong::AddMeasure()
|
|
{
|
|
VLFraction dur = fProperties.back().fTime;
|
|
dur.Normalize();
|
|
VLLyricsNote rest(dur);
|
|
VLChord rchord(dur);
|
|
VLMeasure meas;
|
|
|
|
meas.fPropIdx = fProperties.size()-1;
|
|
meas.fChords.push_back(rchord);
|
|
meas.fMelody.push_back(rest);
|
|
|
|
fMeasures.push_back(meas);
|
|
}
|
|
|
|
void VLSong::InsertMeasure(size_t beginMeasure)
|
|
{
|
|
if (beginMeasure == fMeasures.size()) {
|
|
AddMeasure();
|
|
} else {
|
|
VLSong insertion(false);
|
|
VLFraction dur = Properties(beginMeasure).fTime;
|
|
dur.Normalize();
|
|
VLChord rchord(dur);
|
|
VLLyricsNote note(dur);
|
|
|
|
VLLyricsNote nextNote = fMeasures[beginMeasure].fMelody.front();
|
|
if (nextNote.fTied & VLNote::kTiedWithPrev) {
|
|
note.fPitch = nextNote.fPitch;
|
|
note.fVisual= nextNote.fVisual & VLNote::kAccidentalsMask;
|
|
note.fTied = VLNote::kTiedWithPrev|VLNote::kTiedWithNext;
|
|
}
|
|
|
|
VLMeasure meas;
|
|
meas.fPropIdx = fMeasures[beginMeasure].fPropIdx;
|
|
meas.fChords.push_back(rchord);
|
|
meas.fMelody.push_back(note);
|
|
fMeasures.insert(fMeasures.begin()+beginMeasure, meas);
|
|
|
|
for (size_t r=0; r<fRepeats.size(); ++r) {
|
|
VLRepeat & repeat = fRepeats[r];
|
|
for (size_t e=0; e<repeat.fEndings.size(); ++e) {
|
|
if (repeat.fEndings[e].fBegin >= beginMeasure)
|
|
++repeat.fEndings[e].fBegin;
|
|
if (repeat.fEndings[e].fEnd >= beginMeasure)
|
|
++repeat.fEndings[e].fEnd;
|
|
}
|
|
}
|
|
if (fGoToCoda >= (int)beginMeasure)
|
|
++fGoToCoda;
|
|
if (fCoda >= (int)beginMeasure)
|
|
++fCoda;
|
|
}
|
|
}
|
|
|
|
void VLSong::SetProperties(size_t measure, int propIdx)
|
|
{
|
|
VLFraction dur = fProperties[propIdx].fTime;
|
|
dur.Normalize();
|
|
VLLyricsNote rest(dur);
|
|
VLChord rchord(dur);
|
|
VLMeasure meas;
|
|
|
|
meas.fPropIdx = propIdx;
|
|
meas.fChords.push_back(rchord);
|
|
meas.fMelody.push_back(rest);
|
|
|
|
if (measure < fMeasures.size())
|
|
fMeasures[measure] = meas;
|
|
else
|
|
while (fMeasures.size() <= measure)
|
|
fMeasures.push_back(meas);
|
|
}
|
|
|
|
void VLSong::swap(VLSong & other)
|
|
{
|
|
fProperties.swap(other.fProperties);
|
|
fMeasures.swap(other.fMeasures);
|
|
fRepeats.swap(other.fRepeats);
|
|
std::swap(fGoToCoda, other.fGoToCoda);
|
|
std::swap(fCoda, other.fCoda);
|
|
}
|
|
|
|
void VLSong::clear()
|
|
{
|
|
fProperties.resize(1);
|
|
fMeasures.clear();
|
|
fRepeats.clear();
|
|
|
|
fGoToCoda = -1;
|
|
fCoda = -1;
|
|
}
|
|
|
|
//
|
|
// Deal with chords - a bit simpler
|
|
//
|
|
void VLSong::AddChord(VLChord chord, VLLocation at)
|
|
{
|
|
//
|
|
// Always keep an empty measure in reserve
|
|
//
|
|
while (at.fMeasure+1 >= fMeasures.size())
|
|
AddMeasure();
|
|
|
|
VLChordList::iterator i = fMeasures[at.fMeasure].fChords.begin();
|
|
VLFraction t(0);
|
|
|
|
for (;;) {
|
|
VLFraction tEnd = t+i->fDuration;
|
|
if (tEnd > at.fAt) {
|
|
if (t == at.fAt) {
|
|
//
|
|
// Exact match, replace current
|
|
//
|
|
chord.fDuration = i->fDuration;
|
|
*i = chord;
|
|
} else {
|
|
//
|
|
// Overlap, split current
|
|
//
|
|
chord.fDuration = tEnd-at.fAt;
|
|
i->fDuration = at.fAt-t;
|
|
fMeasures[at.fMeasure].fChords.insert(++i, chord);
|
|
}
|
|
break; // Exit here
|
|
}
|
|
t = tEnd;
|
|
++i;
|
|
}
|
|
}
|
|
|
|
void VLSong::DelChord(VLLocation at)
|
|
{
|
|
VLChordList::iterator i = fMeasures[at.fMeasure].fChords.begin();
|
|
VLFraction t(0);
|
|
|
|
for (;;) {
|
|
if (t == at.fAt) {
|
|
//
|
|
// Found it. Extend previous or make rest
|
|
//
|
|
if (i != fMeasures[at.fMeasure].fChords.begin()) {
|
|
//
|
|
// Extend previous
|
|
//
|
|
VLChordList::iterator j = i;
|
|
--j;
|
|
j->fDuration += i->fDuration;
|
|
fMeasures[at.fMeasure].fChords.erase(i);
|
|
} else {
|
|
//
|
|
// Turn into rest
|
|
//
|
|
i->fPitch = VLNote::kNoPitch;
|
|
}
|
|
break;
|
|
}
|
|
VLFraction tEnd = t+i->fDuration;
|
|
if (tEnd > at.fAt)
|
|
break; // Past the point, quit
|
|
t = tEnd;
|
|
++i;
|
|
}
|
|
//
|
|
// Trim excess empty measures
|
|
//
|
|
if (at.fMeasure == fMeasures.size()-2 && fMeasures[at.fMeasure].IsEmpty())
|
|
fMeasures.pop_back();
|
|
}
|
|
|
|
VLSong::note_iterator::note_iterator(const VLMeasureList::iterator &meas, const VLNoteList::iterator ¬e)
|
|
: fMeasIter(meas), fNoteIter(note)
|
|
{
|
|
}
|
|
|
|
VLSong::note_iterator &
|
|
VLSong::note_iterator::operator--()
|
|
{
|
|
if (fNoteIter == fMeasIter->fMelody.begin()) {
|
|
--fMeasIter;
|
|
fNoteIter = fMeasIter->fMelody.end();
|
|
}
|
|
--fNoteIter;
|
|
return *this;
|
|
}
|
|
|
|
VLSong::note_iterator &
|
|
VLSong::note_iterator::operator++()
|
|
{
|
|
++fNoteIter;
|
|
if (fNoteIter == fMeasIter->fMelody.end()) {
|
|
++fMeasIter;
|
|
fNoteIter = fMeasIter->fMelody.begin();
|
|
}
|
|
return *this;
|
|
}
|
|
|
|
bool
|
|
VLSong::note_iterator::operator==(const note_iterator &other)
|
|
{
|
|
return fMeasIter == other.fMeasIter && fNoteIter == other.fNoteIter;
|
|
}
|
|
|
|
VLSong::note_iterator
|
|
VLSong::begin_note(size_t measure)
|
|
{
|
|
return note_iterator(fMeasures.begin()+measure, fMeasures[measure].fMelody.begin());
|
|
}
|
|
|
|
VLSong::note_iterator
|
|
VLSong::end_note(size_t measure)
|
|
{
|
|
return note_iterator(fMeasures.begin()+measure, fMeasures[measure].fMelody.end());
|
|
}
|
|
|
|
VLSong::note_iterator
|
|
VLSong::end_note()
|
|
{
|
|
return end_note(fMeasures.size()-1);
|
|
}
|
|
|
|
VLLyricsNote VLSong::FindNote(VLLocation at)
|
|
{
|
|
VLNoteList::iterator i = fMeasures[at.fMeasure].fMelody.begin();
|
|
VLFraction t(0);
|
|
|
|
for (;;) {
|
|
VLFraction tEnd = t+i->fDuration;
|
|
if (tEnd > at.fAt)
|
|
return *i;
|
|
t = tEnd;
|
|
++i;
|
|
}
|
|
return ++at.fMeasure < fMeasures.size()
|
|
? fMeasures[at.fMeasure].fMelody.front() : VLLyricsNote();
|
|
}
|
|
|
|
bool VLSong::PrevNote(VLLocation &at)
|
|
{
|
|
uint32_t meas = at.fMeasure;
|
|
VLFraction where = at.fAt;
|
|
for (;;) {
|
|
bool found = false;
|
|
VLNoteList::iterator i = fMeasures[meas].fMelody.begin();
|
|
VLNoteList::iterator end = fMeasures[meas].fMelody.end();
|
|
VLFraction t(0);
|
|
VLFraction prevAt(0);
|
|
while (i != end) {
|
|
VLFraction tEnd = t+i->fDuration;
|
|
if (tEnd > where)
|
|
break;
|
|
if (i->fPitch != VLNote::kNoPitch && !(i->fTied & VLNote::kTiedWithPrev)) {
|
|
prevAt = t;
|
|
found = true;
|
|
}
|
|
t = tEnd;
|
|
++i;
|
|
}
|
|
if (found) {
|
|
at.fMeasure = meas;
|
|
at.fAt = prevAt;
|
|
|
|
return true;
|
|
}
|
|
if (!meas--)
|
|
break;
|
|
where = VLFraction(1000);
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool VLSong::NextNote(VLLocation &at)
|
|
{
|
|
uint32_t meas = at.fMeasure;
|
|
VLFraction where = at.fAt;
|
|
bool first = false;
|
|
for (;;) {
|
|
VLNoteList::iterator i = fMeasures[meas].fMelody.begin();
|
|
VLNoteList::iterator end = fMeasures[meas].fMelody.end();
|
|
VLFraction t(0);
|
|
while (i != end) {
|
|
VLFraction tEnd = t+i->fDuration;
|
|
if ((t > where || first) && i->fPitch != VLNote::kNoPitch
|
|
&& !(i->fTied & VLNote::kTiedWithPrev)
|
|
) {
|
|
at.fMeasure = meas;
|
|
at.fAt = t;
|
|
|
|
return true;
|
|
}
|
|
first = false;
|
|
t = tEnd;
|
|
++i;
|
|
}
|
|
if (++meas < fMeasures.size()) {
|
|
where = VLFraction(0);
|
|
first = true;
|
|
} else
|
|
break;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
static uint8_t & FirstTie(VLMeasure & measure)
|
|
{
|
|
VLNoteList::iterator i = measure.fMelody.begin();
|
|
return i->fTied;
|
|
}
|
|
|
|
static uint8_t & LastTie(VLMeasure & measure)
|
|
{
|
|
VLNoteList::iterator i = measure.fMelody.end();
|
|
--i;
|
|
return i->fTied;
|
|
}
|
|
|
|
//
|
|
// Dealing with notes is similar, but we also have to handle ties
|
|
//
|
|
void VLSong::AddNote(VLLyricsNote note, VLLocation at)
|
|
{
|
|
//
|
|
// Sanity check on accidentals
|
|
//
|
|
note.fVisual = VLPitchAccidental(note.fPitch, note.fVisual, Properties(at.fMeasure).fKey);
|
|
//
|
|
// Always keep an empty measure in reserve
|
|
//
|
|
while (at.fMeasure+1 >= fMeasures.size())
|
|
AddMeasure();
|
|
|
|
VLNoteList::iterator i = fMeasures[at.fMeasure].fMelody.begin();
|
|
VLFraction t(0);
|
|
|
|
for (;;) {
|
|
VLFraction tEnd = t+i->fDuration;
|
|
if (tEnd > at.fAt) {
|
|
if (t == at.fAt) {
|
|
//
|
|
// Exact match, replace current
|
|
//
|
|
if (i->fTied) {
|
|
//
|
|
// Break backward ties, but not forward
|
|
//
|
|
if (i->fTied & VLNote::kTiedWithPrev) {
|
|
i->fTied &= ~VLNote::kTiedWithPrev;
|
|
LastTie(fMeasures[at.fMeasure-1]) &= ~VLNote::kTiedWithNext;
|
|
}
|
|
if (i->fTied & VLNote::kTiedWithNext) {
|
|
VLNoteList::iterator j = i;
|
|
for (size_t tiedMeas = at.fMeasure+1; j->fTied & VLNote::kTiedWithNext;++tiedMeas) {
|
|
j = fMeasures[tiedMeas].fMelody.begin();
|
|
j->fPitch = note.fPitch;
|
|
j->fVisual= note.fVisual;
|
|
}
|
|
}
|
|
}
|
|
//
|
|
// Deliberately leave fLyrics alone unless set in incoming note
|
|
//
|
|
i->fPitch = note.fPitch;
|
|
i->fVisual = note.fVisual;
|
|
if (note.fLyrics.size())
|
|
i->fLyrics = note.fLyrics;
|
|
} else {
|
|
//
|
|
// Overlap, split current
|
|
//
|
|
note.fDuration = tEnd-at.fAt;
|
|
i->fDuration = at.fAt-t;
|
|
i = fMeasures[at.fMeasure].fMelody.insert(++i, note);
|
|
}
|
|
if (i->fPitch == VLNote::kNoPitch) {
|
|
//
|
|
// Merge with adjacent rests
|
|
//
|
|
if (i != fMeasures[at.fMeasure].fMelody.begin()) {
|
|
VLNoteList::iterator j = i;
|
|
--j;
|
|
if (j->fPitch == VLNote::kNoPitch) {
|
|
j->fDuration += i->fDuration;
|
|
fMeasures[at.fMeasure].fMelody.erase(i);
|
|
i = j;
|
|
}
|
|
}
|
|
VLNoteList::iterator j = i;
|
|
++j;
|
|
if (j != fMeasures[at.fMeasure].fMelody.end() && j->fPitch == VLNote::kNoPitch) {
|
|
i->fDuration += j->fDuration;
|
|
fMeasures[at.fMeasure].fMelody.erase(j);
|
|
}
|
|
}
|
|
break; // Exit here
|
|
}
|
|
t = tEnd;
|
|
++i;
|
|
}
|
|
i->fTied = 0;
|
|
if (note.fTied & VLNote::kTiedWithPrev) {// kTiedWithNext is NEVER user set
|
|
auto j = i;
|
|
if (at.fAt != VLFraction(0) || at.fMeasure) {
|
|
if (at.fAt == VLFraction(0)) {
|
|
j = fMeasures[at.fMeasure-1].fMelody.end();
|
|
}
|
|
--j;
|
|
if (note.fPitch != VLNote::kNoPitch && j->fPitch != VLNote::kNoPitch) {
|
|
j->fTied |= VLNote::kTiedWithNext;
|
|
i->fTied |= VLNote::kTiedWithPrev;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void VLSong::DelNote(VLLocation at)
|
|
{
|
|
VLNoteList::iterator i = fMeasures[at.fMeasure].fMelody.begin();
|
|
VLFraction t(0);
|
|
|
|
for (;;) {
|
|
if (t == at.fAt) {
|
|
//
|
|
// Found it. Break ties.
|
|
//
|
|
if (i->fTied & VLNote::kTiedWithNext)
|
|
FirstTie(fMeasures[at.fMeasure+1]) &= ~VLNote::kTiedWithPrev;
|
|
if (i->fTied & VLNote::kTiedWithPrev)
|
|
LastTie(fMeasures[at.fMeasure-1]) &= ~VLNote::kTiedWithNext;
|
|
//
|
|
// Extend previous or make rest
|
|
//
|
|
if (i != fMeasures[at.fMeasure].fMelody.begin()) {
|
|
//
|
|
// Extend previous
|
|
//
|
|
VLNoteList::iterator j = i;
|
|
--j;
|
|
j->fDuration += i->fDuration;
|
|
fMeasures[at.fMeasure].fMelody.erase(i);
|
|
} else {
|
|
//
|
|
// Merge with next if it's a rest, otherwise, just turn into rest
|
|
//
|
|
VLNoteList::iterator j = i;
|
|
++j;
|
|
if (j != fMeasures[at.fMeasure].fMelody.end() && j->fPitch == VLNote::kNoPitch) {
|
|
i->fDuration += j->fDuration;
|
|
fMeasures[at.fMeasure].fMelody.erase(j);
|
|
}
|
|
i->fPitch = VLNote::kNoPitch;
|
|
i->fTied = 0;
|
|
}
|
|
break;
|
|
}
|
|
VLFraction tEnd = t+i->fDuration;
|
|
if (tEnd > at.fAt)
|
|
break; // Past the point, quit
|
|
t = tEnd;
|
|
++i;
|
|
}
|
|
//
|
|
// Trim excess empty measures
|
|
//
|
|
if (at.fMeasure == fMeasures.size()-2 && fMeasures[at.fMeasure].IsEmpty())
|
|
fMeasures.pop_back();
|
|
}
|
|
|
|
VLNote VLSong::ExtendNote(VLLocation at)
|
|
{
|
|
VLNoteList::iterator i = fMeasures[at.fMeasure].fMelody.begin();
|
|
VLNoteList::iterator end= fMeasures[at.fMeasure].fMelody.end();
|
|
|
|
if (i==end)
|
|
return VLNote(); // Empty song, do nothing
|
|
|
|
for (VLFraction t(0); i != end && t+i->fDuration <= at.fAt; ++i)
|
|
t += i->fDuration;
|
|
|
|
if (i == end)
|
|
--i;
|
|
if (i->fPitch == VLNote::kNoPitch)
|
|
return *i; // Don't extend rests
|
|
|
|
for (;;) {
|
|
VLNoteList::iterator j=i;
|
|
++j;
|
|
if (j != fMeasures[at.fMeasure].fMelody.end()) {
|
|
//
|
|
// Extend across next note/rest
|
|
//
|
|
i->fDuration += j->fDuration;
|
|
fMeasures[at.fMeasure].fMelody.erase(j);
|
|
} else if (++at.fMeasure < fMeasures.size()) {
|
|
//
|
|
// Extend into next measure
|
|
//
|
|
VLNoteList::iterator k = fMeasures[at.fMeasure].fMelody.begin();
|
|
if (k->fTied & VLNote::kTiedWithPrev) {
|
|
//
|
|
// Already extended, extend further
|
|
//
|
|
i = k;
|
|
continue; // Go for another spin
|
|
} else {
|
|
for (;;) {
|
|
bool wasTied = k->fTied & VLNote::kTiedWithNext;
|
|
//
|
|
// Extend previous note
|
|
//
|
|
k->fPitch = i->fPitch;
|
|
k->fVisual= i->fVisual;
|
|
k->fTied = VLNote::kTiedWithPrev;
|
|
i->fTied |= VLNote::kTiedWithNext;
|
|
k->fLyrics.clear();
|
|
if (!wasTied)
|
|
break;
|
|
i = k;
|
|
k = fMeasures[++at.fMeasure].fMelody.begin();
|
|
}
|
|
}
|
|
if (at.fMeasure+1 == fMeasures.size())
|
|
AddMeasure();
|
|
}
|
|
break;
|
|
}
|
|
return *i;
|
|
}
|
|
|
|
VLLocation VLSong::TieNote(VLLocation at, bool tieWithPrev)
|
|
{
|
|
VLNoteList::iterator i = fMeasures[at.fMeasure].fMelody.begin();
|
|
VLNoteList::iterator end= fMeasures[at.fMeasure].fMelody.end();
|
|
|
|
if (i==end)
|
|
return VLLocation(); // Empty song, do nothing
|
|
|
|
for (VLFraction t(0); i != end && t+i->fDuration <= at.fAt; ++i)
|
|
t += i->fDuration;
|
|
|
|
if (i == end)
|
|
--i;
|
|
if (i->fPitch == VLNote::kNoPitch)
|
|
return VLLocation(); // Don't tie rests
|
|
|
|
VLNoteList::iterator j=i;
|
|
auto startMeasure = at.fMeasure;
|
|
if (tieWithPrev) {
|
|
if (j != fMeasures[at.fMeasure].fMelody.begin()) {
|
|
--j;
|
|
//
|
|
// Extend across next note/rest
|
|
//
|
|
if (i->fPitch == j->fPitch) {
|
|
// Consolidate identical pitches
|
|
j->fDuration += i->fDuration;
|
|
fMeasures[at.fMeasure].fMelody.erase(i);
|
|
i = j;
|
|
} else {
|
|
i->fTied |= VLNote::kTiedWithPrev;
|
|
j->fTied |= VLNote::kTiedWithNext;
|
|
i->fLyrics.clear();
|
|
}
|
|
} else if (at.fMeasure-- > 0) {
|
|
//
|
|
// Extend into previous measure
|
|
//
|
|
j = fMeasures[at.fMeasure].fMelody.end();
|
|
--j;
|
|
if (j->fPitch != VLNote::kNoPitch) { // Don't tie with rests
|
|
i->fTied |= VLNote::kTiedWithPrev;
|
|
j->fTied |= VLNote::kTiedWithNext;
|
|
i->fLyrics.clear();
|
|
}
|
|
}
|
|
} else {
|
|
++j;
|
|
if (j != fMeasures[at.fMeasure].fMelody.end()) {
|
|
//
|
|
// Extend across next note/rest
|
|
//
|
|
if (i->fPitch == j->fPitch) {
|
|
// Consolidate identical pitches
|
|
i->fDuration += j->fDuration;
|
|
fMeasures[at.fMeasure].fMelody.erase(j);
|
|
} else {
|
|
i->fTied |= VLNote::kTiedWithNext;
|
|
j->fTied |= VLNote::kTiedWithPrev;
|
|
j->fLyrics.clear();
|
|
}
|
|
} else if (++at.fMeasure < fMeasures.size()) {
|
|
//
|
|
// Extend into next measure
|
|
//
|
|
j = fMeasures[at.fMeasure].fMelody.begin();
|
|
if (j->fPitch != VLNote::kNoPitch) { // Don't tie with rests
|
|
i->fTied |= VLNote::kTiedWithNext;
|
|
j->fTied |= VLNote::kTiedWithPrev;
|
|
j->fLyrics.clear();
|
|
}
|
|
}
|
|
}
|
|
while (i->fTied & VLNote::kTiedWithPrev) {
|
|
if (i == fMeasures[startMeasure].fMelody.begin()) {
|
|
if (startMeasure) {
|
|
i = fMeasures[--startMeasure].fMelody.end();
|
|
} else {
|
|
break;
|
|
}
|
|
}
|
|
--i;
|
|
}
|
|
at.fMeasure = startMeasure;
|
|
at.fAt = VLFraction();
|
|
for (j = fMeasures[startMeasure].fMelody.begin(); j != i; ++j) {
|
|
at.fAt = at.fAt+j->fDuration;
|
|
}
|
|
return at;
|
|
}
|
|
|
|
bool VLSong::IsNonEmpty() const
|
|
{
|
|
for (size_t measure=0; measure<fMeasures.size(); ++measure) {
|
|
VLNoteList::const_iterator i = fMeasures[measure].fMelody.begin();
|
|
VLNoteList::const_iterator e = fMeasures[measure].fMelody.end();
|
|
|
|
for (; i!=e; ++i)
|
|
if (i->fPitch != VLNote::kNoPitch)
|
|
return true;
|
|
}
|
|
for (size_t measure=0; measure<fMeasures.size(); ++measure) {
|
|
VLChordList::const_iterator i = fMeasures[measure].fChords.begin();
|
|
VLChordList::const_iterator e = fMeasures[measure].fChords.end();
|
|
|
|
for (; i!=e; ++i)
|
|
if (i->fPitch != VLNote::kNoPitch)
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
static void TransposePinned(int8_t & pitch, int semi)
|
|
{
|
|
if (pitch == VLNote::kNoPitch)
|
|
return;
|
|
|
|
int pitchInOctave = pitch % 12;
|
|
int octave = pitch-pitchInOctave;
|
|
pitchInOctave += semi;
|
|
if (pitchInOctave < 0)
|
|
pitch = octave+pitchInOctave+12;
|
|
else if (pitchInOctave > 11)
|
|
pitch = octave+pitchInOctave-12;
|
|
else
|
|
pitch = octave+pitchInOctave;
|
|
}
|
|
|
|
static inline void FlipAccidentals(uint16_t & visual)
|
|
{
|
|
visual = (visual & ~VLNote::kAccidentalsMask)
|
|
| ((visual & VLNote::kPreferSharps) << 2)
|
|
| ((visual & VLNote::kPreferFlats) << 2);
|
|
}
|
|
|
|
void VLSong::ChangeKey(int section, int newKey, int newMode, bool transpose)
|
|
{
|
|
VLProperties & prop = fProperties[section];
|
|
bool flipAcc= newKey*prop.fKey < 0;
|
|
int semi = 7*(newKey-prop.fKey) % 12;
|
|
prop.fKey = newKey;
|
|
prop.fMode = newMode;
|
|
if (!transpose)
|
|
return;
|
|
|
|
for (size_t measure=0; measure<fMeasures.size(); ++measure) {
|
|
if (fMeasures[measure].fPropIdx != section)
|
|
continue;
|
|
|
|
VLChordList::iterator i = fMeasures[measure].fChords.begin();
|
|
VLChordList::iterator e = fMeasures[measure].fChords.end();
|
|
|
|
for (; i!=e; ++i) {
|
|
TransposePinned(i->fPitch, semi);
|
|
TransposePinned(i->fRootPitch, semi);
|
|
if (flipAcc) {
|
|
FlipAccidentals(i->fVisual);
|
|
FlipAccidentals(i->fRootAccidental);
|
|
}
|
|
i->fVisual = VLPitchAccidental(i->fPitch, i->fVisual, newKey);
|
|
i->fRootAccidental = VLPitchAccidental(i->fRootPitch, i->fRootAccidental, newKey);
|
|
}
|
|
}
|
|
for (int pass=0; pass<2 && semi;) {
|
|
int8_t low = 127;
|
|
int8_t high = 0;
|
|
for (size_t measure=0; measure<fMeasures.size(); ++measure) {
|
|
if (fMeasures[measure].fPropIdx != section)
|
|
continue;
|
|
|
|
VLNoteList::iterator i = fMeasures[measure].fMelody.begin();
|
|
VLNoteList::iterator e = fMeasures[measure].fMelody.end();
|
|
|
|
for (; i!=e; ++i) {
|
|
if (i->fPitch == VLNote::kNoPitch)
|
|
continue;
|
|
i->fPitch += semi;
|
|
if (flipAcc)
|
|
FlipAccidentals(i->fVisual);
|
|
i->fVisual = VLPitchAccidental(i->fPitch, i->fVisual, newKey);
|
|
low = std::min(low, i->fPitch);
|
|
high = std::max(high, i->fPitch);
|
|
}
|
|
}
|
|
if (low < VLNote::kMiddleC-6 && high < VLNote::kMiddleC+7)
|
|
semi = 12; // Transpose an Octave up
|
|
else if (low > VLNote::kMiddleC+7 && high > VLNote::kMiddleC+16)
|
|
semi = -12; // Transpose an Octave down
|
|
else
|
|
break; // Looks like we're done
|
|
flipAcc = false;
|
|
}
|
|
}
|
|
|
|
void VLSong::ChangeOctave(int section, bool transposeUp)
|
|
{
|
|
int semi = transposeUp ? 12 : -12;
|
|
|
|
for (size_t measure=0; measure<fMeasures.size(); ++measure) {
|
|
if (fMeasures[measure].fPropIdx != section)
|
|
continue;
|
|
|
|
VLNoteList::iterator i = fMeasures[measure].fMelody.begin();
|
|
VLNoteList::iterator e = fMeasures[measure].fMelody.end();
|
|
|
|
for (; i!=e; ++i) {
|
|
if (i->fPitch == VLNote::kNoPitch)
|
|
continue;
|
|
i->fPitch += semi;
|
|
}
|
|
}
|
|
}
|
|
|
|
//
|
|
// We try a table based approach for converting the beginning and end of
|
|
// notes
|
|
//
|
|
|
|
static const uint8_t sDiv2_3[] = {0, 2};
|
|
static const uint8_t sDiv2_4[] = {0, 2};
|
|
static const uint8_t sDiv2_6[] = {0, 3};
|
|
static const uint8_t sDiv2_8[] = {0, 4};
|
|
static const uint8_t sDiv2_12[]= {0, 6};
|
|
static const uint8_t * sDiv2[] = {
|
|
NULL, sDiv2_3, sDiv2_4, NULL, sDiv2_6,
|
|
NULL, sDiv2_8, NULL, NULL, NULL, sDiv2_12};
|
|
|
|
static const uint8_t sDiv3_2[] = {0, 1, 1};
|
|
static const uint8_t sDiv3_4[] = {0, 2, 3};
|
|
static const uint8_t sDiv3_6[] = {0, 2, 4};
|
|
static const uint8_t sDiv3_8[] = {0, 3, 6};
|
|
static const uint8_t sDiv3_12[]= {0, 4, 8};
|
|
static const uint8_t * sDiv3[] = {
|
|
sDiv3_2, NULL, sDiv3_4, NULL, sDiv3_6,
|
|
NULL, sDiv3_8, NULL, NULL, NULL, sDiv3_12};
|
|
|
|
static const uint8_t sDiv4_2[] = {0, 0, 1, 1};
|
|
static const uint8_t sDiv4_3[] = {0, 1, 2, 2};
|
|
static const uint8_t sDiv4_6[] = {0, 2, 3, 5};
|
|
static const uint8_t sDiv4_8[] = {0, 2, 4, 6};
|
|
static const uint8_t sDiv4_12[]= {0, 3, 6, 9};
|
|
static const uint8_t * sDiv4[] = {
|
|
sDiv4_2, sDiv4_3, NULL, NULL, sDiv4_6,
|
|
NULL, sDiv4_8, NULL, NULL, NULL, sDiv4_12};
|
|
|
|
static const uint8_t sDiv6_2[] = {0, 0, 0, 1, 1, 1};
|
|
static const uint8_t sDiv6_3[] = {0, 0, 1, 2, 2, 2};
|
|
static const uint8_t sDiv6_4[] = {0, 1, 2, 2, 3, 3};
|
|
static const uint8_t sDiv6_8[] = {0, 2, 3, 4, 6, 7};
|
|
static const uint8_t sDiv6_12[]= {0, 2, 4, 6, 8,10};
|
|
static const uint8_t * sDiv6[] = {
|
|
sDiv6_2, sDiv6_3, sDiv6_4, NULL, NULL,
|
|
NULL, sDiv6_8, NULL, NULL, NULL, sDiv6_12};
|
|
|
|
static const uint8_t sDiv8_2[] = {0, 0, 0, 0, 1, 1, 1, 1};
|
|
static const uint8_t sDiv8_3[] = {0, 0, 1, 1, 1, 2, 2, 2};
|
|
static const uint8_t sDiv8_4[] = {0, 0, 1, 1, 2, 2, 3, 3};
|
|
static const uint8_t sDiv8_6[] = {0, 1, 2, 2, 3, 4, 5, 5};
|
|
static const uint8_t sDiv8_12[]= {0, 2, 3, 5, 6, 8, 9,11};
|
|
static const uint8_t * sDiv8[] = {
|
|
sDiv8_2, sDiv8_3, sDiv8_4, NULL, sDiv8_6,
|
|
NULL, NULL, NULL, NULL, NULL, sDiv8_12};
|
|
|
|
static const uint8_t sDiv12_2[]= {0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1};
|
|
static const uint8_t sDiv12_3[]= {0, 0, 0, 0, 1, 1, 1, 1, 2, 2, 2, 2};
|
|
static const uint8_t sDiv12_4[]= {0, 0, 0, 1, 1, 1, 2, 2, 2, 3, 3, 3};
|
|
static const uint8_t sDiv12_6[]= {0, 0, 1, 1, 2, 2, 3, 3, 4, 4, 5, 5};
|
|
static const uint8_t sDiv12_8[]= {0, 1, 2, 2, 3, 4, 4, 5, 6, 6, 7, 7};
|
|
static const uint8_t * sDiv12[]= {
|
|
sDiv12_2, sDiv12_3, sDiv12_4, NULL, sDiv12_6,
|
|
NULL, sDiv12_8, NULL, NULL, NULL, NULL};
|
|
|
|
static const uint8_t ** sDiv[] = {
|
|
sDiv2, sDiv3, sDiv4, NULL, sDiv6,
|
|
NULL, sDiv8, NULL, NULL, NULL, sDiv12};
|
|
|
|
class VLRealigner {
|
|
public:
|
|
VLRealigner(int oldDiv, int newDiv);
|
|
|
|
VLFraction operator()(VLFraction at);
|
|
private:
|
|
VLFraction fOldFrac;
|
|
VLFraction fNewFrac;
|
|
const uint8_t * fTable;
|
|
};
|
|
|
|
VLRealigner::VLRealigner(int oldDiv, int newDiv)
|
|
: fOldFrac(1, 4*oldDiv), fNewFrac(1, 4*newDiv),
|
|
fTable(sDiv[oldDiv-2][newDiv-2])
|
|
{
|
|
}
|
|
|
|
VLFraction VLRealigner::operator()(VLFraction at)
|
|
{
|
|
VLFraction quarters(4*at.fNum / at.fDenom, 4);
|
|
at = (at-quarters) / fOldFrac;
|
|
|
|
return quarters + fTable[at.fNum / at.fDenom]*fNewFrac;
|
|
}
|
|
|
|
static VLChordList Realign(const VLChordList & chords,
|
|
const VLProperties& fromProp,
|
|
const VLProperties& toProp)
|
|
{
|
|
if (fromProp.fTime == toProp.fTime)
|
|
return chords;
|
|
if (fromProp.fTime < toProp.fTime) {
|
|
VLChord rchord(toProp.fTime-fromProp.fTime);
|
|
VLChordList newChords(chords);
|
|
newChords.push_back(rchord);
|
|
|
|
return newChords;
|
|
} else {
|
|
VLChordList::const_iterator i = chords.begin();
|
|
VLChordList::const_iterator e = chords.end();
|
|
VLFraction at(0);
|
|
VLChordList newChords;
|
|
|
|
for (; i!=e; ++i) {
|
|
VLChord c = *i;
|
|
if (at+c.fDuration >= toProp.fTime) {
|
|
if (at < toProp.fTime) {
|
|
c.fDuration = toProp.fTime-at;
|
|
newChords.push_back(c);
|
|
}
|
|
break;
|
|
} else {
|
|
newChords.push_back(c);
|
|
at += c.fDuration;
|
|
}
|
|
}
|
|
|
|
return newChords;
|
|
}
|
|
}
|
|
|
|
static VLNoteList Realign(const VLNoteList & notes,
|
|
const VLProperties& fromProp,
|
|
const VLProperties& toProp)
|
|
{
|
|
if (fromProp.fTime == toProp.fTime && fromProp.fDivisions == toProp.fDivisions)
|
|
return notes;
|
|
VLNoteList newNotes;
|
|
if (fromProp.fTime < toProp.fTime) {
|
|
VLNote rest(toProp.fTime-fromProp.fTime);
|
|
newNotes = notes;
|
|
newNotes.push_back(rest);
|
|
} else if (fromProp.fTime > toProp.fTime) {
|
|
VLNoteList::const_iterator i = notes.begin();
|
|
VLNoteList::const_iterator e = notes.end();
|
|
VLFraction at(0);
|
|
|
|
for (; i!=e; ++i) {
|
|
VLNote n = *i;
|
|
if (at+n.fDuration >= toProp.fTime) {
|
|
if (at < toProp.fTime) {
|
|
n.fDuration = toProp.fTime-at;
|
|
newNotes.push_back(n);
|
|
}
|
|
break;
|
|
} else {
|
|
newNotes.push_back(n);
|
|
at += n.fDuration;
|
|
}
|
|
}
|
|
} else {
|
|
newNotes = notes;
|
|
}
|
|
if (fromProp.fDivisions != toProp.fDivisions) {
|
|
VLRealigner realign(fromProp.fDivisions, toProp.fDivisions);
|
|
|
|
VLNoteList alignedNotes;
|
|
VLFraction at(0);
|
|
VLFraction lastAt;
|
|
|
|
VLNoteList::iterator i = newNotes.begin();
|
|
VLNoteList::iterator e = newNotes.end();
|
|
|
|
for (; i!=e; ++i) {
|
|
VLLyricsNote n = *i;
|
|
VLFraction newAt = realign(at);
|
|
if (alignedNotes.empty()) {
|
|
alignedNotes.push_back(n);
|
|
lastAt = newAt;
|
|
} else if (newAt != lastAt) {
|
|
alignedNotes.back().fDuration = newAt-lastAt;
|
|
alignedNotes.push_back(n);
|
|
lastAt = newAt;
|
|
}
|
|
at += n.fDuration;
|
|
}
|
|
if (lastAt == at) {
|
|
alignedNotes.pop_back();
|
|
} else {
|
|
alignedNotes.back().fDuration = at-lastAt;
|
|
}
|
|
|
|
return alignedNotes;
|
|
} else
|
|
return newNotes;
|
|
}
|
|
|
|
void VLSong::ChangeDivisions(int section, int newDivisions)
|
|
{
|
|
VLProperties & prop = fProperties[section];
|
|
if (prop.fDivisions == newDivisions)
|
|
return; // Unchanged
|
|
VLProperties newProp = prop;
|
|
newProp.fDivisions = newDivisions;
|
|
|
|
//
|
|
// Only melody needs to be realigned, chords are already quarter notes
|
|
//
|
|
for (size_t measure=0; measure<fMeasures.size(); ++measure)
|
|
if (fMeasures[measure].fPropIdx == section)
|
|
fMeasures[measure].fMelody =
|
|
Realign(fMeasures[measure].fMelody, prop, newProp);
|
|
prop = newProp;
|
|
}
|
|
|
|
void VLSong::ChangeTime(int section, VLFraction newTime)
|
|
{
|
|
VLProperties & prop = fProperties[section];
|
|
if (prop.fTime == newTime) {
|
|
prop.fTime = newTime; // Handle changes like 3/4 -> 6/8
|
|
return; // Trivial or no change
|
|
}
|
|
VLProperties newProp = prop;
|
|
newProp.fTime = newTime;
|
|
for (size_t measure=0; measure<fMeasures.size(); ++measure)
|
|
if (fMeasures[measure].fPropIdx == section) {
|
|
fMeasures[measure].fChords =
|
|
Realign(fMeasures[measure].fChords, prop, newProp);
|
|
fMeasures[measure].fMelody =
|
|
Realign(fMeasures[measure].fMelody, prop, newProp);
|
|
}
|
|
prop = newProp;
|
|
}
|
|
|
|
size_t VLSong::EmptyEnding() const
|
|
{
|
|
size_t full = fMeasures.size();
|
|
|
|
while (full-- && fMeasures[full].IsEmpty()
|
|
&& !DoesEndRepeat(full+1, 0) && !DoesEndEnding(full+1, 0, 0)
|
|
)
|
|
;
|
|
|
|
return fMeasures.size()-(full+1);
|
|
}
|
|
|
|
size_t VLSong::CountStanzas() const
|
|
{
|
|
size_t stanzas = 0;
|
|
|
|
for (size_t measure=0; measure<fMeasures.size(); ++measure) {
|
|
VLNoteList::const_iterator i = fMeasures[measure].fMelody.begin();
|
|
VLNoteList::const_iterator e = fMeasures[measure].fMelody.end();
|
|
|
|
for (; i!=e; ++i)
|
|
if (i->fLyrics.size() > stanzas)
|
|
for (size_t s = stanzas; s < i->fLyrics.size(); ++s)
|
|
if (i->fLyrics[s])
|
|
stanzas = s+1;
|
|
}
|
|
|
|
return stanzas;
|
|
}
|
|
|
|
size_t VLSong::CountTopLedgers() const
|
|
{
|
|
int8_t maxPitch = VLNote::kMiddleC;
|
|
|
|
for (size_t measure=0; measure<fMeasures.size(); ++measure) {
|
|
VLNoteList::const_iterator i = fMeasures[measure].fMelody.begin();
|
|
VLNoteList::const_iterator e = fMeasures[measure].fMelody.end();
|
|
|
|
for (; i!=e; ++i)
|
|
if (i->fPitch != VLNote::kNoPitch)
|
|
maxPitch = std::max(maxPitch, i->fPitch);
|
|
}
|
|
|
|
if (maxPitch > 89) // F''
|
|
return 4;
|
|
else if (maxPitch > 86) // D''
|
|
return 3;
|
|
else if (maxPitch > 83) // B'
|
|
return 2;
|
|
else if (maxPitch > 79) // G'
|
|
return 1;
|
|
else
|
|
return 0;
|
|
}
|
|
|
|
size_t VLSong::CountBotLedgers() const
|
|
{
|
|
int8_t minPitch = VLNote::kMiddleC+VLNote::kOctave;
|
|
|
|
for (size_t measure=0; measure<fMeasures.size(); ++measure) {
|
|
VLNoteList::const_iterator i = fMeasures[measure].fMelody.begin();
|
|
VLNoteList::const_iterator e = fMeasures[measure].fMelody.end();
|
|
|
|
for (; i!=e; ++i)
|
|
if (i->fPitch != VLNote::kNoPitch)
|
|
minPitch = std::min(minPitch, i->fPitch);
|
|
}
|
|
|
|
if (minPitch < 52) // E,
|
|
return 4;
|
|
else if (minPitch < 55) // G,
|
|
return 3;
|
|
else if (minPitch < 59) // B,
|
|
return 2;
|
|
else if (minPitch < 62) // D
|
|
return 1;
|
|
else
|
|
return 0;
|
|
}
|
|
|
|
bool VLSong::FindWord(size_t stanza, VLLocation & at)
|
|
{
|
|
at.fAt = at.fAt+VLFraction(1,64);
|
|
return PrevWord(stanza, at);
|
|
}
|
|
|
|
bool VLSong::PrevWord(size_t stanza, VLLocation & at)
|
|
{
|
|
do {
|
|
VLMeasure & meas = fMeasures[at.fMeasure];
|
|
VLNoteList::iterator note = fMeasures[at.fMeasure].fMelody.begin();
|
|
bool hasWord = false;
|
|
VLFraction word(0);
|
|
VLFraction now(0);
|
|
|
|
while (note != meas.fMelody.end() && now < at.fAt) {
|
|
if (!(note->fTied & VLNote::kTiedWithPrev)
|
|
&& note->fPitch != VLNote::kNoPitch
|
|
)
|
|
if (note->fLyrics.size() < stanza
|
|
|| !(note->fLyrics[stanza-1].fKind & VLSyllable::kHasPrev)
|
|
) {
|
|
word = now;
|
|
hasWord = true;
|
|
}
|
|
now += note->fDuration;
|
|
++note;
|
|
}
|
|
if (hasWord) {
|
|
at.fAt = word;
|
|
|
|
return true;
|
|
} else {
|
|
at.fAt = fProperties[meas.fPropIdx].fTime;
|
|
}
|
|
} while (at.fMeasure-- > 0);
|
|
|
|
at.fMeasure = 0;
|
|
|
|
return false;
|
|
}
|
|
|
|
bool VLSong::NextWord(size_t stanza, VLLocation & at)
|
|
{
|
|
bool firstMeasure = true;
|
|
do {
|
|
VLMeasure & meas = fMeasures[at.fMeasure];
|
|
VLNoteList::iterator note = fMeasures[at.fMeasure].fMelody.begin();
|
|
VLFraction now(0);
|
|
|
|
while (note != meas.fMelody.end()) {
|
|
if (!(note->fTied & VLNote::kTiedWithPrev)
|
|
&& note->fPitch != VLNote::kNoPitch
|
|
&& (!firstMeasure || now>at.fAt)
|
|
)
|
|
if (note->fLyrics.size() < stanza
|
|
|| !(note->fLyrics[stanza-1].fKind & VLSyllable::kHasPrev)
|
|
) {
|
|
at.fAt = now;
|
|
|
|
return true;
|
|
}
|
|
now += note->fDuration;
|
|
++note;
|
|
}
|
|
firstMeasure = false;
|
|
} while (++at.fMeasure < fMeasures.size());
|
|
|
|
return false;
|
|
}
|
|
|
|
std::string VLSong::GetWord(size_t stanza, VLLocation at)
|
|
{
|
|
std::string word;
|
|
|
|
while (at.fMeasure < fMeasures.size()) {
|
|
VLMeasure & meas = fMeasures[at.fMeasure];
|
|
VLNoteList::iterator note = meas.fMelody.begin();
|
|
VLNoteList::iterator end = meas.fMelody.end();
|
|
VLFraction now(0);
|
|
|
|
while (note != end) {
|
|
if (now >= at.fAt && note->fPitch != VLNote::kNoPitch
|
|
&& !(note->fTied & VLNote::kTiedWithPrev)
|
|
) {
|
|
if (word.size())
|
|
word += '-';
|
|
if (stanza <= note->fLyrics.size()) {
|
|
word += note->fLyrics[stanza-1].fText;
|
|
if (!(note->fLyrics[stanza-1].fKind & VLSyllable::kHasNext))
|
|
return word;
|
|
} else
|
|
return "";
|
|
}
|
|
now += note->fDuration;
|
|
++note;
|
|
}
|
|
at.fAt = VLFraction(0);
|
|
++at.fMeasure;
|
|
}
|
|
|
|
return word;
|
|
}
|
|
|
|
void VLSong::SetWord(size_t stanza, VLLocation at, std::string word, VLLocation * nextAt)
|
|
{
|
|
//
|
|
// Always keep an empty measure in reserve
|
|
//
|
|
while (at.fMeasure+1 >= fMeasures.size())
|
|
AddMeasure();
|
|
|
|
uint8_t kind = 0;
|
|
enum State {
|
|
kFindFirst,
|
|
kOverwrite,
|
|
kCleanup
|
|
} state = kFindFirst;
|
|
|
|
do {
|
|
VLMeasure & meas = fMeasures[at.fMeasure];
|
|
VLNoteList::iterator note = fMeasures[at.fMeasure].fMelody.begin();
|
|
VLFraction now(0);
|
|
|
|
while (note != meas.fMelody.end()) {
|
|
if (note->fPitch != VLNote::kNoPitch
|
|
&& !(note->fTied & VLNote::kTiedWithPrev)
|
|
)
|
|
switch (state) {
|
|
case kFindFirst:
|
|
if (now < at.fAt)
|
|
break; // Not yet there, skip this note
|
|
state = kOverwrite;
|
|
// Fall through
|
|
case kOverwrite: {
|
|
if (note->fLyrics.size()<stanza)
|
|
note->fLyrics.resize(stanza);
|
|
size_t sep = word.find_first_of(" \t-");
|
|
size_t esp;
|
|
std::string syll;
|
|
char type= 0;
|
|
if (sep == std::string::npos) {
|
|
esp = sep;
|
|
syll= word;
|
|
} else {
|
|
esp = word.find_first_not_of(" \t-", sep);
|
|
syll= word.substr(0, sep);
|
|
if (esp != std::string::npos) {
|
|
size_t tpos = word.substr(sep, esp-sep).find('-');
|
|
type = (tpos != std::string::npos) ? '-' : ' ';
|
|
word.erase(0, esp);
|
|
}
|
|
}
|
|
switch (type) {
|
|
default:
|
|
//
|
|
// Last syllable in text
|
|
//
|
|
kind &= ~VLSyllable::kHasNext;
|
|
state = kCleanup;
|
|
break;
|
|
case ' ':
|
|
//
|
|
// Last syllable in word
|
|
//
|
|
kind &= ~VLSyllable::kHasNext;
|
|
break;
|
|
case '-':
|
|
kind |= VLSyllable::kHasNext;
|
|
break;
|
|
}
|
|
note->fLyrics[stanza-1].fText = syll;
|
|
note->fLyrics[stanza-1].fKind = kind;
|
|
if (type == '-')
|
|
kind |= VLSyllable::kHasPrev;
|
|
else
|
|
kind &= ~VLSyllable::kHasPrev;
|
|
break; }
|
|
case kCleanup:
|
|
if (nextAt) {
|
|
nextAt->fMeasure = at.fMeasure;
|
|
nextAt->fAt = now;
|
|
nextAt = 0;
|
|
}
|
|
//
|
|
// Delete all subsequent syllables with kHasPrev set
|
|
//
|
|
if (note->fLyrics.size() >= stanza
|
|
&& (note->fLyrics[stanza-1].fKind & VLSyllable::kHasPrev)
|
|
) {
|
|
note->fLyrics[stanza-1].fText = "";
|
|
note->fLyrics[stanza-1].fKind = 0;
|
|
} else
|
|
return;
|
|
}
|
|
now += note->fDuration;
|
|
++note;
|
|
}
|
|
at.fAt = VLFraction(0);
|
|
} while (++at.fMeasure < fMeasures.size());
|
|
if (nextAt) {
|
|
nextAt->fMeasure = 0;
|
|
nextAt->fAt = VLFraction(0);
|
|
}
|
|
}
|
|
|
|
void VLSong::AddRepeat(size_t beginMeasure, size_t endMeasure, int times)
|
|
{
|
|
for (size_t r=0; r<fRepeats.size(); ++r) {
|
|
VLRepeat & rp = fRepeats[r];
|
|
if (rp.fEndings[0].fBegin == beginMeasure
|
|
&& rp.fEndings[0].fEnd >= endMeasure
|
|
) {
|
|
if (rp.fEndings[0].fEnd == endMeasure) {
|
|
//
|
|
// Exact match, just change times
|
|
//
|
|
size_t mask = ((1<<times)-1) ^ ((1<<rp.fTimes)-1);
|
|
if (rp.fTimes < times)
|
|
rp.fEndings[0].fVolta |= mask;
|
|
else if (rp.fTimes > times)
|
|
for (size_t e=0; e<rp.fEndings.size(); ++e)
|
|
rp.fEndings[e].fVolta &= ~mask;
|
|
rp.fTimes = times;
|
|
|
|
return;
|
|
} else {
|
|
fRepeats.erase(fRepeats.begin()+r);
|
|
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
VLRepeat rep;
|
|
|
|
rep.fTimes = times;
|
|
rep.fEndings.push_back(VLRepeat::Ending(beginMeasure, endMeasure,
|
|
(1<<times)-1));
|
|
fRepeats.push_back(rep);
|
|
}
|
|
|
|
void VLSong::DelRepeat(size_t beginMeasure, size_t endMeasure)
|
|
{
|
|
for (size_t r=0; r<fRepeats.size(); ++r) {
|
|
VLRepeat & rp = fRepeats[r];
|
|
if (rp.fEndings[0].fBegin == beginMeasure
|
|
&& rp.fEndings[0].fEnd >= endMeasure
|
|
) {
|
|
fRepeats.erase(fRepeats.begin()+r);
|
|
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
void VLSong::AddEnding(size_t beginMeasure, size_t endMeasure, size_t volta)
|
|
{
|
|
for (size_t r=0; r<fRepeats.size(); ++r) {
|
|
VLRepeat & rp = fRepeats[r];
|
|
if (rp.fEndings[0].fBegin < beginMeasure
|
|
&& rp.fEndings[0].fEnd >= beginMeasure
|
|
) {
|
|
for (size_t e=1; e<rp.fEndings.size(); ++e)
|
|
if (rp.fEndings[e].fBegin == beginMeasure
|
|
&& rp.fEndings[e].fEnd == endMeasure
|
|
) {
|
|
//
|
|
// Found it, just edit volta
|
|
//
|
|
rp.fEndings[0].fVolta |= rp.fEndings[e].fVolta;
|
|
volta &= rp.fEndings[0].fVolta;
|
|
rp.fEndings[0].fVolta &= ~volta;
|
|
rp.fEndings[e].fVolta = volta;
|
|
|
|
return;
|
|
}
|
|
//
|
|
// Not found, add new ending
|
|
//
|
|
volta &= rp.fEndings[0].fVolta;
|
|
rp.fEndings[0].fVolta &= ~volta;
|
|
rp.fEndings[0].fEnd =
|
|
std::max<int8_t>(rp.fEndings[0].fEnd, endMeasure);
|
|
rp.fEndings.push_back(
|
|
VLRepeat::Ending(beginMeasure, endMeasure, volta));
|
|
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
void VLSong::DelEnding(size_t beginMeasure, size_t endMeasure)
|
|
{
|
|
for (size_t r=0; r<fRepeats.size(); ++r) {
|
|
VLRepeat & rp = fRepeats[r];
|
|
if (rp.fEndings[0].fBegin <= beginMeasure
|
|
&& rp.fEndings[0].fEnd > beginMeasure
|
|
)
|
|
for (size_t e=1; e<rp.fEndings.size(); ++e)
|
|
if (rp.fEndings[e].fBegin == beginMeasure) {
|
|
rp.fEndings[0].fVolta |= rp.fEndings[e].fVolta;
|
|
if (e > 1 && e == rp.fEndings.size()-1)
|
|
rp.fEndings[0].fEnd = rp.fEndings[e].fBegin;
|
|
rp.fEndings.erase(rp.fEndings.begin()+e);
|
|
}
|
|
}
|
|
}
|
|
|
|
bool VLSong::CanBeRepeat(size_t beginMeasure, size_t endMeasure, int * times)
|
|
{
|
|
for (size_t r=0; r<fRepeats.size(); ++r) {
|
|
VLRepeat & rp = fRepeats[r];
|
|
if (rp.fEndings[0].fBegin == beginMeasure) {
|
|
//
|
|
// Look for exact match & return
|
|
//
|
|
if (times)
|
|
*times = rp.fTimes;
|
|
if (rp.fEndings[0].fEnd == endMeasure)
|
|
return true;
|
|
if (rp.fEndings.size() > 1) {
|
|
if (rp.fEndings[1].fBegin == endMeasure)
|
|
return true;
|
|
if (rp.fEndings[1].fEnd == endMeasure)
|
|
return true;
|
|
}
|
|
}
|
|
//
|
|
// Inclusions and surroundings are OK. Beginnings may match, but
|
|
// endings must not.
|
|
//
|
|
if (rp.fEndings[0].fBegin >= beginMeasure
|
|
&& rp.fEndings[0].fEnd < endMeasure
|
|
)
|
|
continue;
|
|
if (rp.fEndings[0].fBegin <= beginMeasure
|
|
&& rp.fEndings[0].fEnd > endMeasure
|
|
)
|
|
continue;
|
|
//
|
|
// Look for overlap and reject
|
|
//
|
|
if (rp.fEndings[0].fBegin >= beginMeasure
|
|
&& rp.fEndings[0].fBegin < endMeasure
|
|
)
|
|
return false;
|
|
if (rp.fEndings[0].fEnd > beginMeasure
|
|
&& rp.fEndings[0].fEnd <= endMeasure
|
|
)
|
|
return false;
|
|
}
|
|
//
|
|
// Virgin territory, accept
|
|
//
|
|
if (times)
|
|
*times = 2;
|
|
return true;
|
|
}
|
|
|
|
bool VLSong::CanBeEnding(size_t beginMeasure, size_t endMeasure,
|
|
size_t * volta, size_t * voltaOK)
|
|
{
|
|
for (size_t r=0; r<fRepeats.size(); ++r) {
|
|
VLRepeat & rp = fRepeats[r];
|
|
if (beginMeasure > rp.fEndings[0].fBegin
|
|
&& beginMeasure <= rp.fEndings[0].fEnd
|
|
) {
|
|
//
|
|
// Found right repeat
|
|
//
|
|
//
|
|
// Append new repeat, or carve out from ending
|
|
//
|
|
if (beginMeasure == rp.fEndings[0].fEnd) {
|
|
for (size_t r2=0; r2<fRepeats.size(); ++r2)
|
|
if (r2 != r
|
|
&& fRepeats[r2].fEndings[0].fBegin >= beginMeasure
|
|
&& fRepeats[r2].fEndings[0].fBegin < endMeasure
|
|
)
|
|
return false; // Overlap
|
|
if (volta)
|
|
*volta = rp.fEndings[0].fVolta;
|
|
if (voltaOK)
|
|
*voltaOK = rp.fEndings[0].fVolta;
|
|
|
|
return true;
|
|
} else if (rp.fEndings.size() == 1
|
|
&& endMeasure >= rp.fEndings[0].fEnd
|
|
) {
|
|
if (volta)
|
|
*volta = rp.fEndings[0].fVolta;
|
|
if (voltaOK)
|
|
*voltaOK = rp.fEndings[0].fVolta;
|
|
|
|
return true;
|
|
}
|
|
//
|
|
// Otherwise must match existing
|
|
//
|
|
for (size_t e=1; e<rp.fEndings.size(); ++e)
|
|
if (beginMeasure == rp.fEndings[e].fBegin
|
|
&& endMeasure == rp.fEndings[e].fEnd
|
|
) {
|
|
if (volta)
|
|
*volta = rp.fEndings[e].fVolta;
|
|
if (voltaOK)
|
|
*voltaOK = rp.fEndings[e].fVolta
|
|
| rp.fEndings[0].fVolta;
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool VLSong::DoesBeginRepeat(size_t measure, int * times) const
|
|
{
|
|
for (size_t r=0; r<fRepeats.size(); ++r) {
|
|
const VLRepeat & rp = fRepeats[r];
|
|
if (rp.fEndings[0].fBegin == measure) {
|
|
if (times)
|
|
*times = rp.fTimes;
|
|
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool VLSong::DoesEndRepeat(size_t measure, int * times) const
|
|
{
|
|
for (size_t r=0; r<fRepeats.size(); ++r) {
|
|
const VLRepeat & rp = fRepeats[r];
|
|
if (rp.fEndings[0].fEnd == measure
|
|
&& rp.fEndings.size() == 1
|
|
) {
|
|
if (times)
|
|
*times = rp.fTimes;
|
|
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool VLSong::DoesBeginEnding(size_t measure, bool * repeat, size_t * volta) const
|
|
{
|
|
for (size_t r=0; r<fRepeats.size(); ++r) {
|
|
const VLRepeat & rp = fRepeats[r];
|
|
if (rp.fEndings[0].fBegin < measure
|
|
&& rp.fEndings[0].fEnd >= measure
|
|
&& rp.fEndings.size() > 1
|
|
) {
|
|
size_t v = (1<<rp.fTimes)-1;
|
|
for (size_t e=1; e<rp.fEndings.size(); ++e)
|
|
if (rp.fEndings[e].fBegin == measure) {
|
|
if (repeat) {
|
|
if (e == rp.fEndings.size()-1
|
|
&& rp.fEndings[e].fVolta == v
|
|
) {
|
|
*repeat = false; // Not after last alternative
|
|
} else {
|
|
*repeat = true;
|
|
}
|
|
}
|
|
if (volta)
|
|
*volta = rp.fEndings[e].fVolta;
|
|
|
|
return true;
|
|
} else
|
|
v &= ~rp.fEndings[e].fVolta;
|
|
if (v && rp.fEndings[0].fEnd == measure) {
|
|
//
|
|
// Implied ending for all not mentioned
|
|
//
|
|
if (repeat)
|
|
*repeat = false;
|
|
if (volta)
|
|
*volta = v;
|
|
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool VLSong::DoesEndEnding(size_t measure, bool * repeat, size_t * volta) const
|
|
{
|
|
for (size_t r=0; r<fRepeats.size(); ++r) {
|
|
const VLRepeat & rp = fRepeats[r];
|
|
if (rp.fEndings[0].fBegin < measure
|
|
&& rp.fEndings[0].fEnd+1 >= measure
|
|
&& rp.fEndings.size() > 1
|
|
) {
|
|
size_t v = (1<<rp.fTimes)-1;
|
|
for (size_t e=1; e<rp.fEndings.size(); ++e)
|
|
if (rp.fEndings[e].fEnd == measure) {
|
|
if (repeat)
|
|
if (e == rp.fEndings.size()-1
|
|
&& rp.fEndings[e].fVolta == v
|
|
)
|
|
*repeat = false; // Not after last alternative
|
|
else
|
|
*repeat = true;
|
|
if (volta)
|
|
*volta = rp.fEndings[e].fVolta;
|
|
return true;
|
|
} else
|
|
v &= ~rp.fEndings[e].fVolta;
|
|
if (v && rp.fEndings[0].fEnd+1 == measure) {
|
|
//
|
|
// Implied ending for all not mentioned
|
|
//
|
|
if (repeat)
|
|
*repeat = false;
|
|
if (volta)
|
|
*volta = v;
|
|
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool VLSong::DoesTieWithPrevRepeat(size_t measure) const
|
|
{
|
|
if (fMeasures[measure].fMelody.front().fPitch == VLNote::kNoPitch)
|
|
return false; // Rests don't tie
|
|
for (size_t r=0; r<fRepeats.size(); ++r) {
|
|
const VLRepeat & rp = fRepeats[r];
|
|
if (rp.fEndings[0].fBegin < measure
|
|
&& rp.fEndings[0].fEnd >= measure
|
|
&& rp.fEndings.size() > 1
|
|
) {
|
|
size_t v = (1<<rp.fTimes)-1;
|
|
int8_t firstEnding = rp.fEndings[0].fEnd;
|
|
bool doesStartEnding = false;
|
|
for (size_t e=1; e<rp.fEndings.size(); ++e) {
|
|
firstEnding = std::min(firstEnding, rp.fEndings[e].fBegin);
|
|
if (rp.fEndings[e].fBegin == measure)
|
|
doesStartEnding = true;
|
|
else
|
|
v &= ~rp.fEndings[e].fVolta;
|
|
}
|
|
if (doesStartEnding || (v && rp.fEndings[0].fEnd == measure))
|
|
return fMeasures[firstEnding-1].fMelody.back().fTied
|
|
& VLNote::kTiedWithNext;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool VLSong::DoesTieWithNextRepeat(size_t measure) const
|
|
{
|
|
if (fMeasures[measure].fMelody.back().fPitch == VLNote::kNoPitch)
|
|
return false; // Rests don't tie
|
|
for (size_t r=0; r<fRepeats.size(); ++r) {
|
|
const VLRepeat & rp = fRepeats[r];
|
|
if (rp.fEndings[0].fBegin < measure
|
|
&& rp.fEndings[0].fEnd >= measure
|
|
&& rp.fEndings.size() > 1
|
|
) {
|
|
for (size_t e=1; e<rp.fEndings.size(); ++e) {
|
|
if (rp.fEndings[e].fEnd == measure+1)
|
|
return !(rp.fEndings[e].fVolta & (1<<(rp.fTimes-1)))
|
|
&& (fMeasures[rp.fEndings[0].fBegin].fMelody.front().fTied
|
|
& VLNote::kTiedWithPrev);
|
|
}
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
VLSong::iterator::iterator(const VLSong & song, bool end)
|
|
: fSong(song)
|
|
{
|
|
if (end) {
|
|
fMeasure = fSong.CountMeasures()-fSong.EmptyEnding();
|
|
} else {
|
|
bool repeat;
|
|
fMeasure = 0;
|
|
if (fSong.fCoda > 0
|
|
&& !fSong.DoesEndRepeat(fSong.fCoda)
|
|
&& !fSong.DoesEndEnding(fSong.fCoda, &repeat)
|
|
)
|
|
fStatus.push_back(Repeat(fMeasure, 2));
|
|
AdjustStatus();
|
|
}
|
|
}
|
|
|
|
VLSong::iterator & VLSong::iterator::operator++()
|
|
{
|
|
++fMeasure;
|
|
AdjustStatus();
|
|
|
|
return *this;
|
|
}
|
|
|
|
void VLSong::iterator::AdjustStatus()
|
|
{
|
|
int times;
|
|
size_t volta;
|
|
bool repeat = true;
|
|
if (fSong.DoesEndRepeat(fMeasure)
|
|
|| (fSong.DoesEndEnding(fMeasure, &repeat) && repeat)
|
|
) {
|
|
if (++fStatus.back().fVolta < fStatus.back().fTimes) {
|
|
//
|
|
// Repeat again
|
|
//
|
|
fMeasure = fStatus.back().fBegin;
|
|
|
|
return;
|
|
}
|
|
} else if (!repeat) {
|
|
fStatus.pop_back();
|
|
}
|
|
if (fSong.fCoda > 0 && fMeasure==fSong.fGoToCoda) {
|
|
for (size_t repeat = 0; repeat < fStatus.size(); ++repeat)
|
|
if (fStatus[repeat].fVolta != fStatus[repeat].fTimes-1)
|
|
goto notAtCoda;
|
|
fStatus.clear();
|
|
fMeasure = fSong.fCoda;
|
|
|
|
return;
|
|
notAtCoda:
|
|
;
|
|
}
|
|
if (fMeasure == fSong.CountMeasures()-fSong.EmptyEnding()
|
|
|| (fSong.fCoda > 0 && fMeasure == fSong.fCoda)
|
|
)
|
|
while (fStatus.size())
|
|
if (++fStatus.back().fVolta < fStatus.back().fTimes) {
|
|
fMeasure = fStatus.back().fBegin;
|
|
|
|
return;
|
|
} else
|
|
fStatus.pop_back();
|
|
while (fSong.DoesBeginEnding(fMeasure, 0, &volta)) {
|
|
if (!(volta & (1<<fStatus.back().fVolta))) {
|
|
//
|
|
// Skip this ending this time around
|
|
//
|
|
do {
|
|
++fMeasure;
|
|
} while (!fSong.DoesEndEnding(fMeasure));
|
|
} else
|
|
break;
|
|
}
|
|
if (fSong.DoesBeginRepeat(fMeasure, ×)) {
|
|
if (fStatus.size() && fStatus.back().fVolta == fStatus.back().fTimes)
|
|
fStatus.pop_back();
|
|
fStatus.push_back(Repeat(fMeasure, times));
|
|
}
|
|
}
|
|
|
|
VLSong VLSong::CopyMeasures(size_t beginMeasure, size_t endMeasure)
|
|
{
|
|
VLSong subSong(false);
|
|
|
|
int8_t firstProp = fMeasures[beginMeasure].fPropIdx;
|
|
int8_t lastProp = fMeasures[endMeasure-1].fPropIdx;
|
|
|
|
subSong.fProperties.insert(subSong.fProperties.end(),
|
|
fProperties.begin()+firstProp,
|
|
fProperties.begin()+lastProp+1);
|
|
subSong.fMeasures.insert(subSong.fMeasures.end(),
|
|
fMeasures.begin()+beginMeasure,
|
|
fMeasures.begin()+endMeasure);
|
|
|
|
if (firstProp)
|
|
for (size_t i=0; i<subSong.fMeasures.size(); ++i)
|
|
subSong.fMeasures[i].fPropIdx -= firstProp;
|
|
|
|
for (size_t r=0; r<fRepeats.size(); ++r) {
|
|
VLRepeat rp = fRepeats[r];
|
|
if (rp.fEndings[0].fBegin >= beginMeasure
|
|
&& rp.fEndings[0].fEnd <= endMeasure
|
|
) {
|
|
for (size_t e=0; e<rp.fEndings.size(); ++e) {
|
|
rp.fEndings[e].fBegin -= beginMeasure;
|
|
rp.fEndings[e].fEnd -= beginMeasure;
|
|
}
|
|
subSong.fRepeats.push_back(rp);
|
|
}
|
|
}
|
|
|
|
return subSong;
|
|
}
|
|
|
|
void VLSong::PasteMeasures(size_t beginMeasure, const VLSong & measures, int mode)
|
|
{
|
|
size_t numMeas = measures.CountMeasures();
|
|
size_t nextMeasure = mode==kInsert ? beginMeasure : beginMeasure+numMeas;
|
|
|
|
if (mode == kInsert) {
|
|
int propAt = fMeasures[beginMeasure].fPropIdx;
|
|
int propOffset = 0;
|
|
bool sectionBreak= false;
|
|
VLPropertyList::const_iterator beginProp = measures.fProperties.begin();
|
|
VLPropertyList::const_iterator endProp = measures.fProperties.end();
|
|
|
|
if (beginMeasure) {
|
|
propOffset = fMeasures[beginMeasure-1].fPropIdx;
|
|
sectionBreak= propAt > propOffset;
|
|
if (fProperties[propOffset] == beginProp[0])
|
|
++beginProp;
|
|
else
|
|
++propOffset;
|
|
}
|
|
if (endProp > beginProp && fProperties[propAt] == endProp[-1]
|
|
&& (sectionBreak || (endProp-beginProp) == 1)
|
|
)
|
|
--endProp;
|
|
ptrdiff_t postOffset = endProp - beginProp;
|
|
|
|
fProperties.insert(fProperties.begin()+propAt+!sectionBreak, beginProp, endProp);
|
|
fMeasures.insert(fMeasures.begin()+beginMeasure,
|
|
measures.fMeasures.begin(), measures.fMeasures.end());
|
|
if (propOffset)
|
|
for (size_t meas=beginMeasure; meas<beginMeasure+numMeas; ++meas)
|
|
fMeasures[meas].fPropIdx += propOffset;
|
|
if (postOffset)
|
|
for (size_t meas=beginMeasure+numMeas; meas<fMeasures.size(); ++meas)
|
|
fMeasures[meas].fPropIdx += postOffset;
|
|
|
|
for (size_t r=0; r<fRepeats.size(); ++r) {
|
|
VLRepeat & repeat = fRepeats[r];
|
|
for (size_t e=0; e<repeat.fEndings.size(); ++e) {
|
|
if (repeat.fEndings[e].fBegin >= beginMeasure)
|
|
repeat.fEndings[e].fBegin += numMeas;
|
|
if (repeat.fEndings[e].fEnd >= beginMeasure)
|
|
repeat.fEndings[e].fEnd += numMeas;
|
|
}
|
|
}
|
|
for (size_t r=0; r<measures.fRepeats.size(); ++r) {
|
|
VLRepeat repeat = measures.fRepeats[r];
|
|
for (size_t e=0; e<repeat.fEndings.size(); ++e) {
|
|
repeat.fEndings[e].fBegin += beginMeasure;
|
|
repeat.fEndings[e].fEnd += beginMeasure;
|
|
}
|
|
fRepeats.push_back(repeat);
|
|
}
|
|
if (fGoToCoda >= (int)beginMeasure)
|
|
fGoToCoda += numMeas;
|
|
if (fCoda >= (int)beginMeasure)
|
|
fCoda += numMeas;
|
|
} else {
|
|
if (CountMeasures() < nextMeasure) {
|
|
VLMeasure rest;
|
|
rest.fPropIdx = fMeasures.back().fPropIdx;
|
|
VLFraction dur = fProperties[rest.fPropIdx].fTime;
|
|
rest.fMelody.push_back(VLLyricsNote(dur));
|
|
rest.fChords.push_back(VLChord(dur));
|
|
|
|
fMeasures.insert(fMeasures.end(), nextMeasure-CountMeasures(), rest);
|
|
}
|
|
for (size_t m=0; m<numMeas; ++m) {
|
|
const VLMeasure & srcMeas = measures.fMeasures[m];
|
|
VLMeasure & dstMeas = fMeasures[beginMeasure+m];
|
|
if (mode & kOverwriteChords)
|
|
dstMeas.fChords = Realign(srcMeas.fChords,
|
|
measures.fProperties[srcMeas.fPropIdx],
|
|
fProperties[dstMeas.fPropIdx]);
|
|
if (mode & kOverwriteMelody)
|
|
dstMeas.fMelody = Realign(srcMeas.fMelody,
|
|
measures.fProperties[srcMeas.fPropIdx],
|
|
fProperties[dstMeas.fPropIdx]);
|
|
}
|
|
}
|
|
}
|
|
|
|
void VLSong::DeleteMeasures(size_t beginMeasure, size_t endMeasure, int mode)
|
|
{
|
|
if (beginMeasure == endMeasure)
|
|
return;
|
|
|
|
if (mode == kOverwriteMelody) {
|
|
for (size_t m=beginMeasure; m<endMeasure; ++m) {
|
|
VLLyricsNote rest(Properties(m).fTime);
|
|
fMeasures[m].fMelody.clear();
|
|
fMeasures[m].fMelody.push_back(rest);
|
|
}
|
|
return;
|
|
}
|
|
int8_t firstProp = fMeasures[beginMeasure].fPropIdx;
|
|
int8_t lastProp = fMeasures[endMeasure-1].fPropIdx+1;
|
|
|
|
if (beginMeasure && fMeasures[beginMeasure-1].fPropIdx == firstProp)
|
|
++firstProp;
|
|
if (endMeasure < CountMeasures() && fMeasures[endMeasure].fPropIdx == lastProp-1)
|
|
--lastProp;
|
|
if (lastProp - firstProp == fProperties.size())
|
|
++firstProp;
|
|
if (lastProp > firstProp) {
|
|
fProperties.erase(fProperties.begin()+firstProp,
|
|
fProperties.begin()+lastProp);
|
|
for (size_t m=endMeasure; m<CountMeasures(); ++m)
|
|
fMeasures[m].fPropIdx -= lastProp-firstProp;
|
|
}
|
|
fMeasures.erase(fMeasures.begin()+beginMeasure,
|
|
fMeasures.begin()+endMeasure);
|
|
|
|
size_t delta = endMeasure-beginMeasure;
|
|
for (size_t r=0; r<fRepeats.size(); ) {
|
|
VLRepeat & repeat = fRepeats[r];
|
|
if (repeat.fEndings[0].fBegin >= beginMeasure
|
|
&& repeat.fEndings[0].fEnd <= endMeasure
|
|
) {
|
|
fRepeats.erase(fRepeats.begin()+r);
|
|
} else {
|
|
for (size_t e=0; e<repeat.fEndings.size(); ) {
|
|
if (repeat.fEndings[e].fBegin > beginMeasure)
|
|
repeat.fEndings[e].fBegin =
|
|
std::max(beginMeasure, repeat.fEndings[e].fBegin-delta);
|
|
if (repeat.fEndings[e].fEnd > beginMeasure)
|
|
repeat.fEndings[e].fEnd =
|
|
std::max(beginMeasure, repeat.fEndings[e].fEnd-delta);
|
|
if (e && repeat.fEndings[e].fBegin==repeat.fEndings[e].fEnd)
|
|
repeat.fEndings.erase(repeat.fEndings.begin()+e);
|
|
else
|
|
++e;
|
|
}
|
|
++r;
|
|
}
|
|
}
|
|
if (fGoToCoda >= (int)beginMeasure)
|
|
if (fGoToCoda < endMeasure)
|
|
fGoToCoda = -1;
|
|
else
|
|
fGoToCoda -= delta;
|
|
if (fCoda >= (int)beginMeasure)
|
|
if (fCoda < endMeasure)
|
|
fCoda = -1;
|
|
else
|
|
fCoda -= delta;
|
|
|
|
//
|
|
// Keep an empty measure at the end
|
|
//
|
|
if (!EmptyEnding())
|
|
AddMeasure();
|
|
}
|
|
|
|
VLFract VLSong::TiedDuration(size_t measure)
|
|
{
|
|
VLFraction total(0);
|
|
|
|
while (measure < fMeasures.size()) {
|
|
VLNote n = fMeasures[measure++].fMelody.front();
|
|
|
|
if (!(n.fTied & VLNote::kTiedWithPrev))
|
|
break;
|
|
total += n.fDuration;
|
|
if (!(n.fTied & VLNote::kTiedWithNext))
|
|
break;
|
|
}
|
|
return total;
|
|
}
|
|
|
|
bool VLSong::DoesBeginSection(size_t measure) const
|
|
{
|
|
return measure && measure < fMeasures.size()
|
|
&& fMeasures[measure-1].fPropIdx!=fMeasures[measure].fPropIdx;
|
|
}
|
|
|
|
void VLSong::AddSection(size_t measure)
|
|
{
|
|
int splitIdx = fMeasures[measure].fPropIdx;
|
|
|
|
VLProperties newProp = fProperties[splitIdx];
|
|
if (splitIdx < fProperties.size()-1)
|
|
fProperties.insert(fProperties.begin()+splitIdx, newProp);
|
|
else
|
|
fProperties.push_back(newProp);
|
|
while (measure < fMeasures.size())
|
|
++fMeasures[measure++].fPropIdx;
|
|
}
|
|
|
|
void VLSong::DelSection(size_t measure)
|
|
{
|
|
int delIdx = fMeasures[measure].fPropIdx;
|
|
|
|
fProperties.erase(fProperties.begin()+delIdx);
|
|
while (measure < fMeasures.size())
|
|
--fMeasures[measure++].fPropIdx;
|
|
}
|
|
|
|
std::string VLSong::PrimaryGroove() const
|
|
{
|
|
std::string bestGroove = fProperties[0].fGroove;
|
|
|
|
for (size_t p=1; p<fProperties.size(); ++p)
|
|
if (fProperties[p].fGroove != bestGroove) {
|
|
//
|
|
// Multiple grooves in song, count them the hard way
|
|
//
|
|
std::vector<size_t> numMeas(fProperties.size());
|
|
for (size_t m=0; m<fMeasures.size(); ++m)
|
|
++numMeas[fMeasures[m].fPropIdx];
|
|
|
|
size_t bestCount = numMeas[0];
|
|
for (size_t px=1; px<fProperties.size(); ++px)
|
|
if (fProperties[px].fGroove == bestGroove) {
|
|
bestCount += numMeas[px];
|
|
numMeas[px]= 0;
|
|
}
|
|
|
|
for (; p<fProperties.size(); ++p)
|
|
if (numMeas[p]) {
|
|
std::string curGroove= fProperties[p].fGroove;
|
|
size_t curCount = numMeas[p];
|
|
for (size_t px=p+1; px<fProperties.size(); ++px)
|
|
if (fProperties[px].fGroove == curGroove) {
|
|
curCount += numMeas[px];
|
|
numMeas[px]= 0;
|
|
}
|
|
if (curCount > bestCount) {
|
|
bestGroove= curGroove;
|
|
bestCount = curCount;
|
|
}
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
return bestGroove;
|
|
}
|
|
|
|
#pragma mark -
|
|
#pragma mark class VLSongVisitor
|
|
|
|
//////////////////////// VLSongVisitor ////////////////////////////////
|
|
|
|
VLSongVisitor::~VLSongVisitor()
|
|
{
|
|
}
|
|
|
|
void VLSongVisitor::VisitMeasures(VLSong & song, bool performanceOrder)
|
|
{
|
|
fSong = &song;
|
|
if (performanceOrder) {
|
|
VLSong::iterator e = song.end();
|
|
|
|
for (VLSong::iterator m=song.begin(); m!=e; ++m) {
|
|
VLMeasure & meas = song.fMeasures[*m];
|
|
VLProperties& prop = song.fProperties[meas.fPropIdx];
|
|
VisitMeasure(*m, prop, meas);
|
|
}
|
|
} else {
|
|
size_t e = song.CountMeasures() - song.EmptyEnding();
|
|
if (!e && song.CountMeasures())
|
|
e = 1;
|
|
|
|
for (size_t m=0; m!=e; ++m) {
|
|
VLMeasure & meas = song.fMeasures[m];
|
|
VLProperties& prop = song.fProperties[meas.fPropIdx];
|
|
VisitMeasure(m, prop, meas);
|
|
}
|
|
}
|
|
}
|
|
|
|
void VLSongVisitor::VisitNotes(VLMeasure & measure, const VLProperties & prop,
|
|
bool decomposed)
|
|
{
|
|
VLNoteList decomp;
|
|
VLNoteList::iterator n;
|
|
VLNoteList::iterator e;
|
|
|
|
if (decomposed) {
|
|
measure.DecomposeNotes(prop, decomp);
|
|
// Set slur flags
|
|
bool inSlur = false;
|
|
bool inTie = false;
|
|
uint8_t prevPitch = 0;
|
|
if ((decomp.front().fTied & VLNote::kTiedWithPrev)) {
|
|
inTie = true;
|
|
prevPitch = decomp.front().fPitch;
|
|
auto predecessor = fSong->begin_note(&measure-&fSong->fMeasures[0]);
|
|
bool seenNote = false;
|
|
do {
|
|
--predecessor;
|
|
if (predecessor->fPitch != decomp.front().fPitch) {
|
|
inSlur = true;
|
|
if (!seenNote) {
|
|
decomp.front().fTied |= VLNote::kSlurWithPrev;
|
|
}
|
|
break;
|
|
}
|
|
seenNote = true;
|
|
} while (predecessor->fTied & VLNote::kTiedWithPrev);
|
|
}
|
|
VLLyricsNote *firstInTie = nullptr;
|
|
VLLyricsNote *prevInTie = nullptr;
|
|
for (auto ¬e: decomp) {
|
|
if ((note.fTied & VLNote::kTiedWithPrev) && note.fPitch != prevPitch) {
|
|
if (prevInTie) {
|
|
prevInTie->fTied |= VLNote::kSlurWithNext;
|
|
}
|
|
note.fTied |= VLNote::kSlurWithPrev;
|
|
inSlur = true;
|
|
}
|
|
if (note.fTied & VLNote::kTiedWithNext) {
|
|
if (!inTie) {
|
|
firstInTie = ¬e;
|
|
inTie = true;
|
|
}
|
|
prevInTie = ¬e;
|
|
prevPitch = note.fPitch;
|
|
} else {
|
|
if (inSlur) {
|
|
if (firstInTie) {
|
|
firstInTie->fTied |= VLNote::kStartSlur;
|
|
}
|
|
note.fTied |= VLNote::kEndSlur;
|
|
}
|
|
inSlur = false;
|
|
inTie = false;
|
|
}
|
|
}
|
|
if (inTie) {
|
|
// Find out if measure-final tie contains slur
|
|
auto successor = fSong->begin_note(&measure-&fSong->fMeasures[0]+1);
|
|
if (successor->fPitch != prevPitch) {
|
|
prevInTie->fTied |= VLNote::kSlurWithNext;
|
|
}
|
|
while (!inSlur) {
|
|
if (successor->fPitch != prevPitch) {
|
|
inSlur = true;
|
|
} else if (!(successor->fTied & VLNote::kTiedWithNext)) {
|
|
break;
|
|
} else {
|
|
++successor;
|
|
}
|
|
}
|
|
}
|
|
if (inSlur && firstInTie) {
|
|
firstInTie->fTied |= VLNote::kStartSlur;
|
|
}
|
|
|
|
n = decomp.begin();
|
|
e = decomp.end();
|
|
} else {
|
|
n = measure.fMelody.begin();
|
|
e = measure.fMelody.end();
|
|
}
|
|
|
|
for (; n!=e; ++n)
|
|
VisitNote(*n);
|
|
}
|
|
|
|
void VLSongVisitor::VisitChords(VLMeasure & measure)
|
|
{
|
|
VLChordList::iterator c = measure.fChords.begin();
|
|
VLChordList::iterator e = measure.fChords.end();
|
|
|
|
for (; c!=e; ++c)
|
|
VisitChord(*c);
|
|
}
|
|
|