# Cybernetic music with SuperCollider

Hello

Hope you’re all going well.

Like many, I stumbled upon the works of Roland Kayn and the whole cybernetics movement with the 2008 reissue of his 3 LPs long Simultan.

I was amazed at how different, richer, more diverse it sounded compared to the works of early electronic pionneers using east or west coast synthesis.
The boxset includes diagrams and photos of the analog computer used for these compositions, and the liner notes mention the use of feedback loops.

I would recommend anyone interested to read the article on Wikipedia about Cybernetics, because it’s an example of transdisciplinary movement with researchs on IT, biology, psychology (Palo Alto), among others. I wish there would be more transdisciplinary approaches now !

Anyway, to get back to cybernetics in music, there was also a recent thread on Lines forum, which linked to videos on Youtube by composer La Synthèse Humaine. He uses a Serge modular synthesizer but I thought it would be nice to try and translate his experiments to SuperCollider.

I mentionned that yesterday at the wonderful Notam SC Meetup and people suggested we would start a thread on scsynth.org.
So here we are !

I’ve just started with the first video of La Synthèse Humaine.

1. Feedback loop with a filter only.
https://youtu.be/MnYkqlSIr_I?t=177
(
Ndef(\test, {
var in, sig;
in = LocalIn.ar(1) * -60.dbamp;
sig = DFM1.ar(in, \filter_freq.kr(1000, 0.1), \filter_res.kr(0.1, 0.1));
LocalOut.ar(sig);
sig.tanh;
}).play;

Spec.add(\filter_freq, [0.1, 14000, 'exp', 0, 440, "Hz"].asSpec);
)

Ndef(\test).gui;

1. Feedback loop with sine oscillator
https://youtu.be/MnYkqlSIr_I?t=268
(
Ndef(\test, {
var freq, sig;
freq = LocalIn.ar(1);
sig = DFM1.ar(SinOsc.ar(freq: freq * \sin_freq.kr(1.0, 0.1)), \filter_freq.kr(1000, 0.1), \filter_res.kr(0.1, 0.1));
LocalOut.ar(sig);
sig.tanh;
}).play;

Spec.add(\freq, [0.1, 14000, 'exp', 0, 440, "Hz"].asSpec);
Spec.add(\sin_freq, [0.1, 14000, 'exp', 0, 440, "Hz"].asSpec);
Spec.add(\filter_freq, [0.1, 14000, 'exp', 0, 440, "Hz"].asSpec);

Ndef(\test).gui
)

1. Feedback loop with two sine oscillators and FM synthesis
https://youtu.be/MnYkqlSIr_I?t=501
(
Ndef(\test, {
var in, sig, sine1, sine2;
in = LocalIn.ar(1);
sine2 = SinOsc.ar(\sin2_freq.kr(440.0));
sine1 = SinOsc.ar(freq: \sin_freq.kr(440.0) * (1 + (\fm_amount.kr(0) * sine2)) * in);
sig = DFM1.ar(
sine1,
\filter_freq.kr(1000),
\filter_res.kr(0.1)
);
LocalOut.ar(sig);
LeakDC.ar(sig.tanh);
}).play;

Spec.add(\freq, [0.1, 14000, 'exp', 0, 440, "Hz"].asSpec);
Spec.add(\sin_freq, [0.1, 14000, 'exp', 0, 440, "Hz"].asSpec);
Spec.add(\sin_freq2, [0.1, 14000, 'exp', 0, 440, "Hz"].asSpec);
Spec.add(\fm_amount, [0, 10, 'lin', 0, 0].asSpec);
Spec.add(\filter_freq, [0.1, 14000, 'exp', 0, 440, "Hz"].asSpec);

Ndef(\test).gui;
)


I am not sure my SC code is faithfull to the videos.
I don’t really know why I’m using the “*” operation with the feedback loop, and not the “+” operation (which was my first guess).
Also, I imagine that the delay introduced by LocalIn / LocalOut has an effect on the sound (phase delay ?) but I can’t really notice it.

