Since my knowledge of JUCE is not that great, I am probably doing many things the “improper” way. Also C++ object oriented language, constructor headers and the advanced OOP stuff still confuses me. No give me some assembly language and I am good to go! Besides this synthesizer, I only made one JUCE program before, a Breakout game. Check it out here if you get a chance - Making an arcade game with JUCE? Absolutely just look here! - #5 by DKDiveDude.
First off I don’t use SynthSound at all, not a single line of code. In SynthVoice I do all 8 tone generators, meaning I use a buffer of 16 float channels, 8 stereo channels, from renderNextBlock in PluginProcessor to SynthVoice, which has its own local set of 8 stereo float buffers.
To minimize if statements in SynthVoice’s buffer sample loop, I do a lot of “status” variable setting in the PluginEditor. Then outside of the main buffer loop, SynthVoice’s local variables are updated.
All the hard LFO matrix work is also done in the PluginEditor and more specifically in my small pop-up “LFO Target” component via an intricate variable system so for example back in SynthVoice for a tone generator’s “Fine Pitch” LFO multiplier used for Vibrato, it is just retrieved once per buffer sample loop, after actually, like the following where “tg” is the tone generator one of eight;
lfoMultiplier = lfoClass[lfoTargetTGPitchModule[tg]].getPolyLFOMultiplier (voiceNumber, lfoDepthTGPitch[tg], lfoDirectionTGPitch[tg]);
Then that multiplier is multiplied with the tone generator’s delta either in the main buffer loop, or I more relaxed also just once per buffer sample loop, as with my buffer size of only 512 the audible chance is good enough for me. Anyways so back in the PluginEditor, whenever I turn on, solo, or turn off an LFO I need to go through all possible LFO target’s, which each has a set of 6 array variables specifying if the LFO the target is using is on or off, what LFO is assigned to “module” (tone generator or effect), current LFO level, current LFO override level, current LFO direction (down, center, up), and LFO override direction. Note the current LFO direction doubles as a LFO status, so if it “0” back my LFO class switch simply returns “1” as multiplier, and there are 4 cases, 1 through 4 being; off (1), down (0-1), center(0 to 2), or up (1 to 2). Similar in my PluginEditor’s “LFO Target” component, I do a lot of matrix array variable setting when I assign, or unassign any active LFO to a target, and/or override the LFO’s level and/or direction.
I use a similar system, setting variables back in PluginEditor, for whether or not SynthVoice main buffer loop needs to process any of the 8 tone generators, as that can be if they are turned on, or even being off if used as a modulation, waveshaping, morphing, or distortion source. Also if SynthVoice needs to get FM modulation modifiers, needs to process effects and where, as in the tone generator’s unison stage and/or once a unison set, that being from 1 to 16 unison voices, has been combined into a tone generator stereo set.
Also anytime I need more than one “If” I always use Switch statements.
So in SynthVoice I have roughly the following flow, where I go through buffer samples in tone generator and unison stages, first only one set of unison voices at a time, which can be a loop of only 1 or up to 16;
Buffer Sample Loop
-
Tone Generator Loop (up to 8), whether on or used as a source
- Unison Loop (1 to 16)
- Get samples from wavetable and interpolate linear.
- Duty Cycle processing - Delta manipulation to simulate pulse width but with any waveform.
-
Active modulation source tone generator loop (up to 8) - Only TG’s used as source
- Unison Loop (1 to 16) - AM, RM, and get FM/PM modulation modifiers.
-
Active Tone Generator Loop (up to 8), whether on or used as a source
- Unison Loop (1 to 16). Advance angle by delta which is multiplied with modulation and LFO modifier
At this point I have a unison set of all tone generators, and I continue to do special sample processing, which is waveshaping and effects, only one unison set (1 to 16) a time, and only on tone generators actually on, and only on effect modules on and routed to correct tone generator and TG stage, all predetermined in the PluginEditor. Each processing using a switch to determine which type, and each using its own local unison sample loop. Sure a bit more code, but that way there is no need to Switch during unison samples.
Finally mixing the unison set, voices to a stereo tone generator set and move on to the next buffer sample. And I do this for tone generators, whether they are actually on or just to be used as a source for later on. So loop here is tone generators on or used as source, then a unison set.
Then I am finally done with all work on a unison level, and can move on to any processing on a voice (note) tone generator level, and again only on active tone generators, active effect modules. So here I do;
Active Tone Generator Loop (up to 8), whether on or used as a source
-
Apply gain to try and maintain even level no matter how many unison voices
-
Modulation - AM, RM, PD, and even other forms of PM and FM! Each type/variation using a switch to determine which type, and each using its own local buffer sample loop. Sure a bit more code, but that way there is no need to Switch during buffer samples.
Waveshaping Stage
Tone Generator Loop (up to 8) - Only if actually on!
- Waveshape type using a switch to determine which type, and each again using its own local buffer sample loop. Several variables used here have been set in PluginEditor, and only updates in SynthVoice once per buffer sample loop.
Effect Stage
Active Tone Generator Loop (up to 8) - Only if actually on!
- Active Effect Module Loop - Only active modules and if needed, selected via matrix.
- Using a switch to determine which type, and each again using its own local buffer sample loop.
Wave Morphing Stage
- Active Tone Generator Loop (up to 8) - Only if actually on!
- Using a switch to determine which type, and each again using its own local buffer sample loop.
Phew finally done with all buffer samples, with a bit more work before I hand data over the buffer to PluginProcessor.
Active Tone Generator Loop (up to 8) - Only if actually on!
- Get current ADSR and LFO multipliers
- If one of the active tone generator’s release stage is done, I reset some variables here including filters, so I don’t have to do it on StartNote.
Active Tone Generator Loop (up to 8) - Only if actually on!
-
Active Filter Module Loop (up to 8) - Only if actually on and needed!
- Filter processing - Sample loop. And as in my LFO and Effect matrix, I use an intricate array variable system, where all the “If” work is already done in the PluginEditor
-
Hand over each active tone generator stereo set to main buffer, and I do it with a ramp due to me processing ADSR only once per buffer loop.
-
If all active tone generators are done (released), as each tone generator can have a unique ADSR, I call ClearCurrentNote()
-
Otherwise - I update various local variables as needed, if any related chances has been done in PluginEditor such as unison voices, pitch (octave, semi, fine), phase start, end, size chances (yes I can adjust which part of the wavetable to use), sources (modulation, waveshaping, morphing, effects).
Then I am back over in the PluginProcessor, where I do further processing.
Active Tone Generator Loop (up to 8) - Only if actually on!
- Main level adjustment
- Tremolo - Get LFO multiplier
- Pan adjustment
- DC Filter - Sample loop
- Effects Module Loop - If on and selected via matrix. Sample loop
- Filter - If on and selected via matrix - Sample Loop. So obviously here done on a combined tone generator’s notes level, compared to the filtering over in SynthVoice that is done on a voice (note) level with potential ADSR and Key Tracker, as if those are not needed it will save some CPU cycles, off courses dependent on how many notes is played at once.
- Add tone generator stereo set to main buffer
Global - Only one stereo buffer here!
- Effect Module Loop - Only those active and needed - Sample loop
Filter - If selected via matrix - Sample loop. Again no ADSR and Key Tracker, and not on an individual tone generator level, but if you need to filter all tone generators the same and don’t need ADSR or Key Tracker, it will save some CPU cycles, off courses dependent on how tone generator’s is active and how many notes is played at once.
Then I check if my visual buffer is ready to be fed some data, and if so transfer it. Once done I signal my visual component to plot against me!
I hope this helps you or somebody else in some way, and if anyone have any comments or suggestions I am all ears.