commit 6aaa87b3d0f6ad2ed437e565d7ddd9fc1cd177a7 Author: Marc Hiatt Date: Sun Jan 7 15:01:04 2024 +0100 first diff --git a/DB 1H Time distribution table V3.pdf b/DB 1H Time distribution table V3.pdf new file mode 100644 index 0000000..fa40eb1 Binary files /dev/null and b/DB 1H Time distribution table V3.pdf differ diff --git a/IMG_3122.jpg b/IMG_3122.jpg new file mode 100644 index 0000000..2a17195 Binary files /dev/null and b/IMG_3122.jpg differ diff --git a/readme.md b/readme.md new file mode 100644 index 0000000..791c9e4 --- /dev/null +++ b/readme.md @@ -0,0 +1,69 @@ + +# distances bending + +## materials +the code and its player have four different voices at their disposal: *difference tones*, *noise flute*, *breath noise*, and *future tones*. + +### tones +difference and future tones are generated live by supercollider oscillators. + +the **difference tones** range from 20 to 160 herz. we call them difference tones because their frequencies are defined by differences between some of the frequencies played by clara and bec. + +the **future tones** range from 800 to 6000 herz. at the times when they are played, they anticipate tones that clara and bec will play in the future, meaning later in the piece. + +### samples +noise flute and breath noise are samples provided by clara and bec. + +**noise flute** consists of eight mono samples (the files `S2_480_mix.wav`, `S2_560_mix.wav`, `S2_640_mix.wav`, etc. are named according to frequency; in the score, these will be sample `a`, `b`, `c`, etc.). + +**breath noise** is a single stereo sample that lasts for around five minutes. in one-hour versions of the piece, it is triggered once, at the 35-minute mark. + +## organization +in 2023 a one-hour version of the piece was presented (at oscillation and again at p.a.s.). + +this version had six sections numbered II to VII. the table in `DB 1H Time distribution table V3.pdf` sets out the framework within which the code provided the ability (via `generate-score_2023.scd`) to specify scores for playing the samples and tones. + +for example, in section II, the palette is constrained to: + +- difference tones at 20 40 herz, +- noise flute samples a–c, +- future tones ranging from 800 to 1100 herz. + +the hand-written notes in `IMG_3122.jpg` specify minimum and maximum numbers of occurrences for each class of event. + +for example, in section II, these are: + +- difference tones: at least 0 events, at most 1 event, +- noise flute: at least 0, at most 2 events, +- future tones: at least 1, at most 3 events, no frequency to be played more than once. + +## code + +init.scd + +- instructions for setting up Babyface sound card with bbfpromix (linux) +- initialize server +- set up SynthDefs `\difference`, `\future`, `\mono_sampler`, `\stereo_sampler` +- amplitudes for the samples are hard-coded as arguments to `\mono_sampler` and `\stereo_sampler` +- populate Dicts and set up TUI triggers (with durations) for tones +- load samples +- instantiate sample synths and set up TUI triggers for samples + +generate-score_2023.scd + +- each section of the piece gets a Dictionary containing its sound materials (frequencies, sample identifiers) and its start time and duration (in minutes) +- post the temporal contraints we're working with +- specify the events and post them: this is the score a player can play from +- re-initialize some variables (in case we want to generate a new score) + +triggers.scd + +- pink noise for sound checking +- code blocks for triggering events. for each block, the first line or lines functions as a play button. for tones, the amplitude is also set (and can be altered) here. example: +``` +( ~play_dTone.value( + "20hz", amp: 0.18, out: 1); ) +~dTone_synths["20hz"].set(\gate, 0); +``` +- note that samples don't have amplitudes modifiable at in the trigger blocks; as noted above, they're in init.scd +- the last line in each trigger block is the stop button diff --git a/sclang/generate-score_2023.scd b/sclang/generate-score_2023.scd new file mode 100644 index 0000000..3e2909d --- /dev/null +++ b/sclang/generate-score_2023.scd @@ -0,0 +1,280 @@ + +( +// generate score II - V (unsorted) + +// sound parameters - dif[ference tones], noi[se flute], fut[ure tones] +// +~ii = Dictionary.newFrom( + [\label, "II ", \dif, [[20,40], [1,1]], \noi, [[\a,\b,\c,], [0,1,2]], + \fut, [[800,880,900,1000,1100],[1,2,3]]]); +~iii = Dictionary.newFrom( + [\label, "III", \dif, [[40,80],[1,1]], \noi, [[\d,\e,\f],[0,1,2]], + \fut, [[1760,1800,2020],[1,2,3]]]); +~iv = Dictionary.newFrom( + [\label, "IV ", \dif, [[80,160],[0,1,2]], \noi, [[nil],[0]], + \fut, [[3000,3300,4200],[1,2,3]]]); +~v = Dictionary.newFrom( + [\label, "V ", \dif, [[0],[0]], \noi, [[\g,\h],[0,1,2]], + \fut, [[6000],[1]]]); + +// temporal bounds +"".postln; "Temporal constraints".postln; +[~ii, ~iii, ~iv, ~v].do({ arg item, i; var a, b, c; + + // minutes [start, duration] + a = [[0, 19], [20, 13], [40, 8], [49, 5]]; + + // .put: Associate two objects and add them to the Dictionary. + item.put(\start, a[i].at(0)); + item.put(\duration, a[i].at(1)); + + item.put(\end, a[i].at(1)); // unused? + item.put(\bounds, a.at(i)); // unused? + + // seconds - unused for now + b = a.deepCollect(2, { arg j; j * 60}); + c = b.collect({ arg k; k[1] = k[0] + k[1]}); + + // post + (item[\label].asString + item[\bounds].asString + "minutes").postln }); + + + +// generate scores II - V (unsorted) +"".postln; " s c o r e".postln; + +[~ii, ~iii, ~iv, ~v].do({ arg section, i; + "".postln; + (" * *" + section[\label]).postln; + + // difference tones and noise samples + // may repeat + [\dif, \noi].do({ arg it; // the types of event we want + n = section[it][1].choose; // the no. of events of each type + if ( n > 0, {("##" + it.asString ++ ": ").postln}, {}); // label: type + + n.do({ arg i; + (section[it][0].choose.asString ++ ": ").post; // freq / sample code + ((section[\duration].rand + section[\start]).asString + + "min." + 60.rand.asString).post; // timestamp + "".postln; "".postln; + }); + + }); + + // future tones + // one use per frequency + x = section[\fut][0]; + + n = section[\fut][1].choose; // the no. of events of each type + if ( n > 0, {("##" + \fut.asString ++ ": ").postln}, {}); // label: type + + n.do ({ arg i; + // choose a frequency, return it and delete it from the array + (x.removeAt(x.size.rand).asString ++ ": ").post; + ((section[\duration].rand + section[\start]).asString + + "min." + 60.rand.asString).postln; // timestamp + + }); + +// "".postln; + +}); + +// since removeAt has deleted items from \fut, +// (because we didn't want future tones to be repeatable) +// restore the dictionary so that we can generate +// a new score when we want to + // TODO: make an unrepeating stream up to n and + // use that to select \fut items rather than removing them +f = [ + [[800,880,900,1000,1100],[1,2,3]], + [[1760,1800,2020],[1,2,3]], + [[3000,3300,4200],[1,2,3]], + [[6000],[1]]]; + +// .add: if the key value pair already exists in the Dictionary, the key's value will be replaced +[~ii, ~iii, ~iv, ~v].do({ arg section, i; section.add(\fut -> f[i].value ) }); + +""; +) + + +// current code ends here + +// below here, only scraps of earlier code + + + + +) + +x = ~ii[\fut][0]; +y = x.size.rand; +x.removeAt(x.size.choose); +x; + +( +// generate scores II - V (unsorted, repeats allowed) +"".postln; + +[~ii, ~iii, ~iv, ~v].do({ arg item, i; + ("*" + item[\label]).postln; + + [\dif, \noi, \fut].do({ arg it; // the types of event we want + n = item[it][1].choose; // the no. of events of each type + if ( n > 0, {("##" + it.asString ++ ": ").postln}, {}); // label: type + //"".postln; + n.do({ arg i; + (item[it][0].choose.asString ++ ": ").post; // freq / sample code + ((item[\duration].rand + item[\start]).asString + + "min." + 60.rand.asString).post; // timestamp + "".postln; "".postln; + }); + }); + "".postln; + +}); +"".postln; +) + + + + + + + +( +// generate score II +"".postln; "* II *".postln; +"".postln; +[\dif, \noi, \fut].do({ arg it; // the types of event we want + n = ~ii[it][1].choose; // the no. of events of each type + if ( n > 0, {("##" + it.asString ++ ": ").postln}, {}); // label: type + //"".postln; + n.do({ arg i; + (~ii[it][0].choose.asString ++ ": ").post; // freq / sample code + (~ii[\duration].rand.asString + + "min." + 60.rand.asString).post; // timestamp + "".postln; "".postln; + }); +}); +"".postln; +) + + + + + +( +"".postln; +"~lengthII = ".post; ~lengthII = (11 * 60).postln; // total section time +"n = ".post; n = [1,2,3].choose.postln; // no. of rests +"~lengthRstsII = ".post; ~lengthRstsII = ( ~lengthII - (n * 31.5) ).postln; + +// rest lengths will go in here +a = Array.fill([2,n], 0); + +// iterate over n rest events +n.do({ arg i; var j, k; + j = i - 1; + + // for this rest, put duration in row 0 + k = if( i > 0, // after first iteration + { a[1].at(j) }, { 0 }); // + a[0].put( i, (~lengthRstsII - k).rand.asInteger.max(30) ); + + // put sum of durations through n in row 1 + l = if( i > 0, + { a[0].at(i) + a[1].at(j) }, { a[0].at(i) } ); + a[1].put( i, l); +}); +a; +) + + + + + +a; + +a[0] = ~lengthRstsII.rand.asInteger; + +a[1] = (~lengthRstsII - a[0]).rand.asInteger ; +a[2] = (~lengthRstsII - a[0]) + +~lengthRstsII - a[1]; + + +( +n = 3; +n.do({ + arg i; + j = i - 1; + a[i] = (~lengthRstsII - ( a.at(j) )).rand.asInteger; +}); +) + +( +j = 5; +while( {j > 0}, { + j.postln; + j = j - 1; +} ) +) + +( +n = 3; +n.do({ arg i; var j; + j = n - i - 1; + {j > -1}.while( { + j.postln; + j = (j - 1) + } ) +}); +) + + +( +n.do( { arg i; r = r.put( i, Rest().asString ) } ) +) + +// number of total elements +g = Array.fill(( n * 2 + 1 ), "rest"); + + +// set the note durations to 30s +// n.do({ arg i; g = g.put( (i * 2 + 1), 30) }); +g.size.do( { arg i; g = g.put(i, if( i.even, "Rest()", 30) ) } ); g +) + + + +p = (n * 2 + 1); +d = Array.fill(p, { arg i; + if( + i.even, Rest + ) +}) +"total = ".postln; (~r3 + ~r1 + ~r2 + ~lengthEvtsII); +) +~durations = [Rest(~r1), 30, Rest(~r2), 30, Rest(~r3), 30]; + +( +"n = ".post; n = [1,2,3].choose.postln; +p = (n * 2 + 1); +f = Array.fill( p, { [20,40].choose }).postln; +d = Array.fill( p, { arg i; if(i.even, Rest(30.rand).asString, 30)}); +) + +( +p = Pbind ( + \instrument, "sine", + \freq, Pseq([800, 0, 1100, 600], 1), + \dur, Pseq([2, Rest(1), 2, 2], 1), + \amp, 4 +// \futrrelease, 2, +// \legato, 0.1 +).play; + +) \ No newline at end of file diff --git a/sclang/init.scd b/sclang/init.scd new file mode 100644 index 0000000..86a46c6 --- /dev/null +++ b/sclang/init.scd @@ -0,0 +1,224 @@ +/* to do - +- Stop self-abolition of samples +- triangle waves +- Gui +- DONE Make synths auto-gate after defined periods +- DONE that period randomized for future tones +*/ + +/* +4-ch Babyface setup: +- start Babyface in CC mode (hold SELECT + DIM while plugging in) +- pavucontrol, configuration tab: select Pro Audio profile +- pavucontrol, output devices tab: set babyface as fallback +- bbfpromix: select Hardware output AN1/2, then fade up AN1/2 software playback channels and pan 1 to L and 2 R. +- select Hardware output PH3/4, then fade up PH3/4 software playback channels and pan 3 to L and 4 R. +- Server.default.options.numOutputBusChannels = 4; +- boot sc server +*/ + +"reboot"; +if ( s.serverRunning, {s.quit} ); +( +o = Server.default.options; +o.numOutputBusChannels = 4; +) + +"initialize"; +( + +// initialize synths +s.waitForBoot({ + +// init stages: + // 1 (add synth defs) ** + // 2 ( Gui ) ** + // 3 (populate SinOsc Dicts, provide ~play_) ** + // 4 (populate sampler Array, provide ~play_) ** + + + s.makeBundle(nil, { + + // ** init: stage 1 (add synth defs) ** + + // # difference tones + SynthDef(\difference, + { arg freq = 20, gate = 0, out = 1, + attack = 0.5, amp = 0.2, release = 1.0; + + var env, audio; + env = Env.asr(attack, amp, release).kr(gate: gate); + audio = SinOsc.ar(freq, mul: env); + + Out.ar(out, audio) + }).add; + + + // # future tones + SynthDef(\future, + { arg freq = 800, amp = 0.01, gate = 0, out = 1, + attack = 0.5, sustain = 0.5, release = 0.5; + + var audio, env; + env = Env.asr(attack, sustain, release).kr(gate: gate); + audio = 0.5 * (LFTri.ar(freq, mul: env * amp) + LFTri.ar(freq, mul: env * amp)); + + Out.ar(out, audio) }).add; + + + // # sampler + SynthDef(\mono_sampler, + { arg sample, gate = 0, trigger = -1, + attack = 0.5, amp = 0.3, release = 0.5; + + var env, audio; + env = Env.asr(attack, amp, release).kr(gate: gate); + audio = PlayBuf.ar(1, sample, trigger: trigger, loop: 1); + Out.ar(0, audio * env) + }).add; + + + // # sampler + SynthDef(\stereo_sampler, + { arg sample, gate = 0, trigger = -1, + attack = 0.5, amp = 0.3, release = 0.5; + + var env, audio; + env = Env.asr(attack, amp, release).kr(gate: gate); + audio = PlayBuf.ar(2, sample, trigger: trigger, loop: 1); + Out.ar([2], audio * env) + }).add; + + + // ** init: stage 2 ( Gui ) ** + + // wait for stage 1 to complete + s.sync; + + s.makeGui; + //s.plotTree; + + + + // ** init: stage 3 (populate Dicts, provide ~play_) ** + + // # 3.1 populate difference tone Dict + ~dTone_synths = Dictionary.new(4); + + [20,40,80,160].do({ arg item, i; + ~dTone_synths.add( (item.asString ++ "hz") -> + Synth("difference", [ + freq: item, + amp: [0.4, 0.1, 0.02, 0.01].at(i), + gate: 0 ]) ) }); + + // and set up triggers + ~play_dTone = { arg key, amp = 0.2, out = 0; + {~dTone_synths[key].set( + \gate, 1, + \amp, amp, + \out, out ); + + ~dTone_synths[key].get(\freq, { arg val; + ( val.asInteger.asString + "Hz, 30 sec.").postln }); + + 30.wait; // play for 30 sec + ~dTone_synths[key].set(\gate, 0) // then release + }.fork + }; + + + // # 3.2 populate future tone Dict + ~fTone_synths = Dictionary.new(12); + [800, 880, 900, 1000, 1100, 1760, + 1800, 2020, 3000, 3300, 4200, 6000].do({ arg item, i; + ~fTone_synths.add( (item.asString ++ "hz") -> + Synth ("future", [ + freq: item, + amp: 0.01, + gate: 0 ]) + )}); + + // and set up triggers + ~play_fTone = { arg key, amp = 0.01, out = 2; + { + // trigger the synth we want + ~fTone_synths[key].set( + \gate, 1, + \amp, amp, + \out, out); + + ~futDuration = (16.rand + 15); + + // post feedback + ~fTone_synths[key].get(\freq, { arg val; + (val.asInteger.asString + "Hz," + + ~futDuration.value.asString + "seconds.") + .asString.postln }); + + // wait and then release + ~futDuration.value.wait; + ~fTone_synths[key].set(\gate, 0) + }.fork + }; + + + // 3.3 load samples + ~filenames = ["S2_480_mix.wav", "S2_560_mix.wav", "S2_640_mix.wav", "S2_720_mix.wav", + "S3_960_mix.wav", "S3_1120_mix.wav", "S3_1280_mix.wav", "S3_1440_mix.wav"]; + + ~sampleBuffer = ~filenames.collect({ + arg filename; + Buffer.read(s, Platform.userHomeDir.asString ++ "/a/23/audio/samples/Oscillation/samples/" ++ filename)}); + + ~breathSample = ( Buffer.read(s, + Platform.userHomeDir.asString ++ "/a/23/audio/samples/Oscillation/breath_filter_stereo.wav") ); + + // ** init: stage 4 (populate sampler Array, provide ~play_) ** + + // wait for stage 3 to complete + s.sync; + + + // instantiate synths + ~samp = Array.fill(8, {arg i; Synth(\mono_sampler, [\sample, ~sampleBuffer[i]])}); + + ~playSamp = { arg key; // + + // provide Dict associations ["a" -> 0, "b" -> 1... "h" -> 7] + d = Dictionary.new(8); + ["a", "b", "c", "d", "e", "f", "g", "h"].do({ arg item, i; + d.add( item.asString -> i.asInteger ); + }); + + // trigger samp + { ~samp[d.at(key)].set(\trigger, 1, \gate, 1); + t = (180.rand + 120); + ("Sample" + key.asString + "," + t.asString ++ "sec.").postln; + t.wait; ~samp[d.at(key)].set(\trigger, -1, \gate, 0)}.fork; + }; + + + ~breathSampleSynth = Synth(\stereo_sampler, [\sample, ~breathSample]); + + ~playBreathSamp = { ~breathSampleSynth.set(\trigger, 1, \gate, 1) }; + ~stopBreathSamp = { ~breathSampleSynth.set(\trigger, -1, \gate, 0) }; + + }) // end makeBundle +}) +) + + + + + + + +~dTone_synths.getPairs; +~dTone_synths["20hz"]; // return 20hz synth + + +d.getPairs; +d.at("d").postln; +d.postln; + diff --git a/sclang/triggers.scd b/sclang/triggers.scd new file mode 100644 index 0000000..515b036 --- /dev/null +++ b/sclang/triggers.scd @@ -0,0 +1,124 @@ + + +// test speakers + // ch 0, 1 +y = { Out.ar (0, Pan2.ar(PinkNoise.ar, MouseX.kr(-1, 1), 0.05)) }.play; +y.free; + // ch 2, 3 +z = { Out.ar (2, Pan2.ar(PinkNoise.ar, MouseX.kr(-1, 1), 0.05)) }.play; +z.free; + +s.freeAll; + +// * let's play * + + +// difference tones +( ~play_dTone.value( + "20hz", amp: 0.18, out: 1); ) +~dTone_synths["20hz"].set(\gate, 0); + +( ~play_dTone.value( + "40hz", amp: 0.025, out: 1); ) +~dTone_synths["40hz"].set(\gate, 0); + +( ~play_dTone.value( + "80hz", amp: 0.008, out: 1); ) +~dTone_synths["80hz"].set(\gate, 0); + +( ~play_dTone.value( + "160hz", amp: 0.005, out: 1); ) +~dTone_synths["160hz"].set(\gate, 0); + + +// samples +( ~playSamp.value("a"); ) +~samp[0].set(\trigger, -1, \gate, 0); + +( "b"; +~playSamp.value("b"); ) +~samp[1].set(\trigger, -1, \gate, 0); + +( "c"; +~playSamp.value("c"); ) +~samp[2].set(\trigger, -1, \gate, 0); + +( "d"; +~playSamp.value("d"); ) +~samp[3].set(\trigger, -1, \gate, 0); + +( "e"; +~playSamp.value("e"); ) +~samp[4].set(\trigger, -1, \gate, 0); + +( "f"; +~playSamp.value("f"); ) +~samp[5].set(\trigger, -1, \gate, 0); + +( "g"; +~playSamp.value("g"); ) +~samp[6].set(\trigger, -1, \gate, 0); + +( "h"; +~playSamp.value("h"); ) +~samp[7].set(\trigger, -1, \gate, 0); + + +( "breath noise"; +"start"; ~playBreathSamp.value; ) +"stop"; ~stopBreathSamp.value; + + +// future tones +( ~play_fTone.value("800hz", + amp: 0.026, out: 1); ) +~fTone_synths["800hz"].set(\gate, 0); + +( ~play_fTone.value("880hz", + amp: 0.025, out: 1); ) +~fTone_synths["880hz"].set(\gate, 0); + +( ~play_fTone.value("900hz", + amp: 0.025, out: 1); ) +~fTone_synths["900hz"].set(\gate, 0); + +( ~play_fTone.value("1000hz", + amp: 0.025, out: 1); ) +~fTone_synths["1000hz"].set(\gate, 0); + +( ~play_fTone.value("1100hz", + amp: 0.024, out: 1); ) +~fTone_synths["1100hz"].set(\gate, 0); + +( ~play_fTone.value("1760hz", + amp: 0.023, out: 1); ) +~fTone_synths["1760hz"].set(\gate, 0); + +( ~play_fTone.value("1800hz", + amp: 0.023, out: 1); ) +~fTone_synths["1800hz"].set(\gate, 0); + +( ~play_fTone.value("2020hz", + amp: 0.023, out: 1); ) +~fTone_synths["2020hz"].set(\gate, 0); + +( ~play_fTone.value("3000hz", + amp: 0.023, out: 1); ) +~fTone_synths["3000hz"].set(\gate, 0); + +( ~play_fTone.value("3300hz", + amp: 0.023, out: 1); ) +~fTone_synths["3300hz"].set(\gate, 0); + +( ~play_fTone.value("4200hz", + amp: 0.022, out: 1); ) +~fTone_synths["4200hz"].set(\gate, 0); + +( ~play_fTone.value("6000hz", + amp: 0.021, out: 1); ) +~fTone_synths["6000hz"].set(\gate, 0); + + + + +