Guest User

Stochastic Étude 02

a guest
Oct 28th, 2026
14
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
  1. //////////////////////////////////////////////////
  2. //                                              //
  3. //              Stochastic  Etude 02            //
  4. //              Daniel L. Lythgoe               //
  5. //              2026                            //
  6. //                                              //
  7. //////////////////////////////////////////////////
  8.  
  9. /*
  10. 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.
  11.  
  12. 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.
  13.  
  14. 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.
  15.  
  16. */
  17.  
  18. //
  19. // Key aspects:
  20. // - 1. Granular synthesis using GrainBuf with Dust-based triggering
  21. // - 2. Sample selection via LFNoise0-controlled buffer indexing
  22. // - 3. Modulation techniques: ring modulation, modulo operation, and thresholding
  23. // - 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.
  24. // - 5. Stochastic elements in grain triggering and modulation frequencies
  25. //
  26. // The macro-form is defined by the main global envelope.
  27. // Micro-level composition is additionally controlled through the global envelope, as well as through granular processing and stochastic parameter modulation.
  28. // This approach combines predetermined structure with generative processes, creating a complex soundscape from simple source material.
  29. // 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.
  30.  
  31. //////////////////////////////////////////////////
  32. //                                              //
  33. //              Functions and Set-up            //
  34. //                                              //
  35. //////////////////////////////////////////////////
  36.  
  37. (
  38.  
  39. var times, levels, totalDuration;
  40. totalDuration = 180; // Total Duration of the piece: 3 minutes
  41.  
  42. // Load Samples for buffer variations
  43.     ~path = (PathName(thisProcess.nowExecutingPath)).pathOnly;
  44.     ~path = ~path ++ "samples/";
  45.     ~clicksPath = ~path ++ "clicks_pops_etc/*";
  46.     ~files = SoundFile.collect(~clicksPath);
  47.     ~clickSounds = ~files.collect { |sf| var b;
  48.     b = Buffer.readChannel(Server.local, sf.path,channels:0)};
  49.  
  50.  
  51. // Create the main global envelope for the piece
  52. // Combines fixed values with randomised value. As such, the overall structure will be slightly different each time.
  53. levels = [
  54.     0,0.0,
  55.     Array.rand(30,0.0,0.05),
  56.     0.0, 0.15,0, 0.0,
  57.     Array.rand(50,0.0,0.4),
  58.     0.0,1,1,0.5,
  59.     Array.rand(100,0.4,1.0),
  60.     0.1,0.25,0,0
  61. ].flat;
  62.  
  63. times = [
  64.     20,5,
  65.     Array.rand(30,0.0,1.0),
  66.     10,5,10,5,
  67.     Array.rand(50,0.0,1.0),
  68.     10,5,5,10,
  69.     Array.rand(100,0.0,1.0),
  70.     10,10,10
  71. ].flat;
  72.  
  73. ~binEnv = Env(levels,times);
  74. ~binEnv.duration = totalDuration;
  75.  
  76. ~binEnv.plot;
  77.  
  78.  
  79.  
  80. // GrainBuf SynthDef
  81. SynthDef(\gb1,
  82.     {|rate, moduloWet=0, moduloFrom=40, moduloTo=40.5, binSelector=0, compThresh = 0.3, grainDurMul=1|
  83.  
  84.     var buf, trig, index, first, last, sig, bufCycleSpeed, grainDur, bins, moduloThru, moduloFreq;
  85.  
  86.     trig = Dust.kr(LFTri.kr(0.025, iphase:2).range(0.8,100));                                   // Stochastic grain trigger, Dust.kr density modulated by LFO
  87.     first = ~clickSounds[0].bufnum;                                                             // Buffer index of the first buffer
  88.     last = ~clickSounds[~clickSounds.size - 1].bufnum;                                          // Buffer index of the last buffer
  89.     bufCycleSpeed = SinOsc.kr(0.1).range(0.2,20);                                               // LFO-Modulator for the index cycling component
  90.     index = LFNoise0.kr(bufCycleSpeed).range(first,last);                                       // LFNoise0 to cycle through the buffers
  91.     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
  92.     sig = GrainBuf.ar(8, trig, grainDur, index, 1, 0, 4, WhiteNoise.kr.range(-1.0,1.0));        // Granular UGen, WhiteNoise for panning
  93.     sig = Compander.ar(sig, sig, compThresh, slopeBelow: 0.1, clampTime:0.003, relaxTime:0.05); // Compression
  94.     sig = sig.atan(SinOsc.ar(LFNoise0.ar(10).range(5,100)));                                    // Soft Waveshaping A
  95.     sig = sig.tanh;                                                                             // Soft Waveshaping B
  96.  
  97.     // Binary Operators for SelectX, modulated by the global envelope
  98.     bins = [
  99.             sig,                                                        // Dry signal
  100.             sig ring1: FSinOsc.ar(LFNoise0.kr(10).range(20,30)),        // Ring modulation plus first source.
  101.             sig moddif: FSinOsc.ar(LFNoise0.kr(100).range(9200,9240))   // Returns the smaller of two distances between two points on a circle.
  102.             // sig thresh: SinOsc.ar(LFNoise0.kr(200).range(9200,9220)) // Thresholding: 0 when a < b, otherwise a.
  103.         ];
  104.     sig = SelectX.ar(binSelector * bins.size, bins);                                            //Select
  105.     moduloThru = 1-moduloWet;
  106.     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
  107.     OffsetOut.ar(0,sig);
  108. };
  109. ).add;
  110.  
  111. )
  112.  
  113.  
  114. //////////////////////////////////////////////////
  115. //                                              //
  116. //              Playing the Piece               //
  117. //                                              //
  118. //////////////////////////////////////////////////
  119. (
  120. ~player = { |dur=180|
  121.  
  122.     Routine {
  123.         var grainbufsynth, times;
  124.  
  125.         dur.postln;
  126.         grainbufsynth = Synth(\gb1);
  127.         times = dur * 10;
  128.  
  129.         times.do { |i|
  130.             // envValue returns the value of the global Env at a given time.
  131.             // The global Env is stretched to match the duration in seconds given by the argument 'dur'
  132.             var envValue = ~binEnv.at(i / times * ~binEnv.duration);
  133.             "At index: % -> normalized envValue: % ".format(i, envValue).postln;
  134.  
  135.             grainbufsynth.set(\binSelector, envValue);
  136.  
  137.  
  138.             //
  139.             // Boolean operations defined in terms of the time elapsed in the piece:
  140.             // This allows for "horizontal" composition, i.e. structuring of the piece in time according to to its length
  141.  
  142.             case
  143.             {times < 200}                   {grainbufsynth.set(\grainDurMul, envValue.explin(0.0,1.0,0.4,1))}
  144.             {times >= 200 && times <= 1200} {grainbufsynth.set(\grainDurMul, envValue.linlin(0.0,1.0,1.0,0.3))}
  145.             {times > 1200}                  {grainbufsynth.set(\grainDurMul, envValue.linlin(0.0,1.0,0.1,1.2))};
  146.  
  147.  
  148.  
  149.  
  150.  
  151.  
  152.  
  153.  
  154.  
  155.  
  156.  
  157.  
  158.  
  159.  
  160.  
  161.             //
  162.             // Boolean operations in terms of the value of the global env:
  163.             // This allows for "vertical" composition, i.e. structuring of the piece according to the values and shape of the global envelope
  164.  
  165.             if (envValue > 0.6)
  166.                 {grainbufsynth.set(\moduloWet, 1)}
  167.                 {grainbufsynth.set(\moduloWet, 0)};
  168.  
  169.             case
  170.             {envValue <= 0.75 } {grainbufsynth.set(\moduloFrom, 40, \moduloTo, 40)}
  171.             {envValue >= 0.75 && envValue < 0.9} {grainbufsynth.set(\moduloFrom, 35, \moduloTo, 44)}
  172.             {envValue >= 0.9 && envValue < 1.0} {grainbufsynth.set(\moduloFrom, 35, \moduloTo, 52)};
  173.  
  174.  
  175.  
  176.             0.1.wait;
  177.             if (i >= (times-1)) {grainbufsynth.free};   //.<--------- Free the Synth and end the piece
  178.         }
  179.     };
  180. };
  181. // To play the routine:
  182. ~player.value(180).play; //.<------ arg 'times' = duration of the piece in seconds.
  183. )
  184.  
  185. s.record(numChannels: 8);
  186. s.stopRecording;
  187.  
  188.  
  189.  
Advertisement
Add Comment
Please, Sign In to add comment