Equation Composer

Documentation and Examples

Modules and Synths

A "synth" is the sames as a "Program" in the Equation Composer, and is typically selected using the PRG input. A “module” is the building block of a synth. A module may have any number of inputs or outputs. For example, here are the inputs and outputs for the ModuleLFO:

Modules are patched together in a Synth's .cpp file (which I'll cover later). All inputs and outputs are assumed to be in the range from 0 to 4095. This includes gate signals. A “low” gate signal is 0. A “high” gate signal is 4095. A common mistake is to assume the “high” gate signal is represented by the number 1.

Our second “mantra”, which sums up the EquationComposer architecture, is:

The output of one module is patched to the input of another module.

A very simple synth which generates a ramp waveform might look something like this (SynthTutorial1.cpp):

What this is saying in English is, “Make a synth that contains a ModuleWavetableOsc module. The frequency of the ModuleWavetableOsc module should be controlled by the sr (sample rate) input of the Equation Composer. The wavetable input of the ModuleWavetableOsc module should be set to 1.”

Here's the entire code for SynthTutorial1.cpp, shown in the diagram above:

#include "SynthTutorial1.h"

SynthTutorial1::SynthTutorial1(Inputs* inputs)
  ModuleWavetableOsc *osc = new ModuleWavetableOsc();
  osc->wavetable_input  = new ModuleConstant(1);
  osc->frequency_input  = inputs->sr;
  this->last_module = osc;

There are four types of modules used in building synths:

  1. Functional modules – such as the ModuleWavetableOsc or ModuleEquationPlayer. These modules make up the guts of a synth. These modules do something exciting.
  2. Constant modules – These modules supply constant values to inputs.
  3. Input modules – These are special modules that are directly tied to the Equation Composer's physical inputs.
  4. Output modules – Output modules are special and are used only when a module requires multiple outputs.

Input Modules

Input modules are special modules that are directly tied to the Equation Composer's physical inputs and are passed into each synth for easy access. Here's a list of all of the inputs:

  • inputs->prg;
  • inputs->mod;
  • inputs->sr;
  • inputs->param1;
  • inputs->param2;
  • inputs->param3;
  • inputs->gate;

The following example code assigns the value at the sample rate input (sr) to the frequency input of an Oscillator. This code would normally reside in one of the Synth___.cpp files:

ModuleWavetableOsc *wavetable_osc = new ModuleWavetableOsc();
wavetable_osc->frequency_input  = inputs->sr;

Sometimes you may want a little extra noise filtering on inputs. The Equation Composer has input smoothing available on all analog inputs. To use it, simply add ->smooth to the end of an input, like so:

ModuleWavetableOsc *wavetable_osc = new ModuleWavetableOsc();
wavetable_osc->wavetable_input  = inputs->mod->smooth;

Constant Modules

If I were to ask you, “How would you imagine that you'd assign the value “1” to the wavetable_input of an ModuleOsc object named “osc”? Your answer might be:

osc->wavetable_input = 1; // <== wrong!

Unfortunately that won't work. It breaks our mantra, "The output of one module is patched to the input of another module." The number "1" is most definitely not a module.

The correct way of assigning a constant to a module's input looks like:

osc->wavetable_input = new ModuleConstant(1);

Here are a few other examples:

osc->wavetable_input = new ModuleConstant(2477);
osc->wavetable_input = new ModuleConstant(4);

A note on constant modules and input conversions:

Modules assume that the values at their inputs will always range from 0 to 4095. But what if a module only needs a value (such as wavetable selection) between 0 to 16? In that case, the module will map the input from a 0-4095 value to a 0-16 value. A real-world example is ModuleEquationPlayer's param1, param2, and param3 inputs, which are converted to the range 0 – 255. The code responsible for that conversion can be seen in ModuleEquationPlayer.cpp:

p1 = this->readInput(param1_input, CONVERT_TO_8_BIT); // range: 0 - 255 (2^8)
p2 = this->readInput(param2_input, CONVERT_TO_8_BIT); // range: 0 - 255 (2^8)
p3 = this->readInput(param3_input, CONVERT_TO_8_BIT); // range: 0 - 255 (2^8)

It's important to note that constant modules are immune to this conversion, otherwise it would be nearly impossible to set an exact number on an input because it would be adjusted by the mapping code.

Let's look at an example. If you supply the constant value “673” to an input of a ModuleEquationPlayer....

ModuleEquationPlayer *equation_player = new ModuleEquationPlayer(equations);
equation_player->param1_input = new ModuleContant(673);

then when this value is read in ModuleEquationPlayer.cpp..

p1 = this->readInput(param1_input, CONVERT_TO_8_BIT);

p1 is assigned the value 673. The CONVERT_TO_8_BIT parameter is essentially ignored for constants.

Output Modules

Output Modules are only needed when a module requires more than one output. This is a mind bender: Unlike an input module, an output module is always contained within another module. If you're just building synths out of existing modules, you can ignore this fact. But if you start writing your own modules, it's something to keep in mind.

Let's look at a real world situation. "ModuleENV", which is a simple envelope generator, has both a "normal" output (called, simply, "output"), and an inverted output (called, unsurprisingly, "inverted_output"):

Here's how to wire up the inverted output of a ModuleENV to control a filter's cutoff:

ModuleWavetableOsc *wavetable_osc = new ModuleWavetableOsc();
ModuleLowpassFilter *lowpass_filter = new ModuleLowpassFilter();
ModuleENV *envelope_generator = new ModuleENV();

envelope_generator->slope_input = inputs->param2->smooth; // Remember our good friend: smooth?
envelope_generator->trigger_input = inputs->gate;
envelope_generator->frequency_input = inputs->param1;

// Patch up ocillator
wavetable_osc->frequency_input = inputs->sr;
wavetable_osc->wavetable_input = inputs->mod; 
// Patch up filter
lowpass_filter->audio_input = wavetable_osc;
lowpass_filter->cutoff_input = envelope_generator->inverted_output; // ===== check it out!
lowpass_filter->resonance_input = inputs->param3;

this->last_module = lowpass_filter;