A Basic Vocoder Tutorial, Part 3

    The vocoder effect is everywhere — from the Sylvan Esso's recent release "Ferris Wheel" to more iconic appearances such as Phil Collins' 1981 "In The Air Tonight," Scritti Politti's "Boom! There She Was" or <insert your favorite Kraftwerk tune here>.
    In this tutorial series, we're building a vocoder of our own, with the help of Max 8's MC (multichannel) capabilities. So far, we've looked at real-time analyzers (RTAs) as the heart of our vocoder, and used a pair of RTAs to create a mono vocoder. This time out, we'll create a stereo version of our vocoder and add a few new features.

    The Basic Vocoder - a Recap

    We have two sources for our vocoder: an excitation source and a carrier. In this example, we're using a drum loop as the excitation signal and a synthesizer for the carrier.
    Both signals are split into multiple frequency bands, and we use the amplitude envelope of each band of the exciter and apply that envelope to each corresponding band of the carrier.
    Here is the basic vocoder patcher we created in Part 2 of this tutorial:

    From Mono to Stereo - Making it Multi-Channel

    So far, we have used Max's MC features as a way of passing multiple bands of audio between objects in our vocoder patch. In order to to create stereo (or multi-channel) version of our patch, we will want to make multiple copies of this vocoder — one for each channel of audio input to the vocoder.
    In essence, we will wrap our MC patcher inside of another MC patcher. We can use the Max mc.poly~ object to do this. The magic of this object is that it allows us to process channels in parallel using multiple instances of a loaded patcher. This is exactly what we need!
    The first step is to convert our vocoder patch into a re-usable Max abstraction.
    Since the mc.poly~ object is an MC version of the standard Max poly~ object, we can define the inlets and outlets of our abstractions just as we would with any abstraction loaded in a normal poly~ object — all of the same rules apply, and inlets and outlets are defined using the same in/in~ and out/out~ Max objects. The abstraction we're creating represents one channel (or voice) of the mc.poly~, so we can simply think of traditional mono inputs and outputs when we create our abstraction even though the wrapper is called mc.poly~.
    Don't worry if you need to re-read that last paragraph a few times — it'll make sense when you think about it for a while, even if it seems challenging at first ("Wait, I thought I was in MC, shouldn't I use MC?"). It gets easier with time.
    With that in mind, let's create the abstraction we're going to loaded by mc.poly~ Here it is, ready to go. We will call the abstraction "my.vocoder.voice~." It's worth taking a minute to study the patch and compare it to the original source:
    The observant reader will note that we've made an addition to our previous patching — the section connected to the out 1 object and the bottom of the patch (When the abstraction is loaded, it becomes the second outlet of the mc.poly~ since the out~ 1 with the tilde character is the first outlet).
    This snippet of patching provides the ability to get the metering of the bands out of the mc.poly~ at control rate so that we can make a fancy visualization in the outside patcher.
    The resulting vocoder we get by loading our my.vocoder.voice~ abstraction into an mc.poly~ object with an argument of 2 (stereo) now looks like this:

    Changing the Number of Channels

    The mc.poly~ object is hosting multiple instances of the my.vocoder.voice~ abstraction - that's one instance for each channel of audio coming into the mc.poly~. In our example, we've used an argument to tell the mc.poly~ that we want 2 channels for stereo audio output. In fact, we could specify any number of channels we might want.
    We can also change the number of channels dynamically... sort of. By setting the @voices attribute of mc.poly~ with a message box, we can change the number of channels it processes. But there is a gotcha: the change of voices for mc.poly~ will not take effect until we restart the DSP.
    Changing the number of voices also means we would need to initialize the parameters of the new voices to match those of the existing voices (which can get a little messy with a larger number of channels). For the purposes of this tutorial, we will keep things simple and assume that the voice count is fixed at stereo.

    Parameterization: The Ins and Outs

    We connected all of our parameters to inlets in our my.vocoder.voice~ abstraction. Those parameters are connected to user interface objects in the hosting patcher. In our example, you'll notice that we're using Max for Live UI objects (live.numbox and live.dial) instead of number boxes and dial objects. We're doing that for two reasons:
    • The parameters will be all ready do use if we want to convert our MSP patch to a Max for Live device
    • The Live UI objects work well with Max's Snapshot system.
    The bands parameter changes the number of bands in the vocoder. The bands are represented as channels by MC inside of our abstraction.
    Just as it is with the number of channels feeding our mc.poly~, a change to the number of bands inside of the mc.poly~ will only take effect when the DSP is restarted — this means changing from 20 bands to 32 bands is possible, but you must also restart the DSP.
    The bandwidth (BW) parameter changes how narrow or wide the filters are inside of the vocoder. At a value of 100% the filters have a Q-factor that produces a near-ideal crossover. Values greater than 100% will result in more signal getting through each of the filters, resulting in a higher overall gain of the output.
    Using values less than 100% will result in narrower filters and a reduction in the overall gain of the output. We can compensate for this by using the live.gain~ slider to change the output gain.
    We also need to ensure that all voices of the mc.poly~ are updated when parameters change — otherwise, one channel may change and the other be left with old settings. We'll use a b 1 (bangbang) object and a message box that sends a target 0 message to update all the parameters at once. This will muddy up the look of the patcher, but operate as we would expect the vocoder to behave.
    Our patcher now looks like this after we've added these updates:

    Unfinished Business

    We have now successfully created a Multi-channel, Multi-band vocoder using MC inside of MC!
    If you want more practice, this same technique could be used to create a Multi-channel, Multi-band limiter with the content in the MC tab of the limi~ help patcher.
    What remains to make our vocoder even more compelling? Interestingly, our next steps have very little to do with the heart of our vocoder patch itself. Instead it has everything to do with creating more compelling synthesizer/carrier input, and that's what we'll be looking in the final installment in this Vocoder tutorial series.
    Stay tuned!

    • Oct 02 2020 | 3:58 pm
      I haven't had a chance to watch/read this yet, but wanted to say that I really appreciate these tutorial articles, so thanks so much for making them. :-)
    • Oct 05 2020 | 10:50 pm
      Thanks for these tutorials - they are clear, well thought out and have helped me a lot.