( var formatMusicData, spellingDict, lyNoteNameStr, lyOctStr, lyFinalizeMusic, lyMeasureDef, lyRelMark, lyRelMarkNote, lyHBracket, lyStaffDef, lyTie, lyNoteName, lyCentDev, lyFreqRatio, lyDur, lyNote, lyBeamOpen, lyBeamClosed, consolidateNotes, consolidateRests, primes, hsArrayDimDiff, hsArrayToFreq, hsArraysToFreqRatio; primes = [[2, 1], [3, 2], [5, 4], [7, 4], [11, 8], [13, 8]]; hsArrayToFreq = { arg array; array.collect({arg dim, d; pow(primes[d][0]/primes[d][1], dim)}).product }; hsArraysToFreqRatio = { arg array1, array2; var fArray, num, den, gcd; fArray = array2 - array1; num = 1; den = 1; fArray.do({arg dim, d; if(dim > 0, { num = num * pow(primes[d][0], dim.abs); den = den * pow(primes[d][1], dim.abs); }); if(dim < 0, { num = num * pow(primes[d][1], dim.abs); den = den * pow(primes[d][0], dim.abs); }); }); gcd = gcd(num.asInteger, den.asInteger); [num / gcd, den / gcd].asInteger }; hsArrayDimDiff = { arg array1, array2; var fArray; fArray = array2.drop(1) - array1.drop(1); if(fArray.sum == 0, {1}, {(primes[fArray.abs.indexOf(1) + 1][0] * fArray.sum)}) }; // formats the data for the transcriber formatMusicData = {arg seq, refChord; var maxSize, voices, durs, baseData, musicData; maxSize = 0; # voices, durs = seq.flatten2(seq.maxDepth - 5).flop; baseData = voices.flop.collect({arg voice, v; var isFirstNote, clumps, hdScores, freqs, fDurs, refs; isFirstNote = false; //this gets the reference instrument and is another way to check things refs = voice.collect({arg item, i; var ref, isSus, isChanged, isFund; ref = [-1, [100, 100], 0]; /* if((i > 0), { if((item != voice[i - 1]) && (item != ["Rest"]), { var ins; ins = voices[i].minIndex({arg hsArray, h; var res = 100000; if((h != v) && (hsArray != ["Rest"]), {res = (hsArray.drop(1) - item.drop(1)).abs.sum}); res }); if(voices[i][ins] != ["Rest"], { ref = [ins, hsArraysToFreqRatio.value(voices[i][ins], item)] }, { ref = [ins, [100, 100]] }) }); }); */ //item.postln; //refChord[v].postln; if((item != ["Rest"]) && isFirstNote.not, {isFirstNote = true}); isSus = isFirstNote && (item == refChord[v]); isChanged = (i > 0) && (item != voice[i - 1]) && (item != ["Rest"]); isFund = (item == [0, 0, 0, 0, 0, 0]); if((item != ["Rest"]) && isSus.not && isChanged, { var ins, fr, dd; ins = voices[i].minIndex({arg hsArray, h; var res = 100000; if((h != v) && (hsArray != ["Rest"]), {res = (hsArray.drop(1) - item.drop(1)).abs.sum}); res; }); fr = hsArraysToFreqRatio.value(voices[i][ins], item); dd = hsArrayDimDiff.value(voices[i][ins], item); ref = [ins, fr, dd] }); if(isFund, {ref = [-1, [1, 1], 0]}); ref }); clumps = voice.separate({arg a, b; a != b }); freqs = clumps.collect({arg clump; if(clump[0] != ["Rest"], {(60.midicps * hsArrayToFreq.value(clump[0]))/*.cpsmidi.round(0.25).midicps*/}, {-1})}); fDurs = durs.clumps(clumps.collect({arg clump; clump.size})).collect({arg clump; clump.sum}); refs = refs.clumps(clumps.collect({arg clump; clump.size})).collect({arg clump; clump[0]}); [freqs, (fDurs / 0.125).round, refs].flop; }); musicData = baseData.collect({arg partData, p; var res; res = partData.collect({arg item, i; var freq, dur, ref, amp, sus, note; # freq, dur, ref = item; sus = dur.asInteger; note = sus.collect({[freq, ref, i]}); note }).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, partData.last)}); 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}) ) ] ); //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.rehearsalMarkFormatter = #format-mark-box-numbers " + "\\tempo 2 = 60\n" + "\\numericTimeSignature \\time 2/2\n" + "\\clef " ++ clef ++ "\n" ++ lyStr + "\\fermata" + " }>> \\bar \"|.\" \n} \n\n>>" ++ "\n>>" }; // barline and ossia definition lyMeasureDef = {arg insName, part, beat; var barline = "|", break = ""; barline = "\\bar \"|\""; //if((beat % 24) == 0, {break = "\\break"}); ////if((beat % 16) == 0, {break = "\\break \\noPageBreak"}); ////if((beat % (16 * 3)) == 0, {break = "\\pageBreak"}); ////if(beat != 0, {"}\n>>\n" + barline + break}, {""}) + "\n<<\n" /*++ ossia*/ + "{"; if(beat != 0, {"}\n" + barline + break}, {""}) + "\n" /*++ ossia*/ + "{" }; 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 = [",,", ",", "", "'", "''", "'''", "''''"]; 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"}); }; lyDur = {arg noteLength; switch(noteLength, 1, {"16"}, 2, {"8"}, 3, {"8."}, 4, {"4"}); }; lyBeamOpen = {"["}; lyBeamClosed = {"]"}; 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, dimDiff, ref, padding = true, lower = 3, attachedToNote = true; var res, num, den, ratio; res = "\\markup {" + if(attachedToNote, {""}, {"\\normalsize"}) + "\\lower #" ++ lower + if(padding, {"\\pad-markup #0.2 "}, {" "}); //ratio = "\"" ++ freqRatioMult[0].asInteger ++ "/" ++ freqRatioMult[1].asInteger ++ "\" }"; /* num = freqRatioMult[0].asInteger; den = freqRatioMult[1].asInteger; ratio = if(num > den, {"+" ++ freqRatioMult[0]}, {"-" ++ freqRatioMult[1]}); ratio = "\"" ++ ratio ++ "\" }"; */ ratio = if(dimDiff > 0, {/*"+" ++ */dimDiff.abs.asString ++ "↑"}, {/*"-" ++ */dimDiff.abs.asString ++ "↓"}); ratio = "\" " ++ ratio ++ "\" }"; res = if(ref != -1, { res ++ "\\concat{ \"" ++ ["IV", "III", "II", "I"][ref] ++ "\"\\normal-size-super " ++ ratio ++ "}" }, { res ++ ratio } ); if(attachedToNote, {"_" ++ res}, {res}) }; lyNote = {arg freq, noteLength, freqRatioMult, dimDiff, ref, spellingPref = \sharps, frHide = false, centHide = false, padding = true; lyNoteName.value(freq, spellingPref) ++ lyDur.value(noteLength) ++ if(frHide.not || centHide.not, { "" }, {""}) }; consolidateNotes = {arg lyStr, part; var noteRegex, markupRegex, fullNoteRegex, restRegex, fullRestRegex, res; noteRegex = "(?[a-g](?:es|is)?(?:[,']*?)?4)"; //markupRegex = if(part != 0, {"()?"}, {"()?"}); markupRegex = "()?"; 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, refChord, dir, addr = nil, buttonID = nil; var basePath, scoreFile, musicData, insData, insNames, insNamesShort, insMidi, insClef; basePath = dir; basePath.mkdir; musicData = formatMusicData.value(rawMusicData, refChord); insData = [ ["IV", "IV", "clarinet", "bass"], ["III", "III", "clarinet", "alto"], ["II", "II", "clarinet", "treble"], ["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.postln +/+ /*"includes" +/+ */ "part_" ++ ["IV", "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, dimDiff, 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[1][0]; freqRatioMult = curMusAtom[1][1]; dimDiff = curMusAtom[1][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 + "\\bar \"|.|\""}); //lyMeasureDef.value(sectionData[i], insNames[p], p, i)}); if(isMeasureBound, {lyStr = lyStr + lyMeasureDef.value(insNames[p], p, i)}); //add note data lyStr = lyStr + lyNote.value(freq, noteLength, freqRatioMult, dimDiff, ref, \sharps, isSame || isRest || (ref < 0), isSame || isRest); //beam group if(isBeamStart, {lyStr = lyStr ++ lyBeamOpen.value}); if(isBeamEnd, {lyStr = lyStr ++ lyBeamClosed.value}); lastMusAtom = curMusAtom; }); }); //wrap music and add staff definitions lyStr = "{" ++ lyStr ++ "}\n\\bar \"||\"\n}"; //consolidate notes and rests lyStr = consolidateNotes.value(lyStr, p); //write file lyFile.write(lyStr); lyFile.close; }); if(addr != nil, {addr.sendMsg(buttonID, 0)}); }; //~transcribe.value(~seq); ) //~transcribe.value(~seq);