You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

105 lines
5.4 KiB
Plaintext

(
// SYNTHDEF
SynthDef(\flicker, { |automate = 1, bufs = #[0, 1, 2, 3, 4, 5, 6, 7, 8],
fadeInTrigBusNum = 0, fadeOutTrigBusNum = 2, wakeTrigBusNum = 1|
//~~~~~ Vars ~~~~~
// fadeInTimes, fadeOutTimes, wakeTimes according to example in score with 1 minute delay
// start times / fade ins
var fadeInTimes = [1, 50] * 60;
// end times / fade outs (must be same length as fadeInTimes with fadeInTimes[i] < fadeOutTimes[i])
var fadeOutTimes = [47, 106] * 60;
// These are the times to wake up with a flourish of activity and the reading of an entry in the journal
var wakeTimes = [4, 9, 17, 24, 34, 44, 53, 57, 73, 97, 101] * 60;
// These are the frequency ratios of the ensemble parts
var freqRatios = [2, 3/2, 5/4, 7/4, 11/8, 13/8, 17/16, 19/16, 23/16];
// Triggers
var fadeInTrigs, wakeUpTrigs, fadeOutTrigs;
// Rich-get-richer vars
var pulse, state, runningPulseCount, norms, wealthGainLag, probs, selects;
// Control signal vars
var energy, switches, switchesSmoothed;
// Sonification vars
var lamentations, hums, ensemble, drone, fadeIn, fadeInOutEnv, wakeEnv, fadeOut;
// Timed trigger
var timedTrigger = {|times| Changed.kr(EnvGen.kr(Env.step({|i| i % 2} ! (times.size + 1),
times.differentiate ++ [0.01]), Impulse.kr(0)))};
// Monitor time
var sTrig, sCount, secs, mins;
//~~~~~ Triggers ~~~~~
// Triggers for fadeInTimes
fadeInTrigs = Select.kr(automate, [InTrig.kr(fadeInTrigBusNum), timedTrigger.value(fadeInTimes)]);
// Triggers for fadeOutTimes
fadeOutTrigs = Select.kr(automate, [InTrig.kr(fadeOutTrigBusNum), timedTrigger.value(fadeOutTimes)]);
// Triggers for wakeTimes
wakeUpTrigs = Select.kr(automate, [InTrig.kr(wakeTrigBusNum), timedTrigger.value(wakeTimes)]);
//~~~~~ Rich-Get-Richer Algorithm ~~~~~
// Update resolution
pulse = Impulse.kr(30);
// State of consciousness: asleep or awake; a wake up lasts 60 seconds plus a bit of a tail
state = EnvGen.kr(Env.sine(60), wakeUpTrigs);
// Binary representation of which element was selected
selects = LocalIn.kr(9);
// Running sum of the times each of the 9 elements has been selected over 120 pulses
runningPulseCount = RunningSum.kr(pulse * selects * (state > 0), (ControlRate.ir / 30) * 120);
// Normalize the counts over 121 pulses (adding a 1 in the wake states so probs is always > 1)
norms = ((0.925 * (state > 0) + 0.075 + runningPulseCount) / 129);
// Goes from 0 to 1 over several pulses after an element is chosen ending with 1 to 4 lights on favoring 2 or 3
wealthGainLag = pow((PulseCount.kr(pulse, selects) /
TWChoose.kr(wakeUpTrigs, [1, 2, 3, 4], [1, 2, 2, 1], 1)).clip, 4);
// Probabilities such that the rich get richer except directly after an element is selected
probs = {|i| pow(norms[i] * wealthGainLag[i], state * 4)} ! 9;
// Select an element
selects = TWindex.kr(pulse, probs, 1);
// Feedback binary representation
LocalOut.kr({|i| (i <= selects) * (selects <= i) } ! 9);
//~~~~~ Control Signals ~~~~~
// The norms are basically the amount of energy to each element
energy = norms;
// In a toy model manner, this mimics voltage to the system
switches = {|i| TRand.kr(0, 1, pulse) < energy[i]} ! 9;
// Smooths the signal such that the lag time is greater as the element gets richer
switchesSmoothed = {|i| Lag2.kr(switches[i], 0.25 + (0.75 * energy[i]))} ! 9;
//~~~~~ Sonification ~~~~~
// Playback of the Lassus Lamentations with a LPF as to not overwhelm to overall sound
// Each of the Lamentations as a 2 in 3 chance of sounding
lamentations = {|i| LPF.ar(PlayBuf.ar(2, bufs[i], 1, wakeUpTrigs,
TIRand.kr(0, BufFrames.ir(bufs[i]), wakeUpTrigs), 1), 2880) *
switchesSmoothed[i] * TWChoose.kr(wakeUpTrigs, [0, 1], [1, 2], 1)} ! 9;
// Toy model of an electric hum
hums = {|i| var buf = Array.fill(256, {1.0.rand2}).as(LocalBuf.clear);
LPF.ar(Osc.ar(buf, 60, 0), 960) * switchesSmoothed[i]} ! 9;
// Sustained tones based on energy to that element
ensemble = {|i| SinOsc.ar(240 * freqRatios[i], 0) * energy[i]} ! 9;
// Drone in sleep state that gets attenuated in wake state
drone = {|i| var harm = pow(2, 2 - (i / 3).trunc), amp = (1 - (0.75 * energy.sum)) * (1 / pow(harm, 2));
SinOsc.ar(60 * harm + TRand.kr(-3, 3, switches[i]), 0, amp)} ! 9;
//~~~~~ Mix ~~~~~
// Fade ins (10 secs) / outs (30 secs)
fadeInOutEnv = EnvGen.kr(Env.asr(10, 1, 30, \sine),
Latch.kr(Select.kr(fadeOutTrigs, [1, 0]), fadeInTrigs + fadeOutTrigs));
// Fade wake sounds in and out based on total energy in the system
wakeEnv = pow(0.8 * energy.sum + 0.2, 4);
// Final mix currently set to output stereo where the multiplier is the final output level
// Send to separate channel for more control of the indivuals sounds (e.g. with a mixer)
// The lamentations of a 50 / 50 chance of sounding at all
Out.ar([0,1], Mix.new(lamentations) * fadeInOutEnv * wakeEnv * 0.8 * TIRand.kr(0, 1, wakeUpTrigs));
Out.ar([0,1], Mix.new(hums) * fadeInOutEnv * wakeEnv * 0.075);
Out.ar([0,1], Mix.new(ensemble) * fadeInOutEnv * wakeEnv * 0.12);
Out.ar([0,1], Mix.new(drone) * fadeInOutEnv * 0.2);
//~~~~~ Visualization Control ~~~~~
// Send signals to visualizer (these could be sent and scaled appropriated to control real lights)
{|i| SendTrig.kr(pulse, i, switchesSmoothed[i] * fadeInOutEnv)} ! 9;
{|i| SendTrig.kr(pulse, i + 9, energy[i] * fadeInOutEnv * wakeEnv)} ! 9;
//~~~~~ Monitor Time ~~~~~
sTrig = PulseDivider.kr(pulse, 30); sCount = PulseCount.kr(sTrig);
secs = sCount % 60; mins = (sCount / 60).trunc;
Poll.kr(sTrig, mins + (secs / 100), "time (min.secs)");
}).send(s);
)