Table of Contents

1 Day 1 <2020-11-06 Fri>

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 <2020-11-07 Sat>

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

  1. class

    very ruby-esque:

    4.class // Integer
    "string".class // String
    [1, $a, "a"].class // Array
    true.class // True
    

2.2.2 Methods on Number Types

  1. Numeric Operations

    all the usual suspects

  2. Messages to Numbers
    5.8.round // 6
    4.squared // 16
    2.pow(3) // 8
    -5.abs // 5
    430.div(100) // 4 - Integer division
    430.mod(100) // 3 - Remainder
    

3 Day 3 <2020-11-08 Sun>

3.1 Object Oriented Programming in Supercollider ctd

3.1.1 Methods on Strings

  1. Messages to Strings
    "hello world!".size; // 12
    "hello".reverse; // olleh
    "hello".scramble; // lehol || eholl || ...
    
  2. Operators on Strings
    "hello" ++ "world" // "hello world"
    

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 <2020-11-09 Mon>

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:

  1. x > 5
  2. (x > 5) && y
  3. ((x > 5) && y) > 5

5 Day 5 <2020-11-10 Tue>

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 <2020-11-11 Wed>

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 

day_6_meters.png day_6_scope.png day_6_freq_scope.png day_6_node_tree.png

7 Day 7 <2020-11-12 Thu>

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 <2020-11-13 Fri>

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;

day_8_plot_sine.png

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 <2020-11-14 Sat>

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 <2020-11-15 Sun>

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 <2020-11-16 Mon>

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 <2020-11-17 Tue>

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 <2020-11-18 Wed>

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 <2020-11-19 Thu>

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 <2020-11-20 Fri>

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 <2020-11-21 Sat>

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:

day_16_env_1.png day_16_env_2.png day_16_env_3.png

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 <2020-11-22 Sun>

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 <2020-11-23 Mon>

18.1 ar vs kr

ar UGens run at the samplerate: s.sampleRate

kr run at "blocks rate": s.options.blockSize, e.g. every 64 samples.

(
{
  [
    SinOsc.ar(100),
    SinOsc.kr(100)
  ]
}.plot(0.1);
)

day_18_aliasing.png

19 Day 19 <2020-11-24 Tue>

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 <2020-11-25 Wed>

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 <2020-11-26 Thu>

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 a TrigControl. 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 <2020-11-27 Fri>

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]

22.1.2 Different Array Lengths

(
x = {
  var sig, freqs;
  freqs = Array.series(8, 150, 150);

  // the smaller array "wraps"
  // [SinOsc.ar(150, 0, 0.4), SinOsc.ar(300, 0, 0.05), SinOsc.ar(450, 0, 0.4), ...]
  sig = SinOsc.ar(freqs, 0, [0.4, 0.05]);
}.play;
)

day_22_multichannel_meter.png day_22_multichannel_scope.png

23 Day 23 <2020-11-28 Sat>

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 <2020-11-29 Sun>

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 an Out 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

  1. choose a name
  2. explicitly define the output signal with Out.ar
  3. 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 <2020-11-30 Mon>

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 a Function 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 superclass Thread. 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 <2020-12-01 Tue>

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 <2020-12-02 Wed>

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 <2020-12-03 Thu>

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 <2020-12-04 Fri>

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 <2020-12-05 Sat>

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 <2020-12-06 Sun>

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 <2020-12-07 Mon>

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 <2020-12-08 Tue>

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 <2020-12-09 Wed>

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 <2020-12-10 Thu>

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 <2020-12-11 Fri>

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 <2020-12-12 Sat>

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 <2020-12-13 Sun>

38.1 Order of Execution

The processing Synth must be downstream

head ---> tail

If a sound is to be passed through a filter, the synth that does the filtering must be later in the order of execution than the synth which is its input.

// NO SOUND!
d = Synth(\drops, [\out, ~bus]);
r = Synth(\reverb, [\in, ~bus]);

day_38_order_of_execution_wrong.png

38.2 addAction

  • \addToHead (default) - no Synth as target
  • \addToTail - no Synth as target
  • \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 <2020-12-14 Mon>

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);

39.1.1 Nested Groups

~srcGrp = Group.new;
~testGrp = Group.new(~srcGrp, \addToHead);
~fxGrp = Group.new(~srcGrp, \addAfter);

day_39_nested_groups.png

40 Day 40 <2020-12-15 Tue>

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 <2020-12-16 Wed>

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 <2020-12-17 Thu>

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 <2020-12-18 Fri>

44 Day 44 <2020-12-19 Sat>

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 a play 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 <2020-12-20 Sun>

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 <2020-12-21 Mon>

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 <2020-12-22 Tue>

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 <2020-12-23 Wed>

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 <2021-01-19 Tue>

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 <2021-01-20 Wed>

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 <2021-01-21 Thu> <2021-01-22 Fri>

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); }

day_51_gui.png

51.2 MVC Paradigm

Look up SimpleController

52 Day 53 <2021-01-23 Sat>

(
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 <2021-01-24 Sun> <2021-01-26 Tue>

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 <2021-01-27 Wed> <2021-01-28 Thu>

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 <2021-02-01 Mon>

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 <2021-02-02 Tue>

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 <2021-02-03 Wed>

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 <2021-02-04 Thu> <2021-02-05 Fri>

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 <2021-02-09 Tue>

59.1 SoundIn

Tutorial 20

{ 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 <2021-02-10 Wed> <2021-02-11 Thu> <2021-02-12 Fri>

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 <2021-02-15 Mon>

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 <2021-02-16 Tue>

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 <2021-02-17 Wed> <2021-02-18 Thu>

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 <2021-02-19 Fri>

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 <2021-02-25 Thu>

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 <2021-03-01 Mon>

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 <2021-03-02 Tue>

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 <2021-03-04 Thu>

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 <2021-03-05 Fri>

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 <2021-03-17 Wed>

Variable Length Looper Walkthrough:

https://youtu.be/hPXGv-y68tU

71 Day 78 <2021-03-22 Mon>

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 <2021-03-23 Tue>

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 <2021-03-24 Wed> <2021-03-25 Thu>

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
  • 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 <2021-03-26 Fri>

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 <2021-03-30 Tue>

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 <2021-04-06 Tue> <2021-04-07 Wed>

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 <2021-04-08 Thu>

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]);

Author: Julian Rubisch

Created: 2021-04-08 Thu 15:09

Validate