Best

Geoffroy

16 Likes

The Serge Peak output sounds a bit like the max of two signals with a slight distortion.
What do you think ?

(
Ndef(\test, {
var in, sig, osc1, osc2;
in = LocalIn.ar(1);
osc1 = SinOsc.ar(freq: \osc1_freq.kr(150)) * \osc1_amp.kr(0.4);
osc2 = SinOsc.ar(freq: \osc2_freq.kr(448)) * \osc2_amp.kr(-0.3);
osc1.max(osc2).distort;
}).play;

Spec.add(\osc1_freq, [0.1, 14000, 'exp', 0, 440, "Hz"].asSpec);
Spec.add(\osc2_freq, [0.1, 14000, 'exp', 0, 440, "Hz"].asSpec);
Spec.add(\osc1_amp, [-1, 1, 'lin', 0, 0].asSpec);
Spec.add(\osc2_amp, [-1, 1, 'lin', 0, 0].asSpec);

Ndef(\test).gui;
)

3 Likes

Hi there,

here is a contri with a favourite Cross FM patch of mine - this is dumb simple, yet it can keep me busy for hours riding the sliders in tiny steps and wonder at the impredictable results I’m getting: a perfect chaos theory primer in sound


// simple cross-FM, inspired by Joker Nies
(
Ndef(\xfm, { arg freqA = 32, freqB = 9, modAtoB=540, modBtoA=240;
var fbIn = LocalIn.ar(2);
var sigs = SinOsc.ar([freqA, freqB] + (fbIn.reverse * [modBtoA, modAtoB]));
LocalOut.ar(sigs);
sigs * 0.5;
}).play;

Ndef(\xfm).gui;
);

// some chaotic presets that display roughness, instabilities, or
Ndef('xfm').set('freqA', 9.0, 'freqB', 40.0, 'modAtoB', 205,  'modBtoA', 243.64);
Ndef('xfm').set('freqA', 5, 'freqB', 40.0, 'modAtoB', 90.0, 'modBtoA', 155); // try riding the modBtoA slider just slightly with the arrow buttons > find completely different states; hysteresis when returning. Pitch may move in reverse.
Ndef('xfm').set('freqA', 14, 'freqB', 6.69, 'modAtoB', 151, 'modBtoA', 402);

Ndef('xfm').set('freqA', 5, 'freqB', 40.0, 'modAtoB', 90.0, 'modBtoA', 204); // chaotic oscillation between several states

// settling after a short while - press 'send' on the GUI to restart process deterministically
Ndef('xfm').set('freqA', 5.0, 'freqB', 6.36, 'modAtoB', 342.65, 'modBtoA', 448.48);
Ndef('xfm').set('freqA', 223.46, 'freqB', 6.69, 'modAtoB', 70.57, 'modBtoA', 726.15);
Ndef('xfm').set('freqA', 18, 'freqB', 40.0, 'modAtoB', 90.0, 'modBtoA', 172.0);

// ideas to extend:
// - more than 2 operators in circle feedback topology


Quite certainly, the delay introduced by LocalIn / LocalOut does have an effect on the sound; one can test different configurations with different blockSizes, and the above feedback patch behaves slightly differently

s.options.blockSize_(64 * 4);
s.options.blockSize_(64); // default
s.options.blockSize_(2);
s.options.blockSize_(1);
s.reboot;


but the question is, different effects in relation to what? The lower we go with the blocksize, the more we approximate the analog world, but it’s clear that in relation to electrons’ flow, 1 sample is wayyy long, and we’ll never get really close to the analog with our digital systems.
So, tuning to the sonic result will usually be all that’s needed.

Bests,
Hannes

2 Likes

