What kind of synthesis is this?

Hello everyone, I am a bit obsessed with this EP here and I wonder if anyone happens to identify the synth techniques being used. For some reason I imagine it is quite likely that it is running on SuperCollider (but I am not a regular SC user, so my knowledge of it is very limited).

I can make sense of most of the algorithmic logic triggering things here, and I hear short delays and comb filtering everywhere, tightly integrated with the note generation algorithms. Also lots of white noise with shifting filters. To an extent it seems there is a lot of synth (or sampled) percussion with some resonator, but it seems to go beyond the capabilities of the resonators I know. Any ideas? Thank you for your inputs.

4 Likes

From Wikipedia,

Audio software often has a slightly different ‘sound’ when compared against others. This is because there are different ways to implement the basic building blocks, such as sinewaves, pink noise, and FFT… which result in slightly different aural characteristics.

List of Audio Programming Languages:

ABC notation
ChucK
Real-time Cmix
Common Lisp Music
Csound
Extempore
FAUST
Hierarchical Music Specification Language
Impromptu
ixi lang
JFugue
jMusic
JSyn
Kyma
LilyPond
Max/MSP
Music Macro Language
MUSIC-N
Nyquist
OpenMusic
Orca
Pure Data
Reaktor
Sonic Pi
Structured Audio Orchestra Language
SuperCollider
SynthEdit

Just added as a reference point.

If you were to message the artist / composer with such an in-depth comprehension of their work as you already have asked us in your original thread post… I could almost guarantee they would give you a response… in fact a lot of working class artists & producers would be be nearly elated to do so.

SoundCloud itself has this feature… messages of this nature on the platform are usually rare & warmly welcomed (desktop only… no messaging for mobile)

Hey, maybe you have a look at the thesis by mark fell on pattern synthesis “Works in sound and pattern synthesis” and listen to Mark Fell - Multistability (full album) Raster-Noton - YouTube
and watch this one where he is basically explaining the same thing in max/msp live code your track live - Jacob Sachs-Mishalanie - YouTube and in the meantime you can also have a look at my example below. you can either play around with the tables or update the seed number to generate new sets of data (durations, repetitions, notes). but its better to generate rhythm with it, not pitch, so ive commented out the pitch part. its not perfect but you get these accelarating rhythms into nearly granular terrain when you throw in some short durations. probably also good to tweak some of the fm parameters or use another instrument.

(
SynthDef(\fm, {
	arg out=0, pan=0, amp=0.25, freq=111, atk=0.001, rel=0.2,
	mInd1=0.5, mInd2=0.5, mInd3=0.5, mInd4=0.5, mInd5=0.5, mInd6=0.5;

	var gainEnv = EnvGen.ar(Env.perc(atk, rel), \gt.kr(1), doneAction:Done.freeSelf);
	var sig, cascade_0, cascade_1;

	cascade_0 = SinOsc.ar(freq, SinOsc.ar(freq * \ratio3.kr(1), pi * 0.5).range(0, mInd3) ) * \amp_3.kr(0.5);
	cascade_0 = SinOsc.ar(freq, SinOsc.ar(freq * \ratio2.kr(1), pi * 0.5).range(0, mInd2) + cascade_0.range(0,mInd2)) * \amp_2.kr(0.5);
	cascade_0 = SinOsc.ar(freq, SinOsc.ar(freq * \ratio1.kr(1), pi * 0.5).range(0, mInd1) + cascade_0.range(0,mInd1)) * \amp_1.kr(0.5);

	cascade_1 = SinOsc.ar(freq, SinOsc.ar(freq * \ratio6.kr(1), pi * 0.5).range(0, mInd6) + LocalIn.ar(1).range(0, mInd6)) * \amp_6.kr(0.5);
	cascade_1 = SinOsc.ar(freq, SinOsc.ar(freq * \ratio5.kr(1), pi * 0.5).range(0, mInd5) + cascade_1.range(0,mInd5)) * \amp_5.kr(0.5);
	cascade_1 = SinOsc.ar(freq, SinOsc.ar(freq * \ratio4.kr(1), pi * 0.5).range(0, mInd4) + cascade_1.range(0,mInd4)) * \amp_4.kr(0.5);

	LocalOut.ar(cascade_1 * \fb.kr(0.9));

	sig = Mix([cascade_0, cascade_1]);

	sig = sig * gainEnv;

	sig = Pan2.ar(sig, pan, amp);
	sig = LeakDC.ar(sig);
	OffsetOut.ar(out, sig)
}).add;
)

