Hi SuperCollider hive mind!
I have just started learning SuperCollider and I’m doing some experimentation with feedback delays, more particularly recreating the Karplus-Strong resonator from scratch.
I realize that the built in Pluck class does exactly that, but by doing it from scratch, I can of course get more fine-grained control over the sound.
It seemed like I succeeded with my experiment until I tried playing some higher notes and glitches started to appear. For some reason, the glitches only seem to appear if the number of samples of the wave period resulted by the input frequency of the synthDef is shorter than the block size of the audio device.
Below follows the source code to a proof-of-concept demonstration of the problem, with comments included to walk you through it:
// 1. Setup the server (my devices of choice are commented out).
(
~opts = Server.default.options;
// ~opts.inDevice = 'Windows WASAPI : Microphone (Realtek(R) Audio)';
// ~opts.outDevice = 'Windows WASAPI : Speakers (Realtek(R) Audio)';
~opts.sampleRate = 48000;
~opts.blockSize = 64;
)
// 2. Boot the server.
s.boot;
// 3. Add the two synthDefs: 'pluckTest', featuring the built-in SC Pluck class for a ready-to-use Karplus-Strong pluck emulation, and my own work-in-progress proof-of-concept 'feedbackTest' that for now aims to do the same thing.
(
SynthDef(\pluckTest,{arg out=0,freq=220,gate=1,amp=1;
var pluck;
EnvGen.kr(Env.asr(0, 1, 0.5), gate: gate, doneAction: 2);
pluck = Pluck.ar(
in: BrownNoise.ar(0.1*amp),
trig: gate,
maxdelaytime: 0.5,
delaytime: freq.reciprocal,
decaytime: LinLin.kr(gate, 0, 1, 0.1, 10),
coef: LinLin.kr(gate, 0, 1, 0.9, 0.2)
);
Out.ar(out, pluck.dup);
}).add;
SynthDef(\feedbackTest, {arg out=0,freq=200,gate=1,amp=1;
var sustainFbk = 0.998, releaseFbk = 0.2, sr, buf, bufLength, splOffset, ptr, noiseEnv, noise, sig;
sr = s.sampleRate;
bufLength = (sr * 0.0625).floor;
buf = LocalBuf(bufLength,1).clear;
splOffset = sr / freq;
ptr = Phasor.ar(0, 1, 0, bufLength);
noiseEnv = EnvGen.kr(Env.perc(0, 0.02, amp), gate);
noise = BrownNoise.ar(noiseEnv) * 0.1;
sig = BufRd.ar(1, buf, ptr % bufLength) * LinLin.kr(gate, 0, 1, releaseFbk, sustainFbk) + noise;
sig = OnePole.ar(sig, 0.2);
BufWr.ar(sig, buf, ptr + splOffset);
EnvGen.kr(Env.asr(0, 1, 2), gate: gate, doneAction: 2);
Out.ar(out, sig.dup);
}).add;
)
// (Steps 4-7 are just a demonstration of the two synthDefs where no problem is anticipated. Feel free to skip these and go directly to step 8.)
// 4. A simple pattern for trying out the synthDefs. First with 'pluckTest'...
(
Pbindef(\demo,
\instrument, \pluckTest,
\dur, Pseq([0.25, 0.5, 0.25, 0.25, 0.5], inf),
\freq, Pseq([[88, 132], 440 / 3, 220, 330, 440], inf),
\sustain, 1.65,
\amp, Pseq([0.3, 0.22], inf)
).play;
)
// 5. ...then with 'feedbackTest' (should sound quite similar and without glitches).
Pbindef(\demo, \instrument, \feedbackTest);
// 6. Optionally, compare with 'pluckTest' again.
Pbindef(\demo, \instrument, \pluckTest);
// 7. Stop.
Pbindef(\demo).stop;
// 8. Another pattern featuring higher pitches to demonstrate the issue. First with 'pluckTest'...
(
Pbindef(\jaws,
\instrument, \pluckTest,
\dur, 0.3,
\midinote, Pseq([78, 79], inf),
\amp, 0.5
).play;
)
// 9. ...and then with my 'feedbackTest' (glitches are anticipated at the higher note).
Pbindef(\jaws, \instrument, \feedbackTest);
// Since the higher note's frequency results in a period of fewer samples than the block size, glitches appear (I don't know why).
// 10. Optionally, compare with 'pluckTest' again.
Pbindef(\jaws, \instrument, \pluckTest);
// 11. Stop.
Pbindef(\jaws).stop;
// 12. A hacky proof-of-concept solution is to reboot the server with a lower block size:
(
~opts.blockSize = 32;
s.reboot;
)
// 13. Now, after the reboot is finished, try the second pattern with 'feedbackTest' again. The glitches should be gone.
Pbindef(\jaws, \instrument, \feedbackTest);
Since I am new to SuperCollider, I highly suspect that I have misunderstood some aspect of some core concept of how the software works, so I am therefore reaching out to you, hoping to gain some clarity and also hopefully learn what went wrong with my experiment.
Thanks in advance!