I tried both, and they create wildly different sounds! In fact, any or all of the “*” in that line can be changed to “+” to get very different sounds. As you have it, the frequency will always modulate between positive and negative values (since in is bipolar), which appears to create a more chaotic sound. Using addition instead of multiplication will result in more of a classic FM sound, as this is the standard technique for FM synthesis to add the modulator to the frequency.

Even in the simplest FM synth with 1 modulator and 1 carrier, using multiplication instead of addition creates a much different sound:

(
Ndef(\freqadd, {// frequency + modulator (classic FM sound)
var freq = MouseY.kr(20, 4000, 1);
var mratio = MouseX.kr(1/8, 8, 1);
var mod = SinOsc.ar(freq * mratio) * freq * mratio * \index.kr(5);
var car = SinOsc.ar(freq + mod);
car = LeakDC.ar(car);
car = Pan2.ar(car, \pan.kr(0), \amp.kr(0.3));
}).play;
)

(
Ndef(\freqmul, {// frequency * modulator (more chaotic sounding)
var freq = MouseY.kr(20, 4000, 1);
var mratio = MouseX.kr(1/8, 8, 1);
var mod = SinOsc.ar(freq * mratio) * freq * mratio * \index.kr(5);
var car = SinOsc.ar(freq * mod);
car = LeakDC.ar(car);
car = Pan2.ar(car, \pan.kr(0), \amp.kr(0.3));
}).play;
)


…or use both addition and multiplication, and cross-feedback them into each other (thanks, weego, for the cross-feedback idea ):

(
Ndef(\crossfeedback, {
var freq = MouseY.kr(20, 4000, 1);
var mratio = MouseX.kr(1/8, 8, 1);
var in = LocalIn.ar(2);
var mod = SinOsc.ar(freq * mratio) * freq * mratio * \index.kr(5);
var car = SinOsc.ar([freq * mod * in[1], freq + mod + in[0]]);
LocalOut.ar(car * \fb.kr([10, 5]));
car = LeakDC.ar(Mix(car));
car = Pan2.ar(car, \pan.kr(0), \amp.kr(0.3));
}).play;
)

1 Like

Crossfeedback with the single sample feedback is super cool. I wouldn’t say it is better than going with the block size, but it has a different flavor.

I started making some single sample feedback versions using Faust (compiled for SC):

https://github.com/spluta/SPSynthTools/tree/master/CrossfeedbackSynthesis

I might still make a quark out of it. There is a nice example in the CrossSineTri help file, but that is the only help file I got to.

The easiest solution would probably be to use Daniel Mayer’s fb1 in the miscellaneous quark, though I haven’t done that myself yet.

Sam

1 Like

@weego, I like the cross-feedback FM idea very much. The system reacts very sensitive to parameter changes, so it might pay to control frequencies with mantissa and exponent:

(
Ndef(\xfm, { arg freq1_E = 1, freq1_M = 3.2, freq2_E = 1, freq2_M = 0.9,
mod1_E = 2, mod1_M = 5.4, mod2_E = 2, mod2_M = 2.4, amp = 0.1;

var freq1 = 10 ** freq1_E * freq1_M.lag(0.2);
var freq2 = 10 ** freq2_E * freq2_M.lag(0.2);
var mod1 = 10 ** mod1_E * mod1_M.lag(0.2);
var mod2 = 10 ** mod2_E * mod2_M.lag(0.2);

var fbIn = LocalIn.ar(2);
var sigs = SinOsc.ar([freq1, freq2] + (fbIn.reverse * [mod2, mod1]));

LocalOut.ar(sigs);
sigs * amp.lag(0.2);
}).play;

Spec.add(\freq1_E, [-1, 2, \lin, 1, 1]);
Spec.add(\freq2_E, [-1, 2, \lin, 1, 1]);

Spec.add(\freq1_M, [0.1, 10, 2, 0, 1]);
Spec.add(\freq2_M, [0.1, 10, 2, 0, 1]);

Spec.add(\mod1_E, [-1, 2, \lin, 1, 1]);
Spec.add(\mod2_E, [-1, 2, \lin, 1, 1]);

Spec.add(\mod1_M, [0.1, 10, 2, 0, 1]);
Spec.add(\mod2_M, [0.1, 10, 2, 0, 1]);

Spec.add(\amp, [0, 0.5, \lin, 0, 0.1]);

Ndef(\xfm).gui;
);


