In order to facilitate my experiments with sound synthesis, I have developed a software framework inspired by modular synthesizers, where a synthesizer consists of many connected modules, each with a very specific function. An important feature in modular synthesis is that the output of any module may be used as input in another, yielding endless possibilities in how to setup a synthesizer.
The framework is written in Java, and the core interface of the framework is the Module which has a single method which iterates the module and returns the output:
public interface Module {
/**
* Iterate the state of the module and return the output
* buffer.
*
* @return The output of this module.
*/
double[] getNextSamples();
}
All modules in MOSEF are instances of this interface, exploiting the polymorphism of object-oriented programming to allow the output of any module to be used as input for another. A module may take any number of inputs and give a single output.
A simple example of a Module is an Amplifier which takes a single input, and gains it by a fixed value.
public class Amplifier implements Module {
private final double[] buffer;
private final Module input;
private final double gain;
public Amplifier(MOSEFSettings settings,
Module input, double gain) {
this.buffer = new double[settings.getBufferSize()];
this.input = input;
this.gain = gain;
}
@Override
public double[] getNextSamples() {
double[] inputBuffer = input.getNextSamples();
for (int i = 0; i < buffer.length; i++) {
buffer[i] = gain * inputBuffer[i];
}
return buffer;
}
}
However, this amplifier can easily be changed into an amplifier where the gain is controlled by another Module. In modular synthesis this is called a voltage controlled amplifier (VCA):
public class VCA implements Module {
private final double[] buffer;
private final Module input, gain;
public Amplifier(MOSEFSettings settings,
Module input, Module gain) {
this.buffer = new double[settings.getBufferSize()];
this.input = input;
this.gain = gain;
}
@Override
public double[] getNextSamples() {
double[] inputBuffer = input.getNextSamples();
double[] gainBuffer = gain.getNextSamples();
for (int i = 0; i < buffer.length; i++) {
buffer[i] = gainBuffer[i] * inputBuffer[i];
}
return buffer;
}
}
Note that this a module calls the getNextSamples method on its inputs, so a more complex synthesizer will consist of many modules in a tree structure, where calling getNextSamples on the root module will call all modules the root has a input, each of which will call all modules it has as input and so on.
The framework implements a number of basic modules including
- Mixers,
- Oscillators and LFOs,
- Delays,
- Envelope generators,
- Low pass filters,
- Offsetters,
- Glide/portamento modules,
- MIDI / wav inputs,
- Noise generators,
- Limiters,
- Arpeggiators and sequencers.
The basic modules are available through the MOSEF factory class, simplifying the code needed to design complex synthesizers. For example, pulse-width modulation synthesis where the width of a pulse wave is controlled by a low frequency sine wave may be created as follows. Here the variable m is an instance of the MOSEF factory class, and the width of the pulse wave varies between 0.3 ± 0.1 with frequency 15 Hz.
Module modulator = m.offset(m.sine(15.0), 0.3, 0.1);
Module oscillator = m.pulse(in, modulator);
The framework is available on GitHub as well as some examples on how to use the framework.