Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- //////////////////////////////////////////////////
- // //
- // Stochastic Etude 02 //
- // Daniel L. Lythgoe //
- // 2026 //
- // //
- //////////////////////////////////////////////////
- /*
- This short etude explores stochastic approaches to multichannel granular synthesis using short impulse-like samples such as clicks and pops, with particular attention to the relationship between macro- and micro-level structures. A global envelope - randomised but value-constrained and evolving over the course of the piece - serves as a continuous reference that influences key parameters, including timbre, grain duration, modulation depth, and spatialisation. While this envelope shapes the overall trajectory, local variations emerge through stochastic modulation.
- Developed in SuperCollider, the compositional strategy reflects the structural possibilities and constraints of working directly with code. Horizontal form is articulated through boolean operations tied to elapsed time, while vertical structure emerges from conditional logic responding to the envelope’s dynamic values. This dual approach enables both temporal structuring and moment-to-moment responsiveness within a generative system.
- Techniques such as ring modulation, modulo operations, and thresholding introduce additional spectral complexity. The resulting soundscape balances generative variation with a cohesive formal arc, producing dense, evolving textures from minimal material.
- */
- //
- // Key aspects:
- // - 1. Granular synthesis using GrainBuf with Dust-based triggering
- // - 2. Sample selection via LFNoise0-controlled buffer indexing
- // - 3. Modulation techniques: ring modulation, modulo operation, and thresholding
- // - 4. Dynamic parameter control using a pre-defined envelope (the same envelope controls timbre, sound-shaping, grain duration, as well as the length of the piece.
- // - 5. Stochastic elements in grain triggering and modulation frequencies
- //
- // The macro-form is defined by the main global envelope.
- // Micro-level composition is additionally controlled through the global envelope, as well as through granular processing and stochastic parameter modulation.
- // This approach combines predetermined structure with generative processes, creating a complex soundscape from simple source material.
- // Furthermore, it enables us to use boolean operations (control flows) in the player function. The booleans allow us to compose "horizontally" (in time), while the global envelope allows us to compose "vertically", according to the current value of the global envelope.
- //////////////////////////////////////////////////
- // //
- // Functions and Set-up //
- // //
- //////////////////////////////////////////////////
- (
- var times, levels, totalDuration;
- totalDuration = 180; // Total Duration of the piece: 3 minutes
- // Load Samples for buffer variations
- ~path = (PathName(thisProcess.nowExecutingPath)).pathOnly;
- ~path = ~path ++ "samples/";
- ~clicksPath = ~path ++ "clicks_pops_etc/*";
- ~files = SoundFile.collect(~clicksPath);
- ~clickSounds = ~files.collect { |sf| var b;
- b = Buffer.readChannel(Server.local, sf.path,channels:0)};
- // Create the main global envelope for the piece
- // Combines fixed values with randomised value. As such, the overall structure will be slightly different each time.
- levels = [
- 0,0.0,
- Array.rand(30,0.0,0.05),
- 0.0, 0.15,0, 0.0,
- Array.rand(50,0.0,0.4),
- 0.0,1,1,0.5,
- Array.rand(100,0.4,1.0),
- 0.1,0.25,0,0
- ].flat;
- times = [
- 20,5,
- Array.rand(30,0.0,1.0),
- 10,5,10,5,
- Array.rand(50,0.0,1.0),
- 10,5,5,10,
- Array.rand(100,0.0,1.0),
- 10,10,10
- ].flat;
- ~binEnv = Env(levels,times);
- ~binEnv.duration = totalDuration;
- ~binEnv.plot;
- // GrainBuf SynthDef
- SynthDef(\gb1,
- {|rate, moduloWet=0, moduloFrom=40, moduloTo=40.5, binSelector=0, compThresh = 0.3, grainDurMul=1|
- var buf, trig, index, first, last, sig, bufCycleSpeed, grainDur, bins, moduloThru, moduloFreq;
- trig = Dust.kr(LFTri.kr(0.025, iphase:2).range(0.8,100)); // Stochastic grain trigger, Dust.kr density modulated by LFO
- first = ~clickSounds[0].bufnum; // Buffer index of the first buffer
- last = ~clickSounds[~clickSounds.size - 1].bufnum; // Buffer index of the last buffer
- bufCycleSpeed = SinOsc.kr(0.1).range(0.2,20); // LFO-Modulator for the index cycling component
- index = LFNoise0.kr(bufCycleSpeed).range(first,last); // LFNoise0 to cycle through the buffers
- grainDur = (SinOsc.kr(SinOsc.kr(0.04).range(0.05,1)).range(0.1,0.5)) * grainDurMul; // LFO-Modulated grain duration. The global envelope affects it as a multiplier
- sig = GrainBuf.ar(8, trig, grainDur, index, 1, 0, 4, WhiteNoise.kr.range(-1.0,1.0)); // Granular UGen, WhiteNoise for panning
- sig = Compander.ar(sig, sig, compThresh, slopeBelow: 0.1, clampTime:0.003, relaxTime:0.05); // Compression
- sig = sig.atan(SinOsc.ar(LFNoise0.ar(10).range(5,100))); // Soft Waveshaping A
- sig = sig.tanh; // Soft Waveshaping B
- // Binary Operators for SelectX, modulated by the global envelope
- bins = [
- sig, // Dry signal
- sig ring1: FSinOsc.ar(LFNoise0.kr(10).range(20,30)), // Ring modulation plus first source.
- sig moddif: FSinOsc.ar(LFNoise0.kr(100).range(9200,9240)) // Returns the smaller of two distances between two points on a circle.
- // sig thresh: SinOsc.ar(LFNoise0.kr(200).range(9200,9220)) // Thresholding: 0 when a < b, otherwise a.
- ];
- sig = SelectX.ar(binSelector * bins.size, bins); //Select
- moduloThru = 1-moduloWet;
- sig = (moduloWet * sig.mod(SinOsc.ar(LFNoise0.kr(SinOsc.kr(0.1).range(0.8,12)).range(moduloFrom,moduloTo)))) + (moduloThru * sig); //Dry/Wet control for modulo binary op
- OffsetOut.ar(0,sig);
- };
- ).add;
- )
- //////////////////////////////////////////////////
- // //
- // Playing the Piece //
- // //
- //////////////////////////////////////////////////
- (
- ~player = { |dur=180|
- Routine {
- var grainbufsynth, times;
- dur.postln;
- grainbufsynth = Synth(\gb1);
- times = dur * 10;
- times.do { |i|
- // envValue returns the value of the global Env at a given time.
- // The global Env is stretched to match the duration in seconds given by the argument 'dur'
- var envValue = ~binEnv.at(i / times * ~binEnv.duration);
- "At index: % -> normalized envValue: % ".format(i, envValue).postln;
- grainbufsynth.set(\binSelector, envValue);
- //
- // Boolean operations defined in terms of the time elapsed in the piece:
- // This allows for "horizontal" composition, i.e. structuring of the piece in time according to to its length
- case
- {times < 200} {grainbufsynth.set(\grainDurMul, envValue.explin(0.0,1.0,0.4,1))}
- {times >= 200 && times <= 1200} {grainbufsynth.set(\grainDurMul, envValue.linlin(0.0,1.0,1.0,0.3))}
- {times > 1200} {grainbufsynth.set(\grainDurMul, envValue.linlin(0.0,1.0,0.1,1.2))};
- //
- // Boolean operations in terms of the value of the global env:
- // This allows for "vertical" composition, i.e. structuring of the piece according to the values and shape of the global envelope
- if (envValue > 0.6)
- {grainbufsynth.set(\moduloWet, 1)}
- {grainbufsynth.set(\moduloWet, 0)};
- case
- {envValue <= 0.75 } {grainbufsynth.set(\moduloFrom, 40, \moduloTo, 40)}
- {envValue >= 0.75 && envValue < 0.9} {grainbufsynth.set(\moduloFrom, 35, \moduloTo, 44)}
- {envValue >= 0.9 && envValue < 1.0} {grainbufsynth.set(\moduloFrom, 35, \moduloTo, 52)};
- 0.1.wait;
- if (i >= (times-1)) {grainbufsynth.free}; //.<--------- Free the Synth and end the piece
- }
- };
- };
- // To play the routine:
- ~player.value(180).play; //.<------ arg 'times' = duration of the piece in seconds.
- )
- s.record(numChannels: 8);
- s.stopRecording;
Advertisement
Add Comment
Please, Sign In to add comment