Modulating feedback amplitudes and (extra) delaytimes is also very interesting. Nathaniel Virgo gave a good survey of possible feedback techniques:

https://www.listarc.bham.ac.uk/lists/sc-users-2009/msg56802.html

In rather straight setups like the ones above I suppose there’s not much advantage with Fb1 (besides being able to do single sample feedback with unchanged blocksize). Rather Fb1 is practical for defining arbitrary nonlinear feedback operations and differentiated / varying lookback depths (Exs. 2a-f and 3a-e of its help file).

1 Like

I need to dive deeper into this subject soon but I love the examples so far!

@nathan twitch session last Saturday did focus on cybernetics, and it was super useful (thanks again Nathan !).

I learnt that you can use a low pass filter and pulse dividers on the signal to get some low frequency signals and triggers that can modulate your sound in the feedback chain.
Today I tried it with some recorded sounds and it’s quite amazing.

~snd = "test.wav"

(
SynthDef(\test, {
var snd, local, trig;
local = LocalIn.ar(2);
local = (LPF.ar(local[0], 10) * 1000);
trig = PulseDivider.ar(local, 2).scope;
snd = PlayBuf.ar(2, ~buffer, trigger: Delay1.kr(trig), rate: BufRateScale.kr(~buffer) * local.linlin(-1,1,0.01,1), startPos: TIRand.kr(0, BufFrames.kr(~buffer) / 2 , trig));
snd = CombC.ar(snd, 4, local.linlin(-1,1,0.01, 4));
LocalOut.ar(snd.fold2);
Out.ar(\out.kr(0), snd);
}).play;
)

3 Likes

I’ve been working with various feedback patches in SuperCollider for some time, would need to dig a bit to find some simple examples that I can share here. One thing I like to do is recursive Ndef’s where let a Ndef track the amplitude of itself (or a delayed version of itself) to regulate its own feedback settings (kind of like how a thermostat would work hahaha). Sometimes leads to wonderful behaviors.

4 Likes

@Tijs it’d be amazing if you could post an example of one of those recursive Ndefs you mentioned. Very curious! Thanks!

2 Likes

I second this request, I’d be very curious too!

Fredrik Olofsson and Nathaniel Virgo are longtime SC experts which have done several works related to cybernetics, it definitely worth checking out their Tweets (tons of interesting ideas illustrated by supercondesed SC code):

it’s a pitty that they are not participating in this forum, they have a lot to share in this regard… incredible feedback/recursion ideas:

//be carefull with volumes and use StageLimiter!!!
Fredrik Olofsson
//--tweet0164
play{a=SinOsc;LeakDC.ar(a.ar([1,2],a.ar(Pitch.kr(CombN.ar(InFeedback.ar([1,0]),5,[4.8,4.7])).flop[0]-4)*2pi*a.ar(1/16)))/2}// #SuperCollider

//take a few seconds to emerge
//--tweet0165
play{CombC.ar(BLowPass.ar(Limiter.ar(LeakDC.ar(InFeedback.ar([1,0]))),2e3)+Impulse.ar(0),1,LFTri.ar(1/[6,8])*0.4+0.5)*0.99}// #SuperCollider

SC140 - 01
Nathaniel Virgo
{LocalOut.ar(a=CombN.ar(BPF.ar(LocalIn.ar(2)*7.5+Saw.ar([32,33],0.2),2**LFNoise0.kr(4/3,4)*300,0.1).distort,2,2,40));a}.play//#supercollider

// recursive nirvana grunge