The Global Modulator System
Christoph Hart last edited by Christoph Hart
Global Modulator System
Modulators are an important part of an instrument's design as they allow a dynamic behaviour no matter whether you build a synthetic sound or a sampled instrument.
There are a variety of modulatable parameters (from pitch modulation to the saturation amount of the Saturator Effect) and a extensive collection of modulators for almost every purpose.
Before you continue reading, please make sure you understand the three different Modulator types: Voice Start Modulators, Time Variant Modulators and Envelope Modulators. There is a chapter about the modulators in the Manual.
Every modulator in HISE has a unique and fixed target and this is the Modulator Chain where you add this modulator to. This is due to the underlying architecture of every HISE instrument: the tree structure. Every module has one parent and is completely encapsulated from other parents / siblings.
I chose this structure deliberately because it is the best compromise between performance / stability and flexibility. A complex graph structure with lots of interdependencies could be a nightmare to debug and a flat list would be too inflexible.
However, there are a few cases where you want to break the encapsulation and have one modulator for multiple targets. Here are a few examples:
- You want to use a global velocity curve (which the user can change) that needs to be applied to every Velocity Modulator in your instrument.
- You want a complex LFO to simulate Vibrato that targets both the pitch modulation, the filter frequency of a low pass filter and the volume.
- You have multiple sampler modules (one for the sustain samples and one for the release samples) and you want to modulate both pitches equally using a random modulator. Adding two Random Modulators in each sampler would result in two different modulation values (because they are ... well, random).
While example 1 and 2 would be very annoying to implement with lots of duplicated modules, example 3 is actually impossible to achieve without some help. This is why there is the Global Modulator System, which offers a solution for each scenario.
The Global Modulator System is an addition that tries to work around the encapsulation. This comes with some caveats, which are explained below.
Although the structure is a tree, it must be flattened before the actual sound rendering (so that every module is processed top down). You can safely assume that a module will be processed before its following siblings (otherwise you would not be able to control the signal flow for eg. effects). The Global Modulator System makes use of this principle by introducing a Sound Generator that acts as container for Modulators that will be processed before everything else, save their value and provide their calculated values to any receiving module.
In order to make use of this system, you'll need:
- a Global Modulator Container
- fill its Modulator Chain with either Voice Start Modulators or Time Variant Modulators.
- add one Global Voice Start Modulator / Global Time Variant Modulator for every target that wants to use the global modulator values.
Let's take a look at the individual parts:
The Global Modulator Container
This module can be added to a container (just like any other sound generator) and provides a slot for modulators that will be used as Global Modulators.
The Global Modulator Container must be the first sound generator. If you decide to add this feature after you have added some other sound generators, you can always right click on a header bar and choose "Add Processor before this Module*:
It has a slot for MIDI processors and a slot for modulators that are calculated and stored for their recipients. The MIDI processor slot is exists for some rare corner cases and should better be left untouched.
There is absolutely no reason to have more than one Global Modulator Container in a preset, but you obviously don't need one if you don't want to use any Global Modulators...
Every modulator (except for Envelopes, see below) that is added to this Container can distribute its signal to multiple targets by connecting them to special target modulators.
The Target Modulators
There are two different target modulators: Global Voice Start Modulator and Global Time Variant Modulator. As you may have guessed, the Global Voice Start Modulator can be connected to all Voice Start Modulators in the Global Modulator Container while the Global Time Variant Modulator is used to get the signal of Time Variant Modulators.
But apart from that difference, they look exactly the same. They have a drop down selector which contains every connectable modulator and the possibility to use a lookup table to change the values (this can be used to eg. invert the signal of an LFO between targets).
When you select a modulator in the list, it connects the target modulator to this modulator (from now on called Source Modulator). The intensity of the modulation is controlled by the target modulator, the intensity of the Source modulator has no effect at all.
If you delete the Source Modulator, the target modulator will be disconnected. You can of course use multiple target modulators per source modulator (otherwise this system would be quite ridiculous).
Let's take a look at our examples. Since the solution to all three problems is the same, checking the first example should be enough:
We'll add a Velocity Modulator to the Global Container, and a Global Voice Start Modulator that is connected to this Velocity Modulator for every parameter that should be controlled by the velocity. Then a Table on the user interface that is connected to the Velocity Modulator can be used to change the global velocity curve.
This system offers a solution to a problem that lies within the core of the HISE architecture. When used correctly, this system proved to be stable and perfectly suited for some special cases (like the ones described above). However you have to be careful because it also introduces nasty bugs when not used correctly. The rest of this chapter deals with some (rather complicated) problems and how to avoid them. If you don't want to dive into the technical explanations, just read the headlines below and don't bother about the rest...
You can't use envelope modulators as global modulators. The reason for this is that a envelope modulator is more powerful than the other modulator types because it can change the release time of a note (otherwise it would be a pretty useless envelope). However this leads to extremely complicated behaviour and corner cases where you mess up the voice indexes that are used to get the envelope value for each voice, so I decided to deactivate this.
Consider this example:
- One Global envelope with a long release time.
- Two Samplers.
- The first sampler has a very short sample.
- The second sampler has a long sample (that for our example rings off indefinitely).
If we play a note, both samplers and the envelope start a voice (with the
Index 1). As soon as the first sample is finished playing, the first sampler will reset its voice. If we now play another note while the long sample rings off, the first sampler would be in its idle state and use its first voice again while the second sampler needs to start a new voice with a different index (in our case,
Index 2) because the voice with the
Index 1is still actively ringing off.
The problem: The Global Envelope does not know that the first sampler restarted the voice. The first sampler does not know that the second sampler is blocking the first voice and grabs the release tail of the other sampler for its newly started voice (because it asks the Global Envelope for the value of the first voice).
On the other hand, local envelopes don't have this problem. Let's change this example to:
- Two samplers
- The first sampler has an envelope and a very short sample.
- The second sampler has an envelope and a long sample.
If we play a note, both samplers and the envelope start a voice (with the
Index 1). As soon as the first sample is finished playing, the first sampler will reset its voice and tells its local envelope to reset its voice #1.
If we now play another note while the long sample rings off, the first sampler would be in its idle state, uses its first voice again and tells its envelope to restart voice #1, while the second sampler needs to start a new voice with a different index (in our case,
Index 2) and tells its envelope to restart voice #2. The voice indexes will always stay in sync.
This is really the most simple example that I could think of to explain the problem (you probably have to read this twice to understand). You could imagine what weird interferences are introduced as soon as the voice indexes are messed up.
Time Variant Modulators are not affected by this problem because they produce one monophonic signal per buffer which can be distributed to the rest of the instrument without problems. Even if you modulate this signal with midi messages that aren't received by the other sound generators, it won't cause any weirdness.
Don't change the Note Number with Voice Start Modulators.
Voice Start Modulators calculate their value once whenever a new voice is started. Global Voice Start Modulators calculate their values whenever a voice is started, save those values and tell their targets the last calculated value when they need to know their voice start value (which is a little bit later in the rendering process). However this little time could become a problem, if there are multiple MIDI messages with the same sample position. In this case, the most recent voice calculation would overwrite the values for the earlier message (because all messages are processed in the Global Modulator Container before they are passed to the next sound generators).
Otherwise, the buffer will be divided into smaller chunks and each chunk will be calculated separately (so that nothing will be overwritten.)
And again, it's Complicated Example Time:
- A Global Array Modulator with the value 1.0 for the note number 72 (
C4) and the value 0.2 for the note number 76 (
- A sine wave generator with a Global Voice Start Modulator in its Gain Modulation Chain.
If a buffer contains the MIDI messages 72 and 76 with the same sample position, both voices will get the same value (so either 1.0 or 0.2 depending on which note is first in the list).
Having MIDI notes with the same sample position is not a totally unrealistic scenario. You could naively assume that when pressing two keys simultaneously, the chance is 1 / 44100 (or even higher with other sample rates) that they actually get the same sample position, which would be a probability of
However, hosts (or MIDI input devices) are lazy and try to coalescate (round them to multiples of 4 or 8 for SSE acceleration) or even ignore the sample position and simply put them at the start of the buffer.
This is also how the virtual keyboard in HISE works (since keyboard input runs on the message thread so it would be pretty useless to pretend to know a sample accurate timing). Every note you press on the virtual keyboard will be added at sample position 0. So if you press
E(which are the keys for
E4), as soon as you press them within an interval of 5ms (which is a common buffer size of 512 samples), they will be put into the same buffer and the same position.
To avoid this problem, I added a safety measure by saving the values into an array (with length 128) according to their note number. So in our case, 0.2 will be saved into the position 76 and 1.0 will be saved into position 72. They can't be overwritten anymore because they are now stored in different places. Also, you can assume that if the same note number with the same sample position occurs, there must be a problem with MIDI message duplication somewhere in your MIDI input chain and they really don't need to be treated as two different messages.
Now comes the important part:
The Global Voice Start Modulators that are connected to this modulator will now look at index 72 for the note
C4and at the index 76 for the note
This means you must not change the note number between the Global Modulator and the sound generator (by eg. adding a transposer to the sine wave generator). You can change the channel or the velocity, but the note number must stay the same. You are of course free to add a transposer to the container (to transpose the note before it is processed by the Global Modulator). But this seems like a reasonable trade-off to make the Global Modulator System work with Voice Start Modulators.
You can check this yourself: Copy this patch into the clipboard and choose File -> Replace with clipboard content.
With the recent additions to the event system it is possible to transpose notes and use voice start modulators (because the event stores the note number and the transpose amount separately). Just use
Message.setTransposeAmount(value)and it should not change the note number relation.