( // helper funcs var hsArrayToCents, pDist, hdSum, hsChordalDistance, hsArrayToFreq; // score funcs var isInRange, spacingScore, rangeScore, intervalScore, inclusionScore; // subroutines var genTuples, initVoices, genOrder, genSubMotif, updateVoices; // primary routines var genMotif, genSecondarySeq; // audition funcs var genPatterns, genMidiPatterns; // resource management funcs var writeResources, prettifyArray, setSeeds, sanityCheck, msgInterpret; // global vars (many set by OSC funcs at bottom) var refSeed, seed, lastXChanges, popSize, exPath, dir, primes, dims, tuples, ranges, durFunc, seq, group, player; //------helper funcs hsArrayToCents = { arg hsArray; hsArray.collect({arg dist, p; dist * 1200 * log2(primes[p][0]/primes[p][1])}).sum }; pDist = { arg array1, array2, signed = false; var pDistance; pDistance = hsArrayToCents.value(array1) - hsArrayToCents.value(array2); if(signed, {pDistance}, {abs(pDistance)}) }; hdSum = { arg hsArrays; var size, distances, mean; size = hsArrays.size; distances = (size - 1).collect({arg i; ((i + 1)..(size - 1)).collect({arg j; abs(hsArrays[i] - hsArrays[j]).collect({arg dist, p; dist * log2(primes[p].product)}).sum }); }).flat; mean = distances.sum / distances.size; distances.sum //mean + ((1 / sqrt((pow(distances - mean, 2)).sum / distances.size)) * mean) }; hsChordalDistance = { arg hsArrays1, hsArrays2; var size, distances, mean; size = hsArrays1.size; distances = hsArrays1.size.collect({arg i; hsArrays2.size.collect({arg j; abs(hsArrays1[i] - hsArrays2[j]).collect({arg dist, p; dist * log2(primes[p].product)}).sum }); }).flat; mean = distances.sum / distances.size; distances.sum //mean + ((1 / sqrt((pow(distances - mean, 2)).sum / distances.size)) * mean) }; hsArrayToFreq = { arg array; array.collect({arg dim, d; pow(primes[d][0]/primes[d][1], dim)}).product }; //------score funcs /* isInRange = { arg hsArray, min, max; var cents; cents = hsArrayToCents.value(hsArray); (cents >= min) && (cents <= max) }; */ spacingScore = { arg hsArrays, min; var centsArray; centsArray = hsArrays.collect({arg hsArray; hsArrayToCents.value(hsArray)}).sort({arg a, b; a < b}); centsArray.differentiate.drop(1).collect({arg pDistance; if(pDistance >= min, {1}, {0.01})}).sum; }; rangeScore = { arg hsArray1, hsArray2, min, max, low, signed = false; var pDistance; pDistance = pDist.value(hsArray1, hsArray2, signed); if((pDistance >= min) && (pDistance <= max), {1}, {low}); }; intervalScore = { arg hsArray1, hsArray2, mean, sd, signed = false; var pDistance; pDistance = pDist.value(hsArray1, hsArray2, signed); pDistance.gaussCurve(1, mean, sd) }; inclusionScore = { arg array, test, min = 0.01; if(array.collect({arg v; v.hash}).includes(test.hash), {min}, {1}); }; //------subroutines genTuples = { var tuples; tuples = dims.collect({[-1, 0, 1]}).allTuples.select({arg tuple; (abs(tuple.drop(1)).sum <= 1) && (tuple[0] == 0)}); tuples = tuples ++ tuples.collect({arg tuple; [-3, -2, -1, 1, 2, 3].collect({arg octTrans; tuple.deepCopy.put(0, octTrans)})}).flatten; }; initVoices = { var init, voicesInit; voicesInit = popSize.collect({dims.collect({0})}); /* voicesInit = [dims.collect({0})]; (popSize - 1).do({ arg rep, new; rep = dims.rand; new = voicesInit.last.deepCopy; new[rep] = new[rep] + [-1, 1].choose(); voicesInit = voicesInit.add(new); }); */ voicesInit.deepCopy; }; genOrder = {arg minLength = 0, maxLength = 5; var noProgIns, noSusIns, noSilentIns, prog, sus, silent, order; noProgIns = (popSize - 1).rand + 1; noSusIns = (popSize - noProgIns).rand + 1; noSilentIns = popSize - noSusIns - noProgIns; # prog, sus, silent = (0..(popSize-1)).scramble.clumps([noProgIns, noSusIns, noSilentIns]); prog = (prog.scramble ++ ((maxLength - minLength).rand + minLength).collect({prog.choose}).scramble); if(silent == nil, {silent = []}); [sus.scramble, prog, silent.scramble] }; updateVoices = {arg ins, sus; var voices, candidates, nWeights, nProbs, sel; voices = lastXChanges.deepCopy.last; candidates = sus.collect({arg v; tuples.collect({arg t; voices[v] + t})}).flatten; candidates = difference(candidates.asSet, voices.asSet).asList; nProbs = candidates.collect({arg candidate; var stepScore, recentlySoundedScore, isInRangeScore, regScore, hdScore; //stepScore = intervalScore.value(voices[ins], candidate, 30, 400, 0.1); stepScore = intervalScore.value(voices[ins], candidate, 100, 100); recentlySoundedScore = inclusionScore.value(lastXChanges.flop[ins], candidate, 0); isInRangeScore = rangeScore.value(candidate, candidate.collect({0}), ranges[ins][0], ranges[ins][1], 0, true); regScore = spacingScore.value(voices.deepCopy.put(ins, candidate), 300); hdScore = 1/pow(hdSum.value(voices.deepCopy.put(ins, candidate)), 2); //maybe what you want here is a vector to another root and then favoring movement towards it. //distScore = pow(hsChordalDistance.value(voices, voices.put(ins, candidate)), 2); [stepScore, recentlySoundedScore, isInRangeScore, regScore, hdScore] }); nWeights = [1, 1, 1, 1, 1]; //this handles nWeights of 0; mainly for testing nProbs = nProbs.flop.select({arg scores, s; nWeights[s] != 0}).flop; nWeights = nWeights.select({arg weight; weight != 0}); nProbs = nProbs.flop.collect({arg scores, s; if(scores.sum == 0, {scores}, {scores.normalizeSum * nWeights[s]}) }); nProbs = nProbs.flop.collect({arg scores, s; scores.product}).normalizeSum; sel = candidates.wchoose(nProbs); voices[ins] = sel; lastXChanges = lastXChanges.add(voices).keep(-5); }; genSubMotif = {arg order, lastState, repeatLast = false, startFromLast = false, isLastOrder = false; var sus, prog, silent, res, lastIns, lastXChangesHold, voices, adder; # sus, prog, silent = order; lastXChangesHold = lastXChanges.deepCopy; voices = lastState.deepCopy; lastIns = nil; res = []; "------generating motif".postln; //need to figure out here if voices move between motifs (silent ++ sus ++ prog).do({arg ins, i; if(prog.includes(ins) && repeatLast.not, {updateVoices.value(ins, sus)}); adder = if(silent.includes(ins), {["Rest"]}, {lastXChanges.last.deepCopy[ins]}); if(voices[ins] != adder, { var dur; //dur = [durFunc.value(), 0].wchoose([1, 0].normalizeSum); dur = durFunc.value(lastIns, ins); voices[ins] = adder; res = res.add([voices.deepCopy.postln, dur.round(0.125)]); }); lastIns = ins; }); // pad ending if(isLastOrder, { (0..(popSize-1)).scramble.do({arg ins; if(res.last.first[ins] != ["Rest"], { var dur; voices[ins] = ["Rest"]; //dur = [durFunc.value(), 0].wchoose([1, 0].normalizeSum); dur = durFunc.value(lastIns, ins); res = res.add([voices.deepCopy.postln, dur.round(0.125)]); }); lastIns = ins; }); }); //format and return if(startFromLast, {lastXChanges = lastXChangesHold}); res; }; //------primary routines genMotif = {arg inOrders; var orders, repeats, fSeq; repeats = 1; fSeq = []; repeats.do({arg index; var motif; motif = []; orders = inOrders; orders.do({arg order, o; var lastState, subMotif; lastState = if(o == 0, {popSize.collect({["Rest"]})}, {motif.last.last.first}); subMotif = genSubMotif.value(order, lastState, isLastOrder: o == (orders.size - 1)); motif = motif.add(subMotif); }); sanityCheck.value(motif, index); fSeq = fSeq.add(motif); }); fSeq }; genSecondarySeq = {arg seq; var curdles, fSeq; curdles = []; while({curdles.sum < seq.size}, {curdles = curdles ++ [3.rand + 1]}); fSeq = seq.clumps(curdles).collect({arg clump, m; var repeats, paddedSeq; //add rest paddedSeq = clump.add([[[popSize.collect({["Rest"]}), 0.5.rand]]]); //implement repeats repeats = [0.rand + 1, 1].wchoose([1, 0].normalizeSum); repeats.collect({paddedSeq}); }); fSeq }; //------audition funcs genPatterns = {arg seq; var voices, durs, patterns, res; # voices, durs = seq.flatten2(seq.maxDepth - 5).flop; res = Ppar( voices.flop.collect({arg voice; var clumps, hdScores, freqs, fDurs; clumps = voice.separate({arg a, b; a != b }); freqs = clumps.collect({arg clump; if(clump[0] != ["Rest"], {(60.midicps * hsArrayToFreq.value(clump[0]))}, {Rest(0)})}); fDurs = durs.clumps(clumps.collect({arg clump; clump.size})).collect({arg clump; clump.sum}); Pbind( \instrument, \test, \group, group, \freq, Pseq(freqs, 1), \dur, Pseq(fDurs, 1), \sustain, Pseq(fDurs, 1) ); }); ); res }; genMidiPatterns = {arg seq; var voices, durs, patterns, res, mOut, pbRange; pbRange = 1; //semitones - change this as needed for your situation mOut = MIDIOut.newByName("TiMidity", "TiMidity port 0").latency_(Server.default.latency); # voices, durs = seq.flatten2(seq.maxDepth - 5).flop; res = Ppar( voices.flop.collect({arg voice, v; var clumps, hdScores, freqs, fDurs; mOut.program(v, 70); clumps = voice.separate({arg a, b; a != b }); freqs = clumps.collect({arg clump; if(clump[0] != ["Rest"], {(60.midicps * hsArrayToFreq.value(clump[0]))}, {Rest(0)})}); fDurs = durs.clumps(clumps.collect({arg clump; clump.size})).collect({arg clump; clump.sum}); Pbind( \type, \midi, \chan, v, \noteval, Pseq(freqs.cpsmidi - 24, 1), \note, Pfunc({ | event | event[\noteval].floor }), \dur, Pseq(fDurs, 1), \midiout, mOut, \amp, 1, \bend, Pfunc({ | event | if (event[\note].isRest.not) { var pitchbendvalue = event[\noteval].frac.linlin(0, pbRange, 8192, 8192*2).asInteger; m.bend(v, pitchbendvalue); }; 0; // return something other than nil to avoid stopping the pattern }), ); }); ); res }; //------resource management funcs setSeeds = {arg inRefSeed, inSeed; refSeed = if(inRefSeed.isNumber, {inRefSeed.asInteger}, {nil}); seed = if(inSeed > 1, {inSeed.asInteger}, {rrand(100000, 999999)}); thisThread.randSeed = seed; }; prettifyArray = {arg data, finDepth = 1; var prettyString = "", rCount = 0, writeArray; writeArray = {arg array; var depth, indent; depth = array.maxDepth; indent = rCount.collect({" "}).join(""); prettyString = prettyString ++ indent ++ "[\n"; rCount = rCount + 1; if(depth > 5, { array.do({arg subArray; writeArray.value(subArray); }); }, { array.do({arg data, d; prettyString = prettyString ++ indent ++ " " ++ data.asCompileString ++ if(d != (array.size - 1), {",\n"}, {""}); }); }); rCount = rCount - 1; if(rCount < (finDepth - 1), {prettyString = prettyString.drop((finDepth - 1).neg)}); //if(rCount == 0, {prettyString = prettyString.drop((finDepth - 1).neg)}); prettyString = prettyString ++ "\n" ++ indent ++ "]" ++ if(rCount > 0, {",\n"}, {""}); }; writeArray.value(data); prettyString }; writeResources = {arg seq, path; var dir, file, resString; file = File(path,"w"); resString = "{\nmusic_data:\n"; resString = resString ++ prettifyArray.value(seq, 3); resString = resString ++ ",\nlast_changes:\n"; resString = resString ++ prettifyArray.value(lastXChanges, 1); resString = resString ++ ",\nseed: " ++ seed ++ ",\nref_seed: " ++ refSeed ++ "\n}"; file.write(resString); file.close; }; sanityCheck = {arg motif, index; //print functions - very helpful ("----------" + index + "------------").postln; motif.flatten.do({arg val, v; if(v > 0, { if(motif.flatten[v-1][0].hammingDistance(val[0]) > 1, {"problem 1".postln}); if(motif.flatten[v-1][0].hammingDistance(val[0]) == 0, {"problem 2".postln}); }); val.postln }); "***********".postln; }; msgInterpret = {arg in; var res; res = in.asCompileString; res = res.replace(" ", "").replace("\n", "").replace("\t", ""); res = res.replace("\'", "").replace("\"", "").replace("Rest", "\"Rest\""); res.interpret }; //------global vars primes = [[2, 1], [3, 2], [5, 4], [7, 4], [11, 8], [13, 8]]; ranges = [[-2400, 0], [-1200, 1200], [0, 2400], [0, 2400]]; exPath = thisProcess.nowExecutingPath; dir = exPath.dirname; popSize = 4; dims = primes.size; tuples = genTuples.value(); refSeed = nil; group = Group.new; //------OSC funcs OSCdef(\gen, {arg msg, time, addr, port; var orders, condition; msg.postln; durFunc = nil; addr.sendMsg("/STATE/SEND"); { while({durFunc == nil}, {0.1.wait}); setSeeds.value(msg[1].postln, msg[2]); lastXChanges = if(refSeed == nil, { [initVoices.value().deepCopy]; }, { var file; file = File((dir +/+ "resources" +/+ refSeed ++ "_music" ++ ".json").standardizePath, "r"); msgInterpret.value(file.readAllString.parseJSON["last_changes"]); }); if(msg.size == 4, { orders = msgInterpret.value(msg[3]); }, { var minLength, maxLength; minLength = msg[3]; maxLength = msg[4]; orders = ((maxLength - minLength).rand + minLength).collect({genOrder.value(msg[5], msg[6])}); }); orders.postln; seed.postln; refSeed.postln; seq = genMotif.value(orders); //patterns = genPatterns.value(seq); addr.sendMsg("/current_seed", seed); addr.sendMsg("/order", prettifyArray.value(orders, 1)); addr.sendMsg("/mus_seq", prettifyArray.value(seq, 3)); }.fork; }, \gen); OSCdef(\commit, {arg msg, time, addr, port; var ledgerPath, oldLedger, newLedger, musSeq; //msg.postln; seed.postln; //File.copy(exPath, (dir +/+ "resources" +/+ seed ++ "_code" ++ ".scd").standardizePath); //addr.sendMsg("/SESSION/SAVE", (dir +/+ "resources" +/+ seed ++ "_gui_session" ++ ".json").standardizePath); //addr.sendMsg("/STATE/SAVE", (dir +/+ "resources" +/+ seed ++ "_gui_state" ++ ".state").standardizePath); writeResources.value(seq, (dir +/+ "resources" +/+ seed ++ "_music" ++ ".json").standardizePath); ledgerPath = (dir +/+ "resources" +/+ "piece_ledger" ++ ".json").standardizePath; oldLedger = File(ledgerPath, "r"); musSeq = msgInterpret.value(oldLedger.readAllString.parseJSON["ledger"]); oldLedger.close; File.delete(ledgerPath ++ "_bak"); File.copy(ledgerPath, ledgerPath ++ "_bak"); File.delete(ledgerPath); newLedger = File(ledgerPath, "w"); musSeq = musSeq.add(seed); newLedger.write("{\nledger:\n" ++ prettifyArray.value(musSeq, 1) ++ "\n}"); newLedger.close; //refSeed = seed; }, \commit); OSCdef(\transport, {arg msg, time, addr, port; msg.postln; if(msg[1] == 0, { player.stop; group.set(\gate, 0); }, { var cSize, ledgerPath, ledger, patterns, pSeq; ledgerPath = (dir +/+ "resources" +/+ "piece_ledger" ++ ".json").standardizePath; ledger = msgInterpret.value(File(ledgerPath, "r").readAllString.parseJSON["ledger"]); pSeq = []; if(msg[2].asString != "all", {ledger = ledger.keep(msg[2].asInteger - 1)}); ledger.do({arg rSeed; var file; file = File((dir +/+ "resources" +/+ rSeed.postln ++ "_music" ++ ".json").standardizePath, "r"); pSeq = pSeq.add(msgInterpret.value(file.readAllString.parseJSON["music_data"])); file.close; }); pSeq = pSeq.add(seq); patterns = genPatterns.value(pSeq); player = Pfset(pattern: patterns, cleanupFunc: { addr.sendMsg("/transport", 0); }); player = player.play }); }, \transport); OSCdef(\range, {arg msg; msg.postln; ranges[msg[1]][msg[2]] = msg[3] }, \range); OSCdef(\dur_probs_env, {arg msg; var env, pTable, min, max, cProb; msg.postln; env = Env.pairs([[0, 0]] ++ msg[4..].clump(2) ++ [[1, 0]]).asSignal(256).asList.asArray; pTable = env.asRandomTable; min = msg[1]; max = msg[2]; cProb = msg[3]; durFunc = {arg lIns, cIns; if(lIns.postln == cIns.postln, { pTable.tableRand * (max - min) + min }, { if(1.0.rand < cProb.postln, {0}, {pTable.tableRand * (max - min) + min}).postln; }); }; }, \dur_probs_env); ) ( SynthDef(\test, {arg freq, gate = 1, sustain, amp, dur; var trig, exc, sig1, sig2, noHarms; noHarms = 30; exc = Saw.ar(freq, TRand.ar(0.5, 1, Impulse.ar(freq))) * 0.001 + Dust.ar(10000, 0.01); sig1 = (Klank.ar(`[ Array.series(noHarms, freq, freq), Array.geom(noHarms, 1, 0.2) + Array.fill(noHarms, {rrand(0.01, 0.03)}), Array.fill(noHarms, {rrand(1, 2)}) ], exc) * 0.5).softclip; sig1 = HPF.ar(sig1, 300); Out.ar([0, 1], sig1 * EnvGen.kr(Env.adsr(0.3, 0.3, 0.9, 0.5, 0.9), gate, doneAction: 2)); }).add; ) ( SynthDef(\test, {arg freq, gate = 1, sustain, amp, dur; var trig, exc, sig1, sig2, noHarms, freqFinal, start, end; noHarms = 30; freq = WhiteNoise.ar * 3 + freq; freqFinal = Duty.ar((1/freq), 0, freq); trig = Changed.ar(freqFinal); start = Demand.ar(trig, 0, Dwhite(-1, -0.75)); end = Demand.ar(trig, 0, Dwhite(0.75, 1)); exc = Phasor.ar(trig, (end - start) * freqFinal / SampleRate.ir, start, end, 0) * 0.001 + Dust.ar(10000, 0.01); sig1 = (Klank.ar(`[ Array.series(noHarms, freq, freq), Array.geom(noHarms, 1, 0.2) + Array.fill(noHarms, {rrand(0.01, 0.03)}), Array.fill(noHarms, {rrand(2, 3)}) ], exc) * 0.5).softclip; sig1 = HPF.ar(sig1, 300); Out.ar([0, 1], sig1 * EnvGen.kr(Env.adsr(0.3, 0.3, 0.9, 0.5, 0.9), gate, doneAction: 2)); }).add; ) File((~dir +/+ "resources" +/+ 517313 ++ "_music" ++ ".json").standardizePath, "r").readAllString.parseJSON["last_changes"].asString.interpret[0][0][0].isNumber "{\"a\": 1}".parseYAML["a"].asInteger; "{\"a\": 1}".parseJSON["a"].isNumber;