SynthLab SDK
|
The SynthVoice template is found in the synthvoice.h and synthvoice.cpp files in the SynthLab_SDK/source/ folder.
The SynthVoice Operational Phases are discussed in detail in the synth book and so that theory will not be repeated here. However, we do want to to step through the operational phase methods, declared as virtual in the abstract base class (at the end of this section) as these are the main interface functions that the SynthEngine will be calling. In this discussion, I will assume that your SynthVoice object either maintains and uses a set of SynthModules (as in the Minimal Standalone Synth MinSynth) or some other resources for rendering note-events, and that these resources will respond to MIDI events in some way.
The SynthVoice tempate constructor contains argements that are shared pointers to global synth resources. In the SynthLab paradigm, the SynthEngine will create and own one of each of the resources, then pass a shared pointer to its SynthVoices during construction time.
Constructor
The constructor may be called in stanalone mode, where all of the shared pointer arguments are nullptrs. In this case, the object will synthesize its own local MIDI and parameter data structures. If using SynthModules, you may choose to have the voice object create these resources, or pass those nullptrs into the modules during he constructor sequence and they will synthesize their own.
Member Module Construction
The constructor __may__be called in stanalone mode, where all of the shared pointer arguments are nullptr. In this case, the object will synthesize its own local version, but this will defeat the purpose of the shared pointers if you use any more than one voice object. We will see examples of SynthModule member construction in the next section. The template constructor code is simple, creating missing resources if needed and setting up the mix buffers to hold the audio output data.
For the voice object the reset phase has two parts. The first is the standard object reset that you've seen in the SynthModule and ModuleCore objects. The second is a separate function named initialize that is called after construction and reset have completed. This secondary intialization function brings path information to the voices. For the SynthLab example synths, this is a path to the folder that contains the DLL or plugin bundle and are listed in PCM Sample Files & Database. However, you may changed this at the engine level, and pass whatever path you like back to the voices, which then trickle them down into the SynthModule memhers. Generally the paths are needed for retrieveing PCM samples from WAV files, but you may use it for other storage/retrieval.
The reset() and initialize() functions are shown below. Note that the path arrives in a const char* datatype, required for loading dynamic ModuleCores (see Creating SynthLab-DM Modules) to survive function calls across the thunk barrier. In the template file, these functions are only sparsely populated. Take notice of:
The update() function will be called prior to the SynthVoice::render function and for the tempate code, it is empty as there is nothing to do that invovles the template object's few member variables that pertain to voice stealing and voice state. And, since the voice's SynthModule components will be updated later, just prior to render, this function is sparse in the example project code as well. But, it will be called just prior to the SynthVoice::render method if you have any per-block variables that need to be updated or initialized.
Referring back to the MinSynth C++ object (see Minimal Standalone Synth) you can see that the render() function is perhaps the most important as this is where the modules are manipulated to fill audio buffers with freshly synthesized data. The template code below follows the same pattern as the example synths:
The last item in the list is very important. As the synth book discusses, the final amp EG play a cricital role in the lifecycle of a note event. The last part of the render() function contains the code to detect if the EG has expired; if so the note event is done, if not more render operations will necessarily be called.
The render operation will be manipulating AudioBuffer objects that are the outputs of oscillators and the inputs and outputs of processors. The template voice conatins an AudioBuffer to be used as a temporary mix buffer, or splitting buffer, or however you wish. The template code contains two helper functions for moving data to the mix buffers or to the final output buffers for the Block Audio Processing SynthProcessInfo structure. One of the functions accumulates into the destination, while the other writes over the audio in the destination.
The SynthVoice shares identically prototyped functions as the SynthModules for the note-on and note-off message handlers.
doNoteOn()
In this function, the MIDI note pitch is calculated for the incoming MIDI note number and the voice state member variables are reset in a ready-to-render state. In this function, you will call the same doNoteOn() functions on all of the SynthModules. For oscillators, this is also where you setup glide (portamento) modulation because the GlideModulator object will need to be started prior to rendering audio from the oscillator.
doNoteOff()
The majority of this function is for calling the doNoteOff() handlers on the SynthModule objects. Not much else happens here. However it is critical to call the amp EG's note off handler from this function as it dictates the lifecycle of the event.
processMIDIEvent( )
The SynthEngine will normally be the MIDI message decoder. In SynthLab, the engine splits out note-on and note-off messages and directs them to a SynthVoice object that it has chosen, either a dormant object for a new note-on event, or the currently playing voice for a note-off event. The engine will call the voice's processMIDIEvent passing note-on or note-off messages. The template function simply decodes the message and calls the proper handler on the object. For non-note events, the engine places all desired MIDI information, from CCs to global tuning values, into its MIDI input data arrays that are shared with every voice and every SynthModule so that every part of the synth has access to the MIDI input data via the shared pointer used at construction. This means that you do not ever need to pass non-note information around the objects as they all have instant access to it. In the template fuction below, note that the clearTimestamp() function is called to reset the voice timestamps; this is used in the voice stealing heuristics in the synth book.
This completes the tour of the template object. In the next section, we will convert the MinSynth C++ object into a SynthVoice, to be used with a mono SynthEngine we will design and implement in the SynthEngine Object.
If you want the functionality of the voice within the SynthEngine paradigm, but you want to write your own implementations, use this abstract base class version to subclass your object. You will need to implement the abstract virtual functions. The remaining functions and variables handle queries from the SynthEngine's MIDI message handler. If you do not intend on using the Engine's MIDI messaging, the you may delete them.