Old school vocoders

Hello all

I’ve wanted for a while to have access to an old school vocoder in SC but I haven’t really sat down and worked on it before now. I would love some feedback.

What I mean by old school vocoder is something that happens in the time domain where the signal is split using bandpass filters instead of FFT decomposition.

Anyways, here is what I came up with today. It’s pretty good but not perfect. It creates different versions of the synthdef with different numbers of bands where each band has it’s own controls for attack, release and gain. What do you think?

b = Buffer.read(s, Platform.systemAppSupportDir +/+ "sounds" +/+ "a11wlk01.wav");

(
[8, 16, 32, 64].do{|numbands|
    var defname = "vocoder%bands".format(numbands).asSymbol.postln;

    SynthDef(defname, {|out=0|
        // Input signal. Should be replaced with Input.ar 
        var sig = PlayBuf.ar(numChannels:1, bufnum:b, rate:BufRateScale.kr(b), trigger:1.0, startPos:0.0, loop:1.0, doneAction:0);

        var inputSig = SawDPW.ar(\freq.ar(100));
        var bandrq = \rq.kr(0.05);

        sig = Array.fill(numbands, {|bandNum|
            var bandfreq = bandNum.linexp(0,numbands-1, \minFreq.ir(100), \maxFreq.ir(8000));
            var filtered = BPF.ar(in:sig, freq:bandfreq, rq:bandrq, mul:1.0, add:0.0);

            var osc = BPF.ar(inputSig, bandfreq, bandrq);
            var thisGain = "bandGain%".format(bandNum+1).asSymbol.kr(1);
            var atk = "bandAttack%".format(bandNum+1).asSymbol.kr(0.01);
            var rel = "bandRelease%".format(bandNum+1).asSymbol.kr(0.01);
            osc * thisGain * Amplitude.ar(in:filtered, attackTime:atk, releaseTime:rel) * bandrq.reciprocal
        });

        sig = sig.sum!2;

        Out.ar(out, sig * \amp.kr(1))
    }).add;

}
)

// Pretty straightforward
(
~synth = Synth(\vocoder16bands, [
    \rq, 0.05,
    \freq, 100,
]);
)

~synth.set(\freq, 550);
~synth.set(\freq, 50, \rq, 0.01);

(
~synth2 = Synth(\vocoder8bands, [
    \rq, 0.05,
    \freq, 250,
    \bandAttack1, 0.005, \bandAttack2, 0.001, \bandAttack3, 0.0005,
    \bandRelease1, 0.05, \bandRelease2, 0.01, \bandRelease3, 0.005,
    \bandGain1, 1, \bandGain2, 2, \bandGain3, 2, \bandgain4, 4,
]);
)

Inspirations

1 Like

I think the bandfreqs are off. They should be harmonics, right:

var bandfreq = (\freq.ar*(bandNum+1)).poll;

Otherwise it sounds great. I love the low rq resonance!

Just tuned fun:

(
[1,5/4,3/2,7/4].do{|i|
~synth2 = Synth(\vocoder8bands, [

    \rq, 0.05,

    \freq, 250*i

])};

)

Sam

Oh cool! I’ve been wanting to make one of these as well. Will check out your code when I get home this evening, thanks for sharing!

I’ve enjoyed Doepfer’s take on this with slew limiting in between the envelope follower and amplifiers for each band.

Vocoders get so funky.

I believe the old-school vocoders also used some sort of transient detection that would switch on white or pink noise with sibilances, but I could be wrong.

excited to test! I’ve been making a big project which involves vocoder - I’ve been using Cepstral vocoding mainly but def have a need for some of the oldschool…

Many years ago I coded up an analog design in Csound. I think the source was Electronotes, but I’m not certain.

My recollection is that it was a third octave equal Q design, and included a switch for voiced and unvoiced source. The switch was HP / LP filter pair: more energy in HP → unvoiced. Unvoiced synthesized by filter noise, to taste.

