Super Cool SuperCollider Synthesis Challenge (Prize)?

do you have a good way to deal with additive synthesis in a universal way until we have the sine bank ;)?
imagine for example i want to have only the fundamental Ab2 and the 7th partial F#5 (-31 dev. from 12TET) of the harmonic series with specific amplitudes for example sig = SinOsc.ar([103.826, 726.783 * 2], 0, [0.5, 0.6]).sum;
and another time more partials with a specific amplitude conture for example all even partials up to a specific overtone. i would like to control everything with patterns and not write a new SynthDef every single time for only one specific case. any ideas ? :slight_smile:

(
(1..50).do({|partials|
	SynthDef(\additive ++ partials, {

		var sig, freqs, gainEnv;

		gainEnv = EnvGen.ar(Env.adsr(
			\atk.kr(0.07),
			\dec.kr(0.5),
			\sus.kr(1),
			\rel.kr(2),
			curve: \curve.kr(-4)
		), \gate.kr(1), doneAction:2);

		freqs = Array.fill(partials, {|i|
			\freq.kr(20) * (i+1); // even partials
		});

		sig = freqs.collect({|freq, i|
			var amps = \decay.kr(0.5) / (i+1); // even partials
			SinOsc.ar(freq) * amps;
		});

		sig = Mix(sig);

		sig = sig * gainEnv * \amp.kr(0.3) * \vel.kr(1);
		sig = Splay.ar(sig);
		Out.ar(\out.kr(0), sig)
	}).add;
});
)

x = Synth(\additive10, [\freq, 110, \decay, 0.5]);

x.set(\gate, 0);

Have you looked at DynKlang?

Sam

hey thanks for your help. ive never really investigated deeply. because i thought the values are set and cant be changed, after the SynthDef has been evaluated. i will have a look again if it suits my needs.
is DynKlang the best way to deal with additive synthesis in SC?

EDIT: i confused it with the Klang Ugen.

I don’t think DynKlang would solve any of the efficiency problems. As the help file says, “it is basically a wrapper around SinOsc UGens in order to provide a similar interface to Klang.”

Using an inverse fourier transform should be an efficient approach to additive synthesis (at the expensive of some time and frequency resolution).

i think for the case of having two sinewaves with specific frequencies and amplitudes its not the right approach.

I tried to implement an additive Synth using IFFT. It almost works, but i get some clicks and pops and i dont know why :frowning:
It reads the amplitudes of the partials from a 256-channel control bus and packs them into a FFT chain. The chain is converted to a waveform using IFFT and an OSC plays the waveform.

In the example the 256-channel-control bus for the amplitudes is controlled by a Ndef which you can control with the mouse

Code
~numPartials = 256;
// Add the SynthDef
(
SynthDef(
	\ifft,
	{
		arg freq=440, gate=1, amp=0.6, ampB=0.6, out=#[0,1],
		atk=0.01, rls=0.2, catk=4, crls=4.neg, sustain=0,
		ampBus;
		var env, sig, phases, chainBufSize, mags, buffer, chain,
		phasor, numPartials;

		chainBufSize = 512;
		numPartials = ~numPartials ? 256;

		mags = In.kr(
			ampBus,
			numPartials
		);
		phases = {pi.rand}!(numPartials-1);
		mags = [0] ++ mags;
		phases = [0,0] ++ phases;
		mags = mags * chainBufSize / 4;

		buffer = LocalBuf( chainBufSize );

		chain = FFT(
			buffer,
			DC.ar( 0 )
		);
		chain = PackFFT(
			chain,
			chainBufSize,
			[ mags, phases ].flop.flatten,
			0,
			numPartials,
			1
		);
		IFFT(
			chain,
			1
		);
		phasor = Phasor.ar(
			1,
			chainBufSize * freq / SampleRate.ir,
			0,
			chainBufSize
		);
		sig = BufRd.ar(
			1,
			buffer,
			phasor
		);

		env = Env.new(
			[0, amp, amp, 0],
			[atk, (sustain - atk) max: 0, rls],
			[catk, 0, crls]
		).kr(2);
		sig = sig * env;
		Out.ar( out, sig );
	}
).add;
)

// Create the Ndef that controls the amplitudes of the partials
(
Ndef(\control).clear;
Ndef(\control,
	{
		var width, center, x, left, right, values, numPartials;
		center = MouseX.kr(0.0,1.0);
		width = MouseY.kr(0.1,0.6);
		numPartials = ~numPartials ? 256;

		numPartials = numPartials - 1;
		x = ( 0 .. numPartials ) / numPartials;
		left = (2/width) * ( x - center + ( width/2 ) );
		left = left.lincurve(0, 1, 0, 1, -10);
		right = (2/width) * ( x - center );
		right = right.lincurve(0, 1, 0, 1, -10);
		values = left - right;

		values.collect( { |x| DC.kr(0) + x } );
	}
);
)

(
Pdef(\ifft,
	Pbind(
		\instrument, \ifft,
		\scale, Scale.major,
		\ampBus, Ndef(\control).bus.index,
		\scale, Scale.major,
		\degree, Pseq([ 0,2,4,3 ],inf) + Pseq([[0,2,4],[1,2,5],[0,4,6]],inf) ,
		\db, -25,
		\harmonic, 0.5,
		\dur, 1,
		\atk, 0.5,
		\catk, -7,
		\rls, 1.2,
		\legato, 0.6,
	)
)
)

Pdef(\ifft).play
2 Likes

Another strategy is to precalculate the IFFT into a table, and then use that table to play back.
I once made a proof of concept: supercollider implementation of padsynth algorithm. (the precalculation was rather expensive if I recall correctly, but it calculated many more tables than strictly needed, so it could be heavily optimised still).

Edit: upon retrying that code, it seems in some circumstances it also generates pops and clicks. I’d have to investigate exactly what goes wrong…