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;
)

8 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

3 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

)