//////////////////////////////////////////////////////////////////////////

(
~getRandomSet = {
	arg set;
	var numSteps = rrand(10,30);
	set = set ? ();
	set.putAll((
		durations: {exprand(0.1,2)}!numSteps,
		repetitions: {5.rand}!numSteps,
		notes: {rrand(-24,24)}!numSteps,
        // added motif here
		motif: {rrand(-12,12)}!5
	));
};

~viewSet = {
	arg set;
	var window = Window.closeAll.new("Mark Fell Sequencer", Rect.new(300, 300, 1200, 400));

	var texts = (
		durations: StaticText().string_("durations").font_("Helvetica-Bold", 14),
		repetitions: StaticText().string_("repetitions").font_("Helvetica-Bold", 14),
		notes: StaticText().string_("notes").font_("Helvetica-Bold", 14)
	);

	var sliders = (
		durations: MultiSliderView().background_(Color.rand),
		repetitions: MultiSliderView().background_(Color.rand),
		notes: MultiSliderView().background_(Color.rand)
	);

	var buttons = (
		durations: Button().states_([["scramble durations", Color.black, Color.grey]])
            .action_(~updateDur),
		repetitions: Button().states_([["scramble repetitions", Color.black, Color.grey]])
            .action_(~updateRep),
		notes: Button().states_([["scramble notes", Color.black, Color.grey]])
            .action_(~updateNotes)
	);

	var specs = (
		durations: ControlSpec(0.01,10,\exp),
		repetitions: ControlSpec(0,10,\lin,1),
		notes: ControlSpec(0,72,\lin,0.5), //quarter-tones
	);

	var updater = {
		loop{
			sliders.keysValuesDo{
				arg k,sl;
				sl.value = specs[k].unmap(set[k]);
			};
			0.1.wait;
		};
	}.fork(AppClock);

	sliders.keysValuesDo{
		arg k,sl;
		sl.action = { set[k] = specs[k].map(sl.value).postln };
		sl.elasticMode_(1);
	};

	window.view.layout_(
		HLayout(
			VLayout(
				texts[\durations],
				sliders[\durations],
				buttons[\durations]
			),
			VLayout(
				texts[\repetitions],
				sliders[\repetitions],
				buttons[\repetitions]
			),
			VLayout(
				texts[\notes],
				sliders[\notes],
				buttons[\notes]
			),
		)
	);

	window.onClose = { updater.free };
	window.front.alwaysOnTop_(true);
};

// get random set according to seed number
~makeSeed = {
	arg seed;
	thisThread.randSeed = seed;
	a = ~getRandomSet.();
	~viewSet.(a);
};

// update durations
~updateDur = {
	a.durations = a.durations.scramble;
};

// update repetitions
~updateRep = {
	a.repetitions = a.repetitions.scramble;
};

// update notes
~updateNotes = {
	a.notes = a.notes.scramble;
};

// update motif
~updateMotif = {
	a.motif = a.motif.scramble;
};
)

(
~seed = 1403411654;

~makeSeed.(~seed);

Pdef(\fm,
	Pn(
		Pfindur(6,
			Pbind(
				\instrument, \fm,

				\durMul, 0.5, //multiply duration
				\dur, Pkey(\durMul) * (Pn(Plazy{ Pstutter(Pseq(a.repetitions,1), Pseq(a.durations,1)) },inf)),

				\atk, 0.001,
				\rel, 0.25,

				\midinote, Pseq([[57,60,64,65,70]],inf),
/*
				\octave, Pstutter(3, Pseq([3,4,5], inf)),
				\scale, Scale.minor(\just),
				\degree, Pn(Plazy{ Prout {
					a.notes.do{
						arg note,i;
						var repetitions = a.repetitions.wrapAt(i);
						repetitions.do {
							arg j;
							(note + a.motif.wrapAt(j)).yield};
					}
				}}, inf,
				),
*/

				//FM Parameters
				\ratio3, 0,
				\ratio2, 0,
				\ratio1, 0,

				\ratio6, 0.251,
				\ratio5, 0,
				\ratio4, 0,

				\mInd3, 1.462,
				\mInd2, 1.938,
				\mInd1, 2.873,

				\mInd6, 1.065,
				\mInd5, 1.793,
				\mInd4, 3.283,

				\amp3, 0.629,
				\amp2, 0.241,
				\amp1, 0.288,

				\amp6, 0.780,
				\amp5, 0.034,
				\amp4, 0.433,

				\fb, 0.9,

				\amp, 0.05,
				\pan, Pwhite(-0.75,0.75,inf),
				\out, 0,
			)
		), inf
	)
).play(quant:1);
)

