Transports and Synchronization
Understanding Storage: let, const, @state, @param
Using the Audio Plugin/Desktop Application Template
Programming a Custom UI with JUCE
RNBO Raspberry Pi OSCQuery Runner
RNBO MIDI Synthesizer
It’s time to build our own synthesizer with RNBO, one that we can take anywhere—whether it’s our DAW, a gig, or our own web application.
In this article, we’ll build a polyphonic synthesizer with an envelope generator, filter, and some reverb. To make it special, we’ll make the tuning of the synth adjustable according to different tunings from the Scala archive.
The polyphony attribute
Let’s create a rnbo~ object titled “midisynth,” and make our new synth an 8-voice polyphonic instrument with the attribute
@polyphony 8. Before we enter the RNBO patcher, let’s add a midiin object and patch it to the rightmost inlet—the MIDI inlet of the rnbo~ object—so that we’ll be able to receive MIDI in our RNBO patcher.
Using the Synth Building Blocks
Now, inside RNBO, we’re going to piece together the bones of our synthesizer from the RNBO Synth Building Blocks package. Make sure that you have that package installed from the Package Manager.
If it is, you should be able to click on the Snippets Browser in your RNBO patcher and filter the snippets to find what you want. Let’s start with our oscillator. We want the building block called “osc.analog,” so we can type that into our search bar, and drag the resulting snippet into our patcher.
Let’s give this subpatcher the title “oscillator” to keep track of it as the patch grows. Your object should look like
[p @file sbb.osc.analog @title oscillator].
In the future, if you don’t know which synth building block you are looking for, you can explore the package’s launcher and examples, which is full of helpful and useful material—instead of using the Snippets Browser, you can also copy and paste subpatchers directly from the launcher (available via Extras > synth-building-blocks).
Let’s open up that “oscillator” subpatch and see what’s in this building block. We can see that this abstraction contains a variety of waveforms that you might recognize from analog synthesizers. With the parameter “mode,” we can pick either a noise, sine, sawtooth, triangle, square, or pulse waveform for our oscillator.
We can see that
[param mode] has a default value of 1, meaning that, counting from 0, the second oscillator is the one we’re currently using — cycle~. Let’s navigate back up to our top-level RNBO patcher and get some sound.
notein will get us our MIDI messages from Max, and mtof~ will convert MIDI pitches we receive to frequencies for our oscillators. Then, let’s multiply that signal by 0.1 for now just to protect our ears, and send it to an out~. Up in Max, turn on audio processing and connect rnbo~ to a live.gain and then to dac~. Okay! Now, when we play MIDI notes, we get up to 8 voices that start and then… never stop.
What we need next from the Snippets Browser is an envelope generator. The one we want has parameters for Attack, Decay, Sustain, and Release— type “env.adsr” into the searchbar and drag that snippet in. Let’s title this subpatcher “envelope.” If we look inside, we can see that the first inlet will take the signal we want to have the envelope applied to, and the second inlet will take a gate signal that will trigger the beginning of the envelope and initiate its release.
[*~ 0.1], let’s insert this “envelope” subpatcher. Now, for our gate signal, we can use the velocity information from notein — patch to sig~ from the second outlet of notein, and then test with
[>~ 0.] whether the velocity signal is greater than 0. If it is, we know that the pitch sent out the first notein outlet is from a note-on message, which can start the envelope, and if it is not, we know that we have a note-off message, which can initiate the envelope’s release. So let’s patch that >~ into the right inlet of our “envelope” subpatcher.
Now we can play some notes that start and stop!
Now that we have the envelope generator, we can control the parameters of our envelope. Up in Max, if we right-click on the leftmost inlet of rnbo~, now, because we labeled our subpatchers “envelope” and “oscillator,” we can clearly see the parameters inside of those subpatchers.
Let’s create a message box with
[poly/envelope/attack $1], and patch a number box to that message and then on to rnbo~. We can do the same for "decay," "sustain" (with a floating point number box), and "release." While we’re at it, let’s make one for
[poly/oscillator/mode $1] as well so we can pick our waveform. Let’s send it a “3” to select the fourth waveform: triangle.
Try playing some notes, changing the attack time (in milliseconds) as you do, and hear the envelope change.
In order to get more control over the sound of the synth, we are going to pick two Filter abstractions from the Snippets Browser. For this article, we’ll use the lowpass filter, "sbb.filter.lp," and highpass filter, "sbb.filter.hp," but you can select others, like a bandpass filter, if you like.
If we open up one of these filter abstractions, we can see that it’s looking for the signal to filter in the left inlet, a cutoff frequency in its middle inlet, and the resonance, or Q, of the filter in its right inlet. So let’s create a
[param filterCutoff 130 @min 20 @max 20000], and a
[param filterQ 0.2 @min 0.01 @max 10]. We can patch those to line~ objects with
[append 40] patched in between to create a smooth transition to new filter values, and then into the respective inlets of our filters. Then, we can patch the outlet of our “envelope” subpatch into the left inlets.
Let’s make a new param, "filterType," and make it an enum parameter with three choices: lopass, hipass, and a bypass choice which doesn’t have any filter applied. We can make it default to the first choice with “0,” and then add 1 to the output so that it can drive a [selector~] which will pick between three inlets.
Now, up in Max we can create some controls for our filter. Message boxes for “filterType,” “filterCutoff,” and “filterQ” will get us what we need, and we could make a umenu for selecting the filterType just for ease.
Instead of multiplying the synth’s output by a blanket
[*~ 0.1], let’s scale the signal based on the velocity of incoming MIDI notes. We can do this with the latch~ object, which will pass or hold an input value based on a 1 or 0 in the right inlet. So
[>~ 0] will connect to the right inlet, our sig~ will connect to the left inlet, and the output from latch~ can be divided by 127 to get us into the 0 to 1 range.
Ok, this sounds great. Now, let’s take advantage of RNBO’s support for Scala format tunings and keyboard mappings. We are going to turn this synth into a way to play with some historical tunings we can find on the Scala archive – four of the tunings described by mathematician and music scholar Alexander John Ellis. To do so, pick a few tunings from the scala archive and type their names into scala.list, like:
[scala.list ellis_harm ellis_24 ellis_eb ellis_mteb].
mtof~ will use these tunings if we patch scala.list to a
[set scale] connected to mtof~. Lets' make a
[param setTuning 0 @min 0 @max 3 @steps 4], which gives 4 steps (0, 1, 2, 3) to choose from these four tunings. Now, if we play our synth, we can hear some glorious intervals.
We’re almost done. Most of this patch has been created from using Synth Building Blocks abstractions—but Max ships with other abstractions and files we can use in RNBO as well.
Let’s add some reverb. Navigate to Help > Examples > gen, open up the
gen~.gigaverb example, and copy all of this gen goodness.
Back in RNBO, let’s create
[gen~ @title gigaverb], delete the default contents, and paste our gen code in. Let’s create a
[param reverbTime 10 @min 0.1 @max 100] which will connect to
[set revtime] to set the value of the Gen patcher’s “revtime” param. We can patch our signal into gen~’s inlets.
Now, we want to be able to crossfade between the dry synth and the wet reverb sound. In our gen~ patcher, let’s change the “dry” param to default to 0 so that 0% of the incoming dry sound is included in Gen’s the output signal.
Then, let’s find “sbb.util.xfade” and call the new subpatch “crossfade.”
We’ll make a
[param reverbMix 0.5 @min 0 @max 1] and scale that to the -50 to 50 that our new subpatch is expecting in its rightmost outlet. Let’s connect up our dry and wet signals to our “crossfade” subpatch and connect the outputs—which is now stereo, with an
[out~ 1] and also a
[out~ 2] .
Back up in Max, make two message boxes to control “reverbMix” and “reverbTime” and connect our new second outlet of rnbo~ to live.gain~.
Our synth is basically complete! Let’s just add a
[loadmess 3] and
[set mode] before our “oscillator” subpatch so that our patch will load with the triangle waveform.
Ready for Export
Congratulations—you’ve now built a polyphonic synthesizer / historical Just Intonation exploration tool. Now you can take this synth and bring it to the world, for example, to make an instrument people can play in a web browser, you could use the Web Export in the export sidebar and set up your Web App however you like. If you'd like a starting point to experience your RNBO device in the browser, check out our RNBO Webpage Template article.
First, select the output directory where you need your
patch.export.json. The rest of the settings here can stay as default — we don’t need sample dependencies copied because we don’t have any! Make sure that “Link Polyphony to rnbo~” is selected if you want the same number of polyphonic voices in your web app as you have here in Max.
Click “Export to Selected Target” and then enjoy building your web application. It’s so cool to hear the same sound and quality coming from the browser that we had in our patcher.
Enjoy your new synthesizer, and happy patching!