From a7cf4a28df6aff30c6f7f100276ee5534b94e4f5 Mon Sep 17 00:00:00 2001 From: Matthias Neeracher Date: Mon, 21 May 2007 08:18:58 +0000 Subject: [PATCH] Implement new duration visual model --- Sources/VLModel.cpp | 210 ++++++++++++++++++++++++++++++++++-- Sources/VLModel.h | 37 ++++--- Sources/VLSheetViewNotes.mm | 121 +++++++++------------ Tests/TVLEdit.cpp | 60 +++++------ 4 files changed, 302 insertions(+), 126 deletions(-) diff --git a/Sources/VLModel.cpp b/Sources/VLModel.cpp index 352cacf..0ffb5d8 100644 --- a/Sources/VLModel.cpp +++ b/Sources/VLModel.cpp @@ -154,6 +154,11 @@ VLNote::VLNote(std::string name) fPitch = kNoPitch; // Failed to parse completely } +VLNote::VLNote(VLFraction dur, int pitch) + : fDuration(dur), fPitch(pitch), fTied(0), fVisual(0) +{ +} + void VLNote::Name(std::string & name, bool useSharps) const { name = PitchName(fPitch, useSharps); @@ -274,6 +279,53 @@ void VLNote::MMAName(std::string & name, VLFraction at, VLFraction dur, VLFracti name += '~'; } +void VLNote::MakeRepresentable() +{ + if (fDuration > 1) + fDuration = 1; + fVisual = kWhole; + VLFraction part(1,1); + VLFraction triplet(2,3); + // + // Power of 2 denominators are not triplets + // + bool nonTriplet(!(fDuration.fDenom & (fDuration.fDenom-1))); + while (part.fDenom < 64) { + if (fDuration >= part) { + fDuration = part; + return; + } else if (!nonTriplet && fDuration >= triplet) { + 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) + : VLNote(dur, pitch) +{ +} + struct VLChordModifier { const char * fName; uint32_t fAddSteps; @@ -313,6 +365,11 @@ static const VLChordModifier kModifiers[] = { {NULL, 0, 0} }; +VLChord::VLChord(VLFraction dur, int pitch, int rootPitch) + : VLNote(dur, pitch), fSteps(0), fRootPitch(kNoPitch) +{ +} + VLChord::VLChord(std::string name) { size_t root; @@ -846,6 +903,141 @@ bool VLMeasure::NoChords() const && fChords.front().fPitch == VLNote::kNoPitch; } +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); + + 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; + + VLLyricsNote c = *i; // Current note, remaining duration + VLLyricsNote p = c; // Next partial note + do { + // + // Start with longest possible note + // + p.MakeRepresentable(); + // + // Prefer further triplets + // + if (prevTriplets) { + 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 > 4) { + // + // Break up notes not starting on quarter beat + // - Never cross middle of measure + // + 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 + } + if (p.fVisual & VLNote::kTriplet) { + // + // Distinguish swing 8ths/16ths from triplets + // + VLFraction swung(1, prop.fDivisions < 6 ? 8 : 16); + VLFraction grid(2*swung); + if (p.fDuration == 4*swung/3 && (at % grid == 0)) { + if (p.fDuration == c.fDuration && n!=e + && (n->fDuration == p.fDuration + || (n->fDuration == 2*p.fDuration)) + ) { + ; // Triplet, not swing note + } else { + // + // First swing note (4th triplet -> 8th) + // + p.fVisual = (p.fVisual+1) & VLNote::kNoteHead; + } + } else if (p.fDuration == 2*swung/3 + && ((at+p.fDuration) % grid == 0) + ) { + // + // Second swing note (8th triplet -> 8th) + // + p.fVisual &= VLNote::kNoteHead; + } else if (p.fDuration != c.fDuration + && 2*p.fDuration != c.fDuration + ) { + // + // Get rid of awkward triplets + // + p.fDuration *= VLFraction(3,4); + p.fVisual = (p.fVisual+1) & VLNote::kNoteHead; + } + } + haveDuration: + if (p.fVisual & 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 > 0); + i = n; + } +} + VLSong::VLSong(bool initialize) { if (!initialize) @@ -865,9 +1057,8 @@ VLSong::VLSong(bool initialize) void VLSong::AddMeasure() { VLFraction dur = fProperties.front().fTime; - VLLyricsNote rest = VLLyricsNote(VLRest(dur)); - VLChord rchord; - rchord.fDuration = dur; + VLLyricsNote rest(dur); + VLChord rchord(dur); VLMeasure meas; meas.fChords.push_back(rchord); @@ -1357,9 +1548,8 @@ void VLSong::ChangeTime(VLFraction newTime) VLProperties & prop = fProperties.front(); if (prop.fTime == newTime) return; // No change - VLChord rchord; - rchord.fDuration = newTime-prop.fTime; - VLLyricsNote rnote = VLLyricsNote(VLRest(newTime-prop.fTime)); + VLChord rchord(newTime-prop.fTime); + VLLyricsNote rnote(newTime-prop.fTime); for (size_t measure=0; measure(this) = note; } + VLLyricsNote(const VLNote & note); + VLLyricsNote(VLFraction dur=0, int pitch = kNoPitch); std::vector fLyrics; }; @@ -250,6 +257,8 @@ struct VLMeasure { bool IsEmpty() const; bool NoChords() const; + + void DecomposeNotes(const VLProperties & prop, VLNoteList & decomposed) const; }; struct VLRepeat { diff --git a/Sources/VLSheetViewNotes.mm b/Sources/VLSheetViewNotes.mm index b8c143b..53f1e0a 100644 --- a/Sources/VLSheetViewNotes.mm +++ b/Sources/VLSheetViewNotes.mm @@ -134,7 +134,7 @@ } } -- (void) drawNote:(VLFraction)dur at:(NSPoint)p +- (void) drawNote:(int)visual at:(NSPoint)p accidental:(VLMusicElement)accidental tied:(BOOL)tied { NSPoint s = p; @@ -147,11 +147,11 @@ // Draw note head // NSImage * head; - switch (dur.fDenom) { - case 1: + switch (visual & VLNote::kNoteHead) { + case VLNote::kWhole: head = [self musicElement:kMusicWholeNote]; break; - case 2: + case VLNote::kHalf: head = [self musicElement:kMusicHalfNote]; s.x -= 1.0f; break; @@ -190,19 +190,19 @@ // // // - if (dur.fDenom > 1) { + if (visual > 0) { NSBezierPath * bz = [NSBezierPath bezierPath]; NSPoint s1 = NSMakePoint(s.x, s.y+kStemH); NSImage * flag = nil; - switch (dur.fDenom) { - case 8: + switch (visual) { + case VLNote::kEighth: flag = [self musicElement:kMusicEighthFlag]; break; - case 16: + case VLNote::k16th: flag = [self musicElement:kMusicSixteenthFlag]; s1.y += 5.0f; break; - case 32: + case VLNote::k32nd: flag = [self musicElement:kMusicThirtysecondthFlag]; s1.y += 13.0f; break; @@ -239,34 +239,34 @@ fLastNoteCenter = c; } -- (void) drawRest:(VLFraction)dur at:(NSPoint)p +- (void) drawRest:(int)visual at:(NSPoint)p { // // Draw rest // NSImage * head = nil; - switch (dur.fDenom) { - case 1: + switch (visual) { + case VLNote::kWhole: head = [self musicElement:kMusicWholeRest]; p.y += kWholeRestY; break; - case 2: + case VLNote::kHalf: head = [self musicElement:kMusicHalfRest]; p.y += kHalfRestY; break; - case 4: + case VLNote::kQuarter: head = [self musicElement:kMusicQuarterRest]; p.x -= kNoteX; break; - case 8: + case VLNote::kEighth: head = [self musicElement:kMusicEighthRest]; p.x -= kNoteX; break; - case 16: + case VLNote::k16th: head = [self musicElement:kMusicSixteenthRest]; p.x -= kNoteX; break; - case 32: + case VLNote::k32nd: head = [self musicElement:kMusicThirtysecondthRest]; p.x -= kNoteX; break; @@ -287,63 +287,48 @@ int measIdx = m+system*fMeasPerSystem; if (measIdx >= song->CountMeasures()) break; - const VLMeasure measure = song->fMeasures[measIdx]; - const VLNoteList & melody = measure.fMelody; + const VLMeasure & measure = song->fMeasures[measIdx]; + VLNoteList melody; + measure.DecomposeNotes(song->fProperties[measure.fPropIdx], melody); VLFraction at(0); - VLFraction prevDur(0); - bool triplet = false; for (VLNoteList::const_iterator note = melody.begin(); note != melody.end(); ++note ) { - VLFraction dur = note->fDuration; - VLFraction nextDur(0); - VLNoteList::const_iterator next = note; - if (++next != melody.end()) - nextDur = next->fDuration; - BOOL first = !m || !(note->fTied & VLNote::kTiedWithPrev); - int pitch = note->fPitch; - while (dur > 0) { - VLFraction partialDur; // Actual value of note drawn - VLFraction noteDur; // Visual value of note - prop.PartialNote(at, dur, dur==prevDur || dur==nextDur, - &partialDur); - prop.VisualNote(at, partialDur, triplet, ¬eDur, &triplet); - prevDur = partialDur; - if (pitch != VLNote::kNoPitch) { - [self drawLedgerLinesWithPitch:pitch - at:NSMakePoint([self noteXInMeasure:m at:at], kSystemY)]; - VLMusicElement accidental; - NSPoint pos = - NSMakePoint([self noteXInMeasure:m at:at], - kSystemY+[self noteYWithPitch:pitch - accidental:&accidental]); - VLMusicElement acc = accidental; - int step= [self stepWithPitch:pitch]; - if (acc == accidentals[step]) - acc = kMusicNothing; // Don't repeat accidentals - else if (acc == kMusicNothing) - if (accidentals[step] == kMusicNatural) // Resume signature - acc = prop.fKey < 0 ? kMusicFlat : kMusicSharp; - else - acc = kMusicNatural; - [self drawNote:noteDur - at: pos - accidental: acc - tied:!first]; - accidentals[step] = accidental; - } else { - VLMusicElement accidental; - NSPoint pos = - NSMakePoint([self noteXInMeasure:m at:at], - kSystemY+[self noteYWithPitch:65 - accidental:&accidental]); - [self drawRest:noteDur at: pos]; - } - dur -= partialDur; - at += partialDur; - first = NO; + BOOL tied = (note != melody.begin() || m) + && note->fTied & VLNote::kTiedWithPrev; + int pitch = note->fPitch; + if (pitch != VLNote::kNoPitch) { + [self drawLedgerLinesWithPitch:pitch + at:NSMakePoint([self noteXInMeasure:m at:at], kSystemY)]; + VLMusicElement accidental; + NSPoint pos = + NSMakePoint([self noteXInMeasure:m at:at], + kSystemY+[self noteYWithPitch:pitch + accidental:&accidental]); + VLMusicElement acc = accidental; + int step= [self stepWithPitch:pitch]; + if (acc == accidentals[step]) + acc = kMusicNothing; // Don't repeat accidentals + else if (acc == kMusicNothing) + if (accidentals[step] == kMusicNatural) // Resume signature + acc = prop.fKey < 0 ? kMusicFlat : kMusicSharp; + else + acc = kMusicNatural; + [self drawNote:note->fVisual & VLNote::kNoteHead + at: pos + accidental: acc + tied:tied]; + accidentals[step] = accidental; + } else { + VLMusicElement accidental; + NSPoint pos = + NSMakePoint([self noteXInMeasure:m at:at], + kSystemY+[self noteYWithPitch:65 + accidental:&accidental]); + [self drawRest:note->fVisual & VLNote::kNoteHead at: pos]; } + at += note->fDuration; } } if (fCursorPitch != VLNote::kNoPitch && fCursorMeasure/fMeasPerSystem == system) diff --git a/Tests/TVLEdit.cpp b/Tests/TVLEdit.cpp index 0fdd611..d71b685 100644 --- a/Tests/TVLEdit.cpp +++ b/Tests/TVLEdit.cpp @@ -38,7 +38,7 @@ std::ostream & operator<<(std::ostream & s, VLFraction f) s << whole; f -= whole; if (f.fNum) - s << int(f.fNum) << '/' << int(f.fDenom); + s << '.' << int(f.fNum) << '/' << int(f.fDenom); } else if (f.fNum) { s << int(f.fNum) << '/' << int(f.fDenom); } else { @@ -65,50 +65,44 @@ void PrintName(const VLChord & chord) } } -template class Printer { - VLProperties * fProp; - VLFraction fAt; -public: - Printer(VLProperties * prop) : fProp(prop) {} - - void operator()(const C& obj) { - PrintName(obj); - - std::cout << '@'; - for (VLFraction d = obj.fDuration; d > 0; ) { - VLFraction p; - fProp->PartialNote(fAt, d, &p); - if (d < obj.fDuration) - std::cout << '+'; - std::cout << p; - d -= p; - fAt += p; - } - std::cout << ' '; - } -}; - -void PrintMeasure(const VLMeasure & measure) +void ChordPrinter(const VLChord & chord) { - std::for_each(measure.fChords.begin(), measure.fChords.end(), - Printer(measure.fProperties)); + PrintName(chord); + if (chord.fDuration != VLFraction(1,4)) + std::cout << " * " << chord.fDuration; + std::cout << ' '; +} + +void NotePrinter(const VLLyricsNote & note) +{ + if (note.fTied & VLNote::kTiedWithPrev) + std::cout << "~ "; + PrintName(note); + std::cout << ' ' << note.fDuration + << '[' << ((note.fVisual & VLNote::kTriplet) ? "T" : "") + << (note.fVisual & VLNote::kNoteHead)["124863"] << "] "; +} + +void PrintMeasure(const VLMeasure & measure, const VLProperties & prop) +{ + std::for_each(measure.fChords.begin(), measure.fChords.end(), ChordPrinter); std::cout << std::endl; - std::for_each(measure.fMelody.begin(), measure.fMelody.end(), - Printer(measure.fProperties)); + VLNoteList decomposed; + measure.DecomposeNotes(prop, decomposed); + std::for_each(decomposed.begin(), decomposed.end(), NotePrinter); std::cout << std::endl << std::endl; } void PrintSong(const VLSong & song) { - std::for_each(song.fMeasures.begin(), song.fMeasures.end(), PrintMeasure); + for (size_t i=0; i> command) { int measure; @@ -117,7 +111,7 @@ int main(int, char *const []) switch (command) { case '+': std::cin >> name >> measure >> at; - song.AddNote(name, measure, at); + song.AddNote(VLNote(name), measure, at); PrintSong(song); break;