diff --git a/Sources/VLLilypondDocument.mm b/Sources/VLLilypondDocument.mm index a63f6bf..a806bf2 100644 --- a/Sources/VLLilypondDocument.mm +++ b/Sources/VLLilypondDocument.mm @@ -9,6 +9,7 @@ // #import "VLLilypondDocument.h" +#import "VLLilypondWriter.h" @interface NSMutableString (VLLilypond) @@ -115,6 +116,8 @@ static const char * sKeyNames[] = { - (NSData *)lilypondDataWithError:(NSError **)outError { const VLProperties & prop = song->fProperties.front(); + VLLilypondWriter writer; + writer.Visit(*song); NSBundle * bndl = [NSBundle mainBundle]; NSString * tmpl = [bndl pathForResource:lilypondTemplate @@ -130,12 +133,10 @@ static const char * sKeyNames[] = { [bndl objectForInfoDictionaryKey:@"CFBundleVersion"]]; [ly substituteMacro:@"PAPERSIZE" withValue:@"letter"]; [ly substituteMacro:@"FORMATTING" withValue:@"ragged-last-bottom = ##f"]; - std::string lys; - song->LilypondChords(lys); [ly substituteMacro:@"VLVERSION" withValue: [bndl objectForInfoDictionaryKey:@"CFBundleVersion"]]; [ly substituteMacro:@"CHORDS" withValue: - [NSString stringWithUTF8String:lys.c_str()]]; + [NSString stringWithUTF8String:writer.Chords().c_str()]]; [ly substituteMacro:@"TIME" withValue: [NSString stringWithFormat:@"%d/%d", prop.fTime.fNum, prop.fTime.fDenom]]; @@ -144,14 +145,12 @@ static const char * sKeyNames[] = { sKeyNames[prop.fKey+kMajorOffset]] : [NSString stringWithFormat:@"%s \\minor", sKeyNames[prop.fKey+kMinorOffset]]]; - song->LilypondNotes(lys); [ly substituteMacro:@"NOTES" withValue: - [NSString stringWithUTF8String:lys.c_str()]]; + [NSString stringWithUTF8String:writer.Melody().c_str()]]; if (size_t stanzas = song->CountStanzas()) - for (size_t s=0; s++LilypondStanza(lys, s); + for (size_t s=0; s= 0; + fInPickup = fInPickup && !m && meas.NoChords(); + + // + // Generate chords + // + fAccum.clear(); + VisitChords(meas); + fAccum += measNo; + fChords+= fAccum + '\n'; + + // + // Generate structure elements + // + int times; + size_t volta; + bool repeat; + + fAccum.clear(); + if (fSong->DoesEndRepeat(m)) { + fAccum += "}\n"; + fIndent = ""; + } + if (fSong->DoesBeginEnding(m, &repeat, &volta)) { + fAccum += fSeenEnding ? "}{\n" : "} \\alternative {{\n"; + fAccum += " \\set Score.repeatCommands = #'((volta \""; + const char * comma = ""; + for (int r=0; r<8; ++r) + if (volta & (1<DoesEndEnding(m)) { + fAccum += "}}\n"; + fIndent = ""; + } + if (fSong->DoesBeginRepeat(m, ×)) { + char volta[8]; + sprintf(volta, "%d", times); + fAccum = fAccum + "\\repeat volta "+volta+" {\n"; + fIndent = " "; + fSeenEnding = 0; + fNumEndings = 0; + } + fAccum += fIndent; + if (fSong->fCoda == m) + fAccum += "\\break \\mark \\markup { \\musicglyph #\"scripts.coda\" }\n" + + fIndent; + fMelody += fAccum; + + // + // Generate melody & lyrics + // + fAccum.clear(); + for (size_t stanza=0; stanzafGoToCoda == m+1) + fAccum += "\n" + + fIndent + + "\\mark \\markup { \\musicglyph #\"scripts.coda\" } |"; + else + fAccum += " |"; + fMelody += fAccum + measNo + '\n'; + + // + // Accumulate lyrics + // + const char * nuline = m%4 ? "" : "\n"; + for (size_t stanza=0; stanza0; --ticks) + nm += '\''; + for (int commas = (ref-n.fPitch)/VLNote::kOctave; commas>0; --commas) + nm += ','; + fInPickup = false; + } else if (fInPickup) { + nm = "s"; + } + const char * space = fAccum.size() ? " " : ""; + const char * tie = n.fTied & VLNote::kTiedWithNext ? " ~" : ""; + char duration[32]; + if (n.fTied == VLNote::kTiedWithPrev && n.fVisual == fPrevNote.fVisual+1 + && n.fPitch == fPrevNote.fPitch + ) + strcpy(duration, "."); + else if (n.fVisual & VLNote::kTriplet) + sprintf(duration, "%s\\times 2/3 { %s%d%s }", + space, nm.c_str(), kValue[n.fVisual], tie); + else + sprintf(duration, "%s%s%d%s", space, nm.c_str(), kValue[n.fVisual], tie); + + fAccum += duration; + fPrevNote= n; + + if (n.fPitch != VLNote::kNoPitch && !(n.fTied & VLNote::kTiedWithPrev)) + for (size_t i=0; i VLChord::kDim7th; --step) + if (unaltered & (1 << step)) { + std::string sn = kLilypondStepNames[step]; + if (ext.size() && !isalpha(ext[ext.size()-1]) && sn.size()) + ext += '.'; + ext += sn; + break; + } + } + for (int step = VLChord::kMin2nd; steps; ++step) + if (steps & (1 << step)) { + std::string sn = kLilypondStepNames[step]; + if (ext.size() && !isalpha(ext[ext.size()-1]) && sn.size()) + ext += '.'; + ext += sn; + steps &= ~(1 << step); + } + + if (ext.size()) + name += ':' + ext; + // + // Root + // + if (c.fRootPitch != VLNote::kNoPitch) + name += "/+" + LilypondPitchName(c.fRootPitch, fUseSharps); + + done: + if (fAccum.size()) + fAccum += ' '; + fAccum += name; +} diff --git a/Sources/VLLilypondWriter.h b/Sources/VLLilypondWriter.h new file mode 100644 index 0000000..befd759 --- /dev/null +++ b/Sources/VLLilypondWriter.h @@ -0,0 +1,43 @@ +// +// File: VLLilypondWriter.h +// +// Author(s): +// +// (MN) Matthias Neeracher +// +// Copyright © 2007 Matthias Neeracher +// + +#include "VLModel.h" + +class VLLilypondWriter: public VLSongVisitor { +public: + VLLilypondWriter() {} + + virtual void Visit(VLSong & song); + virtual void VisitMeasure(size_t m, VLProperties & p, VLMeasure & meas); + virtual void VisitNote(VLLyricsNote & n); + virtual void VisitChord(VLChord & c); + + const std::string & Chords() const { return fChords; } + const std::string & Melody() const { return fMelody; } + const std::string & Lyrics(size_t stanza) const { return fLyrics[stanza]; } +private: + std::string fChords; + std::string fMelody; + std::vector fLyrics; + + VLSong * fSong; + bool fUseSharps; + bool fInPickup; + size_t fSeenEnding; + int fNumEndings; + VLNote fPrevNote; + std::string fAccum; + std::string fIndent; + std::vector fL; +}; + +// Local Variables: +// mode:C++ +// End: diff --git a/Sources/VLModel.cpp b/Sources/VLModel.cpp index 0ffb5d8..f46f662 100644 --- a/Sources/VLModel.cpp +++ b/Sources/VLModel.cpp @@ -92,19 +92,6 @@ static std::string PitchName(int8_t pitch, bool useSharps) + std::string(kVLFlatStr); } -static std::string LilypondPitchName(int8_t pitch, bool useSharps) -{ - if (pitch == VLNote::kNoPitch) - return "r"; - pitch %= 12; - if (kScale[pitch] != ' ') - return kScale[pitch] + std::string(); - else if (useSharps) - return kScale[pitch-1] + std::string("is"); - else - return kScale[pitch+1] + std::string("es"); -} - static std::string MMAPitchName(int8_t pitch, bool useSharps) { if (pitch == VLNote::kNoPitch) @@ -164,50 +151,6 @@ void VLNote::Name(std::string & name, bool useSharps) const name = PitchName(fPitch, useSharps); } -void VLNote::LilypondName(std::string & name, VLFraction at, VLFraction prevDur, VLFraction nextDur, bool & triplet, bool & pickup, const VLProperties & prop) const -{ - std::string n = LilypondPitchName(fPitch, prop.fKey >= 0); - if (fPitch != kNoPitch) { - for (int ticks = (fPitch-kMiddleC+kOctave)/kOctave; ticks>0; --ticks) - n += '\''; - for (int commas = (kMiddleC-kOctave-fPitch)/kOctave; commas>0; --commas) - n += ','; - pickup = false; - } else if (pickup) { - n = "s"; - } - - std::vector durations; - VLFraction prevPart(0); - for (VLFraction dur = fDuration; dur.fNum; ) { - char duration[32]; - VLFraction part, visual; - bool grouped = dur==nextDur || - (prevPart!=0 ? dur==prevPart : dur==prevDur); - prop.PartialNote(at, dur, grouped, &part); - prop.VisualNote(at, part, triplet, &visual, &triplet); - if (!triplet && fPitch != kNoPitch && part == dur && 2*visual == prevPart) { - durations.pop_back(); - sprintf(duration, "%s%d.", n.c_str(), visual.fDenom/2); - } else if (triplet) { - sprintf(duration, "\\times 2/3 { %s%d }", n.c_str(), visual.fDenom); - } else { - sprintf(duration, "%s%d", n.c_str(), visual.fDenom); - } - durations.push_back(duration); - prevPart = part; - at += part; - dur -= part; - } - for (size_t i=0; i kDim7th; --step) - if (unaltered & (1 << step)) { - std::string sn = kLilypondStepNames[step]; - if (ext.size() && !isalpha(ext[ext.size()-1]) && sn.size()) - ext += '.'; - ext += sn; - break; - } - } - for (int step = kMin2nd; steps; ++step) - if (steps & (1 << step)) { - std::string sn = kLilypondStepNames[step]; - if (ext.size() && !isalpha(ext[ext.size()-1]) && sn.size()) - ext += '.'; - ext += sn; - steps &= ~(1 << step); - } - - if (ext.size()) - name += ':' + ext; - // - // Root - // - if (fRootPitch != kNoPitch) - name += "/+" + LilypondPitchName(fRootPitch, useSharps); -} - // // MMA supports a large but finite list of chords // @@ -1678,152 +1518,6 @@ size_t VLSong::CountBotLedgers() const return 0; } -void VLSong::LilypondNotes(std::string & notes) const -{ - notes = ""; - std::string indent = ""; - size_t seenEnding = 0; - int numEndings = 0; - size_t endMeasure = fMeasures.size()-EmptyEnding(); - bool pickup = fMeasures[0].NoChords(); - for (size_t measure=0; measurefDuration; - i->LilypondName(note, at, prevDur, nextDur, triplet, pickup, fProperties[fMeasures[measure].fPropIdx]); - prevDur = i->fDuration; - at += i->fDuration; - notes += note+" "; - } - // - // Consolidate triplets - // - size_t trip; - while ((trip = notes.find("} \\times 2/3 { ")) != std::string::npos) - notes.erase(trip, 15); - while ((trip = notes.find("} ~ \\times 2/3 { ")) != std::string::npos) - notes.replace(trip, 17, "~ ", 2); - // - // Swap ties into correct order - // - while ((trip = notes.find("} ~")) != std::string::npos) - notes.replace(trip, 3, "~ } ", 4); - - if (fGoToCoda == measure+1) - notes += "\n" - + indent - + "\\mark \\markup { \\musicglyph #\"scripts.coda\" } |"; - else - notes += '|'; - if (!(measure % 4)) { - char measNo[8]; - sprintf(measNo, " %% %d", measure+1); - notes += measNo; - } - if (measure < fMeasures.size()-1) - notes += '\n'; - } -} - -void VLSong::LilypondChords(std::string & chords) const -{ - chords = ""; - for (size_t measure=0; measure=0; - VLChordList::const_iterator i = fMeasures[measure].fChords.begin(); - VLChordList::const_iterator e = fMeasures[measure].fChords.end(); - - for (; i!=e; ++i) { - std::string chord; - i->LilypondName(chord, useSharps); - chords += chord+" "; - } - if (!(measure % 4)) { - char measNo[8]; - sprintf(measNo, " %% %d", measure+1); - chords += measNo; - } - if (measure < fMeasures.size()-1) - chords += '\n'; - } -} - -void VLSong::LilypondStanza(std::string & lyrics, size_t stanza) const -{ - lyrics = ""; - std::string sep; - for (size_t measure=0; measurefPitch == VLNote::kNoPitch - || (i->fTied & VLNote::kTiedWithPrev) - ) { - continue; // Rest or continuation note, skip - } else if (i->fLyrics.size() < stanza || !i->fLyrics[stanza-1]) { - lyrics += sep + "\\skip1"; - } else { - lyrics += sep + i->fLyrics[stanza-1].fText; - if (i->fLyrics[stanza-1].fKind & VLSyllable::kHasNext) - lyrics += " --"; - } - sep = " "; - } - if ((measure % 4) == 3) { - sep = "\n"; - } - } -} - bool VLSong::FindWord(size_t stanza, size_t & measure, VLFraction & at) { at += VLFraction(1,64); @@ -2534,3 +2228,58 @@ VLFract VLSong::TiedDuration(size_t measure) } return total; } + +VLSongVisitor::~VLSongVisitor() +{ +} + +void VLSongVisitor::VisitMeasures(VLSong & song, bool performanceOrder) +{ + 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(); + + 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); + 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); +} + diff --git a/Sources/VLModel.h b/Sources/VLModel.h index 3333660..54e4621 100644 --- a/Sources/VLModel.h +++ b/Sources/VLModel.h @@ -150,7 +150,6 @@ struct VLNote { VLNote(VLFraction dur=0, int pitch=kNoPitch); VLNote(std::string name); void Name(std::string & name, bool useSharps = false) const; - void LilypondName(std::string & name, VLFraction at, VLFraction prevDur, VLFraction nextDur, bool & triplet, bool & pickup, const VLProperties & prop) const; void MMAName(std::string & name, VLFraction at, VLFraction dur, VLFraction prevDur, VLFraction nextDur, const VLProperties & prop) const; void MakeRepresentable(); void AlignToGrid(VLFraction at, VLFraction grid); @@ -210,7 +209,6 @@ struct VLChord : VLNote { VLChord(VLFraction dur=0, int pitch=kNoPitch, int rootPitch=kNoPitch); VLChord(std::string name); void Name(std::string & base, std::string & ext, std::string & root, bool useSharps = false) const; - void LilypondName(std::string & name, bool useSharps = false) const; bool MMAName(std::string & name, bool useSharps, bool initial) const; }; @@ -274,7 +272,8 @@ struct VLRepeat { std::vector fEndings; }; -struct VLSong { +class VLSong { +public: VLSong(bool initialize = true); void swap(VLSong & other); void clear(); @@ -368,14 +367,28 @@ struct VLSong { size_t CountStanzas() const; size_t CountTopLedgers() const; size_t CountBotLedgers() const; - void LilypondNotes(std::string & notes) const; - void LilypondChords(std::string & chords) const; - void LilypondStanza(std::string & lyrics, size_t stanza) const; VLFract TiedDuration(size_t measure); private: void AddMeasure(); }; +class VLSongVisitor { +public: + virtual ~VLSongVisitor(); + + virtual void Visit(VLSong & song) {} + virtual void VisitMeasure(size_t m, VLProperties & p, VLMeasure & meas) {} + virtual void VisitNote(VLLyricsNote & n) {} + virtual void VisitChord(VLChord & c) {} +protected: + VLSongVisitor() {} + + void VisitMeasures(VLSong & song, bool performanceOrder); + void VisitNotes(VLMeasure & measure, const VLProperties & prop, + bool decomposed); + void VisitChords(VLMeasure & measure); +}; + // Local Variables: // mode:C++ // End: diff --git a/VocalEasel.xcodeproj/project.pbxproj b/VocalEasel.xcodeproj/project.pbxproj index ec3bd7b..4ae8146 100644 --- a/VocalEasel.xcodeproj/project.pbxproj +++ b/VocalEasel.xcodeproj/project.pbxproj @@ -41,6 +41,7 @@ 954DD4E60B44E67F0056C504 /* VLSheetViewSelection.mm in Sources */ = {isa = PBXBuildFile; fileRef = 954DD4E50B44E67F0056C504 /* VLSheetViewSelection.mm */; }; 954F20310BFABD96006CAE0E /* VLMirrorWindow.nib in Resources */ = {isa = PBXBuildFile; fileRef = 954F20300BFABD96006CAE0E /* VLMirrorWindow.nib */; }; 955CBA4F0B2366DD001CF4A1 /* VLKeyValueUndo.mm in Sources */ = {isa = PBXBuildFile; fileRef = 955CBA4D0B2366DD001CF4A1 /* VLKeyValueUndo.mm */; }; + 955DA2960C0551EC008F73B8 /* VLLilypondWriter.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 955DA2940C0551EC008F73B8 /* VLLilypondWriter.cpp */; }; 955E58E5095658AB0045FDA5 /* VLModel.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 955E58E4095658AB0045FDA5 /* VLModel.cpp */; }; 955E59610957C1400045FDA5 /* TVLChord.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 955E59600957C1400045FDA5 /* TVLChord.cpp */; }; 955E59640957C15A0045FDA5 /* VLModel.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 955E58E4095658AB0045FDA5 /* VLModel.cpp */; }; @@ -178,6 +179,8 @@ 954F20300BFABD96006CAE0E /* VLMirrorWindow.nib */ = {isa = PBXFileReference; lastKnownFileType = wrapper.nib; name = VLMirrorWindow.nib; path = English.lproj/VLMirrorWindow.nib; sourceTree = ""; }; 955CBA4C0B2366DD001CF4A1 /* VLKeyValueUndo.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = VLKeyValueUndo.h; path = Sources/VLKeyValueUndo.h; sourceTree = ""; }; 955CBA4D0B2366DD001CF4A1 /* VLKeyValueUndo.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; name = VLKeyValueUndo.mm; path = Sources/VLKeyValueUndo.mm; sourceTree = ""; }; + 955DA2940C0551EC008F73B8 /* VLLilypondWriter.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; name = VLLilypondWriter.cpp; path = Sources/VLLilypondWriter.cpp; sourceTree = ""; }; + 955DA2950C0551EC008F73B8 /* VLLilypondWriter.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; name = VLLilypondWriter.h; path = Sources/VLLilypondWriter.h; sourceTree = ""; }; 955E58E3095658AB0045FDA5 /* VLModel.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = VLModel.h; path = Sources/VLModel.h; sourceTree = ""; }; 955E58E4095658AB0045FDA5 /* VLModel.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = VLModel.cpp; path = Sources/VLModel.cpp; sourceTree = ""; }; 955E595C0957C0FC0045FDA5 /* TVLChord */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.executable"; includeInIndex = 0; path = TVLChord; sourceTree = BUILT_PRODUCTS_DIR; }; @@ -339,6 +342,8 @@ 2A37F4ABFDCFA73011CA2CEA /* Classes */ = { isa = PBXGroup; children = ( + 955DA2940C0551EC008F73B8 /* VLLilypondWriter.cpp */, + 955DA2950C0551EC008F73B8 /* VLLilypondWriter.h */, 95A55C520BD5E5760068A203 /* VLPDFDocument.h */, 95A55C530BD5E5770068A203 /* VLPDFDocument.mm */, 95EDA5A80B06DE46004D8D6E /* VLMIDIDocument.h */, @@ -682,6 +687,7 @@ 9599ED9D0B731CC500A6A2F7 /* VLGrooveController.mm in Sources */, 95A55C540BD5E5770068A203 /* VLPDFDocument.mm in Sources */, 95784D870BFAD795009ABEA4 /* VLMirrorWindow.mm in Sources */, + 955DA2960C0551EC008F73B8 /* VLLilypondWriter.cpp in Sources */, ); runOnlyForDeploymentPostprocessing = 0; };