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.