( var formatMusicData, spellingDict, lyNoteNameStr, lyOctStr, lyFinalizeMusic, lyMeasureDef, lyRelMark, lyRelMarkNote, lyHBracket, lyStaffDef, lyTie, lyNoteName, lyCentDev, lyFreqRatio, lyDur, lyNote, lyBeamOpen, lyBeamClosed, consolidateNotes, consolidateRests; // formats the data for the transcriber formatMusicData = {arg rawMusicData; var maxSize, musicData; maxSize = 0; musicData = rawMusicData.collect({arg partData, p; var res; res = partData.collect({arg item, i; var freq, dur, amp, mult, insRef, sus, note, rest; # freq, dur, amp, mult, insRef = item; sus = dur * sign(amp); note = sus.collect({[freq, mult, insRef, i]}); rest = if(p < rawMusicData.size, {(dur - sus).collect({[-1, -1, -1, i]})}, {[]}); note ++ rest }).flatten; if(res.size > maxSize, {maxSize = res.size}); res }); // make them all the same length maxSize = maxSize.trunc(16) + 16; musicData = musicData.collect({arg partData, p; partData.extend(maxSize, [-1, -1, -1, partData.last[1]])}); musicData }; // constants (spelling dictionary note names and octaves) spellingDict = Dictionary.with(* [ \major -> Dictionary.with(* [0, 7, 2, 9, 4, 11].collect({arg pc; pc->\sharps}) ++ [5, 10, 3, 8, 1, 6].collect({arg pc; pc->\flats}) ), \minor -> Dictionary.with(* [9, 4, 11, 6, 1, 8].collect({arg pc; pc->\sharps}) ++ [2, 7, 0, 5, 10, 3].collect({arg pc; pc->\flats}) ) ] ); lyNoteNameStr = Dictionary.with(* [ \sharps -> ["c", "cis", "d", "dis","e", "f", "fis", "g", "gis", "a", "ais", "b"], \flats -> ["c", "des", "d", "ees","e", "f", "ges", "g", "aes", "a", "bes", "b"], ] ); lyOctStr = [",,", ",", "", "'", "''", "'''", "''''"]; //define staff lyStaffDef = {arg name, nameShort, nameMidi; "\\new Staff = \"" ++ name ++ "\" \\with { \n" ++ "instrumentName = \"" ++ name ++ "\" \n" ++ "shortInstrumentName = \"" ++ nameShort ++ "\" \n" ++ "midiInstrument = #\"" ++ nameMidi ++ "\"\n" ++ "\n}\n" }; // add music preamble lyFinalizeMusic = {arg lyStr, part, name, nameShort, nameMidi, clef; "\\new StaffGroup \\with {\\remove \"System_start_delimiter_engraver\"}\n<<\n" ++ lyStaffDef.value(name, nameShort, nameMidi) ++ "<<\n\n{ " + "\n\\set Score.markFormatter = #format-mark-box-numbers " + "\\tempo 2 = 60\n" + "\\numericTimeSignature \\time 2/2\n" + "\\clef " ++ clef ++ "\n" ++ lyStr ++ " }>> \\bar \"|.\" \n} \n\n>>" ++ "\n>>" }; lyRelMarkNote = {arg root, lastRoot, part, clef; if(root[part][2] != [[1], [1]], { "\\stopStaff s8. \\startStaff \\clef" + clef + "s16 \n" ++ "\\once \\override TextScript.color = #(rgb-color 0.6 0.6 0.6) \n " ++ "\\tweak Accidental.color #(rgb-color 0.6 0.6 0.6) \n " ++ "\\tweak NoteHead.color #(rgb-color 0.6 0.6 0.6) \n " ++ lyNote.value(lastRoot[part][1], 1, lastRoot[part][0], nil, \sharps, true, true, false) + "\\hide c" ++ [nil, "", "'", "''"][part] ++ "8 \n " }, { "\\stopStaff s4. \\startStaff \\clef" + clef + "s16 \n" }) ++ lyNote.value(root[part][3], 1, root[part][2], nil, \sharps, true, false, true) }; lyHBracket = {arg fr, yOffset, sPair1, sPair2, edgeH1, edgeH2; "-\\tweak HorizontalBracket.Y-offset #" ++ yOffset ++ "\n " ++ "-\\tweak HorizontalBracket.shorten-pair #'(" ++ sPair1 + "." + sPair2 ++") \n " ++ "-\\tweak HorizontalBracket.edge-height #'(" ++ edgeH1 + "." + edgeH2 ++ ") \n " ++ "-\\tweak HorizontalBracketText.text" + fr + "\\startGroup \n " }; lyRelMark = {arg root, lastRoot, section, subsection; var sectionMark; sectionMark = "\\mark \\markup { \\bold \\override #'(box-padding . 0.5) \\box " ++ section ++ "." ++ subsection ++ " } \n"; if((section == 1) && (subsection > 1), { "\\once \\override Score.RehearsalMark.self-alignment-X = #0 \n " ++ "\\once \\override Score.RehearsalMark.Y-offset = #5 \n " ++ "\\once \\override Score.RehearsalMark.X-offset = #1 \n " ++ sectionMark }, { "\\mark \\markup { \n" ++ "\\halign #-1 \n " ++ "\\relMark ##{ { \n " ++ "\\time 15/8 \n " ++ "\\once \\override Staff.Clef #'stencil = ##f \n " ++ sectionMark ++ lyRelMarkNote.value(root, lastRoot, 1, "bass") ++ "^\\markup{\\large \\raise #2 \"III\"}" ++ lyHBracket.value(lyFreqRatio.value(root[2][4][2], nil, true, 0, false), 8.5, 1, 2, 1, 1) ++ lyHBracket.value(lyFreqRatio.value(root[2][4][1], nil, true, 0, false), 5.5, 3, 3, 0, 0) ++ "\\hide c16 \n " ++ lyRelMarkNote.value(root, lastRoot, 2, "alto") ++ "^\\markup{\\large \\raise #2 \"II\"}" + "\\stopGroup \\hide c'16 \n " ++ lyHBracket.value(lyFreqRatio.value(root[1][4][2], nil, true, 0, false), 5.5, 1, 3, 0, 0) ++ lyRelMarkNote.value(root, lastRoot, 3, "treble") ++ "^\\markup{\\large \\raise #2 \"I\"}" + "\\stopGroup \\stopGroup \n " ++ "\\hide c''16 \n " ++ "}#}}" }); }; // barline and ossia definition lyMeasureDef = {arg sectionData, insName, part, beat; var ossia = "", barline = "|", break = ""; if(sectionData != nil, { var root, lastRoot, section, subsection; # root, lastRoot, section, subsection = sectionData; ossia = lyRelMark.value(root, lastRoot, section, subsection); barline = "\\bar \"||\""; if(sectionData[4], {barline = "\\bar \"|.|\""}); if(sectionData[5], {barline = "\\bar \".|\""}); }); if((beat % 16) == 0, {break = "\\break \\noPageBreak"}); if((beat % (16 * 3)) == 0, {break = "\\pageBreak"}); if(beat != 0, {"}\n>>\n" + barline + break}, {""}) + "\n<<\n" ++ ossia + "{" }; // add tie lyTie = {"~"}; lyNoteName = {arg freq, spellingPref = \sharps; if(freq != -1, { lyNoteNameStr[spellingPref][((freq.cpsmidi).round(1) % 12)] ++ lyOctStr[(((freq).cpsmidi).round(1) / 12).asInteger - 2]; },{"r"}); }; lyCentDev = {arg freq, padding = true; var centDev; centDev = ((freq.cpsmidi - (freq.cpsmidi).round(1)) * 100).round(1).asInteger; "^\\markup { " ++ if(padding, {"\\pad-markup #0.2 \""}, {"\""}) ++ if(centDev >= 0, {"+"}, {""}) ++ centDev.asString ++ "\"}" }; lyFreqRatio = {arg freqRatioMult, ref, padding = true, lower = 3, attachedToNote = true; var res, ratio; res = "\\markup {" + if(attachedToNote, {""}, {"\\normalsize"}) + "\\lower #" ++ lower + if(padding, {"\\pad-markup #0.2 "}, {" "}); ratio = "\"" ++ freqRatioMult[0].product.asInteger ++ "/" ++ freqRatioMult[1].product.asInteger ++ "\" }"; res = if(ref != nil, { res ++ "\\concat{ \"" ++ [nil, "III", "II", "I"][ref] ++ "\"\\normal-size-super " ++ ratio ++ "}" }, { res ++ ratio } ); if(attachedToNote, {"_" ++ res}, {res}) }; lyNote = {arg freq, noteLength, freqRatioMult, ref, spellingPref = \sharps, addMarkup = true, frHide = false, padding = true; lyNoteName.value(freq, spellingPref) ++ lyDur.value(noteLength) ++ if(addMarkup, { "" }, {""}) }; lyDur = {arg noteLength; switch(noteLength, 1, {"16"}, 2, {"8"}, 3, {"8."}, 4, {"4"}); }; lyBeamOpen = {"["}; lyBeamClosed = {"]"}; consolidateNotes = {arg lyStr, part; var noteRegex, markupRegex, fullNoteRegex, restRegex, fullRestRegex, res; noteRegex = "(?[a-g](?:es|is)?(?:[,']*?)?4)"; markupRegex = if(part != 0, {"()?"}, {"()?"}); fullNoteRegex = noteRegex ++ markupRegex ++ "(?:\\h+~\\h+\\k)"; restRegex = "(?r4)"; fullRestRegex = "(?r4)(?:(\\h+)\\k)"; res = lyStr; [6, 4, 3, 2].do({arg len; [fullNoteRegex, fullRestRegex].do({arg regex; res.findRegexp(regex ++ "{" ++ (len-1) ++ "}").clump(3).do({arg match; var word, note, markup, lyDur; word = match[0][1]; note = match[1][1]; markup = match[2][1]; lyDur = switch(len, 6, {"1."}, 4, {"1"}, 3, {"2."}, 2, {"2"}); res = res.replace(word, note.replace("4", lyDur) ++ markup)}); }); }); res.replace("", ""); }; ~transcribe = {arg rawMusicData, sectionData, seed; var basePath, scoreFile, musicData, insData, insNames, insNamesShort, insMidi, insClef; basePath = ~dir +/+ ".." +/+ "lilypond" +/+ "seed_" ++ seed; basePath.mkdir; (basePath +/+ "includes").mkdir; scoreFile = File(basePath +/+ "tkam_score.ly".standardizePath,"w"); scoreFile.write(File.readAllString(basePath +/+ ".." +/+ "template" +/+ "tkam_score_template.ly").replace("seed: xxx", "seed: " ++ seed)); scoreFile.close; musicData = formatMusicData.value(rawMusicData); insData = [ ["*", "*", "clarinet", "\"treble_8\""], ["III", "III", "clarinet", "bass"], ["II", "II", "clarinet", "alto"], ["I", "I", "clarinet", "treble"] ]; insNames = insData.slice(nil, 0); insNamesShort = insData.slice(nil, 1); insMidi = insData.slice(nil, 2); insClef = insData.slice(nil, 3); musicData.do({arg part, p; var lyFile, lyStr, lastMusAtom, measureCount, spellingPref, tmpSectionData, pcRoot, partLookup, quality; //create file lyFile = File(basePath +/+ "includes" +/+ "part_" ++ ["star", "III", "II", "I"][p] ++ ".ly".standardizePath,"w"); //start lypond directives lyStr = ""; lastMusAtom = nil; measureCount = 0; spellingPref = \sharps; tmpSectionData = nil; part.clump(4).do({arg beat, i; var gSum; gSum = 0; beat.separate({arg a, b; ((a[0] != -1) || (b[0] != -1)) && (a != b)}).do({arg group, g; var noteLength, curMusAtom, freq, freqRatioMult, ref, isSame, isRest, isFirst, isLast, isTied, isMeasureBound, isBeamStart, isBeamEnd; noteLength = group.size; gSum = gSum + noteLength; curMusAtom = group[0]; freq = curMusAtom[0]; freqRatioMult = curMusAtom[1]; ref = curMusAtom[2]; # isSame, isRest, isFirst, isLast = [curMusAtom == lastMusAtom, freq == -1, g == 0, gSum == 4]; # isTied, isMeasureBound = [isSame && isRest.not, isFirst && ((i % 4) == 0)]; # isBeamStart, isBeamEnd = [(noteLength != 4) && isFirst, (noteLength != 4) && isLast]; //add ties if(isTied, {lyStr = lyStr + lyTie.value}); //add barline and ossia definition if(isMeasureBound, {lyStr = lyStr + lyMeasureDef.value(sectionData[i], insNames[p], p, i)}); //add note data if(sectionData[i] != nil, { tmpSectionData = sectionData[i]; }); if(isTied.not, { partLookup = if((p != 0) || [1, 2, 3].includes(ref).not , {p}, {ref}); pcRoot = ((tmpSectionData[0][partLookup][3].cpsmidi).round(1) % 12).asInteger; quality = if(tmpSectionData[0][partLookup][1][2] == [[ 1, 5 ], [ 1, 2, 2 ]], {\major}, {\minor}); spellingPref = spellingDict[quality][pcRoot]; if(p == 0, {[(i / 4).asInteger, partLookup, pcRoot, quality]}); }); lyStr = lyStr + lyNote.value(freq, noteLength, freqRatioMult, ref, spellingPref, isSame.not && isRest.not); //beam group if(isBeamStart, {lyStr = lyStr ++ lyBeamOpen.value}); if(isBeamEnd, {lyStr = lyStr ++ lyBeamClosed.value}); lastMusAtom = curMusAtom; }); }); //wrap music and add staff definitions lyStr = lyFinalizeMusic.value(lyStr, p, insNames[p], insNamesShort[p], insMidi[p], insClef[p]); //consolidate notes and rests lyStr = consolidateNotes.value(lyStr, p); //write file lyFile.write(lyStr); lyFile.close; }); }; //~~~~~~~~~~~~GENERATE SCORE DATA~~~~~~~~~~~~ ~genScoreData = {arg ensData; var res; res = ensData.collect({arg partData; partData.flop.collect({arg data, d; if(d == 1, {data.differentiate ++ [10]}, {[0] ++ data})}) }); res.collect({arg part; part.flop}) }; )