// // 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= 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; measurefPitch != VLNote::kNoPitch) return true; } for (size_t measure=0; measurefPitch != 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; measurefPitch, 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; measurefPitch == 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; measurefPitch == 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 6/8 return; // Trivial or no change } VLProperties newProp = prop; newProp.fTime = newTime; for (size_t measure=0; measurefLyrics.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; measurefPitch != 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; measurefPitch != 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()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= endMeasure ) { if (rp.fEndings[0].fEnd == endMeasure) { // // Exact match, just change times // size_t mask = ((1< times) for (size_t e=0; e= 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= beginMeasure ) { for (size_t e=1; e(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 beginMeasure ) for (size_t e=1; 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 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 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= 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= measure && rp.fEndings.size() > 1 ) { size_t v = (1<= measure && rp.fEndings.size() > 1 ) { size_t v = (1<= measure && rp.fEndings.size() > 1 ) { size_t v = (1<= measure && rp.fEndings.size() > 1 ) { for (size_t e=1; e 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<= beginMeasure && rp.fEndings[0].fEnd <= endMeasure ) { for (size_t e=0; e 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) repeat.fEndings[e].fBegin += numMeas; if (repeat.fEndings[e].fEnd >= beginMeasure) repeat.fEndings[e].fEnd += numMeas; } } for (size_t r=0; r= (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 firstProp) { fProperties.erase(fProperties.begin()+firstProp, fProperties.begin()+lastProp); for (size_t m=endMeasure; m= beginMeasure && repeat.fEndings[0].fEnd <= endMeasure ) { fRepeats.erase(fRepeats.begin()+r); } else { for (size_t e=0; e 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 numMeas(fProperties.size()); for (size_t m=0; m 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); }