// update seed
~makeSeed.(1403411654);
~makeSeed.(1403251255);
~makeSeed.(1403255);

Pfset(~makeSeed.(1403411654), Pdef(\fm));
Pfset(~makeSeed.(1403251255), Pdef(\fm));
Pfset(~makeSeed.(1403255), Pdef(\fm));
7 Likes

Yes, maybe I am focusing too much on the problem (I have a habit of trying to reverse engineer sound I like), not on the solution. I am aware that in principle all these might sound almost the same, but I wanted to know if there are specific synths or methods that are common in SC that might sound like that. These are all extremely flexible digital tools but they aren’t transparent in the end – they also push artists in a given direction, depending on interface/language/default options etc.

I’ll try that, even though the artist himself is quite mysterious (zero references or interviews or profiles in social media), and it seems me to be a nickname, not a real name (it is the name of a famous German cyclist, already dead).

You should absolutely reverse engineer. This is the only way to learn. For me this is Maths Maths Maths, all day long, though this record is very well engineered, and I agree it is excellent, and there is more detail there than I am putting in (and many more layers going on, obviously).

The spectrograms really help with this pure tone stuff, since you can see each wave and how it is moving.

These aren’t perfect, but good enough for today. You need the latest version of the Maths quark for them to work.

({
	var dur_up_down = MouseX.kr(1,5);
	var ctl_maths = Maths2.ar(dur_up_down,dur_up_down, 0.9);

	var trig_rate = ctl_maths[0].linexp(0,1,5,50);
	var trig = Impulse.kr(trig_rate);

	var snd_maths0 = Maths2.ar(0.001, min(0.2, 1/trig_rate), 0.99, 0, 1, trig)[0];
	var freq0 = snd_maths0.linlin(0,1,50,300);
	var sound0 = SinOsc.ar(freq0, 0.0, 0.5)*snd_maths0.sqrt;

	var snd_maths = Maths2.ar(0.001, 0.018, 0.99, 0, 1, trig)[0];
	var env = Lag.ar((snd_maths-0.001)>0, 0.02);

	var freq1 = snd_maths.linlin(0,1,5300,6800);
	var sound1 = SinOsc.ar(freq1*[1,4,8], 0.0, 0.075).sum*env;

	var freq2 = snd_maths.linlin(0,1,3000,4100);
	var sound2 = SinOsc.ar(freq1*[1,4,8], 0.0, 0.075).sum*env;

	var mix = RLPF.ar(sound0+sound1+sound2, ctl_maths[0].linlin(0,1,400, 20000), ctl_maths[0].linlin(0,1,0.4,0.95), 1).dup;

	var out = mix+DelayC.ar(mix, 0.1, 0.018, ctl_maths[0].linlin(0,1,0.25,0));

	out = out;

	out.tanh
}.play)

({
	var maths, synth, trig, env;

	trig = Impulse.ar(0.5);
	maths = Maths2.ar(0.01, 1, 0.95, 0, 1, trig);
	env = Lag.ar((maths[0]-0.001)>0, 0.02);

	synth = Array.fill(5, {|i| SinOsc.ar((i+1)*maths[0].linlin(0,1,100,2500), 0, 0.05)}).sum*env;

	CombC.ar(synth.dup, 0.05, 0.04, 2)+CombC.ar(synth.dup, 0.03, 0.03, 2, 0.1, synth)
}.play)
4 Likes

these are really nice, thanks a lot :slight_smile:

Thanks @dietcv, I am acquainted with these algorithmic procedures and I am TOTAL sucker for Mark Fell’s sounds. So much that I am releasing this here soon (shhh! don’t tell anyone about this link, it’s not mastered yet):

What do you think? :slight_smile:

This is a nice implementation you did, thanks! I am not used to these things and graphic interfaces in SC, only in Max/MSP. But as a matter of fact I was more searching for the synth part of it than the event generation/control, and I believe there’s much more to it than FM only.

2 Likes

Aaargh, I am such a noob in SC that I couldn’t get it to work (even though I am on 3.12.0 and I installed the latest Maths quark and the latest SC3plugins). Could there be any other dependency I am not aware of? I am now really curious about it!

false
true
false
false
false
false
Select arg: 'which' has bad input: true
 ARGS:
   which: true True
   array: 0 Integer
   2: 0.8 Float
Select arg: 'which' has bad input: true
 ARGS:
   which: true True
   array: a BinaryOpUGen BinaryOpUGen
   2: a BinaryOpUGen BinaryOpUGen
