{
   "author" : "56228375",
   "name" : "Automatic composition of tonal canons",
   "description" : "Slightly older project which I forgot about until I hit on it by coincidence again... having some fun generating tonal canons (using the terminology \"tonal\" no doubt is a bit of stretch...). The music starts off slowly, then gradually adds voices and rhythmic variations playing in canon and after a while dies out. You can tweak some parameters in the beginning of the program, e.g. to generate microtonal canons or to generate more/less voices, assign different instruments, transpositions, to generate more/less variations. Some probabilities are hard-coded in the program. It should be easy to make these configurable too. It's basically a translation into supercollider of an older python based canon generator I once made (see http://a-touch-of-music.blogspot.com/2013/08/algorithmic-composition-generating.html for some explanation and a link to the python code).",
   "ancestor_list" : [],
   "labels" : [
      "pattern",
      "generative",
      "theoryquark",
      "canon"
   ],
   "id" : "1-5bs",
   "is_private" : null,
   "code" : "// first things first: if you haven't done so already, please install the theory quark\r\n(\r\nQuarks.install(\"https://github.com/shimpe/theoryquark\"); \r\n)\r\n\r\n// then fire off the canon\r\n(\r\no=s.options;\r\no.memSize = 2.pow(16);\r\no.maxNodes_(4096);\r\ns.quit;\r\n\r\ns.waitForBoot({\r\n    // start of user definable code\r\n    var scramble_first= true; // set to true for a wilder effect\r\n    // how many different versions to generate from the base material\r\n    var noVariations = 2;\r\n    // melodic resolution is chromatic half tone; 1 = half tone; 3 = quarter tone; etc...\r\n    var halfToneSubDiv = 1;\r\n    // assign a transposition to every voice (array size determines the number of voices)\r\n    var voice_transpositions = [ 0, 1, -1, 1 ]*12;\r\n    // assign an instrument to every voice: same size as voice_transpositions\r\n    var voice_instruments = [ \\cheappiano, \\cheappiano, \\apad_mh, \\flute];\r\n    // generate base material according to this chord progression\r\n    var chordnotes = [\r\n        [\"c4\",\"eb4\",\"g4\", \"c5\"],\r\n        [\"d4\",\"f4\",\"ab4\", \"bb4\"],\r\n        [\"eb4\",\"g4\",\"c5\",\"d5\"],\r\n        [\"f4\",\"ab4\",\"c5\", \"f5\"],\r\n        [\"g4\",\"bb4\",\"d5\", \"f5\"] ];\r\n    var durations =  [2, 2/3, 2/3, 2/3, 2];\r\n    var scale = TheoryScale.new(\"c\", \"minor\", \"c4 d4 eb4 f4 g4 ab4 bb4\");\r\n    // end of user modifiable code\r\n\r\n    var serialized_chorddegrees;\r\n    var serialized_durations;\r\n    var zipped_deg_dur;\r\n    var spiced = [];\r\n    var pattern = [];\r\n    var canon;\r\n    var spiced_degs = [];\r\n    var spiced_notes = [];\r\n    var spiced_durs = [];\r\n    var pseq = [];\r\n    var chordmidinotes,chorddegrees;\r\n    var chordnotes_scrambled;\r\n    if ((scramble_first), {chordnotes_scrambled = chordnotes.collect({ |c| c.scramble })}, { chordnotes_scrambled = chordnotes} );\r\n\r\n    SynthDef(\\flute, {\r\n        | out = 0, freq = 440, amp = 1.0, a = 0.1, r = 0.1|\r\n        //var fmod = 1; // clean\r\n        //var fmod = LFCub.kr(freq:1/12).range(1, LFNoise2.kr(freq:12.0).range(1,1.1)); // tone deaf flute\r\n        var fmod = LFCub.kr(freq:1/12).range(1, LFNoise2.kr(freq:12.0).range(1,1.02)); // flute-like sound\r\n        var env = EnvGen.ar(Env.perc(a, r), levelScale:0.5, doneAction:2);\r\n        var snd = SinOsc.ar(freq * fmod)!2;\r\n        Out.ar(bus:out, channelsArray:(env*(amp*snd).tanh));\r\n    }).add;\r\n\r\n    SynthDef(\\apad_mh, {arg freq=880, amp=0.5, attack=0.4, decay=0.5, sustain=0.8, release=1.0, gate=1.0, out=0;\r\n        var env,sig,mod1,mod2,mod3;\r\n        env=EnvGen.kr(Env.adsr(attack,decay,sustain,release),gate,levelScale:amp,doneAction:2);\r\n        mod1=SinOsc.kr(6).range(freq*0.99,freq*1.01);\r\n        mod2=LFNoise2.kr(1).range(0.2,1);\r\n        mod3=SinOsc.kr(rrand(4.0,6.0)).range(0.5,1);\r\n        sig=SinOsc.ar([freq,mod1],0,env).distort;\r\n        sig=sig*mod2*mod3;\r\n        Out.ar(out,sig);\r\n    },\r\n    metadata:(\r\n        credit: \"A simple sustained sound with vibrato --Mike Hairston\",\r\n        tags: [\\pad,\\vibrato,\\sustained]\r\n    )).add;\r\n\r\n    SynthDef(\\cheappiano, { arg out=0, freq=440, amp=0.1, dur=1, pan=0;Ê\r\n        var sig, in, n = 6, max = 0.04, min = 0.01, delay, pitch, detune, hammer;\r\n        freq = freq.cpsmidi;\r\n        hammer = Decay2.ar(Impulse.ar(0.001), 0.008, 0.04, LFNoise2.ar([2000,4000].asSpec.map(amp), 0.25));\r\n        sig = Mix.ar(Array.fill(3, { arg i;\r\n            detune = #[-0.04, 0, 0.03].at(i);\r\n            delay = (1/(freq + detune).midicps);\r\n            CombL.ar(hammer, delay, delay, 50 * amp)\r\n        }) );\r\n\r\n        sig = HPF.ar(sig,50) * EnvGen.ar(Env.perc(0.0001,dur, amp * 4, -1), doneAction:2);\r\n        Out.ar(out, Pan2.ar(sig, pan));\r\n    },\r\n    metadata: (\r\n        credit: \"based on something posted 2008-06-17 by jeff, based on an old example by james mcc\",\r\n        tags: [\\casio, \\piano, \\pitched]\r\n    )).add;\r\n\r\n    s.sync;\r\n\r\n    u = (); // u stands for utils\r\n    u.t = (); // u.t stands for utils.transform\r\n    u.t.p = TheoryNoteParser.new;\r\n    u.t.nop = { | degree, duration | [ [degree], [duration] ]; };\r\n    u.t.one_to_three = {\r\n        // transformation that transforms a single note into three notes keeping original duration\r\n        | degree, duration |\r\n        var durmod = [ [1, 1, 2], [2, 1, 1], [1, 2, 1], [1, 1, 1] ].collect({|el| el.normalizeSum}).choose;\r\n        var direction = [ -1, 1, 2, -2, 4, -4].choose;\r\n        [   [ degree, duration*(durmod[0])],\r\n            [ degree+direction, duration*(durmod[1])],\r\n            [ degree, duration*(durmod[2])]\r\n        ]\r\n    };\r\n    u.t.two_to_three = {\r\n        // transformation that transforms two notes into three notes keeping original duration\r\n        | degree, duration, next_degree |\r\n        var dur_subdiv =  [\r\n            [ 1.0/3, 2.0/3],\r\n            [ 2.0/3, 1.0/3],\r\n            [ 0.5, 0.5    ],\r\n            [ 0.75, 0.25  ],\r\n            [ 0.25, 0.75  ]\r\n        ].choose;\r\n        var new_deg = ((degree + next_degree)/2.0).round(1/halfToneSubDiv);\r\n        if ((new_deg == degree), {\r\n            [ [degree, duration] ];\r\n        }, {\r\n            [\r\n                [ degree, dur_subdiv[0]*duration ],\r\n                [ new_deg, dur_subdiv[1]*duration ]\r\n            ];\r\n        });\r\n    };\r\n    u.t.two_to_four = {\r\n        // transformation that transforms two notes into four notes keeping original duration\r\n        | degree, duration, next_degree |\r\n        var dur_subdiv = [\r\n            [ 0.5, 0.25, 0.25 ],\r\n            [ 0.25, 0.5, 0.25 ],\r\n            [ 0.333, 0.333, 0.334 ],\r\n        ].choose;\r\n        var direction = [ 1, -1].choose;\r\n        var mid_deg = ((degree + next_degree)/2.0).round(1/halfToneSubDiv);\r\n        //\"2-to-4\".postln;\r\n        if ((mid_deg == degree), {\r\n            [\r\n                [degree, duration*dur_subdiv[0]],\r\n                [next_degree + direction, duration*dur_subdiv[1]],\r\n                [next_degree-direction, duration*dur_subdiv[2] ]\r\n            ];\r\n        }, {\r\n            [\r\n                [degree, duration*dur_subdiv[0]],\r\n                [mid_deg, duration*dur_subdiv[1]],\r\n                [next_degree+direction, duration*dur_subdiv[2]]\r\n            ];\r\n        });\r\n    };\r\n    u.t.spiceup_singlenotes = {\r\n        // runs over all notes and randomly replaces some notes with three notes\r\n        | deg_dur, probability = 0.3 |\r\n        var deg = deg_dur[0];\r\n        var dur = deg_dur[1];\r\n        probability.coin.if({\r\n            u[\\t][\\one_to_three].value(deg, dur);\r\n        }, {\r\n            [ [deg, dur] ];\r\n        });\r\n    };\r\n    u.t.spiceup_twonotes = {\r\n        // runs over all notes and randomly replaces some consecutive notes with three or four notes\r\n        | deg_dur, next_deg_dur, probability = 0.3, techniques = #[\\two_to_three, \\two_to_four] |\r\n        var deg = deg_dur[0];\r\n        var dur = deg_dur[1];\r\n        var technique = techniques.choose;\r\n        var next_deg = next_deg_dur[0];\r\n        probability.coin.if({\r\n            u[\\t][technique].value(deg, dur, next_deg);\r\n        }, {\r\n            [ [deg, dur] ];\r\n        });\r\n    };\r\n\r\n    chordmidinotes = chordnotes_scrambled.collect({|chord| u[\\t][\\p].asMidi(chord)});\r\n    chorddegrees = chordmidinotes.collect({|chordmidi| scale.midiToDegreeNotNorm(chordmidi); });\r\n    serialized_chorddegrees = chorddegrees.lace(chorddegrees.flat.size);\r\n    serialized_durations = durations.wrapExtend(chorddegrees.flat.size);\r\n    zipped_deg_dur = (chorddegrees.flat.size).collect({ |i| [serialized_chorddegrees[i], serialized_durations[i]] });\r\n\r\n    // first make a collection of noVariations copies of the base material\r\n    noVariations.do({\r\n        | i |\r\n        spiced = spiced.add(zipped_deg_dur);\r\n    });\r\n\r\n    // now spice up the base material\r\n    // some transformations require at least one previous note (hence i>1), or two previous notes (hence i>2)\r\n    // whether a transformation is done or not is left to coincidence\r\n    noVariations.do({\r\n        | i |\r\n\r\n        if ((i > 0), {\r\n\r\n            spiced[i] = spiced[i].collect({\r\n                |el|\r\n                u[\\t][\\spiceup_singlenotes].value(el, 0.6);\r\n            }).flatten(1);\r\n\r\n            spiced[i] = spiced[i].collect({\r\n                |el, idx|\r\n                u[\\t][\\spiceup_twonotes].value(el, spiced[i].foldAt(idx+1), 0.5, [\\two_to_four]);\r\n            }).flatten(1);\r\n        });\r\n\r\n        if ((i > 1), {\r\n\r\n            spiced[i] = spiced[i].collect({\r\n                |el|\r\n                u[\\t][\\spiceup_singlenotes].value(el);\r\n            }).flatten(1);\r\n\r\n            spiced[i] = spiced[i].collect({\r\n                |el, idx|\r\n                u[\\t][\\spiceup_twonotes].value(el, spiced[i].foldAt(idx+1), 0.3, [\\two_to_three, \\two_to_four]);\r\n            }).flatten(1);\r\n        });\r\n\r\n        if ((i > 2), {\r\n            spiced[i] = spiced[i].collect({\r\n                |el|\r\n                u[\\t][\\spiceup_singlenotes].value(el);\r\n            }).flatten(1);\r\n            0.33.coin.if({\r\n                spiced[i] = spiced[i].collect({\r\n                    |el, idx|\r\n                    u[\\t][\\spiceup_twonotes].value(el, spiced[i].foldAt(idx+1), 0.3, [\\two_to_three, \\two_to_four]);\r\n                }).flatten(1);\r\n            });\r\n        });\r\n    });\r\n\r\n    // unfold the chords into canon melodies\r\n    noVariations.do({\r\n        | i |\r\n        spiced_degs = spiced_degs.add(spiced[i].lace(spiced[i].size * 2).keep(spiced[i].size));\r\n        spiced_notes = spiced_notes.add(scale.degreeNotNormToMidi(spiced_degs[i]));\r\n        spiced_durs = spiced_durs.add(spiced[i].lace(spiced[i].size * 2).drop(spiced[i].size));\r\n    });\r\n\r\n    // collect all material into patters\r\n    noVariations.do({\r\n        | i |\r\n        pattern = pattern.add(Pbind(\r\n            \\instrument, \\melody2,\r\n            \\midinote, Pseq(spiced_notes[i], 3*noVariations),\r\n            \\dur, Pseq(spiced_durs[i], 3*noVariations),\r\n            \\amp, 0.1,\r\n            \\a, Pkey(\\dur),\r\n        ));\r\n    });\r\n\r\n    // apply transpositions for the different voices\r\n    noVariations.do({\r\n        |v|\r\n        var sz = voice_transpositions.size;\r\n        voice_transpositions.do({\r\n            |t, i|\r\n            var idx = ((v*sz)+i);\r\n            pseq = pseq.add(idx*durations.sum);\r\n            pseq = pseq.add(Pbindf(pattern[v], \\ctranspose, t, \\instrument, voice_instruments[i]));\r\n        });\r\n    });\r\n\r\n    // put all material in parallel to get the final canon\r\n    canon = Ptpar(pseq);\r\n\r\n    // play the canon\r\n    canon.play;\r\n});\r\n)"
}
