Table of Contents
- 1. Day 1
- 2. Day 2
- 3. Day 3
- 4. Day 4
- 5. Day 5
- 6. Day 6
- 7. Day 7
- 8. Day 8
- 9. Day 9
- 10. Day 10
- 11. Day 11
- 12. Day 12
- 13. Day 13
- 14. Day 14
- 15. Day 15
- 16. Day 16
- 17. Day 17
- 18. Day 18
- 19. Day 19
- 20. Day 20
- 21. Day 21
- 22. Day 22
- 23. Day 23
- 24. Day 24
- 25. Day 25
- 26. Day 26
- 27. Day 27
- 28. Day 28
- 29. Day 29
- 30. Day 30
- 31. Day 31
- 32. Day 32
- 33. Day 33
- 34. Day 34
- 35. Day 35
- 36. Day 36
- 37. Day 37
- 38. Day 38
- 39. Day 39
- 40. Day 40
- 41. Day 41
- 42. Day 42
- 43. Day 43
- 44. Day 44
- 45. Day 45
- 46. Day 46
- 47. Day 47
- 48. Day 48
- 49. Day 49
- 50. Day 50
- 51. Day 51 & 52
- 52. Day 53
- 53. Day 54 & 55
- 54. Day 56 & 57
- 55. Day 58
- 56. Day 59
- 57. Day 60
- 58. Day 61 & 62
- 59. Day 63
- 60. Day 64, 65, 66
- 61. Day 67
- 62. Day 68
- 63. Day 69 & Day 70
- 64. Day 71
- 65. Day 72
- 66. Day 73
- 67. Day 74
- 68. Day 75
- 69. Day 76
- 70. Day 77
- 71. Day 78
- 72. Day 79
- 73. Day 80 + 81
- 74. Day 82
- 75. Day 83
- 76. Day 84 + 85
- 77. Day 86
1 Day 1
1.1 Emacs Shortcuts
Keybinding | Command |
---|---|
C-c C-o |
sclang-start |
C-c C-p b |
sclang-server-boot |
C-c C-p q |
sclang-server-quit |
C-c < |
sclang-clear-post-buffer |
C-c C-h |
sclang-find-help |
C-c h |
sclang-find-help-in-gui |
C-M-h |
sclang-goto-help-browser |
C-c C-c |
sclang-eval-region-or-line |
C-c C-d |
sclang-eval-region |
C-M-x |
sclang-eval-defun |
C-c C-e |
sclang-eval-expression |
C-c C-s |
sclang-main-stop |
C-c C-p r r |
sclang-server-record |
C-c C-p r p |
sclang-server-pause-recording |
C-c C-p r n |
sclang-server-prepare-for-record |
C-c C-p r s |
sclang-server-stop-recording |
C-c RET |
sclang-show-method-args |
C-c : |
sclang-find-definitions |
C-c ; |
sclang-find-references |
2 Day 2
2.1 Data Types
2.1.1 Integers
2.1.2 Floats
2.1.3 Strings
2.1.4 Characters
are prefixed with a $
: $a
, $7
2.1.5 Arrays
can contain multiple types
[1, 2, $3, 3.14]
2.1.6 Symbols
always size 0
\symbol
or 'symbol'
2.1.7 Booleans
True
(class), true
(value)
False
(class), false
(value)
2.1.8 Functions
{ 1 + 2 }
2.2 Object Oriented Programming in Supercollider
Pass messages to objects ()
receiver.method;
equivalent method(receiver);
5.squared squared(5)
Evaluate single line: C-c C-c
2.2.1 Methods on Object
3 Day 3
3.1 Object Oriented Programming in Supercollider ctd
3.2 Variables
Have to be declared:
var value; // local variable value = 9; value = value * 2;
Sclang is a dynamic interpreter, i.e. all code you want to run has to be selected and run together - the scope. Running L1 above separated from L2 will result in ERROR: Variable 'value' not defined.
In emacs: select a region and hit C-c C-c
3.3 Code Blocks
Enclose in parentheses:
(
var value;
value = 9;
value = value * 2;
)
In emacs: place point anywhere in the block and hit C-M-x
(sclang-eval-defun
)
3.4 Global Variables
Are prefixed with a ~
, but can't start with a capital letter or special characters:
( ~value = 9; ~value = ~value * 2; ~A; // Syntax error - unexpected classname )
"Survive" scopes or linewise execution
3.4.1 Alternative: Single character variable names are reserved for global vars
( v = 9; v = v * 2; )
4 Day 4
4.1 Comparative Operations
>, <, ==, >=, <=, !=
4.2 Conditional Operations
4.2.1 if
is a function that operates on Boolean
therefore there are two notations:
if(condition, trueFunc, falseFunc) // or condition.if(trueFunc, falseFunc) // for example ( x = 10.rand; if( x > 5, {"greater than 5"}, {"not greater than 5"} ) )
4.3 Operator precedence
Supercollider quirk: from left to right! (legacy from SmallTalk?)
6 * 3 + 2 // 20 2 + 6 * 3 // 24!!!
=> use parentheses!
Evaluates x > 5 && y > 5
from left to right:
x > 5
(x > 5) && y
((x > 5) && y) > 5
5 Day 5
5.1 Functions
implicit returns (again very Ruby-ish)
// define it ( ~func = { var val; val = 5; val = val + 2; }; ) // evaluate it ~func.value;
5.1.1 with arguments
( ~func = { arg input; // "arg" defines an argument var output; output = input + 2; }; ) ~func.value(4)
6 Day 6
Starting with Week 2 video
6.1 SC is actually sclang
, scsynth
, scide
completely decoupled. one computer could be running scsynth
, to which all others connect
6.1.1 sclang
is client side
6.1.2 scide
is the GUI (that we're not using)
6.1.3 scsynth
is the audio server
needs to be booted
s.boot; // "s" is reserved for the instance of the local server // prints all available audio devices, sample rate s.quit; Server.local.boot; // `local` is a class method that returns the local instance of the audio server Server.local.quit; s.makeWindow; // on emacs, displays a buffer (in the IDE, pops up a window): // [Boot][K] localhost [-> default] // [prepare rec] // Avg CPU: ?% Peak CPU: ?% // UGens: ? Synths: ? Groups: ? SynthDefs: ? s.meter; // open the digital meters s.scope; // open waveform scope FreqScope.new; // open frequency analyzer s.plotTree; // nodetree visualization
7 Day 7
7.1 Make Sound!
// `play` a function and fill it with one or more Unit Generators (UGens) // in other words: functions *respond* to the `play` method { SinOsc.ar }.play; // from docs { Blip.ar(Blip.kr(4, 5, 500, 60), 59, 0.1)!2 }.play; // assign UGen to a global variable and call `free` to stop it. ~sine = { SinOsc.ar }.play; ~sine.free; // stereo / multichannel are simply arrays of ugens ~sine = { [SinOsc.ar, SinOsc.ar] }.play; // shorthand: ~sine = { SinOsc.ar.dup(2) }.play; // even shorter: ! syntax shorthand ~sine = { SinOsc.ar!2 }.play;
UGen is a class that deals with audio signals (generate, process, play) - an "analog modular synthesizer". Building blocks for signal chains (objects in Max/PD)
All UGens respond to one or more of the following class (factory) methods:
ar(arg1, arg2, ... )
kr(arg1, arg2, ... )
ir(arg1, arg2, ... )
audio, kontrol, initialization rate (ir
runs only once)
Emacs master kill switch: sclang-main-stop (C-c C-s)
8 Day 8
8.1 UGen Arguments
// frequency, initial phase, amplitude factor, amplitude offset SinOsc.ar(freq, phase, mul, add) // plot it - argument is the amount of time to plot ~sine = { SinOsc.ar(300, pi, 1, 0) }.plot(0.02); // arguments are either positional OR keyword OR mixed - useful to skip: ~sine = { SinOsc.ar(300, mul: 0.5, 0) }.play;
8.2 Mixing / Additive Synthesis
( ~sum = { var sig; sig = SinOsc.ar(400, mul: 0.2); sig = sig + SinOsc.ar(250, mul: 0.3); sig = sig + SinOsc.ar(1400, mul: 0.07); }.play; ) // or define 3 variables ( ~sum = { var sig1, sig2, sig3; sig1 = SinOsc.ar(400, mul: 0.2); sig2 = SinOsc.ar(250, mul: 0.3); sig3 = SinOsc.ar(1400, mul: 0.07); sig1 + sig2 + sig3 }.play; )
8.3 Modulation / Vibrato
( ~vib = { var sig, vib; vib = SinOsc.ar(6, mul: 20, add: 300); sig = SinOsc.ar(vib, mul: 0.2)!2; }.play; ) // less annoying way to specify a range: `range`! ( ~vib = { var sig, vib; vib = SinOsc.ar(6).range(280, 320); sig = SinOsc.ar(vib, mul: 0.2)!2; }.play; ) ( ~vib = { var sig, mod; mod = LFSaw.ar(1).range(0, 500); sig = Pulse.ar(200 + mod, 0.5, 0.2)!2; }.play; )
8.3.1 Signals are streams of numbers
So you can do:
( x = { var sig; sig = SinOsc.ar(400); sig = sig * 0.2; sig = sig!2; }.play; )
8.4 Multichannel Expansion
Specify an array for the argument of a UGen:
( x = { var sig; sig = SinOsc.ar([300, 400]); // the array "expands" outward => [ SinOsc.ar(300), SinOsc.ar(400) ] sig = sig * 0.2; sig = sig!2; }.play; )
9 Day 9
9.1 Multichannel Expansion Exploration
// make a series of random impulses // expand them to 8 channels of random density // make a super simple Karplus Strong synth with a CombL // right now we're hearing only the first two channels // Mix mixes everything down to one channel // to make a stereo dump we need to feed it an array formed like [ [a, b], [c, d], [e, f] ] => this will mix down to [ a + c + e, b + d + f] ( { Mix.new( [ CombL.ar( Dust.ar( Array.fill(8, {5.rand + 1}) , 0.3), delaytime: Array.fill(8, {0.005 + 0.001.rand}) )!2 ] ) }.play; )
10 Day 10
Starting with Week 3 Video
10.1 Revisiting Arrays
10.1.1 Accessing and inserting
x = [1, 2, 3, 4]; y = ["hi", $y, \things, 4.9]; x.size; // 4 // access elements y.at(0); // hi y[0]; // hi // arrays are *mutable*, `.put` modifies the instance x.put(2, 500); // [1, 2, 500, 4] // but have a fixed size when created x.put(6, 1); // Error: Index out of range // append -> `add` creates a *copy* with extended size a = x.add(6000); // arrays are initialized with size of the next power of 2 z = [1, 7, 16]; z.add(94); // z == [1, 7, 16, 94] z.add(172); // z == [1, 7, 16, 94] // => you can't depend on .add // => always assign to a new variable // same goes for `insert` z = z.insert(1, "hi");
10.1.2 Other utils
x = [1, 2, 3, 4]; // shuffle the array order x = x.scramble; // [2, 4, 3, 1] // reverse x = x.reverse; // [1, 3, 4, 2] // duplicate and mirror pivoting around last item x = x.mirror; // [1, 3, 4, 2, 4, 3, 1] // whole array backwards x = x.mirror2; // [1, 3, 4, 2, 2, 4, 3, 1] // sort x = x.sort; // [1, 2, 3, 4] // rotate, like Max's zl.rot (by amount n) x = x.rotate(1); // [4, 1, 2, 3] // access one item randomly x.choose;
10.2 Math with Arrays
x = [1, 2, 3, 4]; // 2 is added/multiplied/... to each individual item x + 2; // [3, 4, 5, 6] x * 2; // [2, 4, 6, 8] // methods on arrays [1, 2, 3, 4].squared; // [1, 4, 9, 16] [1, 2, 3, 4].odd; // [true, false, true, false] [1, 2, 3, 4].even; // [false, true, false, true]
11 Day 11
11.1 Iteration
this is again very ruby-like 😍
[ ].collect {} // collect [1, 2, 3, 4].collect { arg num; num * 2; } // [1, 2, 3, 4] // alternative for arg [1, 2, 3, 4].collect { |num| num * 2; } // [1, 2, 3, 4] x = [1, 2, 3, 4] y = [1, 2, 3, 4].collect { |num| num.isPrime; } // [false, true, true, false] // do - for side effects // returns its RECEIVER, like Ruby's tap, capturing the result makes no sense z = x.do { |num| num.isPrime.postln; } // prints [false, true, true, false], but RETURNS [1, 2, 3, 4] // select - filter an array x.select { |num| num.isPrime; } // [2, 3] // reject - does the opposite x.reject { |num| num.isPrime; } // [1, 4] // any - boolean that returns true if at least one is true [4, 5].any { |num| num.isPrime; } // true [4, 6].any { |num| num.isPrime; } // false
11.1.1 with index
( [10, 20, 30, 40].do({ |num, index| [num, index].postln; }) )
11.2 Array class methods
// initialize with a given size x = Array.new(5); // initialize with an arithmetic series x = Array.series(8, 5, 2); // 8 items, starting at 5, step 2 => [ 5, 7, 9, 11, 13, 15, 17, 19 ] x = Array.series(8, 5, -2); // [ 5, 3, 1, -1, -3, -5, -7, -9 ] // geometric series x = Array.geom(8, 2, 2); // [ 2, 4, 8, 16, 32, 64, 128, 256 ] // interpolate a range - like numpy (size, start, end) x = Array.interpolation(8, 100, 150); // [ 100.0, 107.14285714286, 114.28571428571, 121.42857142857, 128.57142857143, 135.71428571429, 142.85714285714, 150.0 ] // fill (size, scalar or function) x = Array.fill(7, 4); // [4, 4, 4, 4, 4, 4, 4] x = Array.fill(7, {"hello".scramble}) // [ hlleo, lhleo, olelh, heoll, lhole, ehlol, hello ]
12 Day 12
12.1 Randomness
Check help file "Randomness"
10.rand; // integer in the range of 0-9 10.do({10.rand.postln}); // shorthand for [0, .... 9].do (similar to 10.times in Ruby) // rrand - ranged rand - inclusive! // equal "linear" probability // accepts negative arguments rrand(1, 100); // returns Integers rrand(1, 100.0); // returns Floats rrand(-50, 50); // exprand - exponential distribution // tend towards the lower number // arguments must NOT cross 0 exprand(1, 100); exprand(-2, 100); // nan exprand(-2, -200); // is fine
12.2 On Arrays
Array.exprand(8, 1, 100); Array.rand(8, 1, 100); // ATTENTION ! Array.fill(8, rrand(1, 100)); // same random number copied 8 times - rrand is interpreted FIRST, then array is filled Array.fill(8, {rrand(1,100)}); // function is evaluated every time (1..100) // [1, ... 100] like a Ruby range (1,4..100); // [1, 4, ... 100]
13 Day 13
13.1 Sound with Arrays
13.1.1 Aleatoric additional synth
( x = Array.exprand(16, 200, 2000); x.do({ |n| { var sig; sig = SinOsc.ar(n).range(0, 0.05)!2; }.play; }); )
13.1.2 Control amplitude by array index
( x = Array.exprand(16, 200, 2000); x = x.sort; // need to sort it first from low to high x.do({ |n, i| var amp; amp = (1/(i+1)); amp = amp * 0.2; { var sig; sig = SinOsc.ar(n); sig = sig * amp; sig = sig!2; }.play; }); ) // with rounded frequencies ( x = Array.exprand(16, 200, 2000).round(200); x = x.sort; // need to sort it first from low to high x.do({ |n, i| var amp; amp = (1/(i+1)); amp = amp * 0.1; { var sig; sig = SinOsc.ar(n); sig = sig * amp; sig = sig!2; }.play; }); )
13.1.3 Passing arguments to synths
( x = Array.exprand(16, 200, 2000); x = x.sort; // need to sort it first from low to high ~fn = { |freq=300, amp=0.05| // default arguments var sig; sig = SinOsc.ar(freq); sig = sig * amp; sig = sig!2; }; // capture the result ~sounds = x.collect({ |n, i| var amp; amp = (1/(i+1)); amp = amp * 0.2; // syntax to plug in arguments to `play` ~fn.play(args: [\freq, n, \amp, amp]); }); ) ~sounds.do({ |n| // n is a `Synth`, set the argument `freq` n.set(\freq, exprand(200, 2000)) })
14 Day 14
Starting with Week 4 video
14.1 Separate Function Definition from Function Execution
Conceptually separate the setup phase from the place where you execute/perform your sounds.
// function definition ( f = { var sig; sig = SinOsc.ar(220); sig = sig * 8; sig = sig.softclip; // clip the signal back into range -1 .. 0 sig = Pan2.ar(sig, 0, 0.2); // -1 = left, 1 = right }; ) // function execution // store the resulting synthesis process in a global variable x = f.play; x.free;
14.2 Function Arguments
"Input jacks" on your modular synthesizer.
// function definition ( f = { |freq=330, boost=4| var sig; sig = SinOsc.ar(freq); sig = sig * boost; sig = sig.softclip; // clip the signal back into range -1 .. 0 sig = Pan2.ar(sig, 0, 0.2); // -1 = left, 1 = right }; ) // function execution // store the resulting synthesis process in a global variable x = f.play; // pairs of [\symbol, value] x.set(\freq, exprand(50, 800)); // convert from MIDI to Hz x.set(\freq, 50.midicps); // choose from an array x.set(\freq, (35,37..79).choose.midicps) // similar to free, but fades out giving a "hidden" envelope that is created automatically on the synthesis function x.release(1); x.free; // define initial arguments x = f.play(args: [\freq, 250]);
14.3 MIDI To frequency
60.midicps; // midi to "cycles per second"
14.4 Envelopes
// similar to free, but fades out giving a "hidden" envelope that is created automatically on the synthesis function x.release(1);
15 Day 15
15.1 Polyphony
( f = { |freq=330, boost=4, pan=0, amp=0.1| var sig; sig = SinOsc.ar(freq); sig = sig * boost; sig = sig.softclip; // clip the signal back into range -1 .. 0 sig = Pan2.ar(sig, pan, amp); // -1 = left, 1 = right }; ) x = f.play(args: [\freq, (40,45..70).choose.midicps]); // if played multiple times, only the last one (the one x is pointing to) is released x.release(1); // => use arrays // generate 12 synths with randomized params ( a = []; 12.do({ a = a.add( f.play( fadeTime: exprand(3, 7), // NEW - attack time! args: [ \freq, ((32,39..85).choose - ( [0, 2].choose )).midicps, // subtract either 0 or 2 \boost, exprand(1,8), \pan, rrand(-0.8, 0.8), \amp, exprand(0.04, 0.1) ]) ); }) ) // and release them one after the other a.do({ |synth| synth.release(exprand(1,10)); }) s.plotTree s.scope
16 Day 16
16.1 Envelopes
"Default" envelopes created when a function is created - fadeTime
, release
16.1.1 AR Type EnvGen
// Envelope "definition" ( Env.new( levels: [0, 1, 0.2, 0.2, 0], times: [1, 2.5, 2, 3], curve: [2, -8, 0, -4] // exponent ).plot; ) ( f = { |freq=330, boost=4, pan=0, amp=0.1| var sig, env; env = EnvGen.ar( Env.new( levels: [0, 1, 0.2, 0.2, 0], times: [1, 2.5, 2, 3], curve: [2, -8, 0, -4] // exponent ) ); sig = SinOsc.ar(freq); sig = sig * boost; sig = sig.softclip; // clip the signal back into range -1 .. 0 sig = Pan2.ar(sig, pan, amp); // -1 = left, 1 = right sig = sig * env; }; ) // will occupy CPU when faded out! x = f.play(args: [\freq, exprand(50, 500)]);
Here are some EnvGen
's with different curve
parameters:
16.1.2 Enter Done
(doneAction
argument)
( f = { |freq=330, boost=4, pan=0, amp=0.1| var sig, env; env = EnvGen.ar( Env.new( levels: [0, 1, 0.2, 0.2, 0], times: [1, 2.5, 2, 3], curve: [2, -8, 0, -4] // exponent ), doneAction: 2 // Done.freeSelf ); sig = SinOsc.ar(freq); sig = sig * boost; sig = sig.softclip; // clip the signal back into range -1 .. 0 sig = Pan2.ar(sig, pan, amp); // -1 = left, 1 = right sig = sig * env; }; ) 12.do { x = f.play(args: [\freq, exprand(50, 500)]) };
17 Day 17
17.1 Excursion: Head-Tail Recursion in SuperCollider
( ~array = Array.exprand(5, 30, 300).round; f = { |array, acc| if(array.isEmpty, acc, { var head; head = array.removeAt(0); acc = acc + head; f.(array, acc); } ) }; ~array.postln; f.(~array, 0).postln; )
18 Day 18
19 Day 19
19.1 Excursion: Head-Tail Recursion Revisited
We can use head/tail recursion to sequence sounds via a nested array and onFree
:
( ~sound = { arg freq=330, boost=4, pan=0, amp=0.1, times=[0.01, 0.1]; var sig, env; env = EnvGen.ar( Env.new( levels: [0, 1, 0], times: times, curve: [2, -8] // exponent ), doneAction: 2 // Done.freeSelf ); sig = SinOsc.ar(freq); sig = sig * boost; sig = sig.softclip; // clip the signal back into range -1 .. 0 sig = Pan2.ar(sig, pan, amp); // -1 = left, 1 = right sig = sig * env; }; ~recursion = { |array| if(array.isEmpty, {}, { var head; head = array.removeAt(0); ~sound.play(args: head).onFree { ~recursion.(array); }; } ); } ) ( ~array = 80.collect { |val| [\freq, ((32,39..85).choose - ( [0, 2].choose )).midicps, \boost, exprand(1,8), \pan, rrand(-0.8, 0.8), \amp, exprand(0.04, 0.1), \times, [exprand(0.01, 0.3), exprand(0.1, 1)]]; }; ~recursion.(~array); )
20 Day 20
20.1 (Finite) Envelopes ctd
Tack doneAction
only onto the longest envelope (or, the one truly signalling that the sound is over)!
( x = { var sig, env, freqenv; env = EnvGen.kr( Env.new( [0, 1, 0], [0.1, 0.3], [0, 0] ) ); freqenv = EnvGen.kr( Env.new( [15000, 500], [4], [-12] ), doneAction: 2 ); sig = Saw.ar(80); sig = LPF.ar(sig, freqenv); sig = sig * 0.2; sig = sig * env; sig = sig!2; }.play; )
Other envelopes:
Env.perc(attackTime:, releaseTime:, level:, curve:)
Env.triangle(dur:)
Env.linen()
- 3 segments
20.2 Infinite-Length/Sustaining Envelopes
Need to pass gate
to EnvGen
:
( f = { |gate=1| var sig, env; env = EnvGen.kr( Env.adsr(0.01, 0.3, 0.5, 1), gate ); sig = SinOsc.ar(200); sig = sig * 0.2; sig = sig * env; sig = sig!2; }; ) x = f.play(args: [\gate, 0]); // start x.set(\gate, 1); // finish and trigger doneAction x.set(\gate, 0); // now can't start again -> remove doneAction x.set(\gate, 1); // have to free it myself now x.free;
21 Day 21
21.1 Sustaining Envelopes ctd
The gate
argument will only (re)start the synth if there is a non-positive to positive transition 0 -> 1
From the SynthDef
help:
trigger rate Arguments that begin with "
t_
" (e.g.t_trig
), or that are specified as\tr
in the def's rates argument (see below), will be made as aTrigControl
. Setting the argument will create a control-rate impulse at the set value. This is useful for triggers.
TrigControl
arguments (essentially all starting with t_
) if set to a non-zero value output that value for a very small amount of time, then snap back to 0.
( f = { |t_gate=1| var sig, env; env = EnvGen.kr( Env.perc(0.01, 0.5), t_gate ); sig = SinOsc.ar(200); sig = sig * 0.2; sig = sig * env; sig = sig!2; }; ) x = f.play(args: [\t_gate, 0]); // since it is a `TrigControl` argument now, don't have to reset it to 0 since it automatically does that! x.set(\t_gate, 1); x.free;
22 Day 22
22.1 Multichannel Expansion
The server interprets arrays of signals as multi-channel audio (e.g. sig!2
).
( x = { var sig; sig = LFSaw.ar((80,90..150)); // [80, 90, 100, .. 150] sig = LPF.ar(sig, 500); sig = sig * 0.1; }.play; ) // configure number of output channels s.options.numOutputBusChannels = 8; s.meter; s.scope;
22.1.1 Propagation through a UGen
( x = { var sig; sig = LFSaw.ar([80, 81]); sig = LPF.ar(sig, [500, 3000]); sig = sig * 0.1; }.play; ) LFSaw.ar([80, 81]) // ---> [LFSaw.ar(80), LFSaw.ar(81)] // --> [LPF.ar(LFSaw.ar(80), 500) * 0.1, LPF.ar(LFSaw.ar(81), 3000) * 0.1]
23 Day 23
23.1 Multichannel - Downmixing
23.1.1 Summing
( x = { var sig, freqs; freqs = Array.exprand(8, 200, 1200); sig = SinOsc.ar(freqs, 0, 0.05); // [SinOsc, SinOsc, ...].sum sig = sig.sum; // monofy sig!2; }.play; ) s.meter; s.options.numOutputBusChannels = 8;
23.1.2 Iteration
( x = { var sig, freqs; freqs = Array.exprand(8, 200, 1200); sig = SinOsc.ar(freqs, 0, 0.05); // returns an array of 8 stereophonic signals! sig = sig.collect({ |n| // all panned to center // Pan2.ar(n, 0) Pan2.ar(n, rrand(-1.0, 1.0)); }); // [[so, so], [so, so], ...].sum (in case of Pan2.ar(n, 0)) sig = sig.sum; }.play; ) s.meter; s.options.numOutputBusChannels = 8; [[1, 2], [3, 4]].sum;
23.1.3 Splay
aligns multi channel audio linearly across the stereo field
( x = { var sig, freqs; freqs = Array.exprand(50, 40, 5000); sig = SinOsc.ar(freqs, 0, 0.05); sig = Splay.ar(sig); }.play; ) s.meter; s.options.numOutputBusChannels = 8; [[1, 2], [3, 4]].sum;
24 Day 24
Starting with Week 6 video
24.1 SynthDef
and Synth
SynthDef
defines an interconnection of UGen
s, it defines a synthesis algorithm (blueprint/recipe).
The server application uses synth definitions as templates for creating Synth nodes.
Methods such as
Function-play
, etc. are simply conveniences which automatically create such a def.[…]
play
wraps the UGens in a SynthDef and sends it to the target. When this asynchronous command is completed, it creates one synth from this definition.[…]
NOTE: Some UGens are added in this process.
- an
Out
UGen for playing the audio to the first audio busses. If the function returns anOut
UGen, this is omitted.- an envelope with a
\gate
control for releasing and crossfading. If the function provides its own releasable envelope, this is omitted.
( f = { |freq=100| var sig; sig = Pulse.ar(freq * [1, 1.01], 0.5, 0.04); //the SinOsc class requires an .ar method to create an audio rate instance. sig = sig + SinOsc.ar(freq * 1.5 * [1, 1.004], 0, 0.15); sig = sig + BrownNoise.ar(0.04!2); sig = sig * Line.ar(0,1,10); } ) x = f.play; s.scope; x = f.play(args: [\freq, 1250]); x.set(\freq, 150); x.set(\gate, 0, \fadeTime, 5);
24.2 make a SynthDef
- choose a name
- explicitly define the output signal with
Out.ar
- call
.add
( // argument 2 is a `ugenGraphFunction` SynthDef.new(\ocean, { |freq=100, gate=1, rel=3| var sig, env; sig = Pulse.ar(freq * [1, 1.01], 0.5, 0.04); env = EnvGen.kr( Env.asr(3, 1, rel, [1, -1]), gate, doneAction: 2 ); //the SinOsc class requires an .ar method to create an audio rate instance. sig = sig + SinOsc.ar(freq * 1.5 * [1, 1.004], 0, 0.15); sig = sig + BrownNoise.ar(0.04!2); sig = sig * env; // `bus` and signal Out.ar(0, sig); }).add; )
24.3 Execute it using a Synth
// don't need to call `play`! x = Synth.new(\ocean); x.free; // with arguments x = Synth.new(\ocean, [\freq, 250]); x.set(\freq, 350); // sending \gate and \fadeTime doesn't work, because they are generated by `Function.play`, only after adding a custom envelope x.set(\gate, 0, \fadeTime, 5); x.set(\gate, 0, \rel, 0.02); s.meter;
25 Day 25
25.1 SynthDef
ctd
SC assigns Out.ar(out)
to ascending bus channels starting at out
.
( // a MONO synth SynthDef.new(\ocean, { |freq=100, gate=1, rel=3, out=0| var sig, env; sig = Pulse.ar(freq, 0.5, 0.04); env = EnvGen.kr( Env.asr(3, 1, rel, [1, -1]), gate, doneAction: 2 ); sig = sig + SinOsc.ar(freq, 0, 0.15); sig = sig + BrownNoise.ar(0.04); sig = sig * env; sig = sig!2; Out.ar(out, sig); }).add; ) // .new can be omitted Synth(\ocean, [\out, 0]);
So if assigning a stereo signal to an array of output busses
sig = sig!2; Out.ar([0, 1], sig); // 0 => sig, 1 => sig + sig, 2 => sig // overlaps!
25.2 Routine
A
Routine
runs aFunction
and allows it to be suspended in the middle and be resumed again where it left off. This functionality is supported by the Routine's superclassThread
. Effectively, Routines can be used to implement co-routines as found in Scheme and some other languages.
A bit like JS Generators or Ruby fibers (?)
( r = Routine.new({ "hello".postln; 1.wait; "how are you ?".postln; 3.wait; "good bye".postln; }); ) // steps through them one by one r.play; // stops the routine, cannot be resumed r.stop; // start over r.reset;
A sounding routine
( // a MONO synth SynthDef.new(\ocean, { |freq=100, gate=1, atk=0.01, rel=1, out=0, pan=0| var sig, env; sig = Pulse.ar(freq, 0.5, 0.04); env = EnvGen.kr( Env.new([0, 1, 0], [atk, rel], [0, -4]), doneAction: 2 ); sig = sig + SinOsc.ar(freq, 0, 0.15); sig = sig + BrownNoise.ar(0.04); sig = sig * env; sig = Pan2.ar(sig, pan); Out.ar(out, sig); }).add; ) Synth(\ocean); ( r = Routine.new({ Synth(\ocean, [\freq, 60.midicps, \pan, rrand(-0.5, 0.5)]); wait(0.5); Synth(\ocean, [\freq, 62.midicps, \pan, rrand(-0.5, 0.5)]); wait(0.5); Synth(\ocean, [\freq, 67.midicps, \pan, rrand(-0.5, 0.5)]); }); ) r.reset; r.play;
26 Day 26
26.1 Routine
Iteration
( Routine.new({ 100.do { Synth(\ocean, [\freq, rrand(48, 72).midicps, \pan, rrand(-0.5, 0.5)]); wait(exprand(0.02, 0.5)); } }).play; )
26.1.1 repeat infinitely
wait
is mandatory, else we're hitting an infinite loop and SC will crash!
( r = Routine.new({ inf.do { Synth(\ocean, [\freq, rrand(48, 72).midicps, \pan, rrand(-0.5, 0.5)]); wait(exprand(0.02, 0.5)); } }); ) r.reset; r.play; r.stop;
26.2 fork
is a method defined on Function
, which creates and plays a Routine
:
Returns a Routine using the receiver as it's function, and plays it in a TempoClock.
( r = { inf.do { Synth(\ocean, [\freq, rrand(48, 72).midicps, \pan, rrand(-0.5, 0.5)]); wait(exprand(0.02, 0.5)); } }.fork; )
26.3 Subroutines
( r = Routine.new({ { var num; num = rrand(48, 72); 3.do { Synth(\ocean, [\freq, num.midicps, \pan, rrand(-0.5, 0.5)]); wait(0.15); }; 3.do { Synth(\ocean, [\freq, rrand(48, 72).midicps, \pan, rrand(-0.5, 0.5)]); wait(0.15); }; }.loop; }).play; )
26.4 Task
Like Routine
, but pause
- and resume
-able
( t = Task.new({ { var num; num = rrand(48, 72); 3.do { Synth(\ocean, [\freq, num.midicps, \pan, rrand(-0.5, 0.5)]); wait(0.15); }; 3.do { Synth(\ocean, [\freq, rrand(48, 72).midicps, \pan, rrand(-0.5, 0.5)]); wait(0.15); }; }.loop; }); ) t.start; t.pause; t.resume;
27 Day 27
Starting with Week 7 video
27.1 Sampling in SuperCollider with Buffer
Buffer
is the server's version of an Array
containing only Floats
. Has to be WAV or AIFF.
~brass = Buffer.read(s, "/Users/jrubisch/Documents/_CODE/100daysofcode/supercollider/samples/brass.wav"); ~water = Buffer.read(s, "/Users/jrubisch/Documents/_CODE/100daysofcode/supercollider/samples/water.wav"); // only for debugging basically ~brass.play;
27.1.1 Accessing Buffer Information
~brass.numChannels; ~water.numChannels; // in seconds ~brass.duration; ~water.duration; // frames are equivalent to audio samples of all channels at one specific time // so samples = frames * channels ~brass.numFrames; ~water.numFrames; ~brass.sampleRate; ~water.sampleRate; // bufnum is a "Global ID" of Buffers ~brass.bufnum; ~water.bufnum; // clear buffers off the server ~brass.free; ~water.free; // or free all Buffer.freeAll;
28 Day 28
28.1 Audio File Management
Save .scd
file along with audio files in an ./audio
folder, use thisProcess
special variable:
( thisProcess.nowExecutingPath; // /Users/..../some_dir/my.scd ~path = PathName.new(thisProcess.nowExecutingPath).parentPath ++ "samples/"; // /Users/..../some_dir/samples ~brass = Buffer.read(s, ~path ++ "brass.wav"); ~water = Buffer.read(s, ~path ++"water.wav"); ) ~brass.plot; ~water.plot;
28.2 PlayBuf
numChannels
- cannot be dynamically modulated!bufnum
ratio
trigger
- zero to non-zero triggers (via trigger argument)startPos
- in frames
( { var sig; sig = PlayBuf.ar( 2, ~brass.bufnum, rate: 0.5, doneAction: 2 // for oneShot samples ); sig = sig * 0.2; }.play; )
28.2.1 BufRateScale
to compensate sample rate differences
( { var sig; sig = PlayBuf.ar( 2, ~brass.bufnum, rate: BufRateScale.ir(~brass.bufnum) * 5.midiratio, // multiply with rate _here_ doneAction: 2 // for oneShot samples ); sig = sig * 0.2; }.play; )
28.2.2 midiratio
to transpose by semitones
5.midiratio // 1.33484.... -12.midiratio // 0.5
28.2.3 trigger
and startPos
( x = { |t_trig=1| var sig; sig = PlayBuf.ar( 2, ~brass.bufnum, rate: BufRateScale.ir(~brass.bufnum) * 5.midiratio, // multiply with rate _here_ trigger: t_trig, startPos: 300000, doneAction: 0 // to retrigger ); sig = sig * 0.2; }.play; ) x.set(\t_trig, 1); ~brass.numFrames;
29 Day 29
29.1 Impulse
and Dust
Specifically designed to output triggers
// created like so Impulse.ar(100) Dust.ar(100) ( x = { |t_trig=1| var sig; sig = PlayBuf.ar( 2, ~brass.bufnum, rate: BufRateScale.ir(~brass.bufnum) * 5.midiratio, // multiply with rate _here_ trigger: Dust.kr(4), startPos: 300000, doneAction: 0 // to retrigger ); sig = sig * 0.2; }.play; )
29.1.1 loop
argument tells PlayBuf
to loop, doneAction
is ignored!
30 Day 30
Starting with Week 8 video.
30.1 PlayBuf
SynthDefs
( ~path = PathName.new(thisProcess.nowExecutingPath).parentPath ++ "samples/"; // /Users/..../some_dir/samples ~brass = Buffer.read(s, ~path ++ "brass.wav"); ~water = Buffer.read(s, ~path ++"water.wav"); ) ( SynthDef.new(\pb, { |buf=0, rate=1, t_trig=1, spos=0, loop=0, da=2, amp=0.5, out=0| var sig; sig = PlayBuf.ar( 2, buf, rate: BufRateScale.ir(buf) * rate, trigger: t_trig, startPos: spos, loop: loop, doneAction: da ); sig = sig * amp; Out.ar(out, sig); }).add; ) Synth(\pb, [\buf, ~water]);
! You cannot declare an argument for the number of channels on PlayBuf
!
Arguments are really a UGen
of type Control
=> Make two SynthDef
s, one for mono, one for stereo:
( SynthDef.new(\pb1, { |buf=0, rate=1, t_trig=1, spos=0, loop=0, da=2, amp=0.5, out=0| var sig; sig = PlayBuf.ar( 1, buf, rate: BufRateScale.ir(buf) * rate, trigger: t_trig, startPos: spos, loop: loop, doneAction: da ); sig = sig * amp!2; Out.ar(out, sig); }).add; SynthDef.new(\pb2, { |buf=0, rate=1, t_trig=1, spos=0, loop=0, da=2, amp=0.5, out=0| var sig; sig = PlayBuf.ar( 2, buf, rate: BufRateScale.ir(buf) * rate, trigger: t_trig, startPos: spos, loop: loop, doneAction: da ); sig = sig * amp; Out.ar(out, sig); }).add; ) Synth(\pb2, [\buf, ~water]);
31 Day 31
Control PlayBuf
via arguments
( Synth(\pb2, [ \buf, ~water, \spos, ~water.numFrames * 0.5, \loop, 1, \rate, 1.5 ]); )
31.1 Iterate over PlayBuf
s
// random rate ( z = 5.collect({ Synth(\pb2, [ \buf, ~water, \spos, ~water.numFrames * 0.5, \loop, 1, \rate, rrand(-0.5, 1.3) ]); }) ) s.plotTree; z.collect({ |n| n.free; }); // use iteration counter ( z = 5.collect({ |n| Synth(\pb2, [ \buf, ~water, \spos, ~water.numFrames * 0.5, \loop, 1, \rate, n.midiratio ]); }) ) // tenth of a semitone up ( z = 5.collect({ |n| Synth(\pb2, [ \buf, ~water, \spos, ~water.numFrames * 0.5, \loop, 1, \rate, (n/10).midiratio, \amp, 0.15 ]); }) )
31.2 Routine
and PlayBuf
( r = Routine.new({ loop { // optional: bring in conditional logic to switch between mono and sstereo var buf; buf = [~water, ~brass].choose; if( buf.numChannels == 2, { Synth(\pb2, [ \buf, buf, \spos, 0, \loop, 0, \da, 2, \rate, rrand(-12.0, 5.0).midiratio, \amp, 0.15 ]); }, { Synth(\pb1, [ \buf, buf, \spos, 0, \loop, 0, \da, 2, \rate, rrand(-12.0, 5.0).midiratio, \amp, 0.15 ]); } wait(1/3); } }).play; ) r.stop;
31.2.1 Aside: wchoose
weighted choose:
buf = [~water, ~brass].wchoose([0.67, 0.33]);
31.2.2 Choose mono or stereo SynthDef
"polymorphically"
( r = Routine.new({ loop { // optional: bring in conditional logic to switch between mono and sstereo var buf, def; buf = [~water, ~brass].choose; def = "pb" ++ (buf.numChannels.asString).asSymbol; Synth(def, [ \buf, buf, \spos, 0, \loop, 0, \da, 2, \rate, rrand(-12.0, 5.0).midiratio, \amp, 0.15 ]); wait(1/3); } }).play; ) r.stop;
32 Day 32
32.1 Playing a segment of a buffer with an amplitude envelope
( SynthDef.new(\pb2env, { |buf=0, rate=1, t_trig=1, spos=0, loop=1, da=0, amp=0.5, out=0, atk=0.1, sus=0.5, rel=0.01| var sig, env; env = EnvGen.ar( Env.new( [0, 1, 1, 0], [atk, sus, rel], [0, 0, 0] ), doneAction: 2 ); sig = PlayBuf.ar( 2, buf, rate: BufRateScale.ir(buf) * rate, trigger: t_trig, startPos: spos, loop: loop, doneAction: da ); sig = sig * env; sig = sig * amp; Out.ar(out, sig); }).add; ) ) ( Synth(\pb2env, [ \buf, ~water, \spos, ~water.numFrames * 0.5 ]); )
32.2 Segments and Routine
s
( r = Routine.new({ loop { Synth(\pb2env, [ \buf, ~brass, \spos, rrand(~water.numFrames * 0.3, ~water.numFrames * 0.6), \atk, 0.2, \sus, 0, \rel, 0.2 ]); wait(rrand(0.05, 0.3)); } }).play; ) r.stop;
Fun with transposition and rhythm:
( var semi = -24; r = Routine.new({ loop { Synth(\pb2env, [ \buf, ~brass, \spos, rrand(~water.numFrames * 0.3, ~water.numFrames * 0.6), \atk, 0.01, \sus, 0.2, \rel, 0.08, \rate, semi.midiratio ]); semi = semi + 1; if(semi > 0, { semi = rrand(-12, -2); } ); wait([0.4, 0.2, 0.1].choose); } }).play; ) r.stop;
33 Day 33
33.1 BufRd
- Buffer Reading Oscillator
Like cycle~
in Max, needs a phase between 0 and buf.numFrames - 1
.
{BufRd.ar(2, ~brass, Line.ar(0, ~brass.numFrames - 1, ~brass.duration, doneAction: 2))}.play; // backwards {BufRd.ar(2, ~brass, Line.ar(~brass.numFrames - 1, 0, ~brass.duration, doneAction: 2))}.play; // or any UGen {BufRd.ar(2, ~brass, SinOsc.ar(1/~brass.duration).range(0, ~brass.numFrames - 1))}.play;
34 Day 34
Starting with Week 9 video
34.1 Effects, Groups, Buses
( SynthDef.new(\drops, { |freqMin=100, freqMax=3000, gate=1, amp=0.3| var sig, trigEnv, env, freq, trig, reverb; freq = LFNoise1.kr(0.2!2).exprange(freqMin, freqMax); sig = SinOsc.ar(freq); trig = Dust.ar(1!2); trigEnv = EnvGen.ar(Env.perc(0.002, 0.1), trig); sig = sig * trigEnv; // add a reverb of 250m roomsize and 4 seconds reverb time reverb = GVerb.ar(sig, 250, 4); // lowpass filter it reverb = LPF.ar(reverb, 1200); // mix dry and wet sig = (sig + reverb) * 0.5; env = EnvGen.kr(Env.asr(0.01, 1, 1, -1), gate, doneAction: 2); sig = sig * env * amp; Out.ar(0, sig); }).add; ) x = Synth(\drops); // reverb ends abruptly x.set(\gate, 0); x.free; s.plotTree;
34.2 Modularize Generator and Reverb
( SynthDef.new(\drops, { |freqMin=100, freqMax=3000, gate=1, amp=0.3, out=0| var sig, trigEnv, env, freq, trig; freq = LFNoise1.kr(0.2!2).exprange(freqMin, freqMax); sig = SinOsc.ar(freq); trig = Dust.ar(1!2); trigEnv = EnvGen.ar(Env.perc(0.002, 0.1), trig); sig = sig * trigEnv; env = EnvGen.kr(Env.asr(0.01, 1, 1, -1), gate, doneAction: 2); sig = sig * env * amp; Out.ar(out, sig); }).add; SynthDef.new(\reverb, { |in=0, out=0| var sig, reverb; // bus, numChannels sig = In.ar(in, 2); reverb = sig.copy; // add a reverb of 250m roomsize and 4 seconds reverb time reverb = GVerb.ar(reverb, 250, 4); // lowpass filter it reverb = LPF.ar(reverb, 1200); // mix dry and wet sig = (sig + reverb) * 0.5; Out.ar(out, sig); }).add; ) // listening on bus... r = Synth(\reverb, [\in, 48]); // playing on bus... d = Synth(\drops, [\out, 48]); // reverb ends abruptly d.set(\gate, 0); d.free; s.options.numOutputBusChannels; // 2 bus 0-1 s.options.numInputBusChannels; // 2 bus 2-3 s.options.numAudioBusChannels; // 1024 s.options.numPrivateAudioBusChannels; // 1024 - 2 - 2 = 1020 s.scope;
35 Day 35
35.1 Excursion: Resonating PlayBufs
( ~path = PathName.new(thisProcess.nowExecutingPath).parentPath ++ "samples/"; ~brass = Buffer.read(s, ~path ++ "brass.wav"); ~water = Buffer.read(s, ~path ++ "water.wav"); ) ( SynthDef.new(\pb, { |buf=0, rate=1, t_trig=1, spos=0, loop=0, da=2, amp=0.5, out=0, atk=0.1, sus=0.5, rel=0.01| var sig, env; env = EnvGen.ar( Env.new( [0, 1, 1, 0], [atk, sus, rel], [0, 0, 0] ), doneAction: 2 ); sig = PlayBuf.ar( 2, buf, rate: BufRateScale.ir(buf) * rate, trigger: t_trig, startPos: spos, loop: loop, doneAction: da ); sig = sig * amp * env; Out.ar(out, sig); }).add; SynthDef.new(\resonator, { |in=0, out=0, freq=440.0, q=10, decay=1.0, pan=0| var sig; // bus, numChannels sig = In.ar(in, 2); sig = Mix.ar(sig); sig = Resonz.ar(sig, freq: freq, bwr: 1/q); sig = Pan2.ar(sig, pos: pan); Out.ar(out, sig); }).add; ) ( ~freq = rrand(42, 72).midicps.postln; ~count = 16; z = ~count.collect({ |n| r = Routine.new({ loop { Synth(\pb, [ \buf, ~water, \spos, ~water.numFrames * rrand(0.2, 0.8), \atk, rrand(0.1, 0.7), \sus, exprand(0, 3), \out, (16 + (n * 2)), \rate, (-12..12).choose.midiratio ]); wait(rrand(0.15, 0.3)); } }).play; Synth(\resonator, [ \in, (16 + (n * 2)), \freq, ~freq, \pan, rrand(-0.8, 0.8) ]); }); )
36 Day 36
36.1 S/H Bandwidth Resonating Playbufs
( ~path = PathName.new(thisProcess.nowExecutingPath).parentPath ++ "samples/"; ~brass = Buffer.read(s, ~path ++ "brass.wav"); ~water = Buffer.read(s, ~path ++ "water.wav"); ) ( SynthDef.new(\pb, { |buf=0, rate=1, t_trig=1, spos=0, loop=0, da=2, amp=0.5, out=0, atk=0.1, sus=0.5, rel=0.01| var sig, env; env = EnvGen.ar( Env.new( [0, 1, 1, 0], [atk, sus, rel], [0, 0, 0] ), doneAction: 2 ); sig = PlayBuf.ar( 2, buf, rate: BufRateScale.ir(buf) * rate, trigger: t_trig, startPos: spos, loop: loop, doneAction: da ); sig = sig * amp * env; Out.ar(out, sig); }).add; SynthDef.new(\resonator, { |in=0, out=0, freq=440.0, bwr, gain, decay=1.0, pan=0| var sig; // bus, numChannels sig = In.ar(in, 2); // monofy sig = Mix.ar(sig); sig = Resonz.ar(sig, freq: freq, bwr: In.kr(bwr)); sig = Pan2.ar(sig, pos: pan); sig = sig * In.kr(gain); Out.ar(out, sig); }).add; SynthDef.new(\mod, { |out_sh, out_gain| Out.kr(out_gain, XLine.kr(0.5, 4, 10)); Out.kr(out_sh, Latch.kr((XLine.kr(5, 0.001, 10) + LFNoise1.kr(1).range(0.001, 0.01)), Impulse.kr(5))); }).add; ) ( ~freq = rrand(60, 84).midicps.postln; ~count = 10; ~audioBuses = ~count.collect({Bus.audio(s, 2)}); ~controlBuses = ~count.collect({Bus.control(s)}); ~gainBuses = ~count.collect({Bus.control(s)}); ~routines = []; z = ~count.collect({ |n| Synth(\mod, [\out_sh, ~controlBuses[n], \out_gain, ~gainBuses[n]]); ~routines.add(Routine.new({ loop { Synth(\pb, [ \buf, ~water, \spos, ~water.numFrames * rrand(0.2, 0.8), \atk, rrand(0.1, 0.7), \sus, exprand(0, 3), \out, ~audioBuses[n], \rate, (-12..12).choose.midiratio ]); wait(rrand(0.15, 0.3)); } })); ~routines.collect { |r| r.play; }; Synth(\resonator, [ \in, ~audioBuses[n], \freq, ~freq, \gain, ~gainBuses[n], \bwr, ~controlBuses[n], \pan, rrand(-0.8, 0.8) ]); }); ) ~routines.collect { |r| r.stop; } ~routines.collect { |r| r.reset; r.play; } ~controlBuses.collect({ |b| b.free; }); ~gainBuses.collect({ |b| b.free; }); ~audioBuses.collect({ |b| b.free; }); s.scope;
37 Day 37
37.1 The Bus
Object
A language-side object that keeps a reference to an Audio/Control-Bus
~bus = Bus.audio(s, 2); // Bus(audio, 4, 2, localhost) - index 4, 2 channels - starts at 4 because 0-3 are reserved by hardware ~bus2 = Bus.audio(s, 4); // Bus(audio, 6, 4, localhost) // reset bus allocation counter s.newBusAllocators; // listening on bus... r = Synth(\reverb, [\in, ~bus]); // playing on bus... d = Synth(\drops, [\out, ~bus]); d.set(\gate, 0);
38 Day 38
38.1 Order of Execution
38.2 addAction
\addToHead
(default) - no Synth astarget
\addToTail
- no Synth astarget
\addAfter
\addBefore
\addReplace
swap out an existing Synth
Synth.new(defName, args, target, addAction: 'addToHead')
// FIXED with addAction d = Synth(\drops, [\out, ~bus]); r = Synth(\reverb, [\in, ~bus], addAction: \addToTail); // FIXED by specifying a TARGET // put it immediately after d d = Synth(\drops, [\out, ~bus]); r = Synth(\reverb, [\in, ~bus], target: d, addAction: \addAfter); d.free; r.free;
38.2.1 addReplace
( SynthDef.new(\drops2, { |freqMin=100, freqMax=3000, gate=1, amp=0.3, out=0| var sig, trigEnv, env, freq, trig; freq = LFNoise1.kr(0.2!2).exprange(freqMin, freqMax); sig = Pulse.ar(freq); trig = Dust.ar(1!2); trigEnv = EnvGen.ar(Env.perc(0.002, 0.1), trig); sig = sig * trigEnv; env = EnvGen.kr(Env.asr(0.01, 1, 1, -1), gate, doneAction: 2); sig = sig * env * amp; Out.ar(out, sig); }).add; ) d = Synth(\drops, [\out, ~bus]); r = Synth(\reverb, [\in, ~bus], target: d, addAction: \addAfter); p = Synth(\drops2, [\out, ~bus], target: d, addAction: \addReplace); d.free; p.free; r.free;
39 Day 39
39.1 Groups
Group
and Synth
are subclasses of Node
.
To organize Synths on the server. Make a "source" group and an "effects" group and put them into the correct order of execution.
set
messages get relayed to everything in the Group.
~grp0 = Group.new; ~grp1 = Group.new; ~grp2 = Group.new; ~grp0.free; ~grp1.free; ~grp2.free; ~grp = 10.collect({Group.new}); ~srcGrp = Group.new; ~fxGrp = Group.new(~srcGrp, \addAfter); d = Synth(\drops, [\out, ~bus], target: ~srcGrp); p = Synth(\drops2, [\out, ~bus, \amp, 0.05], target: ~srcGrp); r = Synth(\reverb, [\in, ~bus], target: ~fxGrp); d.free; p.free; ~srcGrp.set(\gate, 0);
40 Day 40
Starting with Week 10 video
40.1 Patterns
Library of objects that represent algorithmic sequences
Routine
is a subclass of Stream
. Stream
descendants respond to the message next
.
( SynthDef.new(\blip, { |freq=300, amp=0.4, pan=0, out=0| var sig, env; env = EnvGen.ar(Env.perc(0.002, 0.2), doneAction: 2); sig = SinOsc.ar(freq); sig = sig * env; sig = Pan2.ar(sig, pan, amp); Out.ar(out, sig); }).add; ) ( r = Routine.new({ "hi".postln; 1.wait; "hello".postln; 2.wait; "bye".postln; }) ) r.play; // returns the value of `wait` or `yield` r.next; // use it to sequence frequencies ( r = Routine.new({ 200.yield; 300.yield; 400.yield; }) ) Synth(\blip, [\freq, r.next]); r.reset();
41 Day 41
41.1 Stream
Interface, ctd
nextN(n)
, all
( r = Routine.new({ var i = 0; loop { i.yield; i = i + 1; } }); ) r.next; r.reset; r.nextN(10); ( r = Routine.new({ var i = 0; 50.do { i.yield; i = i + 1; } }); ) r.all; // beware of infinite loops!
41.2 Patterns
provide this kind of functionality
need to be converted into a Stream
with asStream
. They are not the sequence, they are stateless blueprints/recipes for sequences of numbers. For example, Pseries
provides an arithmetic series:
p = Pseries(start: 0, step: 1, length: inf); p.next; // doesn't work q = p.asStream; q.next; q.nextN(10); p = Pseries(start: 0, step: 1, length: 20); q = p.asStream; r = p.asStream; q.all; // [ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19 ] r.next; // 0
Can derive multiple Streams
from the same Pattern
=> independent state.
42 Day 42
42.1 Primary Patterns
p = Pseq(list: [1, 14, 200, 97], repeats: 2).asStream; p.nextN(10); // [ 1, 14, 200, 97, 1, 14, 200, 97, nil, nil ] p = Prand(list: [1, 14, 200, 97], repeats: inf).asStream; p.nextN(10); // [ 200, 1, 200, 1, 200, 14, 200, 97, 200, 97 ] // exclusive rand - never 2 same items in a row p = Pxrand(list: [1, 14, 200, 97], repeats: inf).asStream; p.nextN(10); // [ 200, 1, 200, 1, 200, 14, 200, 97, 200, 97 ] // choose a random order and repeat over p = Pshuf(list: [1, 14, 200, 97], repeats: inf).asStream; p.nextN(10); // [ 200, 97, 1, 14, 200, 97, 1, 14, 200, 97 ] p = Pshuf(list: [1, 14, 200, 97], repeats: inf).asStream; p.nextN(10);
42.2 Nested Patterns
If a Stream
encounters a Pattern
in its output sequence, it converts that to a Stream
and embeds the results.
p = Pseq([1, 2, 3, Prand([100, 200, 300], 1)], inf).asStream; p.nextN(10); // [ 1, 2, 3, 200, 1, 2, 3, 300, 1, 2 ] p = Pseq([1, 2, 3, Prand([100, 200, 300], inf)], inf).asStream; // "stuck" in the internal infinite pattern p.nextN(10); // [ 1, 2, 3, 300, 200, 200, 200, 300, 100, 100 ]
42.3 Math with Patterns
// -> a Pbinop / BinaryOpStream p = (Pseq([1, 2, 3], inf) * Pseq([100, 200], inf)).asStream; // 1 * 100, 2 * 200, 3 * 100, 1 * 200, 2 * 100, ... p.nextN(10); // [ 100, 400, 300, 200, 200, 600, 100, 400, 300, 200 ] p = Ptuple([Pbrown(1, 24, 4, inf), Pseries(0, 1, inf)]).asStream; p.nextN(10); // [ [ 19, 0 ], [ 17, 1 ], [ 16, 2 ], [ 12, 3 ], [ 8, 4 ], [ 12, 5 ], [ 9, 6 ], [ 5, 7 ], [ 3, 8 ], [ 5, 9 ] ]
42.4 Pattern "Composition" with Pfunc
// nextFunc, resetFunc p = Pfunc({sin(rrand(10, 20).sqrt + 4)}).asStream; p.nextN(10);
43 Day 43
44 Day 44
44.1 Pbind
Hyper-engineered for flexibly producing a sequence of sound.
Doesn't output numbers, like other Patterns
, but Event
.
( p = Pbind( \instrument, \blip, // use the following synthdef \dur, 0.2, \freq, Pseq([200, 300, 400], inf) ); ) x = p.play; x.pause; x.resume; x.reset;
44.2 Event
inherits from Collection
- orderless (from
Set
) - unique keys (from
Dictionary
) - can treat keys like methods (cf Ruby's
Struct
)
e = Event.new; // ( ) e.add(\foo -> 5); // ( 'foo': 5 ) e.add(\bar -> 12); // ( 'bar': 12, 'foo': 5 ) e.add(\more_stuff -> 101) // ( 'more_stuff': 101, 'bar': 12, 'foo': 5 ) e.at(\foo); // 5 // LITERAL syntax e = (foo: 5, bar: 12, more_stuff: 101); e[\foo]; // 5 e.foo; // 5 (like `Struct`)
especially handy for modeling objects
~sw = (); ~sw.add(\bread -> "wheat"); ~sw.add(\toasted -> true); ~sw.add(\mayo -> false);
44.3 Events
are meant to be play
ed
An
Event
specifies an action to be taken in response to aplay
message. Its key/value pairs specify the parameters of that action.
Empty events play because in Event.sc
, there's a default SynthDef
:
// Event.sc, L165 *makeDefaultSynthDef { SynthDef(\default, { arg out=0, freq=440, amp=0.1, pan=0, gate=1; var z; z = LPF.ar( Mix.new(VarSaw.ar(freq + [0, Rand(-0.4,0.0), Rand(0.0,0.4)], 0, 0.3, 0.3)), XLine.kr(Rand(4000,5000), Rand(2500,3200), 1) ) * Linen.kr(gate, 0.01, 0.7, 0.3, 2); OffsetOut.ar(out, Pan2.ar(z, pan, amp)); }, [\ir]).add; }
( ~whoami = ( name: "julian", play: { ("my name is " ++ \name.envirGet).postln; } ); ) ~whoami.play; // prints "my name is julian" // even empty Events play! - default instrument ().play; // ( 'instrument': default, 'msgFunc': a Function, 'amp': 0.1, 'server': localhost, 'sustain': 0.8, 'isPlaying': true, 'freq': 261.6255653006, 'hasGate': true, 'id': [ 1142 ] ) (freq: 200).play; // ( 'instrument': default, 'msgFunc': a Function, 'amp': 0.1, 'server': localhost, 'sustain': 0.8, 'isPlaying': true, 'freq': 200, 'hasGate': true, 'id': [ 1142 ] ) (freq: 400, amp: 0.5, sustain: 0.03).play; (instrument: \blip, freq: 200).play;
45 Day 45
Starting with Week 11 video
45.1 Pbind
ctd
goes through its keys and and extracts values from patterns, then binds keys to values (creates pairs)
e.g. binds \blip
to instrument
, \freq
to 200
, etc.
(freq: 200, sustain: 2).play; ( p = Pbind( \freq, Pseq([300, 500, 700], inf), \amp, Prand([0.1, 0.2], inf) ); q = p.asStream; // -> a Routine ) q.next(Event.new); // "placeholder" Event to store emitted Events // -> ( 'amp': 0.2, 'freq': 300 ) // -> ( 'amp': 0.2, 'freq': 500 ) // -> ( 'amp': 0.1, 'freq': 700 ) q.nextN(10, ()); // empty event literal // -> [ ( 'freq': 300, 'amp': 0.1 ), ( 'freq': 500, 'amp': 0.1 ), ( 'freq': 700, 'amp': 0.2 ), ( 'freq': 300, 'amp': 0.2 ), ( 'freq': 500, 'amp': 0.1 ), ( 'freq': 700, 'amp': 0.1 ), ( 'freq': 300, 'amp': 0.1 ), ( 'freq': 500, 'amp': 0.1 ), ( 'freq': 700, 'amp': 0.1 ), ( 'freq': 300, 'amp': 0.2 ) ] x = p.play; // -> an EventStreamPlayer x.pause; x.resume;
46 Day 46
46.1 Pbind
ctd.
EventStreamPlayer~/~Pbind
automates the process of doing Synth.new
over and over.
// duration key ( p = Pbind( \dur, Pseq([0.5, 0.2], inf), // in beats, (default tempo clock) \freq, Pseq([300, 500, 700], inf), \amp, Prand([0.1, 0.2], inf) ); ) x = p.play; x.stop;
When using a custom SynthDeef
, the keys in the Event
correspond to the arguments in the SynthDef
. Arguments are actually instances of a UGen
called Control
.
( p = Pbind( \instrument, \blip, \dur, Pseq([0.15, 0.3], inf), // in beats, (default tempo clock) \freq, Pseq([300, 500, 700], inf), \amp, Prand([0.1, 0.8], inf) ); ) x = p.play; x.stop;
What values can be used? Look at the source code of Event
:
partialEvents = ( pitchEvent: ( note: #{ (~degree + ~mtranspose).degreeToKey( ~scale, ~scale.respondsTo(\stepsPerOctave).if( { ~scale.stepsPerOctave }, ~stepsPerOctave ) ); }, midinote: #{ (((~note.value + ~gtranspose + ~root) / ~scale.respondsTo(\stepsPerOctave).if( { ~scale.stepsPerOctave }, ~stepsPerOctave) + ~octave - 5.0) * (12.0 * ~scale.respondsTo(\octaveRatio).if ({ ~scale.octaveRatio }, ~octaveRatio).log2) + 60.0); }, freq: #{ (~midinote.value + ~ctranspose).midicps * ~harmonic; }, ), durEvent: (), ampEvent: (), serverEvent: (), bufferEvent: (), midiEvent: (), //... ) // for example, ways to express frequency: // midinote, ctranspose, harmonic ( p = Pbind( \instrument, \blip, \dur, 0.3, \midinote, Pseq([60, 72], inf), \ctranspose, Pseq([0, 0, 1, 1, 2, 2], inf), \harmonic, Pseq((1!6) ++ (2!6), inf), // [1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2] \amp, 0.2 ); ) x = p.play; x.stop; // note, gtranspose, root ( p = Pbind( \instrument, \blip, \dur, 0.3, \note, Pseq([0, 2, 4, 6], inf), // 0 = midinote 60 \gtranspose, 10, \root, 4, \amp, 0.2 ).trace; // postln the pattern ) x = p.play; x.stop; // scale, degree, mtranspose ( p = Pbind( \instrument, \blip, \dur, 0.3, \degree, Pseq([0, 2, 1, 3, 2, 4], inf), // integer index into scale \scale, #[0, 2, 4, 5, 7, 9, 11], \amp, 0.2 ).trace; // postln the pattern ) x = p.play; x.stop;
47 Day 47
Need to satisfy Event
's contract to be able to use different frequency calculations. E.g. renaming freq
to hz
in our SynthDef will generally work, but all other computations relying on freq
will no longer work.
47.1 Other parameters to override
// db for amp ( p = Pbind( \instrument, \blip, \dur, Pseq([0.15, 0.3], inf), // in beats, (default tempo clock) \freq, Pseq([300, 500, 700], inf), \db, Prand([-30, -3], inf) ); ) x = p.play; x.stop;
47.2 Pbind
Envelopes and Gates
SynthDef
's working with Pbind
must free themselves (doneAction: 2
)
// ADSR with gate ( SynthDef.new(\blip, { |freq=300, amp=0.4, pan=0, out=0, atk=0.002, sus=0, rel=0.2, atkcrv=1, relcrv=(-10), gate=1| var sig, env; env = EnvGen.ar( Env.adsr, gate, doneAction: 2); sig = SinOsc.ar(freq); sig = sig * env; sig = Pan2.ar(sig, pan, amp); Out.ar(out, sig); }).add; ) // EventStreamPlayer handles sending of \gate 0 ( p = Pbind( \instrument, \blip, \dur, Pseq([0.15, 0.3], inf), // in beats, (default tempo clock) \freq, Pseq([300, 500, 700], inf), \db, Prand([-30, -3], inf), \sustain, 2 ); ) x = p.play; x.stop; ///////// Event.sc if (~hasGate == true) {currentEnvironment.set(\gate, 0) } {currentEnvironment.sendOSC([11, ~id]) }; ~isPlaying = false;
48 Day 48
48.1 TempoClock
Side note: finite EventStreams: length of shortest pattern in Pbind
.
( SynthDef.new(\blip, { |freq=300, amp=0.4, pan=0, out=0, atk=0.002, rel=0.2| var sig, env; env = EnvGen.ar( Env.perc(atk, rel), doneAction: 2); sig = SinOsc.ar(freq); sig = sig * env; sig = Pan2.ar(sig, pan, amp); Out.ar(out, sig); }).add; ) ( p = Pbind( \instrument, \blip, \dur, Pseq([0.2], 10), // length 10 \freq, Pseq([300, 500, 700], 2), // length 6 \amp, Prand([0.02, 0.05], inf), ); x = p.play; ) TempoClock.default; TempoClock.default.beats; // current time in beats from server boot TempoClock.default.tempo; // -> 1 (beats per SECOND) t = TempoClock.new(90/60).permanent_(true); // allow this clock to survive a main-stop ( p = Pbind( \instrument, \blip, \dur, Pseq([0.2], inf), \freq, Pseq([300, 500, 700], inf), \amp, Prand([0.02, 0.05], inf), ); // pass the TempoClock in here. x = p.play(t); ) // manually set the tempo t.tempo_(2); t.tempo_(1);
48.2 Synchronizing two Patterns
t = TempoClock.new(130/60).permanent_(true); ( ~p1 = Pbind( \instrument, \blip, \dur, Pseq([0.5], inf), \note, Pseq([0, 7, 10, 3, 5, 7], inf), \amp, 0.2, \rel, 0.08, \pan, Pseq([-1, 1], inf) ); // pass the TempoClock in here. ~q1 = ~p1.play(t); ~p2 = Pbind( \instrument, \blip, \dur, Pseq([1/3], inf), \note, Pseq([0, 7, 10, 3, 5, 7], inf), \amp, 0.1, \gtranspose, 19, \rel, 0.08, \pan, Pseq([1, -1], inf) ); // pass the TempoClock in here. ~q2 = ~p2.play(t); )
48.2.1 Quantizing
For example in live coding, if you want a new eventstream to drop in and align itself to the tempo clock:
t = TempoClock.new(130/60).permanent_(true); ( ~p1 = Pbind( \instrument, \blip, \dur, Pseq([0.5], inf), \note, Pseq([0, 7, 10, 3, 5, 7, 5, 3], inf), \amp, 0.2, \rel, 0.08, \pan, Pseq([-1, 1], inf) ); ~q1 = ~p1.play(t, quant: 4); // quantize to the next WHOLE note ) ( ~p2 = Pbind( \instrument, \blip, \dur, Pseq([1/4], inf), \note, Pseq([10, 3, 5, 7], inf), \amp, 0.1, \gtranspose, 17, \rel, 0.08, \pan, Pseq([1, -1], inf) ); ~q2 = ~p2.play(t, quant: 4); )
49 Day 49
Starting with Week 12 video
49.1 Realtime Control Options for Patterns
49.1.1 Wrap parameters in Pdefn
:
// ADSR with gate ( SynthDef.new(\blip, { |freq=300, amp=0.4, pan=0, out=0, atk=0.002, sus=0, rel=0.2, atkcrv=1, relcrv=(-10), gate=1| var sig, env; env = EnvGen.ar( Env.new([0, 1, 1, 0], [atk, sus, rel], [atkcrv, 0, relcrv]), gate, doneAction: 2); sig = SinOsc.ar(freq); sig = sig * env; sig = Pan2.ar(sig, pan, amp); Out.ar(out, sig); }).add; ) Synth(\blip, [\freq, 400, \rel, 3, \amp, 0.3]); ( p = Pbind( \instrument, \blip, \dur, Pseq([1/8], inf), \note, Pseq([0, 3, 5, 10, 14], inf), \gtranspose, 0, \root, Pdefn(\blip_root, 0), \amp, Pseq([0.8, Pgeom(0.3, 0.9, 7)], inf), \atk, 0, \sus, 0, \rel, Pexprand(0.2, 2, inf), \relcrv, -12, \pan, Pseq([0, 0.5, 0, -0.5], inf) ); ) q = p.play; q.stop; Pdefn(\blip_root, 1);
49.1.2 Wrap the entire pattern in a Pdef
, so that we can overwrite it in place (kind of like a singleton):
( Pdef(\p, Pbind( \instrument, \blip, \dur, Pseq([1/8], inf), \note, Pseq([0, 3, 5, 10, 14], inf), \gtranspose, 0, \root, -10, // try 1, 2, 3 and re-evaluate \amp, Pseq([0.8, Pgeom(0.3, 0.9, 7)], inf), \atk, 0, \sus, 0, \rel, Pexprand(0.2, 2, inf), \relcrv, -12, \pan, Pseq([0, 0.5, 0, -0.5], inf) ); ); ) Pdef(\p).play; Pdef(\p).stop;
49.1.3 Wrap it in a Pbindef
:
( Pbindef( \p, \instrument, \blip, \dur, Pseq([1/8], inf), \note, Pseq([0, 3, 5, 10], inf), \gtranspose, 0, \root, 0, \amp, Pseq([0.8, Pgeom(0.3, 0.9, 7)], inf), \atk, 0, \sus, 0, \rel, Pexprand(0.2, 2, inf), \relcrv, -12, \pan, Pseq([0, 0.5, 0, -0.5], inf) ); ) Pbindef(\p).play(quant: 2); Pbindef(\p).stop; Pbindef(\p, \root, 4, \dur, Pseq([1/16], inf)).quant_(2);
50 Day 50
50.1 GUIs in Supercollider
( Window.closeAll; // class method to close all dangling windows // invisible w = Window.new; // now it's visible w.front; w.alwaysOnTop = true; // or w.alwaysOnTop_(true); // allows chaining w = Window.new("a window", bounds: Rect.new(50, 500, 300, 500)) .front .alwaysOnTop_(true); ) w.close; // also change the bounds programmatically: w.bounds_(Rect(500, 500, 300, 400)); // use the current bounds as a starting point: w.bounds_(w.bounds.translate(Point(100, -100)));
51 Day 51 & 52
51.1 GUI Elements (View
)
Slider |
x top |
n bottom |
|
c center |
|
r random |
( SynthDef.new(\blip, { |freq=300, amp=0.4, pan=0, out=0, gate=1| var sig, env; env = EnvGen.ar( Env.asr, gate, doneAction: 2); sig = SinOsc.ar(freq); sig = sig * env; sig = Pan2.ar(sig, pan, amp); Out.ar(out, sig); }).add; ) x = Synth.new(\blip, [\amp, 0.1]).register; // can now call x.isPlaying ( Window.closeAll; // class method to close all dangling windows // from bottom left of screen w = Window.new("a window", bounds: Rect.new(50, 500, 300, 500)) .front .alwaysOnTop_(true); v = View.new(w, Rect(100, 100, 100, 100)) .background_(Color.blue(0.5, 0.5)); // value, alpha // .background_(Color.new(0.5, 0.2, 0.9)); // RGB // .background_(Color.new255(51, 168, 216)); // RGB 8-bit // from top left of a window ~sl = Slider.new(w, Rect(20, 20, 30, 150)) .action_({ // "listener" |view| // name is arbitrary, represents "this" (the View) view.value.postln; // 0-1 if( x.isPlaying, { x.set(\amp, view.value/4) }; ) }); ~btn = Button.new(w, Rect(60, 20, 100, 30)) .states_([ ["click to start", Color.white, Color.rand], // label, background ["click to stop", Color.black, Color.rand] ]) .action({ |view| if ( view.value == 1, // value associated with the state, starting at 0 { x = Synth.new(\blip, [\amp, ~sl.value/4]).register; // initialize with slider's value/4 }, { x.set(\gate, 0); } ) }); ) x.free; w.view.children; // -> [ a View, a Slider, a Button ] // iterate w.view.children.do { |a| a.visible_(false); }
51.2 MVC Paradigm
Look up SimpleController
52 Day 53
( Window.closeAll; // class method to close all dangling windows // from bottom left of screen w = Window.new("a window", bounds: Rect.new(50, 500, 500, 500)) .front .alwaysOnTop_(true); v = View.new(w, Rect(100, 100, 100, 100)) .background_(Color.blue(0.5, 0.5)); // value, alpha // .background_(Color.new(0.5, 0.2, 0.9)); // RGB // .background_(Color.new255(51, 168, 216)); // RGB 8-bit // from top left of a window ~sl = Slider.new(w, Rect(20, 20, 30, 150)) .action_({ // "listener" |view| // name is arbitrary, represents "this" (the View) view.value.postln; // 0-1 if( x.isPlaying, { x.set(\amp, view.value/4) }; ) }); ~btn = Button.new(w, Rect(60, 20, 100, 30)) .states_([ ["click to start", Color.white, Color.rand], // label, background ["click to stop", Color.black, Color.rand] ]) .action({ |view| if ( view.value == 1, // value associated with the state, starting at 0 { x = Synth.new(\blip, [\amp, ~sl.value/4]).register; // initialize with slider's value/4 }, { x.set(\gate, 0); } ) }); ~knob = Knob.new(w, Rect(100, 200, 40, 40)); ~ms = MultiSliderView.new(w, Rect(100, 250, 120, 120)) .size_(10) .indexThumbSize_(10) .action({ |view| view.value.postln; // -> [0.0, 0.0, ...] }); ~slider2D = Slider2D.new(w, Rect(300, 250, 120, 120)) .size_(10) .indexThumbSize_(10) .action({ |view| view.x.postln; view.y.postln; }); )
Use ControlSpec
to specify min/max/warp/step of Controls
.
~freqSpec = ControlSpec.new(...); ~freqSpec.map(slider.value);
52.1 Layouts
Help > Browse > GUI > Layout
( Window.closeAll; // class method to close all dangling windows // from bottom left of screen w = Window.new("a window", bounds: Rect.new(50, 500, 300, 500)) .front .alwaysOnTop_(true); // w.view.layout_(FlowLayout.new(bounds: w.view, margin: Point(10, 50), gap: Point(5, 5))); // syntax shortcut for Point: @ w.view.decorator_(FlowLayout.new(bounds: w.view.bounds, margin: 10@50, gap: 5@5)); ~knobs = 20.collect({Knob.new(w, 50@50)}) ) ~knobs[10].visible_(false); ~knobs[11].value_(0.5) // doesn't fire action ~knobs[11].valueAction_(0.5) // fires action
53 Day 54 & 55
Starting with Week 13 video
53.1 MIDI in Supercollider
MIDIClient.init; MIDIClient.sources; MIDIClient.destinations; MIDIClient.restart; // To connect to a device, use the index of the sources/destinations array MIDIIn.connect(3); // etc // or MIDIIn.connectAll; MIDIFunc.trace(true); // monitor MIDI messages coming in via PostBuffer
53.2 Making a MIDI Sampler
( b = PathName.new(PathName.new(thisProcess.nowExecutingPath).parentPath ++ "samples/").entries.collect({ |path| Buffer.read(s, path.fullPath); }); ) ( SynthDef.new(\playbuf, { |buf=0, gate=1, amp=1, pan=0, out=0| var sig, env; env = EnvGen.kr(Env.asr(0.01, 1, 0.1), gate, doneAction: 2); sig = PlayBuf.ar(1, buf, BufRateScale.ir(buf), doneAction: 2); sig = sig * env; sig = Pan2.ar(sig, pan, amp); Out.ar(out, sig); }).add; ) // alternative // ~myFunc = MIDIFunc.noteOn({}); ~notes = Array.fill(128, nil); ( MIDIdef.noteOn(\on, { // args may depend on the MIDI message |val, num, chan, src| ~notes.put(num, Synth.new(\playbuf, [ \buf, b.at(num - 21), // assuming b is an array of 128 buffers \gate, 1, \amp, val.linexp(0, 127, 0.02, 0.5), \pan, 0, \out, 0 ])); }); // listen for noteOffs and set gate to 0 MIDIdef.noteOff(\off, { |val, num, chan, src| ~notes.at(num).set(\gate, 0); ~notes.put(num, nil); }); )
54 Day 56 & 57
54.1 "Oversampling" with two Buffer Arrays
( SynthDef.new(\playbuf, { |buf=0, gate=1, amp=1, pan=0, out=0, rev_out=0, rev_amp=0| var sig, env; env = EnvGen.kr(Env.asr(0.01, 1, 0.1), gate, doneAction: 2); sig = PlayBuf.ar(1, buf, BufRateScale.ir(buf), doneAction: 2); sig = sig * env; sig = Pan2.ar(sig, pan, amp); Out.ar(out, sig); Out.ar(rev_out, sig * rev_amp); }).add; ) ( SynthDef.new(\reverb, { |in, out| var sig = In.ar(in, 2); sig = GVerb.ar(sig, 250, 4, drylevel: 0); sig = LPF.ar(sig, 1200); Out.ar(out, sig); }).add; ) ( // refer to a MIDIdef MIDIdef(\on).disable; MIDIdef(\off).disable; ) ( // refer to a MIDIdef MIDIdef(\on).enable; MIDIdef(\off).enable; ) ~notes = Array.fill(128, nil); ~rev_bus = Bus.audio(s, 2); // b is an Array of size 2 (loud, quiet), containing 88 piano samples each ( MIDIdef.noteOn(\on, { // args may depend on the MIDI message |val, num, chan, src| // split between 0-63 and 64-127 var dynamic; dynamic = value.linlin(0, 127, 1, 0).round.asInteger; ~notes.put(num, Synth.new(\playbuf, [ \buf, b.at(dynamic).at(num - 21), // assuming b is an array of 128 buffers \gate, 1, \amp, val.linexp(0, 127, 0.02, 0.5), \pan, 0, \out, 0, \rev_out, ~rev_bus, \rev_amp, ~rev_send.dbamp ])); }, (21..108)); // constrain incoming note numbers // listen for noteOffs and set gate to 0 MIDIdef.noteOff(\off, { |val, num, chan, src| ~notes.at(num).set(\gate, 0); ~notes.put(num, nil); }); // use a CC to control reverb send MIDIdef.cc(\rev, { |val, num, chan, src| ~rev_send = val.linlin(0, 127, -60, 0); // set the send for new notes ~notes.do({ // update all already playing notes |synth| synth.set(\rev_amp, ~rev_send.dbamp); }); }, 22); // controller number! ) ( ~rev = Synth.new(\reverb, [\in, ~rev_bus, \out, 0]); ~rev_send = -20; )
55 Day 58
55.1 Miscellaneous Tips and Tricks
55.1.1 Env alternative syntax
Env....kr
wraps the envelope in an EnvGen
UGen.
( SynthDef.new(\test, { |freq=440, amp=0.2, out=0, pan=0, gate=1| var sig, env; env = Env.adsr.kr(Done.freeSelf, gate); // doneAction, gate sig = SinOsc.ar(freq); sig = Pan2.ar(sig, pan); sig = sig * amp * env; Out.ar(0, sig); }).add; )
55.1.2 NamedControl
returns a UGen
but can also be retrieved by doing \symbol.kr(default)
From the Symbol
docs:
Inside SynthDefs and UGen functions, symbols can be used to conveniently specify control inputs of different rates and with lags (see: NamedControl, ControlName, and Control).
( SynthDef.new(\test, { var sig, env; env = Env.adsr.kr(Done.freeSelf, NamedControl.new(\gate, 1, \kr)); // doneAction, gate sig = SinOsc.ar(NamedControl.new(\freq, 440, \kr)); sig = Pan2.ar(sig, NamedControl.new(\pan, 0, \kr)); sig = sig * NamedControl.new(\amp, 0, \kr) * env; Out.ar(0, NamedControl.new(\out, 0, \ir)); }).add; ) // alternative syntax: NamedControl.new(\amp, 0, \kr) \amp.kr(0) // ===> ( SynthDef.new(\test, { var sig, env; env = Env.adsr.kr(Done.freeSelf, \gate.kr(1)); // doneAction, gate sig = SinOsc.ar(\freq.kr(440)); sig = Pan2.ar(sig, \pan.kr(0)); sig = sig * \amp.kr(0.3) * env; Out.ar(\out.ir(0), sig); }).add; )
56 Day 59
56.1 Array Arguments
( SynthDef.new(\test, { |freq=#[80, 80.5], amp=0.2, out=0, pan=0| // has to be an array LITERAL (#) var sig, env; env = Env.perc.kr(2); // doneAction, gate sig = LFSaw.ar(freq); sig = sig * amp * env; Out.ar(0, sig); }).add; ) Synth(\test); // takes care of the array business, "implicit conversion" ( SynthDef.new(\test, { var sig, env; env = Env.perc.kr(2); sig = LFSaw.ar(\freq.kr([80, 80.5])); sig = sig * \amp.kr(0.3) * env; Out.ar(\out.ir(0), sig); }).add; )
57 Day 60
57.1 Lag~/~VarLag
for Glissando
Technically a low pass filter
( SynthDef.new(\test, { var sig, env; env = Env.adsr.kr(2); sig = LFSaw.ar(\freq.kr(80).varlag(\freqlag.kr(1), \freqcrv.kr(-50))); sig = Pan2.ar(sig, \pan.kr(0)); sig = sig * \amp.kr(0.3) * env; Out.ar(\out.ir(0), sig); }).add; ) x = Synth(\test); x.set(\freq, rrand(40, 70).midicps);
58 Day 61 & 62
Starting with Week 1 of Intermediate Coding with Supercollider
58.1 Organizing Code
ServerTree
to register functions to be evaluated when the NodeTree rebuilds
( // events counter ~counter = 0; // create a blank slate on the server ServerTree.removeAll; // allocate new buses for FX s.newBusAllocators; ~reverbBus = Bus.audio(s, 2); // language side object, don't need server booted ~makeNodes = { s.bind({ // bundles them together as a single OSC message, guarantees order of execution ~fxGroup = Group.new; ~reverb = Synth(\reverb, [\in, ~reverbBus], ~fxGroup); }); }; // events ~events = [ {~sampPlayer = ~sampPat.play(quant: 1)}, {~beepPlayer = ~beepPat.play(quant: 1)}, // ... {~sampPlayer.stop; ~beepPlayer.stop;} ]; // callback to be executed after server boots s.waitForBoot({ s.freeAll; Buffer.freeAll; s.sync; // SynthDefs here... // wait until SynthDefs are loaded s.sync; ~bufPath = PathName.new(thisProcess.nowExecutingPath).parentPath ++ "samples/"; b = PathName(~bufPath).entries.collect({ |pathname| Buffer.read(s, pathname.fullPath); }); s.sync; ServerTree.add(~makeNodes); ServerTree.run; s.sync; // Patterns }); ) b[0].play; b[1].play; ~events[0].value; // ... // evaluate repeatedly ( ~events[~counter].value; ~counter = ~counter + 1; // make it wrap if(~count >= ~events.size, {~count = 0;}); )
59 Day 63
59.1 SoundIn
{ SoundIn.ar(0) }.play; {SinOsc.ar(100) * 0.5}.play; ServerOptions.devices; Server.default.options.device; // NumOutputBuses.ir; ( s.options.device = "Fireface UCX (24007248)"; // s.options.inDevice = "Fireface UCX (24007248)"; // s.options.outDevice = "Fireface UCX (24007248)"; s.options.numInputBusChannels = 8; s.options.numOutputBusChannels = 8; s.reboot; )
60 Day 64, 65, 66
A variable length looper + pattern
( s.options.memSize = 2.pow(20); // ca. 1GB s.reboot; ) MIDIClient.init; MIDIClient.sources; MIDIIn.connectAll; MIDIFunc.trace(false); // note 68 record ( MIDIdef.noteOn(\rec_on, { |val, num, chan, src| ~rec = Synth(\rec, [\bufnum, b]); }, 68); MIDIdef.noteOff(\rec_off, { |val, num, chan, src| ~rec.set(\rec, 0); }, 68); // note 64 launch MIDIdef.noteOn(\pb_launch, { if(~pb.isPlaying, {~pb.set(\t_stop, 1)}); ~pb = Synth(\playback, [\bufnum, b, \end, ~rec_end, \rate, ~rate]).register; // need to register so we can use `isPlaying` }, 64); // note 65 stop MIDIdef.noteOn(\pb_stop, { ~pb.set(\t_stop, 1); }, 65); // CC 112 playback rate MIDIdef.cc(\pb_rate, { |val, num, chan, src| ~rate = val.linlin(ex0, 127, -2.0, 2.0); if(~pb.isPlaying, {~pb.set(\rate, ~rate)}); }, 112); ) // see https://scsynth.org/t/looper-with-a-variable-length/818/6 ( Buffer.freeAll; b = Buffer.alloc(s, s.sampleRate * 10.0, 2); ) ( SynthDef(\rec, { |in=0, bufnum=0, rec=1| var sig = SoundIn.ar(in!2), stopTrig = (rec <= 0), phase = Phasor.ar(0, 1, 0, BufFrames.kr(bufnum)); // RecordBuf.ar(sig, bufnum, doneAction: 2, loop: 0); replaced with BufWr BufWr.ar(sig, bufnum, phase); SendReply.ar(K2A.ar(stopTrig), '/recEnded', phase.poll); FreeSelf.kr(stopTrig); }).add; OSCdef(\ended, { |msg| // msg is ['/recEnded', nodeID, replyID, value0...] // so the data point is msg[3] ~rec_end = msg[3]; // save ending frame index }, '/recEnded', s.addr); SynthDef(\playback, { |out=0, bufnum=0, start=0, end=44100, loop=0, t_stop=0, t_trig=0, rate=1| var sig, phasor; phasor = Phasor.ar( trig: t_trig, rate: BufRateScale.kr(bufnum) * rate, start: start, end: end, resetPos: start ); // phasor.poll; // sig = PlayBuf.ar(2, doneAction: 2, loop: loop); replaced with BufRd sig = BufRd.ar(2, bufnum, phasor, loop); Out.ar(out, sig); FreeSelf.kr(t_stop); }).add; )
61 Day 67
Add a pbind to the sampler
( p = Pbind( \instrument, \playback, \dur, Prand([0.15, 0.3], inf), // in beats, (default tempo clock) // \freq, Pseq([300, 500, 700], inf), \note, Pseq([0, 7, 10, 3, 5, 7, 5, 3], inf), \amp, Pseq([0.8, Pgeom(0.3, 0.9, 7)], inf), \rel, Pexprand(0.2, 2, inf), \sus, Pexprand( ); )
62 Day 68
Send live updates to a Pbindef
:
( s.options.memSize = 2.pow(20); // ca. 1GB s.reboot; ) ( MIDIClient.init; MIDIIn.connectAll; MIDIFunc.trace(false); ) // note 68 record ( ~mtranspose = 0; MIDIdef.noteOn(\rec_on, { |val, num, chan, src| ~rec = Synth(\rec, [\bufnum, b]); }, 68); MIDIdef.noteOff(\rec_off, { |val, num, chan, src| ~rec.set(\rec, 0); }, 68); // note 64 launch MIDIdef.noteOn(\pb_launch, { // if(~pb.isPlaying, {~pb.set(\t_stop, 1)}); if(Pbindef(\pattern).isPlaying, { Pbindef(\pattern).stop; }); // ~pb = Synth(\playback, [\bufnum, b, \end, ~rec_end, \rate, ~rate]).register; Pbindef(\pattern).play; }, 64); // note 65 stop MIDIdef.noteOn(\pb_stop, { // ~pb.set(\t_stop, 1); Pbindef(\pattern).stop; }, 65); // CC 112 playback rate MIDIdef.cc(\pb_rate, { |val, num, chan, src| // ~rate = val.linlin(0, 127, -2.0, 2.0); ~mtranspose = val.linlin(0, 127, -24, 24).round; if(Pbindef(\pattern).isPlaying, { Pbindef(\pattern, \mtranspose, ~mtranspose) }); }, 112); ) // see https://scsynth.org/t/looper-with-a-variable-length/818/6 ( Buffer.freeAll; b = Buffer.alloc(s, s.sampleRate * 10.0, 2); ) ( ~rec_end = 0; SynthDef(\rec, { |in=0, bufnum=0, rec=1| var sig = SoundIn.ar(in!2), stopTrig = (rec <= 0), phase = Phasor.ar(0, 1, 0, BufFrames.kr(bufnum)); // RecordBuf.ar(sig, bufnum, doneAction: 2, loop: 0); replaced with BufWr BufWr.ar(sig, bufnum, phase); SendReply.ar(K2A.ar(stopTrig), '/recEnded', phase.poll); FreeSelf.kr(stopTrig); }).add; OSCdef(\ended, { |msg| // msg is ['/recEnded', nodeID, replyID, value0...] // so the data point is msg[3] ~rec_end = msg[3]; // save ending frame index }, '/recEnded', s.addr); SynthDef(\playback, { |out=0, bufnum=0, start=0, end=44100, loop=0, t_stop=0, t_trig=0, rate=1, amp=0.4, pan=0, atk=0.002, sus=0, rel=0.2, atkcrv=1, relcrv=(-10), gate=1, note=0| var sig, phasor, env; env = Env.new([0, 1, 1, 0], [atk, sus, rel], [atkcrv, 0, relcrv]).ar(gate: gate, doneAction: 2); phasor = Phasor.ar( trig: t_trig, rate: BufRateScale.kr(bufnum) * note.midiratio, start: start, end: end, resetPos: start ); sus.poll; // phasor.poll; // sig = PlayBuf.ar(2, doneAction: 2, loop: loop); replaced with BufRd sig = BufRd.ar(2, bufnum, phasor, loop); sig = sig * env * amp; Out.ar(out, sig); FreeSelf.kr(t_stop); }).add; ) ( Pbindef(\pattern, \instrument, \playback, \dur, Prand([0.15, 0.3], inf), // in beats, (default tempo clock) \degree, Pseq([0, 5, 7, 3], inf), \mtranspose, 0, \amp, Pseq([0.8, Pgeom(0.3, 0.9, 5)], inf), \start, Pbrown(0, ~rec_end * 0.75, 1000, inf), \atk, Pexprand(0.002, 0.2, inf), \rel, Pexprand(0.07, 0.7, inf), \sus, Pexprand(0.005, 0.2, inf) ); )
63 Day 69 & Day 70
Fiddling around with multichannel expansion in patterns
( Pbindef(\pattern, \instrument, \playback, \dur, Prand([1, 2], inf) / 8, // in beats, (default tempo clock) \note, Prand([[[0, 2, 3, 7]], [[0, 2, 4, -2]]], inf) + Prand([[[0, 0, 0, 0]], [[-12, 12, 12, 7]], [[-7, 5, 7, -12]]], inf), \gate, Prand([[[1, 1, 0, 0]], [[0, 0, 1, 1]], [[0, 1, 1, 0]], [[0, 0, 0, 0]]], inf), \gtranspose, 0, \amp, Pseq([0.5, Pgeom(0.3, 0.9, 4)], inf) * Prand([[[0.75, 0.5, 0, 0]], [[0, 0, 0.7, 0.7]], [[0.75, 0, 0, 0.7]]], inf), \start, Pbrown(0, ~rec_end * 0.75, 1000, inf), \rq, Prand([[[0.3, 0.4]], [[0.5, 0.7]]], inf), // multichannel expand \atkcrv, Pseq(Array.interpolation(64, -5, -3), inf), // increasing ramp \atk, Pexprand(0.002, 0.01, inf), \rel, Pexprand(0.05, 0.3, inf), \sus, Pexprand(0.02, 0.15, inf), \stretch, ~stretch, \density, ~density ); )
64 Day 71
Controlling the density of generated sounds with a Pif
:
( // CC 28 "density" MIDIdef.cc(\density, { |val, num, chan, src| ~density = val.asFloat.linlin(0.0, 127.0, 0.0, 1.0); if(Pbindef(\pattern).isPlaying, { ~density.postln; Pbindef(\pattern, \amp, Pseq([0.5, Pgeom(0.3, 0.9, 4)], inf) * Pif(Pfunc({ ~density.coin }), Prand([[[1, 1, Rest(0), Rest(0)]], [[Rest(0), Rest(0), 1, 1]], [[Rest(0), 1, 1, Rest(0)]], [[1, Rest(0), Rest(0), 1]]], inf), Pseq([[[Rest(0), Rest(0), Rest(0), Rest(0)]]], inf))) }); }, 28); ) ( Pbindef(\pattern, \instrument, \playback, \dur, Prand([1, 2], inf) / 8, // in beats, (default tempo clock) \note, Prand([[[0, 2, 3, 7]], [[0, 2, 4, -2]]], inf) + Prand([[[0, 0, 0, 0]], [[-12, 12, 12, 7]], [[-7, 5, 7, -12]]], inf), \gtranspose, 0, \amp, Pseq([0.5, Pgeom(0.3, 0.9, 4)], inf) * Pif(Pfunc({ ~density.coin }), Prand([[[1, 1, Rest(0), Rest(0)]], [[Rest(0), Rest(0), 1, 1]], [[Rest(0), 1, 1, Rest(0)]], [[1, Rest(0), Rest(0), 1]]], inf), Pseq([[[Rest(0), Rest(0), Rest(0), Rest(0)]]], inf)), \start, Pbrown(0, ~rec_end * 0.75, 1000, inf), \rq, Prand([[[0.3, 0.4]], [[0.5, 0.7]]], inf), // multichannel expand \atkcrv, Pseq(Array.interpolation(64, -5, -3), inf), // increasing ramp \atk, Pexprand(0.002, 0.01, inf), \rel, Pexprand(0.05, 0.3, inf), \sus, Pexprand(0.02, 0.15, inf), \stretch, ~stretch ); )
65 Day 72
Parameterizing the number of channels and creating factory methods for note generation etc:
( ~gtranspose = 0; ~rec_end = 0; ~stretch = 1.0; ~density = 0.0; ~num_channels = 12; ~create_notes = { |variation_count=10| Array.fill(variation_count, {[ Array.fill(~num_channels, {[0, 2, 3, 7, 4, 2].choose}).scramble ]}) }; ~create_transpositions = { |variation_count=10| Array.fill(variation_count, {[ [[[0, 0, 0, 0]], [[-12, 12, 12, 7]], [[-7, 5, 7, -12]]].choose ]}) }; ~create_rqs = { |variation_count=10| Array.fill(variation_count, Array.exprand(~num_channels, 8.0, 0.5)) }; ServerTree.removeAll; s.newBusAllocators; s.options.memSize = 2.pow(20); // ca. 1GB MIDIClient.init; MIDIIn.connectAll; MIDIFunc.trace(false); // note 68 record MIDIdef.noteOn(\rec_on, { |val, num, chan, src| ~rec = Synth(\rec, [\bufnum, b]); }, 68); MIDIdef.noteOff(\rec_off, { |val, num, chan, src| ~rec.set(\rec, 0); }, 68); // note 64 launch MIDIdef.noteOn(\pb_launch, { // if(~pb.isPlaying, {~pb.set(\t_stop, 1)}); if(Pbindef(\pattern).isPlaying, { Pbindef(\pattern).stop; }); // ~pb = Synth(\playback, [\bufnum, b, \end, ~rec_end, \rate, ~rate]).register; Pbindef(\pattern).play; }, 64); // note 65 stop MIDIdef.noteOn(\pb_stop, { // ~pb.set(\t_stop, 1); Pbindef(\pattern).stop; }, 65); // CC 112 transpose MIDIdef.cc(\transpose, { |val, num, chan, src| // ~rate = val.linlin(0, 127, -2.0, 2.0); ~gtranspose = val.linlin(0, 127, -24, 24).round; if(Pbindef(\pattern).isPlaying, { Pbindef(\pattern, \gtranspose, ~gtranspose) }); }, 112); // CC 32 "stretch" MIDIdef.cc(\stretch, { |val, num, chan, src| ~stretch = val.asFloat.linexp(0.0, 127.0, 1.0, 10.0); if(Pbindef(\pattern).isPlaying, { Pbindef(\pattern, \stretch, ~stretch) }); }, 32); // CC 28 "density" MIDIdef.cc(\density, { |val, num, chan, src| ~density = val.asFloat.linlin(0.0, 127.0, 0.0, 1.0); if(Pbindef(\pattern).isPlaying, { ~density.postln; Pbindef(\pattern, \amp, Pseq([0.5, Pgeom(0.3, 0.9, 4)], inf) * Pif(Pfunc({ ~density.coin }), Prand([[[1, 1, Rest(0), Rest(0)]], [[Rest(0), Rest(0), 1, 1]], [[Rest(0), 1, 1, Rest(0)]], [[1, Rest(0), Rest(0), 1]]], inf), Pseq([[[Rest(0), Rest(0), Rest(0), Rest(0)]]], inf))) }); }, 28); s.waitForBoot({ s.freeAll; Buffer.freeAll; s.sync; SynthDef(\rec, { |in=0, bufnum=0, rec=1| var sig = SoundIn.ar(in), stopTrig = (rec <= 0), phase = Phasor.ar(0, 1, 0, BufFrames.kr(bufnum)); // RecordBuf.ar(sig, bufnum, doneAction: 2, loop: 0); replaced with BufWr BufWr.ar(sig, bufnum, phase); SendReply.ar(K2A.ar(stopTrig), '/recEnded', phase.poll); FreeSelf.kr(stopTrig); }).add; // see https://scsynth.org/t/looper-with-a-variable-length/818/6 OSCdef(\ended, { |msg| // msg is ['/recEnded', nodeID, replyID, value0...] // so the data point is msg[3] ~rec_end = msg[3]; // save ending frame index }, '/recEnded', s.addr); SynthDef(\playback, { |out=0, bufnum=0, start=0, end=44100, loop=0, t_stop=0, t_trig=0, rate=1, pan=0, atk=0.002, sus=0, rel=0.2, atkcrv=1, relcrv=(-10), gtranspose=0, stretch=1.0| var sig, phasor, env, gate; gate = \gate.kr(Array.fill(~num_channels, 1)); env = Env.new([0, 1, 1, 0], [atk, sus, rel], [atkcrv, 0, relcrv]).ar(gate: gate, doneAction: 2); phasor = Phasor.ar( trig: t_trig, rate: BufRateScale.kr(bufnum) * (\note.kr(Array.fill(~num_channels, 0)) + gtranspose).midiratio, start: start, end: end, resetPos: start ); sig = BufRd.ar(1, bufnum, phasor, loop); sig = BLowPass.ar(sig, Env([Rand(500, 1000), Rand(5000, 3000), Rand(500, 100)], [atk, sus], [8, 0, 0]).kr(gate: gate), rq: \rq.kr(Array.fill(~num_channels, 1))); sig = sig * env * \amp.kr(Array.fill(4, 0.5)) * (-3*~num_channels).dbamp; sig = Splay.ar(sig); Out.ar(out, sig); FreeSelf.kr(t_stop); }).add; s.sync; b = Buffer.alloc(s, s.sampleRate * 10.0, 1); s.sync; }) ) ( Pbindef(\pattern, \instrument, \playback, \dur, Pwrand([1, 2, 4, 6], [0.5, 0.3, 0.15, 0.05], inf) / 8, // in beats, (default tempo clock) \note, Prand(~create_notes.(100), inf) + Prand(~create_transpositions.(100), inf), \gtranspose, 0, \amp, Pseq([1.0, Pgeom(0.7, 0.9, 4)], inf) * Pif(Pfunc({ ~density.coin }), Prand([[[1, 1, Rest(0), Rest(0)]], [[Rest(0), Rest(0), 1, 1]], [[Rest(0), 1, 1, Rest(0)]], [[1, Rest(0), Rest(0), 1]]], inf), Pseq([[[Rest(0), Rest(0), Rest(0), Rest(0)]]], inf)), /* * Prand([[[0.75, 0.5, 0, 0]], [[0, 0, 0.7, 0.7]], [[0.75, 0, 0, 0.7]]], inf) */ \start, Pbrown(0, ~rec_end * 0.75, 1000, inf), \rq, Prand(~create_rqs.(100), inf), // multichannel expand \atkcrv, Pseq(Array.interpolation(64, -5, -3), inf), // increasing ramp \atk, Pexprand(0.002, 0.01, inf), \rel, Pexprand(0.05, 0.3, inf), \sus, Pexprand(0.02, 0.15, inf), \stretch, ~stretch ); )
66 Day 73
Create a rest factory and improve the transposition factory:
( ~gtranspose = 0; ~rec_end = 0; ~stretch = 1.0; ~density = 0.0; ~num_channels = 12; ~create_notes = { |variation_count=10| Array.fill(variation_count, {[ Array.fill(~num_channels, {[0, 2, 3, 7, 4, 2].choose}).scramble ]}) }; ~create_transpositions = { |variation_count=10| Array.fill(variation_count, {[ Array.fill(~num_channels, {[0, 12, -12, 5, 7, -7].wchoose([0.5, 0.15, 0.15, 0.1, 0.5, 0.5])}).scramble ]}) }; ~create_rests = { |variation_count=10| Array.fill(variation_count, {[ Array.fill(~num_channels, {[1, Rest(0)].choose}).scramble ]}) }; ~create_rqs = { |variation_count=10| Array.fill(variation_count, Array.exprand(~num_channels, 8.0, 0.5)) }; ServerTree.removeAll; s.newBusAllocators; s.options.memSize = 2.pow(20); // ca. 1GB MIDIClient.init; MIDIIn.connectAll; MIDIFunc.trace(false); // note 68 record MIDIdef.noteOn(\rec_on, { |val, num, chan, src| ~rec = Synth(\rec, [\bufnum, b]); }, 68); MIDIdef.noteOff(\rec_off, { |val, num, chan, src| ~rec.set(\rec, 0); }, 68); // note 64 launch MIDIdef.noteOn(\pb_launch, { // if(~pb.isPlaying, {~pb.set(\t_stop, 1)}); if(Pbindef(\pattern).isPlaying, { Pbindef(\pattern).stop; }); // ~pb = Synth(\playback, [\bufnum, b, \end, ~rec_end, \rate, ~rate]).register; Pbindef(\pattern).play; }, 64); // note 65 stop MIDIdef.noteOn(\pb_stop, { // ~pb.set(\t_stop, 1); Pbindef(\pattern).stop; }, 65); // CC 112 transpose MIDIdef.cc(\transpose, { |val, num, chan, src| // ~rate = val.linlin(0, 127, -2.0, 2.0); ~gtranspose = val.linlin(0, 127, -24, 24).round; if(Pbindef(\pattern).isPlaying, { Pbindef(\pattern, \gtranspose, ~gtranspose) }); }, 112); // CC 32 "stretch" MIDIdef.cc(\stretch, { |val, num, chan, src| ~stretch = val.asFloat.linexp(0.0, 127.0, 1.0, 100.0); if(Pbindef(\pattern).isPlaying, { Pbindef(\pattern, \stretch, ~stretch) }); }, 32); // CC 46 "density" / Fader 1 MIDIdef.cc(\density, { |val, num, chan, src| ~density = val.asFloat.linlin(0.0, 127.0, 0.0, 1.0); if(Pbindef(\pattern).isPlaying, { ~density.postln; Pbindef(\pattern, \amp, Pseq([0.5, Pgeom(0.3, 0.9, 4)], inf) * Pif(Pfunc({ ~density.coin }), Prand(~create_rests.(100), inf))) }); }, 46); s.waitForBoot({ s.freeAll; Buffer.freeAll; s.sync; SynthDef(\rec, { |in=0, bufnum=0, rec=1| var sig = SoundIn.ar(in), stopTrig = (rec <= 0), phase = Phasor.ar(0, 1, 0, BufFrames.kr(bufnum)); // RecordBuf.ar(sig, bufnum, doneAction: 2, loop: 0); replaced with BufWr BufWr.ar(sig, bufnum, phase); SendReply.ar(K2A.ar(stopTrig), '/recEnded', phase.poll); FreeSelf.kr(stopTrig); }).add; // see https://scsynth.org/t/looper-with-a-variable-length/818/6 OSCdef(\ended, { |msg| // msg is ['/recEnded', nodeID, replyID, value0...] // so the data point is msg[3] ~rec_end = msg[3]; // save ending frame index }, '/recEnded', s.addr); SynthDef(\playback, { |out=0, bufnum=0, start=0, end=44100, loop=0, t_stop=0, t_trig=0, rate=1, pan=0, atk=0.002, sus=0, rel=0.2, atkcrv=1, relcrv=(-10), gtranspose=0, stretch=1.0| var sig, phasor, env, gate; gate = \gate.kr(Array.fill(~num_channels, 1)); env = Env.new([0, 1, 1, 0], [atk, sus, rel] * stretch, [atkcrv, 0, relcrv]).ar(gate: gate, doneAction: 2); phasor = Phasor.ar( trig: t_trig, rate: BufRateScale.kr(bufnum) * (\note.kr(Array.fill(~num_channels, 0)) + gtranspose).midiratio, start: start, end: end, resetPos: start ); sig = BufRd.ar(1, bufnum, phasor, loop); sig = BLowPass.ar(sig, Env([Rand(500, 1000), Rand(5000, 3000), Rand(500, 100)], [atk, sus], [8, 0, 0]).kr(gate: gate), rq: \rq.kr(Array.fill(~num_channels, 1))); sig = sig * env * \amp.kr(Array.fill(~num_channels, 0.5)).poll * (-3*~num_channels).dbamp; sig = Splay.ar(sig); Out.ar(out, sig); FreeSelf.kr(t_stop); }).add; s.sync; b = Buffer.alloc(s, s.sampleRate * 10.0, 1); s.sync; }) ) ( Pbindef(\pattern, \instrument, \playback, \dur, Pwrand([1, 2, 4, 6], [0.7, 0.2, 0.07, 0.03], inf) / 8, // in beats, (default tempo clock) \note, Prand(~create_notes.(100), inf) + Prand(~create_transpositions.(100), inf), \gtranspose, 0, \amp, Pseq([1.0, Pgeom(0.7, 0.9, 4)], inf) * Pif(Pfunc({ ~density.coin }), Prand(~create_rests.(100), inf), Pseq([[Rest(0)!~num_channels]], inf)), \start, Pbrown(0, ~rec_end * 0.75, 1000, inf), \rq, Prand(~create_rqs.(100), inf), // multichannel expand \atkcrv, Pseq(Array.interpolation(64, -5, -3), inf), // increasing ramp \atk, Pexprand(0.002, 0.01, inf), \rel, Pexprand(0.05, 0.3, inf), \sus, Pexprand(0.02, 0.15, inf), \stretch, ~stretch ); ) MIDIFunc.trace(true);
67 Day 74
Add pitch variations:
( Server.killAll; ~gtranspose = 0; ~rec_end = 0; ~stretch = 1.0; ~density = 0.0; ~num_channels = 12; ~pitch_env_amp = 0.0; ~pitch_range = 0.0; ~create_notes = { |variation_count=10| Array.fill(variation_count, {[ Array.fill(~num_channels, {[0, 2, 3, 7, 4, 2].choose}).scramble ]}) }; ~create_transpositions = { |variation_count=10| Array.fill(variation_count, {[ Array.fill(~num_channels, {[0, 12, -12, 5, 7, -7].wchoose([0.5, 0.15, 0.15, 0.1, 0.5, 0.5])}).scramble ]}) }; ~create_rests = { |variation_count=10| Array.fill(variation_count, {[ Array.fill(~num_channels, {[1, Rest(0)].choose}).scramble ]}) }; ~create_rqs = { |variation_count=10| Array.fill(variation_count, Array.exprand(~num_channels, 12.0, 1.0)) }; ServerTree.removeAll; s.newBusAllocators; s.options.memSize = 2.pow(20); // ca. 1GB MIDIClient.init; MIDIIn.connectAll; MIDIFunc.trace(false); // note 68 record MIDIdef.noteOn(\rec_on, { |val, num, chan, src| ~rec = Synth(\rec, [\bufnum, b]); }, 68); MIDIdef.noteOff(\rec_off, { |val, num, chan, src| ~rec.set(\rec, 0); }, 68); // note 64 launch MIDIdef.noteOn(\pb_launch, { // if(~pb.isPlaying, {~pb.set(\t_stop, 1)}); if(Pbindef(\pattern).isPlaying, { Pbindef(\pattern).stop; }); // ~pb = Synth(\playback, [\bufnum, b, \end, ~rec_end, \rate, ~rate]).register; Pbindef(\pattern).play; }, 64); // note 65 stop MIDIdef.noteOn(\pb_stop, { // ~pb.set(\t_stop, 1); Pbindef(\pattern).stop; }, 65); // CC 112 transpose MIDIdef.cc(\transpose, { |val, num, chan, src| // ~rate = val.linlin(0, 127, -2.0, 2.0); ~gtranspose = val.linlin(0, 127, -24, 24).round; if(Pbindef(\pattern).isPlaying, { Pbindef(\pattern, \gtranspose, ~gtranspose) }); }, 112); // CC 32 "stretch" MIDIdef.cc(\stretch, { |val, num, chan, src| ~stretch = val.asFloat.linexp(0.0, 127.0, 1.0, 100.0); if(Pbindef(\pattern).isPlaying, { Pbindef(\pattern, \stretch, ~stretch) }); }, 32); // CC 28 "pitch_range" MIDIdef.cc(\pitch_range, { |val, num, chan, src| ~pitch_range = val.asFloat.linexp(0.0, 127.0, 1.0, 2.0) - 1.0; if(Pbindef(\pattern).isPlaying, { Pbindef(\pattern, \pitch_range, ~pitch_range) }); }, 28); // CC 46 "density" / Fader 1 on Channel 0 MIDIdef.cc(\density, { |val, num, chan, src| ~density = val.asFloat.linlin(0.0, 127.0, 0.0, 1.0); if(Pbindef(\pattern).isPlaying, { ~density.postln; Pbindef(\pattern, \amp, Pseq([1.0, Pgeom(0.7, 0.9, 5)], inf) * Pif(Pfunc({ ~density.coin }), Prand(~create_rests.(100), inf), Pseq([[Rest(0)!~num_channels]], inf))) }); }, 46, 0); // CC 46 "pitch envelope amplitude" / Fader 2 on Channel 1 MIDIdef.cc(\pitch_env_amp, { |val, num, chan, src| ~pitch_env_amp = val.linlin(0, 127, -24, 24).postln; if(Pbindef(\pattern).isPlaying, { Pbindef(\pattern, \pitch_env_amp, ~pitch_env_amp) }); }, 46, 1); s.waitForBoot({ s.freeAll; Buffer.freeAll; s.sync; SynthDef(\rec, { |in=0, bufnum=0, rec=1| var sig = SoundIn.ar(in), stopTrig = (rec <= 0), phase = Phasor.ar(0, 1, 0, BufFrames.kr(bufnum)); // RecordBuf.ar(sig, bufnum, doneAction: 2, loop: 0); replaced with BufWr BufWr.ar(sig, bufnum, phase); SendReply.ar(K2A.ar(stopTrig), '/recEnded', phase.poll); FreeSelf.kr(stopTrig); }).add; // see https://scsynth.org/t/looper-with-a-variable-length/818/6 OSCdef(\ended, { |msg| // msg is ['/recEnded', nodeID, replyID, value0...] // so the data point is msg[3] ~rec_end = msg[3]; // save ending frame index }, '/recEnded', s.addr); SynthDef(\playback, { |out=0, bufnum=0, start=0, end=44100, loop=0, t_stop=0, t_trig=0, rate=1, pan=0, atk=0.002, sus=0, rel=0.2, atkcrv=1, relcrv=(-10), gtranspose=0, stretch=1.0| var sig, phasor, env, gate; gate = \gate.kr(Array.fill(~num_channels, 1)); env = Env.new([0, 1, 1, 0], [atk, sus, rel] * stretch, [atkcrv, 0, relcrv]).ar(gate: gate, doneAction: 2); phasor = Phasor.ar( trig: t_trig, rate: BufRateScale.kr(bufnum) * ((\note.kr(Array.fill(~num_channels, 0)) * \pitch_range.kr(~pitch_range) + gtranspose + Env.perc(atk, rel, \pitch_env_amp.kr(0.0), -12).ar(gate: gate)) ).midiratio, start: start, end: end, resetPos: start ); sig = BufRd.ar(1, bufnum, phasor, loop); sig = BLowPass.ar(sig, Env([Rand(500, 1000), Rand(5000, 3000), Rand(500, 100)], [atk, rel], [8, 0]).kr(gate: gate), rq: \rq.kr(Array.fill(~num_channels, 1))); sig = sig * env * (\amp.kr(Array.fill(~num_channels, 0.5)) * (-3*~num_channels - 6).dbamp); sig = Splay.ar(sig); Out.ar(out, sig); FreeSelf.kr(t_stop); }).add; s.sync; b = Buffer.alloc(s, s.sampleRate * 10.0, 1); s.sync; }) ) ( Pbindef(\pattern, \instrument, \playback, \dur, Pwrand([1, 2, 4, 6], [0.7, 0.2, 0.07, 0.03], inf) / 8, // in beats, (default tempo clock) \note, Prand(~create_notes.(100), inf) + Prand(~create_transpositions.(100), inf), \gtranspose, ~gtranspose, \amp, Pseq([1.0, Pgeom(0.7, 0.9, 5)], inf) * Pif(Pfunc({ ~density.coin }), Prand(~create_rests.(100), inf), Pseq([[Rest(0)!~num_channels]], inf)), \start, Pbrown(0, ~rec_end * 0.75, 1000, inf), \rq, Prand(~create_rqs.(100), inf), // multichannel expand \atkcrv, Pseq(Array.interpolation(64, -5, -3), inf), // increasing ramp \atk, Pexprand(0.002, 0.05, inf), \rel, Pexprand(0.05, 0.3, inf), \sus, Pexprand(0.02, 0.15, inf), \stretch, ~stretch, \pitch_env_amp, ~pitch_env_amp, \pitch_range, ~pitch_range ); )
68 Day 75
Fixing some nasty clicks using t_gate
( SynthDef.new(\blip, { |freq=300, amp=0.4, pan=0, out=0, atk=0.002, sus=0, rel=0.2, atkcrv=1, relcrv=(-10)| var sig, env; var gate = \t_gate.kr(Array.fill(5, 1)); env = Env.new([0, 1, 1, 0], [atk, sus, rel], [atkcrv, 0, relcrv]).kr(gate: gate, doneAction: 2); sig = SinOsc.ar({rrand(200, 400)}!5); sig = sig * env * 0.1; sig = Splay.ar(sig); Out.ar(out, sig); }).add; ) ( ~patt = Pbindef(\pattern2, \instrument, \blip, \dur, Pwrand([1, 2, 4, 6, 8, 12], [20, 10, 5, 3, 2, 1].normalizeSum, inf) / 4, \atk, Pexprand(0.002, 0.5, inf), \rel, Pexprand(0.2, 0.3, inf), \sus, Pexprand(0.02, 0.15, inf), ); ~patt.play; ) ~patt.stop;
69 Day 76
Adding pitch modulation with LFNoise1
:
( Server.killAll; ~gtranspose = 0; ~rec_end = 0; ~stretch = 1.0; ~density = 0.0; ~num_channels = 12; ~pitch_env_amp = 0.0; ~pitch_range = 0.0; ~pitch_mod_amp = 0.0; ~create_notes = { |variation_count=10| Array.fill(variation_count, {[ Array.fill(~num_channels, {[0, 2, 3, 7, 4, 2].choose}).scramble ]}) }; ~create_transpositions = { |variation_count=10| Array.fill(variation_count, {[ Array.fill(~num_channels, {[0, 12, -12, 5, 7, -7].wchoose([0.5, 0.15, 0.15, 0.1, 0.5, 0.5])}).scramble ]}) }; ~create_rests = { |variation_count=10| Array.fill(variation_count, {[ Array.fill(~num_channels, {[1, Rest(0)].choose}).scramble ]}) }; ~create_rqs = { |variation_count=10| Array.fill(variation_count, Array.exprand(~num_channels, 10.0, 1.0)) }; ServerTree.removeAll; s.newBusAllocators; s.options.memSize = 2.pow(20); // ca. 1GB MIDIClient.init; MIDIIn.connectAll; MIDIFunc.trace(false); // note 68 record MIDIdef.noteOn(\rec_on, { |val, num, chan, src| ~rec = Synth(\rec, [\bufnum, b]); }, 68); MIDIdef.noteOff(\rec_off, { |val, num, chan, src| ~rec.set(\rec, 0); }, 68); // note 64 launch MIDIdef.noteOn(\pb_launch, { // if(~pb.isPlaying, {~pb.set(\t_stop, 1)}); if(Pbindef(\pattern).isPlaying, { Pbindef(\pattern).stop; }); // ~pb = Synth(\playback, [\bufnum, b, \end, ~rec_end, \rate, ~rate]).register; Pbindef(\pattern).play; }, 64); // note 65 stop MIDIdef.noteOn(\pb_stop, { // ~pb.set(\t_stop, 1); Pbindef(\pattern).stop; }, 65); // CC 112 transpose MIDIdef.cc(\transpose, { |val, num, chan, src| // ~rate = val.linlin(0, 127, -2.0, 2.0); ~gtranspose = val.linlin(0, 127, -24, 24).round; if(Pbindef(\pattern).isPlaying, { Pbindef(\pattern, \gtranspose, ~gtranspose) }); }, 112); // CC 32 "stretch" MIDIdef.cc(\stretch, { |val, num, chan, src| ~stretch = val.asFloat.linexp(0.0, 127.0, 1.0, 100.0); if(Pbindef(\pattern).isPlaying, { Pbindef(\pattern, \stretch, ~stretch) }); }, 32); // CC 28 "pitch_range" MIDIdef.cc(\pitch_range, { |val, num, chan, src| ~pitch_range = val.asFloat.linexp(0.0, 127.0, 1.0, 2.0) - 1.0; if(Pbindef(\pattern).isPlaying, { Pbindef(\pattern, \pitch_range, ~pitch_range) }); }, 28); // CC 10 "pitch_mod_amp" MIDIdef.cc(\pitch_mod_amp, { |val, num, chan, src| ~pitch_mod_amp = val.asFloat.linlin(0.0, 127.0, 0.0, 10.0); if(Pbindef(\pattern).isPlaying, { Pbindef(\pattern, \pitch_mod_amp, ~pitch_mod_amp) }); }, 10); // CC 46 "density" / Fader 1 on Channel 0 MIDIdef.cc(\density, { |val, num, chan, src| ~density = val.asFloat.linlin(0.0, 127.0, 0.0, 1.0); if(Pbindef(\pattern).isPlaying, { ~density.postln; Pbindef(\pattern, \amp, Pseq([1.0, Pgeom(0.7, 0.9, 5)], inf) * Pif(Pfunc({ ~density.coin }), Prand(~create_rests.(100), inf), Pseq([[Rest(0)!~num_channels]], inf))) }); }, 46, 0); // CC 46 "pitch envelope amplitude" / Fader 2 on Channel 1 MIDIdef.cc(\pitch_env_amp, { |val, num, chan, src| ~pitch_env_amp = val.linlin(0, 127, -24, 24).postln; if(Pbindef(\pattern).isPlaying, { Pbindef(\pattern, \pitch_env_amp, ~pitch_env_amp) }); }, 46, 1); s.waitForBoot({ s.freeAll; Buffer.freeAll; s.sync; SynthDef(\rec, { |in=0, bufnum=0, rec=1| var sig = SoundIn.ar(in), stopTrig = (rec <= 0), phase = Phasor.ar(0, 1, 0, BufFrames.kr(bufnum)); // RecordBuf.ar(sig, bufnum, doneAction: 2, loop: 0); replaced with BufWr BufWr.ar(sig, bufnum, phase); SendReply.ar(K2A.ar(stopTrig), '/recEnded', phase.poll); FreeSelf.kr(stopTrig); }).add; // see https://scsynth.org/t/looper-with-a-variable-length/818/6 OSCdef(\ended, { |msg| // msg is ['/recEnded', nodeID, replyID, value0...] // so the data point is msg[3] ~rec_end = msg[3]; // save ending frame index }, '/recEnded', s.addr); SynthDef(\playback, { |out=0, bufnum=0, start=0, end=44100, loop=0, t_stop=0, t_trig=0, rate=1, pan=0, atkcrv=1, relcrv=(-10), gtranspose=0| var sig, phasor, env, t_gate; var dur = \dur.kr / \tempo.kr(1); var atk = \atk.kr(0.002); var sus = \sus.kr(0); var rel = \rel.kr(0.2); t_gate = \t_gate.kr(Array.fill(~num_channels, 1)); env = Env.new([0, 1, 1, 0], [atk, sus, rel], [atkcrv, 0, relcrv]).kr(gate: t_gate, timeScale: dur * \env_stretch.kr(1.0), doneAction: 2); phasor = Phasor.ar( trig: t_trig, rate: BufRateScale.kr(bufnum) * (\note.kr(Array.fill(~num_channels, 0)) * \pitch_range.kr(~pitch_range) + gtranspose + Env.perc(atk, rel, \pitch_env_amp.kr(0.0), -12).ar(gate: t_gate) + LFNoise1.ar(6, \pitch_mod_amp.kr(0.0)) ).midiratio, start: start, end: end, resetPos: start ); sig = BufRd.ar(1, bufnum, phasor, loop); sig = BLowPass.ar(sig, Env([Rand(200, 800), Rand(5000, 3000), Rand(500, 100)], [atk, rel], [3, 0]).kr(gate: t_gate, timeScale: dur), rq: \rq.kr(Array.fill(~num_channels, 1))); sig = sig * env * (\amp.kr(Array.fill(~num_channels, 0.5)) * (-3*~num_channels).dbamp); sig = Splay.ar(sig); Out.ar(out, sig); }).add; s.sync; b = Buffer.alloc(s, s.sampleRate * 10.0, 1); s.sync; }) ) ( Pbindef(\pattern, \instrument, \playback, \tempo, Pfunc {thisThread.clock.tempo}, \dur, Pwrand([1, 2, 4, 6, 8, 12], [80, 20, 5, 3, 2, 1].normalizeSum, inf) / 8, // in beats, (default tempo clock) \note, Prand(~create_notes.(100), inf) + Prand(~create_transpositions.(100), inf), \gtranspose, ~gtranspose, \amp, Pseq([1.0, Pgeom(0.7, 0.9, 5)], inf) * Pif(Pfunc({ ~density.coin }), Prand(~create_rests.(100), inf), Pseq([[Rest(0)!~num_channels]], inf)), \start, Pbrown(0, ~rec_end * 0.75, 1000, inf), \rq, Prand(~create_rqs.(100), inf), // multichannel expand \atkcrv, Pseq(Array.interpolation(64, -5, -3), inf), // increasing ramp \atk, Pexprand(0.005, 0.5, inf), \rel, Pexprand(0.2, 0.3, inf), \sus, Pexprand(0.02, 0.15, inf), \env_stretch, ~stretch, \pitch_env_amp, ~pitch_env_amp, \pitch_range, ~pitch_range, \pitch_mod_amp, ~pitch_mod_amp ); )
70 Day 77
Variable Length Looper Walkthrough:
71 Day 78
Prepare the looper to hold multiple buffers:
~num_buffers.collect { |bufnum| // RECORD MIDIdef.noteOn('rec_on_' ++ bufnum, { |val, num, chan, src| ~rec = Synth(\rec, [\bufnum, ~buffers[bufnum]]); }, (68 + bufnum)); MIDIdef.noteOff('rec_off_' ++ bufnum, { |val, num, chan, src| ~rec.set(\rec, 0); }, (68 + bufnum)); // PLAYBACK MIDIdef.noteOn('pb_launch_stop_' ++ bufnum, { if(Pbindef('pattern_' ++ bufnum).isPlaying, { Pbindef('pattern_' ++ bufnum).stop; }, { Pbindef('pattern_' ++ bufnum, \end, ~rec_end).play; }); }, (64 + bufnum)); // DENSITY // CC 46 "density" / Fader 1 on Channel 0, 2, 4, 6 MIDIdef.cc('density_' ++ bufnum, { |val, num, chan, src| ~density = val.asFloat.linlin(0.0, 127.0, 0.0, 1.0); if(Pbindef('pattern_' ++ bufnum).isPlaying, { "DENSITY " ++ bufnum ++ ": " ++ ~density.postln; Pbindef('pattern_' ++ bufnum, \amp, Pseq([1.0, Pgeom(0.7, 0.9, 5)], inf) * Pif(Pfunc({ ~density.coin }), Prand(~create_rests.(100), inf), Pseq([[Rest(0)!~num_channels]], inf))) }); }, 46, bufnum * 2); };
72 Day 79
Make the specified pattern dynamic:
~num_buffers.collect { |bufnum| var pattern_key = ('pattern_' ++ bufnum).asSymbol; // RECORD MIDIdef.noteOn(('rec_on_' ++ bufnum).asSymbol, { |val, num, chan, src| ~rec = Synth(\rec, [\bufnum, ~buffers[bufnum]]); }, (68 + bufnum)); MIDIdef.noteOff(('rec_off_' ++ bufnum).asSymbol, { |val, num, chan, src| ~rec.set(\rec, 0); }, (68 + bufnum)); // PLAYBACK MIDIdef.noteOn(('pb_launch_stop_' ++ bufnum).asSymbol, { if(Pbindef(pattern_key).isPlaying, { Pbindef(pattern_key).stop; }, { Pbindef(pattern_key, \end, ~rec_end).play; }); }, (64 + bufnum)); // DENSITY // CC 46 "density" / Fader 1 on Channel 0, 2, 4, 6 MIDIdef.cc(('density_' ++ bufnum).asSymbol, { |val, num, chan, src| ~density[bufnum] = val.asFloat.linlin(0.0, 127.0, 0.0, 1.0); if(Pbindef(pattern_key).isPlaying, { ("DENSITY " ++ bufnum ++ ": " ++ ~density[bufnum]).postln; Pbindef(pattern_key, \amp, Pseq([1.0, Pgeom(0.7, 0.9, 5)], inf) * Pif(Pfunc({ ~density[bufnum].coin }), Prand(~create_rests.(100), inf), Pseq([[Rest(0)!~num_channels]], inf))) }); }, 46, bufnum * 2); // TRANSPOSE MIDIdef.cc(('transpose_' ++ bufnum).asSymbol, { |val, num, chan, src| ~gtranspose[bufnum] = val.linlin(0, 127, -24, 24).round; ("GTRANSPOSE " ++ bufnum ++ ": " ++ ~gtranspose[bufnum]).postln; if(Pbindef(pattern_key).isPlaying, { Pbindef(pattern_key, \gtranspose, ~gtranspose[bufnum]) }); }, 46, bufnum * 2 + 1); }; // ... ( ~num_buffers.collect { |bufnum| Pbindef(('pattern_' ++ bufnum).asSymbol, \instrument, \playback, \bufnum, ~buffers[bufnum], \tempo, Pfunc {thisThread.clock.tempo}, \dur, Pwrand([1, 2, 4, 6, 8, 12], [80, 20, 5, 3, 2, 1].normalizeSum, inf) / 8, // in beats, (default tempo clock) \note, Prand(~create_notes.(100), inf) + Prand(~create_transpositions.(100), inf), \gtranspose, ~gtranspose[bufnum], \amp, Pseq([1.0, Pgeom(0.7, 0.9, 5)], inf) * Pif(Pfunc({ ~density[bufnum].coin }), Prand(~create_rests.(100), inf), Pseq([[Rest(0)!~num_channels]], inf)), \start, 0, //Pbrown(0, ~rec_end * 0.75, 1000, inf), \end, ~rec_end, \filter_freq, Pseq([60, 66, 70, 72].collect {|midinote| midinote.midicps}, inf), \rq, Prand(~create_rqs.(100), inf), // multichannel expand \atkcrv, Pseq(Array.interpolation(64, -5, -3), inf), // increasing ramp \atk, Pexprand(0.005, 0.5, inf), \rel, Pexprand(0.2, 0.6, inf), \sus, Pexprand(0.02, 0.15, inf), \env_stretch, ~stretch, \pitch_env_amp, ~pitch_env_amp, \pitch_range, ~pitch_range, \pitch_mod_amp, ~pitch_mod_amp ); }; )
Video here: https://youtu.be/zuMjn-EtNMY
73 Day 80 + 81
Granular Synthesis, following Intermediate Coding with Supercollider, Week 5
GrainBuf
(see Tutorials 25&26) and TGrains
Use mono buffers!
Variations
pos
:- backwards from 1 to 0
- fixed value
- time scaling by stretching
Line
to double length
trigger
:- increase/decrease
Dust
frequency
- increase/decrease
dur
( ~path = PathName.new(thisProcess.nowExecutingPath).parentPath ++ "samples/"; // b = Buffer.read(s, ~path ++ "brass.wav"); // if the file is stereo b = Buffer.readChannel(s, ~path ++ "brass.wav", channels: [0]); ) ( SynthDef(\grainbuf, { var sig, env; env = Env([0, 1, 1, 0], [\atk.ir(1), \sus.ir(4), \rel.ir(4)], [1, 0 ,-1]).kr(2); sig = GrainBuf.ar( numChannels: 2, trigger: Dust.kr(40), dur: 0.1, sndbuf: \buf.kr(0), rate: BufRateScale.kr(\buf.kr(0)) * \rate.kr(1), // pos: Line.kr(0, 1, BufDur.kr(\buf.kr(0))), pos: Line.kr(0, 1, BufDur.kr(\buf.kr(0)) * \timescale.kr(1)), // time scaling // pos: Line.kr(1, 0, BufDur.kr(\buf.kr(0))), // backwards // pos: 0.4, // fixed value interp: 2, pan: 0, envbufnum: -1 ); sig = sig * env * \amp.kr(0.5); Out.ar(\out.kr(0), sig); }).add; ) b.play; Synth(\grainbuf, [\buf, b, \amp, 1]);
74 Day 82
TGrains
( ~path = PathName.new(thisProcess.nowExecutingPath).parentPath ++ "samples/"; // b = Buffer.read(s, ~path ++ "brass.wav"); // if the file is stereo b = Buffer.readChannel(s, ~path ++ "brass.wav", channels: [0]); ) ( SynthDef(\tgrains, { var sig, env; env = Env([0, 1, 1, 0], [\atk.ir(1), \sus.ir(4), \rel.ir(4)], [1, 0 ,-1]).kr(2); sig = TGrains.ar( numChannels:2, trigger: Dust.kr(40), bufnum: \buf.kr(0), rate: \rate.kr(1), centerPos: \pos.kr(0.5), // in seconds (!) dur: 0.05, // in secs pan: 0, amp: 1, // default is 0.1 (!) ); sig = sig * env * \amp.kr(0.5); Out.ar(\out.kr(0), sig); }).add; ) Synth(\tgrains, [\buf, b, \amp, 0.5, \pos, 1]);
75 Day 83
Live Version:
c = Buffer.alloc(s, s.sampleRate * 3); ( SynthDef(\tgrains_live, { var sig, env, mic, ptr, pos, buf = \buf.kr(0); // we want a triggerable, sustaining envelope env = Env.asr(\atk.ir(0.1), \sus.ir(1), \rel.ir(1)).kr(2, \gate.kr(1)); mic = SoundIn.ar(0); ptr = Phasor.ar(0, 1, 0, BufFrames.kr(buf)); BufWr.ar(mic, buf, ptr); // sample counter pos = ((ptr/SampleRate.ir) - 0.25) % (BufDur.kr(buf)); // centerPos will actually do the wrapping automatically, included modulo here for reference pos = pos + LFNoise1.kr(100).bipolar(0.2); // beware of discontinuities by "overtaking" the write pointer // make sure the read pointer is always behind the record pointer sig = TGrains.ar( numChannels:2, trigger: Dust.kr(40), bufnum: buf, rate: \rate.kr(1), centerPos: pos, dur: 0.05, // in secs pan: 0, amp: 1, // default is 0.1 (!) ); sig = sig * env * \amp.kr(0.5); Out.ar(\out.kr(0), sig); }).add; ) Synth(\tgrains_live, [\buf, c]);
76 Day 84 + 85
Multichannel Expanded Version - Lots of Grain Pointers
c = Buffer.alloc(s, s.sampleRate * 3); ( SynthDef(\tgrains_live, { var sig, env, mic, ptr, pos, buf = \buf.kr(0); // we want a triggerable, sustaining envelope env = Env.asr(\atk.ir(0.1), \sus.ir(1), \rel.ir(1)).kr(2, \gate.kr(1)); mic = SoundIn.ar(0); ptr = Phasor.ar(0, 1, 0, BufFrames.kr(buf)); BufWr.ar(mic, buf, ptr); // sample counter pos = ((ptr/SampleRate.ir) - 0.25) % (BufDur.kr(buf)); // centerPos will actually do the wrapping automatically, included modulo here for reference pos = pos - (0, 0.25..1.25); // subtract an array of 7 values -> 7 channel pos ramp signal pos = pos + LFNoise1.kr(100!7).bipolar(0.15); // beware of discontinuities by "overtaking" the write pointer // make sure the read pointer is always behind the record pointer sig = TGrains.ar( numChannels:2, trigger: Dust.kr(40), bufnum: buf, rate: \rate.kr(1), centerPos: pos, dur: 0.05, // in secs pan: 0, amp: Array.geom(7, 1, -3.dbamp) // decrease amplitude for delayed pointers ); // array of 7 stereo signals - sum them together sig = sig.sum; sig = sig * env * \amp.kr(0.5); Out.ar(\out.kr(0), sig); }).add; ) ~rbus = Bus.audio(s, 2); ( SynthDef(\reverb, { var sig, wet; sig = In.ar(\in.ir(0), 2); wet = GVerb.ar(sig.sum, 250, 4); wet = LPF.ar(wet, 100); sig = sig.blend(wet, \mix.kr(0.2)); Out.ar(\out.ir(0), sig); }).add; ) ServerTree.removeAll; ServerTree.add({Synth(\reverb, [\in, ~rbus])}); c.zero; Synth(\tgrains_live, [\buf, c, \out, ~rbus]);
77 Day 86
Live Harmonizer
c = Buffer.alloc(s, s.sampleRate * 3); ( SynthDef(\tgrains_live, { var sig, env, mic, ptr, pos, buf = \buf.kr(0); // we want a triggerable, sustaining envelope env = Env.asr(\atk.ir(0.1), \sus.ir(1), \rel.ir(1)).kr(2, \gate.kr(1)); mic = SoundIn.ar(0); ptr = Phasor.ar(0, 1, 0, BufFrames.kr(buf)); BufWr.ar(mic, buf, ptr); // sample counter pos = (ptr/SampleRate.ir) - 0.15; // we want the read pointer close to the write pointer pos = pos + LFNoise1.kr(100).bipolar(0.02); // beware of discontinuities by "overtaking" the write pointer // make sure the read pointer is always behind the record pointer sig = TGrains.ar( numChannels:2, trigger: Dust.kr(200), bufnum: buf, rate: \rate.kr(1) * \harm.kr([-9, -7, -4, 0]).midiratio, centerPos: pos, dur: LFNoise1.kr(100).exprange(0.06, 0.08), // in secs pan: LFNoise1.kr(100).bipolar(0.5), amp: 1 ); // array of 7 stereo signals - sum them together sig = sig.sum; sig = sig * env * \amp.kr(0.5); Out.ar(\out.kr(0), sig); }).add; ) c.zero; Synth(\tgrains_live, [\buf, c]);