diff --git a/Sources/VLMMADocument.mm b/Sources/VLMMADocument.mm index cca8701..c1a1fe3 100644 --- a/Sources/VLMMADocument.mm +++ b/Sources/VLMMADocument.mm @@ -9,6 +9,7 @@ // #import "VLMMADocument.h" +#import "VLMMAWriter.h" @implementation VLDocument (MMA) @@ -16,6 +17,8 @@ { char buf[32]; NSBundle * bndl = [NSBundle mainBundle]; + VLMMAWriter writer; + writer.Visit(*song); const VLProperties & prop = song->fProperties.front(); std::string mmaFile = std::string("// Generated by VocalEasel ") @@ -30,20 +33,7 @@ mmaFile += buf; sprintf(buf, "KeySig %d%c\n", labs(prop.fKey), prop.fKey>=0 ? '#' : '&'); mmaFile += buf; - mmaFile += '\n'; - - std::string mmas; - size_t meas = 0; - VLSong::iterator end = song->end(); - for (VLSong::iterator i=song->begin(); i!=end; ++i) { - size_t m = *i; - sprintf(buf, "%-5d", ++meas); - mmaFile += buf; - song->fMeasures[m].MMAChords(mmas, prop, i==song->begin()); - mmaFile += mmas; - song->fMeasures[m].MMANotes(mmas, prop, song->TiedDuration(m+1)); - mmaFile += "\t{ " + mmas + " }\n"; - } + mmaFile += '\n'+writer.Measures(); return [[NSString stringWithUTF8String:mmaFile.c_str()] dataUsingEncoding:NSUTF8StringEncoding]; diff --git a/Sources/VLMMAWriter.cpp b/Sources/VLMMAWriter.cpp new file mode 100644 index 0000000..122c9b2 --- /dev/null +++ b/Sources/VLMMAWriter.cpp @@ -0,0 +1,256 @@ +// +// File: VLMMAWriter.h +// +// Author(s): +// +// (MN) Matthias Neeracher +// +// Copyright © 2007 Matthias Neeracher +// + +#include "VLMMAWriter.h" + +void VLMMAWriter::Visit(VLSong & song) +{ + fSong = &song; + fMeas = 0; + fInitial= true; + fMeasures.clear(); + + VisitMeasures(song, true); +} + +void VLMMAWriter::VisitMeasure(size_t m, VLProperties & p, VLMeasure & meas) +{ + char buf[8]; + sprintf(buf, "%-3d", ++fMeas); + + fUseSharps = p.fKey >= 0; + + // + // Generate chords + // + fAccum.clear(); + VisitChords(meas); + std::string chords = buf+fAccum; + + // + // Generate melody and account for ties + // + bool setLastDur = false; + fAccum.clear(); + fTied = meas.fMelody.front().fTied & VLNote::kTiedWithPrev; + if (fTied && meas.fMelody.size() == 1) { + VisitNotes(meas, p, true); + if (meas.fMelody.back().fTied & VLNote::kTiedWithNext) { + fAccum = "~<>~;"; + } else { + fAccum = "~<>;"; + } + } else { + VisitNotes(meas, p, true); + if (meas.fMelody.back().fTied & VLNote::kTiedWithNext) { + fAccum.replace(fAccum.find_last_of(';'), 0, "~", 1); + setLastDur = true; + } + } + + std::string melody = fAccum; + + fMeasures += chords+"\t{ " + melody + " }\n"; + if (setLastDur) + fLastDur = fMeasures.find_last_of("123468"); +} + +static const char kScale[] = "c d ef g a b"; + +static std::string MMAPitchName(int8_t pitch, bool useSharps, bool showNatural=false) +{ + if (pitch == VLNote::kNoPitch) + return "r"; + char name[3]; + name[2] = 0; + name[1] = showNatural?'n':0; + pitch %= 12; + if (kScale[pitch] != ' ') { + name[0] = kScale[pitch]; + } else if (useSharps) { + name[0] = kScale[pitch-1]; + name[1] = '#'; + } else { + name[0] = kScale[pitch+1]; + name[1] = '&'; + } + if (!showNatural) + name[0] = toupper(name[0]); + + return name; +} + +static std::string MMAOctave(int8_t pitch) +{ + std::string name; + if (pitch != VLNote::kNoPitch) { + for (int raise = (pitch-VLNote::kMiddleC)/VLNote::kOctave; raise>0; --raise) + name += '+'; + for (int lower = (VLNote::kMiddleC+VLNote::kOctave-1-pitch)/VLNote::kOctave; lower>0; --lower) + name += '-'; + } + + return name; +} + +void VLMMAWriter::VisitNote(VLLyricsNote & n) +{ + char buf[4]; + std::string dur; + if (n.fDuration.fNum == 1) + if (!(n.fDuration.fDenom & (n.fDuration.fDenom-1))) { + sprintf(buf, "%d", n.fDuration.fDenom); + dur = buf; + } else if (n.fDuration.fDenom == 3) { + dur = "23"; // Half note triplet + } else if (n.fDuration.fDenom == 6) { + // + // Quarter note triplet / swing 8th + // + dur = n.fVisual==VLNote::kEighth ? "81" : "43"; + } else if (n.fDuration.fDenom == 12) { + // + // Eighth note triplet / swing 8th / swing 16th + // + dur = n.fVisual==VLNote::kEighth ? "82" : "3"; + } else if (n.fDuration.fDenom == 24) { + dur = "6"; // 16th note triplet + } + if (n.fTied & VLNote::kTiedWithPrev) { + if (fTied) { + fMeasures.replace(fLastDur+1, 0, '+'+dur); + if (!(n.fTied & VLNote::kTiedWithNext)) + fAccum += "~"; + } else { + size_t d = fAccum.find_last_of("123468"); + fAccum.replace(d+1, 0, '+'+dur); + } + return; + } + fTied = false; + if (fAccum.size() > 1) + fAccum += ' '; + fAccum += dur+MMAPitchName(n.fPitch, fUseSharps, true)+MMAOctave(n.fPitch)+';'; +} + +#define _ VLChord:: + +// +// MMA supports a large but finite list of chords +// +static const VLChordModifier kMMAModifiers[] = { + {"", 0, 0}, + {"+", _ kmAug5th, _ km5th}, + {"11", _ kmMin7th | _ kmMaj9th | _ km11th, 0}, + {"11b9", _ kmMin7th | _ kmMin9th | _ km11th, 0}, + {"13", _ kmMin7th | _ kmMaj9th | _ km11th | _ kmMaj13th, 0}, + {"5", 0, _ kmMaj3rd}, + {"6", _ kmDim7th, 0}, + {"69", _ kmDim7th | _ kmMaj9th, 0}, + {"7", _ kmMin7th, 0}, + {"7#11", _ kmMin7th | _ kmAug11th, 0}, + {"7#5", _ kmMin7th | _ kmAug5th, _ km5th}, + {"7#5#9", _ kmMin7th | _ kmAug5th | _ kmAug9th, _ km5th}, + {"7#5b9", _ kmMin7th | _ kmAug5th | _ kmMin9th, _ km5th}, + {"7#9", _ kmMin7th | _ kmAug9th, 0}, + {"7#9#11", _ kmMin7th | _ kmAug9th | _ kmAug11th, 0}, + {"7b5", _ kmMin7th | _ kmDim5th, _ km5th}, + {"7b5#9", _ kmMin7th | _ kmDim5th | _ kmAug9th, _ km5th}, + {"7b5b9", _ kmMin7th | _ kmDim5th | _ kmMin9th, _ km5th}, + {"7b9", _ kmMin7th | _ kmMin9th, 0}, + {"7sus", _ kmMin7th | _ km4th, _ kmMaj3rd}, + {"7sus2", _ kmMin7th | _ kmMaj2nd, _ kmMaj3rd}, + {"9", _ kmMin7th | _ kmMaj9th, 0}, + {"9#11", _ kmMin7th | _ kmMaj9th | _ kmAug11th, 0}, + {"9#5", _ kmMin7th | _ kmMaj9th | _ kmAug5th, _ km5th}, + {"9b5", _ kmMin7th | _ kmMaj9th | _ kmDim5th, _ km5th}, + {"9sus", _ kmMaj9th, 0}, + {"M13", _ kmMaj7th | _ kmMaj13th, 0}, + {"M7", _ kmMaj7th, 0}, + {"M7#11", _ kmMaj7th | _ kmMaj9th | _ kmAug11th, 0}, + {"M7#5", _ kmMaj7th | _ kmAug5th, _ km5th}, + {"M7b5", _ kmMaj7th | _ kmDim5th, _ km5th}, + {"M9", _ kmMaj7th | _ kmMaj9th, 0}, + {"aug9", _ kmMin7th | _ kmMaj9th | _ kmAug5th, _ km5th}, + {"dim3", _ kmMin3rd | _ kmDim5th, _ kmMaj3rd | _ km5th}, + {"dim7", _ kmMin3rd | _ kmDim5th | _ kmDim7th, _ kmMaj3rd | _ km5th}, + {"m", _ kmMin3rd, _ kmMaj3rd}, + {"m#5", _ kmMin3rd | _ kmAug5th, _ kmMaj3rd | _ km5th}, + {"m(maj7)", _ kmMin3rd | _ kmMaj7th, _ kmMaj3rd}, + {"m(sus9)", _ kmMin3rd | _ kmMaj9th, _ kmMaj3rd}, + {"m11", _ kmMin3rd | _ kmMin7th | _ kmMaj9th | _ km11th, _ kmMaj3rd}, + {"m6", _ kmMin3rd | _ kmDim7th, _ kmMaj3rd}, + {"m69", _ kmMin3rd | _ kmDim7th | _ kmMaj9th, _ kmMaj3rd}, + {"m7", _ kmMin3rd | _ kmMin7th, _ kmMaj3rd}, + {"m7b5", _ kmMin3rd | _ kmMin7th | _ kmDim5th, _ kmMaj3rd | _ km5th}, + {"m7b9", _ kmMin3rd | _ kmMin7th | _ kmMin9th, _ kmMaj3rd}, + {"m9", _ kmMin3rd | _ kmMin7th | _ kmMaj9th, _ kmMaj3rd}, + {"m9b5", _ kmMin3rd | _ kmMin7th | _ kmMaj9th | _ kmDim5th, _ kmMaj3rd | _ km5th}, + {"mM7", _ kmMin3rd | _ kmMaj7th, _ kmMaj3rd}, + {"mb5", _ kmMin3rd | _ kmDim5th, _ kmMaj3rd | _ km5th}, + {"sus", _ km4th, _ kmMaj3rd}, + {"sus2", _ kmMaj2nd, _ kmMaj3rd}, + {"sus9", _ kmMaj9th, 0}, + {NULL, 0, 0} +}; + +void VLMMAWriter::VisitChord(VLChord & c) +{ + int quarters = static_cast(c.fDuration*4.0f+0.5f); + if (!quarters--) + return; + std::string name; + if (c.fPitch == VLNote::kNoPitch) { + name = fInitial ? "z" : "/"; + } else { + fInitial = false; + + std::string base, ext; + base = MMAPitchName(c.fPitch, fUseSharps); + + size_t best = 0; + size_t bestBits = 32; + size_t bestScore= 0; + for (size_t i=0; kMMAModifiers[i].fName; ++i) { + uint32_t steps = (VLChord::kmUnison | VLChord::kmMaj3rd | VLChord::km5th) + | kMMAModifiers[i].fAddSteps + &~kMMAModifiers[i].fDelSteps; + if (c.fSteps == steps) { + // + // Exact match + // + best = i; + break; + } + steps ^= c.fSteps; + size_t bits=0; + size_t score=0; + for (uint32_t b=steps; b; b &= (b-1)) + ++bits; + for (size_t b=0; b<32; ++b) + if (steps & (1<= part) { fDuration = part; return; - } else if (!nonTriplet && fDuration >= triplet) { + } else if (fVisual > kWhole && !nonTriplet && fDuration >= triplet) { fDuration = triplet; fVisual |= kTriplet; return; @@ -269,12 +269,6 @@ VLLyricsNote::VLLyricsNote(VLFraction dur, int pitch) { } -struct VLChordModifier { - const char * fName; - uint32_t fAddSteps; - uint32_t fDelSteps; -}; - #define _ VLChord:: static const VLChordModifier kModifiers[] = { diff --git a/Sources/VLModel.h b/Sources/VLModel.h index 54e4621..03ec455 100644 --- a/Sources/VLModel.h +++ b/Sources/VLModel.h @@ -212,6 +212,12 @@ struct VLChord : VLNote { bool MMAName(std::string & name, bool useSharps, bool initial) const; }; +struct VLChordModifier { + const char * fName; + uint32_t fAddSteps; + uint32_t fDelSteps; +}; + struct VLProperties { VLFraction fTime; // Time (non-normalized) int8_t fKey; // Circle of fifths from C, >0 sharps, <0 flats diff --git a/VocalEasel.xcodeproj/project.pbxproj b/VocalEasel.xcodeproj/project.pbxproj index 4ae8146..4c98457 100644 --- a/VocalEasel.xcodeproj/project.pbxproj +++ b/VocalEasel.xcodeproj/project.pbxproj @@ -35,6 +35,7 @@ 952DCD78096BBB11001C2316 /* VLSheetViewChords.mm in Sources */ = {isa = PBXBuildFile; fileRef = 952DCD77096BBB11001C2316 /* VLSheetViewChords.mm */; }; 9530A7020BD9E16700635FEC /* display.tiff in Resources */ = {isa = PBXBuildFile; fileRef = 9530A7010BD9E16700635FEC /* display.tiff */; }; 953722670AE9F0E100B6E483 /* VLLilypondDocument.mm in Sources */ = {isa = PBXBuildFile; fileRef = 953722660AE9F0E100B6E483 /* VLLilypondDocument.mm */; }; + 9545C5C30C092F4600251547 /* VLMMAWriter.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 9545C5C10C092F4600251547 /* VLMMAWriter.cpp */; }; 95498DBD0AE3812F006B5F81 /* VLSoundSched.mm in Sources */ = {isa = PBXBuildFile; fileRef = 95498DBC0AE3812F006B5F81 /* VLSoundSched.mm */; }; 954BBD860AEDDE5300BBFD5F /* VLAppController.mm in Sources */ = {isa = PBXBuildFile; fileRef = 954BBD850AEDDE5300BBFD5F /* VLAppController.mm */; }; 954BBD9A0AEDE81500BBFD5F /* VLPitchTransformer.mm in Sources */ = {isa = PBXBuildFile; fileRef = 954BBD990AEDE81500BBFD5F /* VLPitchTransformer.mm */; }; @@ -166,6 +167,8 @@ 9530A7010BD9E16700635FEC /* display.tiff */ = {isa = PBXFileReference; lastKnownFileType = image.tiff; name = display.tiff; path = Resources/display.tiff; sourceTree = ""; }; 953722650AE9F0E100B6E483 /* VLLilypondDocument.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = VLLilypondDocument.h; path = Sources/VLLilypondDocument.h; sourceTree = ""; }; 953722660AE9F0E100B6E483 /* VLLilypondDocument.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; name = VLLilypondDocument.mm; path = Sources/VLLilypondDocument.mm; sourceTree = ""; }; + 9545C5C10C092F4600251547 /* VLMMAWriter.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; name = VLMMAWriter.cpp; path = Sources/VLMMAWriter.cpp; sourceTree = ""; }; + 9545C5C20C092F4600251547 /* VLMMAWriter.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; name = VLMMAWriter.h; path = Sources/VLMMAWriter.h; sourceTree = ""; }; 95498DBB0AE3812F006B5F81 /* VLSoundSched.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; name = VLSoundSched.h; path = Sources/VLSoundSched.h; sourceTree = ""; }; 95498DBC0AE3812F006B5F81 /* VLSoundSched.mm */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.objcpp; name = VLSoundSched.mm; path = Sources/VLSoundSched.mm; sourceTree = ""; }; 954BBD840AEDDE5300BBFD5F /* VLAppController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = VLAppController.h; path = Sources/VLAppController.h; sourceTree = ""; }; @@ -342,6 +345,8 @@ 2A37F4ABFDCFA73011CA2CEA /* Classes */ = { isa = PBXGroup; children = ( + 9545C5C10C092F4600251547 /* VLMMAWriter.cpp */, + 9545C5C20C092F4600251547 /* VLMMAWriter.h */, 955DA2940C0551EC008F73B8 /* VLLilypondWriter.cpp */, 955DA2950C0551EC008F73B8 /* VLLilypondWriter.h */, 95A55C520BD5E5760068A203 /* VLPDFDocument.h */, @@ -688,6 +693,7 @@ 95A55C540BD5E5770068A203 /* VLPDFDocument.mm in Sources */, 95784D870BFAD795009ABEA4 /* VLMirrorWindow.mm in Sources */, 955DA2960C0551EC008F73B8 /* VLLilypondWriter.cpp in Sources */, + 9545C5C30C092F4600251547 /* VLMMAWriter.cpp in Sources */, ); runOnlyForDeploymentPostprocessing = 0; };