/* H. James Harkins: Blue Rondo Phase Composed Dec. 7-9, 2012, to commemorate the life and work of Dave Brubeck. A minimalist treatment of one of Brubeck's most memorable themes. License: CC BY-SA: Attribution-ShareAlike See: http://creativecommons.org/licenses/by-sa/3.0/ This license lets others remix, tweak, and build upon your work even for commercial purposes, as long as they credit you and license their new creations under the identical terms. This license is often compared to "copyleft" free and open source software licenses. All new works based on yours will carry the same license, so any derivatives will also allow commercial use. */ ( // init stuff: run this once for multiple plays TempoClock.tempo = 126/60; s.waitForBoot { SynthDef(\fm, { |out, gate = 1, freq = 440, amp = 0.1, mod_lev = 1, mod_ratio = 1, det = 3, car_detune = 1.003, car_buf, mod_buf, car_vs, mod_vs, pan = 0| var sig, mod, car_amp, mod_amp, holdgate; var sensitivity = { |ugen, sens| (ugen - 1) * sens + 1 }; var mod_env = Env(#[0, 1, 0.4, 0, 0], #[0.01, 0.12, 8, 0.5], -4, releaseNode: 3), car_env = Env(#[0, 1, 0.5, 0, 0], #[0.01, 0.1, 12, 0.1], -4, releaseNode: 3); holdgate = Latch.kr(gate, gate); car_amp = sensitivity.value(gate, car_vs) * amp; mod_amp = sensitivity.value(gate, mod_vs) * EnvGen.kr(mod_env, gate); mod = Osc.ar(mod_buf, freq.madd(mod_ratio * [1, car_detune], det * [1, -1]), 0, mod_amp * mod_lev); freq = freq * [1, car_detune]; sig = Mix(Osc.ar(car_buf, freq + (mod * freq), 0, car_amp)) * EnvGen.kr(car_env, gate, doneAction:2); Out.ar(out, Pan2.ar(sig, pan)); }).add; SynthDescLib.at(\fm).msgFuncKeepGate = true; SynthDef(\analog, { |out, gate = 1, freq = 440, amp = 0.1, det = 1.003, ffreq = 2000, rq = 1, ffbump = 1, ffdecay = 0.12, pan = 0, freqlag = 0.08| var sig = Mix(Saw.ar(Lag.kr(freq, freqlag) * [1, det])), ffreqEnv = EnvGen.kr(Env([1, ffbump, 1], [0.01, ffdecay], \exp)), ampEnv = EnvGen.kr(Env.adsr(0.01, 0.25, 0.6, 0.15), gate, doneAction: 2); sig = RLPF.ar(sig, Lag.kr(ffreq, 0.08) * ffreqEnv, Lag.kr(rq, 0.08)); Out.ar(out, Pan2.ar(sig, pan, amp)); }).add; b = Buffer.alloc(s, 2048, 1, completionMessage: { |buf| buf.sine1Msg(#[1]) }); c = Bus.control(s, 1); a = Bus.audio(s, 2); SynthDef(\vardelay, { |in, out, dtime = 1| var sig = In.ar(in, 2), delay = DelayL.ar(sig, 2, dtime); Out.ar(out, sig + delay.reverse); }).add; s.sync; z = Synth(\vardelay, [in: a, out: 0, dtime: 1]); y = { var sig = In.ar(0, 2); sig = FreeVerb2.ar(sig[0], sig[1], 0.45, 0.45, 0.5); ReplaceOut.ar(0, sig); }.play(addAction: \addToTail); }; ) ( // the piece var ctlSynth, fadeSynth, earlyStop = true, fadeWatcher; p = Penvir( ( // stock phrases, used starting in the middle section phrases: ( main: Pbind( \degree, Pseq([2, 0, 2, 0, 2, 0, -1, 0, 1], 1), \dur, 1/3, \legato, Pseq([Pseq([1.01, 0.3], 3), Pseq([1.01, 1.01, 0.3], 1)]) ), main2: Pbind( \degree, Pseq([2, 0, 2, 0, 2, 0, 3, 2, 1], 1), \dur, 1/3, \legato, Pseq([Pseq([1.01, 0.3], 3), Pseq([1.01, 1.01, 0.3], 1)]) ), aux: Pbind( \degree, Pslide((0..4), 3, len: 3, step: 1, start: 0, wrapAtEnd: false), \dur, 1/3, \legato, Pseq([1.01, 1.01, 0.3], 3) ), mainP: Pbind( \degree, Pseq([2, 4, 3.1, 5, 4, 6, 5, 6, 7], 1), \dur, 1/3, \legato, Pseq([Pseq([1.01, 0.3], 3), Pseq([1.01, 1.01, 0.3], 1)]) ), main2P: Pbind( \degree, Pseq([6, 4, 5, 3.1, 4, 2, 3.1, 2, 1.1], 1), // 3.1, 1.1 = sharp \dur, 1/3, \legato, Pseq([Pseq([1.01, 0.3], 3), Pseq([1.01, 1.01, 0.3], 1)]) ), auxP: Pbind( \degree, Pslide(#[6, 5, 4, 3.1, 2], 3, len: 3, step: 1, start: 0, wrapAtEnd: false), \dur, 1/3, \legato, Pseq([1.01, 1.01, 0.3], 3) ), ), // function to handle an "interesting" transposition by a diatonic third transpose3rd: { |deg| if(deg == 3.1) { 1.9 } { 2 } }, // default parameters for all fm-synth events fmProto: ( instrument: \fm, mod_lev: 6.8475657815777, mod_ratio: 1, car_detune: 1.00851763877, car_vs: 0.81102362204724, mod_vs: 0.81102362204724, car_buf: b, mod_buf: b ), ), Pspawner({ |sp| ~ostPat = PbindProxy( \instrument, \fm, \octave, 4, \degree, Ptuple([ 0, Pseq([ Pseq(#[6, 5.9], { rrand(4, 9) }), Pseq(#[6, 5.9, 5, 4.9, 4], 1) ], inf) ]), \gate, Pseg( Pseq(#[0.1, 0.55], inf), Pwhite(15, 25, inf) ), \dur, Pwhite(3, 14, inf) / 3, \amp, 0.15 ); ~ost = sp.par(Pchain(~ostPat, ~fmProto)); c.set(3 / thisThread.clock.tempo); z.map(\dtime, c); // play opening gestures, with progressively less time between each ~accel = { |modDur| while { (~dur = ~durStr.next) > 1 } { ~dur = modDur.value(~dur); sp.par( Pfindur( ~dur * 1.4, Pbindf(~pat, \ffreq, Env( [rrand(1500, 3000), rrand(200, 400), rrand(5000, 10000), rrand(100, 200)], [0.8, 0.2, 0.25] * ~dur, \exp ) ) ) ); sp.wait(~dur); }; }; ~pat = Pmono(\analog, \degree, Pseq(#[2, 0], inf), \dur, 1/3, \ffbump, 6, \rq, 0.2, \pan, Plazy({ Pseries(2pi.rand, 2pi / rrand(20.0, 40.0), inf).sin }), ); ~durStr = Env(#[5, 1], #[54]).asStream; ~accel.value({ |dur| (rrand(1, 3) * 2 + dur).roundUp(2) + 1 }); ~bassPat = PbindProxy( \octave, #[2, 3], \degree, 0, \freqlag, 3.5, \ffreq, Pexprand(200, 800, inf) * Pwrand(#[1, 8, 22], #[0.6, 0.3, 0.1], inf), \rq, 0.2, \dur, Pseq([Pn(2/3, { rrand(1, 3) }), Pn(1/3, { rrand(5, 14) })], inf), \amp, Env(#[0, 0.082], #[40], 2) ); ~bass = sp.par(Pchain(Pmono(\analog, \dummy, 0), ~bassPat)); ~pat = Pmono(\analog, \degree, Pseq([2, 0, 2, 0, 2, 0, -1, 0, 1, Pseq(#[2, 0], inf)], 1), \dur, 1/3, \ffbump, 6, \rq, 0.2, \pan, Plazy({ Pseries(2pi.rand, 2pi / rrand(20.0, 40.0), inf).sin }), ); ~durStr = Env(#[5, 1], #[40]).asStream; ~accel.value({ |dur| (rrand(1, 3) * 2 + dur).roundUp(2) }); 3.do { sp.par( PmonoArtic( \analog, \degree, Pseq([2, 0, 2, 0, 2, 0, -1, 0, 1], 1), \dur, 1/3, \legato, Pseq([Pseq([1.01, 0.3], 3), Pseq([1.01, 1.01, 0.3], 1)]), \ffbump, 6, \ffreq, Env([rrand(2000, 3000), rrand(250, 500)], #[3], \exp), \amp, 0.1 * 3.dbamp, \out, a, \pan, -0.8 ) ); sp.wait(6); }; // Reichian phasing control ctlSynth = { DemandEnvGen.kr( level: Dswitch1([ Dseries(3, -1/3, 9), Dseries(8/3, -1/3, 8), ], Dseq(#[0, 1], inf)) / TempoClock.tempo, dur: Dseq(#[24, 12], inf) * (3 / TempoClock.tempo), ) }.play(outbus: c); ~notes = PbindProxy( \add, 0, \dg, Pseq([2, 0, 2, 0, 2, 0, -1, 0, 1], inf), \degree, Pkey(\dg) + Pkey(\add), \dur, 1/3, \legato, Pseq([Pseq([1.01, 0.3], 3), Pseq([1.01, 1.01, 0.3], 1)], inf), \ffbump, 6, \ffreq, Pseg( Pseq([Pwhite(2000, 3000, 1), Pwhite(250, 500, 1)], inf), Pseq(#[2.99, 0.01], inf), \exp ), \out, a, \pan, -0.8, \amp, Pseq(0.1 * [3.dbamp, Pn(1.5.dbamp, inf)]) ); ~noteStream = sp.par( Pchain( PmonoArtic(\analog, \out, a), ~notes ) ); sp.wait(24); // Here's something you probably wouldn't have guessed: // Pmono can play chords! ~notes.set(\add, #[0, 2]); sp.wait(24); ~notes.set(\add, 0); ~notes.set(\dg, Pseq(#[4, 2, 4, 2, 4, 2, 1.1, 2, 3.1], inf).collect { |d| [d, d + ~transpose3rd.value(d)] }); ~ostPat.set(\degree, Ptuple([ 2, Pseq([ Pseq(#[8, 7], { rrand(4, 9) }), Pseq(#[8, 7, 6, 5.9], 1) ], inf) ])); ~bassPat.set(\degree, 2); sp.wait(48); ~notes.set(\add, Pfunc { nil }); // cheat way to stop the ~notes pattern ~phrasePat = PatternProxy(Pseq(#[main, main2, main, aux], inf)); ~addPat = PatternProxy(Pn(0, inf)); // recognizable theme [0, Ptuple([0, Pfunc({ |ev| ~transpose3rd.value(ev[\degree]) })])].do { |add, i| ~addPat.source = add; ~phrasePat.source = Pseq(#[main, main2, main, aux], inf); ~ostPat.set(\degree, Ptuple([ 0, Pseq([ Pseq(#[6, 5.9], { rrand(4, 9) }), Pseq(#[6, 5.9, 5, 4.9, 4], 1) ], inf) ])); ~bassPat.set(\degree, 0); if(i == 0) { ~noteStream = sp.par(Pchain( PmonoArtic(\analog, \ffbump, 6, \degree, Pkey(\degree) + ~addPat, \pan, -0.8, \out, a ), Psym(~phrasePat, ~phrases) )); }; sp.wait(48); ~phrasePat.source = Pseq(#[mainP, main2P, mainP, auxP], inf); ~ostPat.set(\degree, Ptuple([ 2, Pseq([ Pseq(#[8, 7], { rrand(4, 9) }), Pseq(#[8, 7, 6, 5.9], 1) ], inf) ])); ~bassPat.set(\degree, 2); sp.wait(48); }; // final section: harmony changes sp.par(Prout({ loop { ~phrasePat.source = Pxrand(#[main, main2, main, aux], inf); ~ostPat.set(\degree, Ptuple([ 0, Pseq([ Pseq(#[6, 5.9], { rrand(4, 9) }), Pseq(#[6, 5.9, 5, 4.9, 4], 1) ], inf) ])); ~bassPat.set(\degree, 0); Event.silent(48).yield; // after fadeout starts, don't switch to A minor variant if(fadeSynth.notNil) { nil.alwaysYield }; ~phrasePat.source = Pxrand(#[mainP, main2P, mainP, auxP], inf); ~ostPat.set(\degree, Ptuple([ 2, Pseq([ Pseq(#[8, 7], { rrand(4, 9) }), Pseq(#[8, 7, 6, 5.9], 1) ], inf) ])); ~bassPat.set(\degree, 2); Event.silent(48).yield; }; })); // need to schedule the fadeout before the final loop{} sp.par(Prout({ Event.silent(80).yield; fadeSynth = { |bus| var sig = In.ar(bus, 2), eg = EnvGen.kr(Env(#[1, 0], #[32], -2)); SendReply.kr(eg <= 0, '/stopNow'); ReplaceOut.ar(bus, sig * eg); }.play(addAction: \addToTail, args: [bus: 0]); })); fadeWatcher = OSCFunc({ earlyStop = false; if(p.isPlaying) { p.stop }; }, '/stopNow', s.addr).oneShot; // overlap phrases ~phraseStream = ~phrasePat.asStream; ~waitStream = ((Pwhite(5, 16, inf) * Env(#[1, 0.2], #[66], 2.5)).roundUp / 3).asStream; ~ampStream = Env(#[0.1, 0.047], #[66]).asStream; loop { sp.par( // will play one phrase Pchain( PmonoArtic(\analog, \ffreq, Env([rrand(6000, 14000), rrand(180, 700)], #[3], \exp), \ffbump, min(6, 18000 / Pkey(\ffreq)), \degree, Pkey(\degree) + ~addPat, \pan, -0.8, \amp, ~ampStream, \out, a ), ~phrases[~phraseStream.next] ) ); sp.wait(~waitStream.next); }; }) ).play( // global "key signature" protoEvent: Event.default.proto_((root: 5)) ); // some cleanups, which will work if you stop the pattern early or let it run ~updater.remove; ~updater = SimpleController(p).put(\stopped, { ctlSynth.free; if(earlyStop) { fadeSynth.free } { SystemClock.sched(6, { fadeSynth.free }); }; fadeWatcher.free; }); )