SynthLab SDK
ModuleCore Progamming

Each SynthModule is capable of loading up to four (4) module cores that are subclassed from the ModuleCore base. However it is also important to understand that SynthModules do not need to load or implement ModuleCores - they may implement their funcionality directly. The DCA object is an example of a SynthModule that has no cores and that implements its functions directly in the 5 operational phase methods: reset( ), update( ), render( ), doNoteOn( ) and doNoteOff( ).

We will first modify the LFOCore to add a new LFO waveform to it. Then, we will change one of the LFO mod knob's behavior. After that, we will design a ModuleCore from scratch to make a simple filter.

You can find the files used in this tutorial in the SynthLab_SDK/examples/programming_guide folder.

ModuleCore Types, Names and Preferred Index Values

SynthLab ModuleCore objects export three identifier variables:

  • one denotes the type of module
  • another is the module name as you would like it to appear to the user
  • the last is a preferred module core index (on the range [0, 3]) that is used when the module is loaded for DM synths only; if you set the preferred index value to 0, then the core will also be the default core

The type is required when loading dyanmic modules (mini-DLLs) in SynthLab-DM and the name is required for dynamic string loading in the advanced SynthLab projects. If you are not using these options then these identifiers do not matter but you might as well get used to setting them. SynthLab defines the following constants for the various module types:

const uint32_t UNDEFINED_MODULE = 0;
const uint32_t LFO_MODULE = 1; // --- LFO
const uint32_t EG_MODULE = 2; // --- EG
const uint32_t DCA_MODULE = 3; // --- DCA
const uint32_t FILTER_MODULE = 4; // --- Filter
const uint32_t WTO_MODULE = 5; // --- Wavetable Oscillator
const uint32_t VAO_MODULE = 6; // --- Virtual Analog Oscillator
const uint32_t FMO_MODULE = 7; // --- FM Operator Oscillator
const uint32_t PCMO_MODULE = 8; // --- PCM Sample Oscillator
const uint32_t KSO_MODULE = 9; // --- Karplus-Strong Sample Oscillator
const uint32_t OSC_MODULE = 10; // --- General Oscillator

Module Strings

Each SynthModule and ModuleCore includes a statically declared array of 16 strings called Module Strings. Either of these objects can export these strings for the user to see in a GUI control. The module strings are set in the constructor of the SynthModule or ModuleCore but we will concentrate on the core here. The module strings are used as selection items on the GUI that sets a zero-indexed parameter value when the user selects a string. For oscillators, these module strings are waveform names, for EGs they are contours (e.g. "ADSR") while for filters, they are filter types (e.g. "Moog LPF4").

In the lfocore.cpp file, you can find these string definitions, right after the module name and ID value is set.

moduleType = LFO_MODULE;
moduleName = "ClassicLFO";
preferredIndex = 0; // <---- ordering for user, DM only
// --- our LFO waveforms //"clip sine";//
Module Strings, zero-indexed for your GUI Control:
- triangle, sine ,ramp_up, ramp_dn, exp_up, exp_dn, exp_tri, square, rand_SH1, pluck
coreData.moduleStrings[0] = "triangle"; coreData.moduleStrings[8] = "rand S&H1";
coreData.moduleStrings[1] = "sine"; coreData.moduleStrings[9] = "pluck";
coreData.moduleStrings[2] = "ramp up"; coreData.moduleStrings[10] = empty_string.c_str();
coreData.moduleStrings[3] = "ramp dn"; coreData.moduleStrings[11] = empty_string.c_str();
coreData.moduleStrings[4] = "exp up"; coreData.moduleStrings[12] = empty_string.c_str();
coreData.moduleStrings[5] = "exp dn"; coreData.moduleStrings[13] = empty_string.c_str();
coreData.moduleStrings[6] = "exp tri"; coreData.moduleStrings[14] = empty_string.c_str();
coreData.moduleStrings[7] = "square"; coreData.moduleStrings[15] = empty_string.c_str();

Mod Knobs and Labels

Each SynthModule and ModuleCore includes a statically declared array of 4 mod knob labels. These labels are placed over the mod knob controls on the GUI. The mod knobs are known as A, B, C, and D. These labels indicate the functionality of the knob control for the user, and for programmers as well. All mod knobs output unipolar values on the range [0.0, +1.0]. Mod Knob A always initializes to the midpoint value of 0.5 while the other three initialize to 0.0. The mod knob labels are set after the module strings in the constructors. Here are the labels that complete the LFOCore constructor:

// --- modulation control knobs
coreData.modKnobStrings[MOD_KNOB_A] = "Shape";
coreData.modKnobStrings[MOD_KNOB_B] = "Delay";
coreData.modKnobStrings[MOD_KNOB_C] = "FadeIn";
coreData.modKnobStrings[MOD_KNOB_D] = "BPM Sync";

There are three helper functions to allow you to map the unipolar mod knob value to a range of values with a linear, log or anti-log mapping. For example:

// --- map mod knob B value linearly from 0.0 to 100.0 (milliseconds, for a delay control)
double delay = getModKnobValueLinear(parameters->modKnobValue[MOD_KNOB_B], 0.0, 100.0);
// --- map mod knob A value logarithmically from 10.0 to 1000.0
double shape = getModKnobValueLog(parameters->modKnobValue[MOD_KNOB_A], 10.0, 1000.0);

In the next series of modules, we'll modify the LFOCore and create a brand new filtering core. In general, you should open the core's .cpp file and step through the five operational phase functions, plus the constructor and make note of what is happening in each. You will soon see that the cores all follow a nearly identical programming pattern, in part because of the adherence to the base class overrides. And, within each module type (filters, oscillators, etc...) you will see even more design patterns emerge with great similarity between the object coding.

ModuleCore Programming Guide