Server.default.options.numWireBufs = 1024;

(
{
	var voicedCarrier;
	var snd, numBands, bandFreqs, carrier;
	numBands = 32;
	bandFreqs = (0..numBands - 1).linexp(0, numBands - 1, 100, 8000);
	snd = PlayBuf.ar(1, ~buffer, BufRateScale.kr(~buffer));
	voicedCarrier = Saw.ar((60 + [0, 3, 7]).midicps);
	carrier = SelectX.ar((ZeroCrossing.ar(snd).cpsmidi.lag(0.05) > 5000.cpsmidi).lag(0.05), [voicedCarrier, PinkNoise.ar]);
	snd = Amplitude.ar(BPF.ar(snd, bandFreqs, 0.05), 0.01, 0.05);
	snd = BPF.ar(carrier, bandFreqs, 0.05) * snd;
	snd = snd.sum;
	snd = snd * 30.dbamp;
	snd = Limiter.ar(snd);
	snd ! 2;
}.play;
)

11 Likes

Fantastic !
Why are you multiplying the band passed signal with its amplitude ? is it a kind of compressor ?

It’s not. It’s multiplying one carrier band by the amplitude of the corresponding band in the modulator (the other input signal).

hjh

Oh yeah my bad ! Thanks James

Wow nathan that’s great. Neat trick with the zerocrossing detection!

Beginner here - I’m getting an error when loading the code “GraphDef_Load: exceeded number of interconnect buffers.”
I’m sure I’ve read it somewhere (a command line using ServerOptions) to increase from the default buffer size but couldn’t find it. Could you please help me in this regards? Thanks

I’ve updated the post to add Server.default.options.numWireBufs = 1024;, which should fix it. It should be run before the server is booted. Best to add that to your startup file.

1 Like

Thank you soo much Nathan. Didn’t know where the startup file was either but there was another forum which explained it - its under file → open startup file and past the code, save it.

Okay wow this ZeroCrossing trick with PinkNoise really makes a big difference for sibilance in voices in a vocoder. I added it to mine now, and here it is with 32 bands

Vocoder with no pink noise trick for zero crossings:

Vocoder with the pink noise trick for zero crossings:

Source

4 Likes

@nathan The zero crossing trick is really nice! So simple, yet so effective.

(Off topic: “Samurai Cop” is hilarious! There is probably not a single scene that didn’t make me laugh. Knowing the background story makes it even funnier. Highly recommended for a fun movie night with friends!)

1 Like

(cool, I made this inspired by vocoder, just for saying

)

Thanks for this thread and the example code. I had a try at improving on it but I couldn’t get anything that sounded quite right. It’s hard to get a bank of BPF Ugens to sound decent and I found that using the BandSplitter quark used too much cpu. My best effort is here if anyone is interested: Sennheiser VSM201 Vocoder Emulation in SuperCollider · GitHub

Demo sound for the above code: Dropbox - vocoder_sc_demo.mp3 - Simplify your life

I ended up abandoning the idea of making a “classic” filter based vocoder and just used an FFT based version instead. Doesn’t sound quite as cool though…

For anyone else who wants to take up the task I found some good resources:

I concentrated on the Sennheiser VSM 201 vocoder because there are some good documents around for it.

The architecture is actually quite simple: 20 channels between 100Hz and 8kHz. Each has an envelope follower. Replacement signal (white or pink noise) is added in for unvoiced sounds, much like the patch suggested above. More details are in those magazine articles above - they’re very illuminating!

If I were to carry on I’d be looking for a way to construct a “clean” filterbank. Pseudo-Quadrature-Mirror-Filters are an interesting option (used in MP3 encoding and more recently in neural vocoders) but I haven’t tried to implement those in SC yet. If anyone else has any suggestions I’d love to hear them.

The SignalBox quark includes a method to design an array of gaussian windowed filter kernels that can be used to split up the signal. These aren’t QMF, but could be worth a try…