SynthLab SDK
SynthModule With Cores

So far, we've been using the following SynthModules that are all designed with the ModuleCore paradigm and you have seen most of their inner workings as a result of researching the practice objects we've made so far. These are container modules whose main job is to maintain their set of cores, select them, and then call functions on them as needed.

If you want to create a container module, the coding is relatively simple using the template files and the only thing you really need to modify will be the addition of shared databases (if needed) and altering the type of custom GUI parameter structure will be used. The rest of the code may be left intact and without modification.

Empty SynthModule Template

The SDK contains a pair of source files for an empty SynthModule that is a core container object in the files synthmodule_withcores.h and synthmodule_withcores.cpp. Use this as a starting point for your own modules that contain cores. It's constructor is setup to accept a ficitious shared SynthModuleWithCores parameter structure and you will need to change that for your particular object. You may also need to add an argument to the constructor if:

  1. you are implementing a wavetable synth, where you will want to add the wavetable database interface pointer like this (note also the change of the parameter structure type)
  2. you are implementing a PCM Sample synth, where you will want to add the PCMSample database interface pointer like this (note also the change of the parameter structure type)

Wavetable database:

// --- synth module with wavetable database
SynthModuleWithCores(std::shared_ptr<MidiInputData> _midiInputData,
std::shared_ptr<WTOscParameters> _parameters,
std::shared_ptr<WavetableDatabase> _waveTableDatabase,
uint32_t blockSize = 64);
//

PCM Sample Database:

// --- synth module with PCM database
SynthModuleWithCores(std::shared_ptr<MidiInputData> _midiInputData,
std::shared_ptr<PCMOscParameters> _parameters,
std::shared_ptr<PCMSampleDatabase> _sampleDatabase,
uint32_t blockSize = 64);
//

The majority of the operational functions just forward the function calls to either the selected core, or to all of the cores depending on the kind of function. Check out that code which is very lean and simple. Notice that the render() function calls its own update() function first to force the core update to occur just prior to render(). Make sure you do not accidentally call the update() function twice in your projects.

bool SynthModuleWithCores::update()
{
coreProcessData.unisonDetuneCents = unisonDetuneCents;
if(!selectedCore) return false;
return selectedCore->update(coreProcessData);
}
bool SynthModuleWithCores::render(uint32_t samplesToProcess)
{
// --- update parameters for this block
update();
coreProcessData.samplesToProcess = samplesToProcess;
if(!selectedCore) return false;
return selectedCore->render(coreProcessData);
}
//

The constructor is important because it is where the audio buffers and default parameters are synthesized. It is also where the all-important coreProcessData member is initialized with the values that the cores will need to do their work. After that, the cores are created and added. The majority of the variables and methods are from the SynthModule base class.

SynthModuleWithCores::SynthModuleWithCores(std::shared_ptr<MidiInputData> _midiInputData,
std::shared_ptr<OscParameters> _parameters,
uint32_t blockSize)
: SynthModule(_midiInputData)
, parameters(_parameters)
{
// --- standalone ONLY: parameters
if (!parameters)
parameters.reset(new OscParameters);
// --- create our audio buffers
audioBuffers.reset(new SynthProcessInfo(OSC_INPUTS, OSC_OUTPUTS, blockSize));
// --- setup the core processing structure for dynamic cores
coreProcessData.inputBuffers = getAudioBuffers()->getInputBuffers();
coreProcessData.outputBuffers = getAudioBuffers()->getOutputBuffers();
coreProcessData.modulationInputs = modulationInput->getModulatorPtr();
coreProcessData.modulationOutputs = modulationOutput->getModulatorPtr();
coreProcessData.moduleParameters = parameters.get();
coreProcessData.midiInputData = midiInputData->getIMIDIInputData();
// --- add up to 4 cores here like this:
//
// std::shared_ptr<OscCore> defaultCore = std::make_shared<OscCore>();
// addModuleCore(std::static_pointer_cast<ModuleCore>(defaultCore));
// --- core[0]
selectDefaultModuleCore();
}
/* C-TOR */

From this point, you will use this module just like all the others you've used so far. The interface functions are identical and for testing you only need to worry about the kind of custom parameter structure the module requires.


synthlab_4.png