Select arg: 'which' has bad input: true
 ARGS:
   which: true True
   array: a BinaryOpUGen BinaryOpUGen
   2: a MulAdd MulAdd
Select arg: 'which' has bad input: true
 ARGS:
   which: true True
   array: 0 Integer
   2: 0.98 Float
Select arg: 'which' has bad input: true
 ARGS:
   which: true True
   array: a BinaryOpUGen BinaryOpUGen
   2: a BinaryOpUGen BinaryOpUGen
Select arg: 'which' has bad input: true
 ARGS:
   which: true True
   array: a BinaryOpUGen BinaryOpUGen
   2: a MulAdd MulAdd
Select arg: 'which' has bad input: true
 ARGS:
   which: true True
   array: 0 Integer
   2: 0.98 Float
Select arg: 'which' has bad input: true
 ARGS:
   which: true True
   array: a BinaryOpUGen BinaryOpUGen
   2: a BinaryOpUGen BinaryOpUGen
Select arg: 'which' has bad input: true
 ARGS:
   which: true True
   array: a BinaryOpUGen BinaryOpUGen
   2: a MulAdd MulAdd
SynthDef temp__2 build failed
ERROR: Select arg: 'which' has bad input: true

PROTECTED CALL STACK:
	SynthDef:checkInputs	0x12069b000
		arg this = a SynthDef
		var firstErr = Select arg: 'which' has bad input: true
	SynthDef:finishBuild	0x120694e00
		arg this = a SynthDef
	a FunctionDef	0x120688c40
		sourceCode = "<an open Function>"
	Function:prTry	0x11f055000
		arg this = a Function
		var result = nil
		var thread = a Thread
		var next = nil
		var wasInProtectedFunc = false
	
CALL STACK:
	Exception:reportError
		arg this = <instance of Error>
	Nil:handleError
		arg this = nil
		arg error = <instance of Error>
	Thread:handleError
		arg this = <instance of Thread>
		arg error = <instance of Error>
	Object:throw
		arg this = <instance of Error>
	Function:protect
		arg this = <instance of Function>
		arg handler = <instance of Function>
		var result = <instance of Error>
	SynthDef:build
		arg this = <instance of SynthDef>
		arg ugenGraphFunc = <instance of Function>
		arg rates = nil
		arg prependArgs = nil
	Function:play
		arg this = <instance of Function>
		arg target = <instance of Group>
		arg outbus = 0
		arg fadeTime = 0.02
		arg addAction = 'addToHead'
		arg args = nil
		var def = nil
		var synth = nil
		var server = <instance of Server>
		var bytes = nil
		var synthMsg = nil
	Interpreter:interpretPrintCmdLine
		arg this = <instance of Interpreter>
		var res = nil
		var func = <instance of Function>
		var code = "({
	var dur_up_down = MouseX..."
		var doc = nil
		var ideClass = <instance of Meta_ScIDE>
	Process:interpretPrintCmdLine
		arg this = <instance of Main>
^^ The preceding error dump is for ERROR: Select arg: 'which' has bad input: true

No. It is my hacky code…but it works on my machine, which I don’t understand. Try reinstalling the quark. This unfortunately means you need to delete the folder inside you quarks directory, then run:

Quarks.uninstall(“Maths”)
Quarks.install("Maths”)

Sorry that you are my debugger right now…

Sam

Linexp the third Argument of Maths has to be a ugen. Exchange with \linexp.kr(0.99)

hi Sam, I did it but it still doesn’t work. I am probably a very bad debugger b/c I know nothing about SC. Anyway, I noticed that upon install I get:

WARNING: LFPulseReset not found

ugh…disaster. it downloaded an old version. not sure why. Did it say:

Maths installed
→ Quark: Maths[1.5.1]

You don’t want to update. Reinstall

No, both installing master and “1” resulted in having 1.0.0 installed. It only worked when I got it directly from Github.

But now it works, thanks a lot for your help! I’ll try to better understand this comb filter implementation. I think it is something along those lines (and a more finely controlled delay that takes a given number of repetitions instead of simple feedback).

Very well put.

At this and upon giving the record a shot… I’d have to admit I completely agree with you.

  • The sense of phrasing is strongly ‘algorithmic’
  • SC is arguable champion of “the list” in this category
  • Resonant sound quality is remarkable

The reply from @dietcv is also quite impressive… accurately reflecting the quality of mathematical sequencing that feels allusional to our language specifically.

Though in all truth, every language in the list is able to produce such an effect… programming is in itself intrinsically bound to logical or mathematically calibrated transitioning of events… and so as soon as ‘audio’ steps in to the picture, the potentiality for this kind of mathematically based orchestration becomes damn near inescapable.

A great deal of SC’s value in these regards lie in it’s relatively minimal, to some extent progressive in terms of aesthetics, and above all, it’s expansiveness in terms of it’s syntactical interface to it’s unique and innovative lexicon of algorithmic & musical score notation… it’s the ease for the artist or for the programmer accustomed to higher level thinking to be able to engage with the language in such a compatible and practical way that makes SC held in such a high regard by those who are able to realize it’s (at times overwhelming and challenging) power & caliber of expression.

On reverse engineering the applied resonanting design scheme… the person you want to talk to is @nathan .

Really strong developer, and definitely gaining traction in terms of composition and technique… but the guy is a genius when it comes to synthesis / design.

If you’re lucky, he or someone else here may be somehow familiar with the concept used in the track to some degree.

@Sam_Pluta …he’s another one of those few rare synth-genius / exceptional talents that we’re extremely lucky to have in this community.

Always treat these guys with a ton of respect, they really deserve it.

1 Like

Thanks Rainer! As for the synth, it sounds like FM or additive, but you can implement it really efficiently with Karplus-Strong. Here’s an approximate replication with Pluck:

(
{
	var trig, snd, freqs;
	trig = Impulse.ar(LFNoise2.kr(1).linexp(-1, 1, 1, 100));
	freqs = (60.5 + [0, 2, 4, 5, 7, 9, 10]).midicps;
	snd = Pluck.ar(Hasher.ar(Sweep.ar(trig)) * -10.dbamp, trig, freqs.reciprocal, freqs.reciprocal, 0.9, 0.5);
	snd = LeakDC.ar(snd).sum;
	snd = MoogFF.ar(snd, LFNoise2.kr(1).linexp(-1, 1, 500, 16000), 0);
	snd = snd ! 2;
	snd;
}.play(fadeTime: 0);
)

You might want to play with the 0.9 argument to Pluck, since it controls the decay time. Mine is a lot brighter than the original, maybe another MoogFF with zero resonance in there would help.

7 Likes

Another neato trick is convolution, which is the same as a fixed filter bank. I bet dollars to donuts if you use a sample of a chord as an impulse response, and feed into it the kinds of noise bursts that are usually used with Karplus-Strong, and modulate filtering on the noise bursts, you’d get pretty close.

s.boot;

b = Buffer.alloc(s, 16384, 1);

(
a = {
	var notes = [49, 54, 56, 59, 63, 65, 70, 73, 75];
	var harmonics = [1, 2, 3.5, 2, 4, 7];
	var freqs = notes.midicps *.x harmonics *.x [1, 1.008];
	var exc = PinkNoise.ar * EnvGen.ar(Env.perc(0.001, 0.01));
	var sig = Klank.ar(`[freqs, 1 ! freqs.size, b.duration ! freqs.size], exc) * 0.05;
	RecordBuf.ar(sig, b, loop: 0, doneAction: 2);
	sig.dup
}.play;
)

(
z = {
	var pulse = Dust.ar(LFDNoise3.kr(0.2).exprange(3, 15));
	var excEg = Decay2.ar(pulse, 0.001, 0.02);
	var noise = PinkNoise.ar * excEg;
	var ffreq = LFDNoise3.kr(0.2).exprange(200, 10000);
	var frq = LFDNoise3.kr(0.12372).exprange(0.02, 0.4);
	
	noise = BLowPass4.ar(noise, ffreq, frq);
	
	(Convolution2.ar(noise, b, framesize: b.numFrames) * 0.1).dup
}.play;
)

(Not fully controlling distortion there… exercise for the reader.)

Edit: I realize that this convolution kernel comes from a filter bank, so you could skip the convolution and plug the exciter directly into the filter bank – but, it’s still useful to point out convolution because it works with any arbitrary impulse response, generated by any technique (or recorded). Use PartConv for longer IRs.

hjh

4 Likes

Thanks A LOT, Nathan, I think you nailed it entirely! I thought of Karplus-Strong, but I imagined there was more going on than KS and a filter. And it is amazing how little code you need to get at it.

Hi Rainer, I totally agree with you! Specially with this:

Yes, I am really thankful to everyone that took their time to contribute to this thread! I’m really impressed with the quality of the answers and I deeply appreciate the effort each one has put in it. <3

Thanks a lot, I’ll look deeper into it as well!