WavetableController
This API class will provide methods for customizing the resynthesis feature of the Wavetable Synthesiser
.
Create this object with Synth.getWavetableController() , then call one of the methods to setup the resynthesis.
Class methods
getResynthesisOptions
Returns a JSON object with the current resynthesis options.
WavetableController.getResynthesisOptions()
Returns a JSON object with the current resynthesis options. Usually you call this method, then make your changes and call setResynthesisOptions()
with the modified object.
loadData
Loads a file (or buffer) into the wavetable synth.
WavetableController.loadData(var bufferOrFile, var sampleRate, var loopRange)
This function allows you to programmatically create waveforms and send it to the wavetable synth.
The function expects three parameters:
- the buffer with the wavetable signal
- the sample rate (just use the current samplerate for the best sound quality).
- the loop range that defines the length of a single cycle (this way you can create wavetables with more than one cycle)
Note that a wavetable synthesiser is also a AudioSampleProcessor and this function is basically a duplicate / combination of the Audiofile.loadBuffer() / Audiofile.loadFile() method
// Create a buffer with two cycles a 2048 samples
const var bf = Buffer.create(2048 * 2);
// Add a sine waveform in the first cycle
for(i = 0; i < 2048; i++)
bf[i] = Math.sin(i / 2048.0 * 2.0 * Math.PI);
// Add a saw waveform in the second cycle
for(i = 2048; i < 4096; i++)
bf[i] = 2.0 * Math.fmod(i / 2048.0 - 0.5, 1.0) - 1.0;
// Create a reference to the wavetable controller
const var wt = Synth.getWavetableController("Wavetable Synthesiser1");
// Pass the cycles into the wavetable synthesiser.;
// By supplying the `[0, 2048]` loop range the wavetable synthesiser will
// automatically create two cycles so you can morph between the sine and the saw wave
wt.loadData(bf, Engine.getSampleRate(), [0, 2048]);
Thanks to the mip-mapping process of the wavetable synthesiser, you do not need to care about band-limiting or aliasing at all as the wavetable synthesiser will automatically band limit the waveforms for each octave by removing the FFT bins that lie beyond the Nyquist frequency.
You can also use a FFT object to create a harmonic series, then use the inverse FFT to create the wavetable cycle data. This is much more faster than creating the cycles in HiseScript directly and can be used to create any complex harmonic series (eg. by assigning a slider pack to the phase / harmonic buffers)
// Create a buffer with two cycles a 2048 samples
const var bf = Buffer.create(2048 * 2);
// Create an FFT object
const var fft = Engine.createFFT();
// This enables the inverse FFT step
fft.setEnableInverseFFT(true);
// We don't need any windowing here as we
// directly synthesise the cycles on the frequency domain
fft.setWindowType(fft.Rectangle);
reg cycleIndex = 0;
// This function will be called on the frequency domain
// and contains a buffer with the frequency bins
fft.setMagnitudeFunction(function(data)
{
if(cycleIndex++ == 0)
{
// Set the second FFT bin to 0dB
// this will translate to a sine wave
// with the root frequency
data[1] = 1.0;
}
else
{
// create a few harmonics in the second cycle
data[1] = 0.5;
data[3] = 0.2;
data[5] = 0.1;
}
}, false);
// Here we pass in the cycle length, this means that
// the FFT will be processed twice for the full buffer
fft.prepare(2048, 1);
// Process the (empty) buffer and get the resynthesized
// data back
const var processed = fft.process(bf);
// Create a reference to the wavetable controller
const var wt = Synth.getWavetableController("Wavetable Synthesiser1");
// Pass the resynthesised cycles into the wavetable synthesiser.;
wt.loadData(processed, Engine.getSampleRate(), [0, 2048]);
resynthesise
Resynthesises the wavetables from the currently loaded audio file.
WavetableController.resynthesise()
This will resynthesise the wavetable based on the current options.
saveAsAudioFile
Saves the currently loaded wavetable as audio file. Edit on GitHub
WavetableController.saveAsAudioFile( var outputFile)
saveAsHwt
Saves the currently loaded wavetable as HWT file somewhere.
WavetableController.saveAsHwt( var outputFile)
You can use this function to bake the completely processed wavetable into a .hwt file that you then can load as standard wavetable.
Note that this will also include the post processing steps (and loading .hwt files will bypass the post processing to avoid duplication).
setEnableResynthesisCache
This will store all resynthesised wavetables to the given directory and reused if the same file is loaded again.
WavetableController.setEnableResynthesisCache( var cacheDirectory, bool clearCache)
The resynthesis step might take a few seconds so in order to increase the loading times of user generated patches (or during development) you can define a cache directory by passing in a file object (either a File
, a String containing an absolute path or one of the constants of the FileSystem
object (eg. FileSystem.AudioFiles
)).
If this method is called with a directory, any time the wavetable synthesiser has resynthesised an audio file, it will create a cached version from this file and the (currently used Resynthesis options) in the provided directory. If the file is then loaded again with the same settings, it will skip the resynthesis process to speed up the loading time.
Note that the cache will not contain the post processing functions as they will be executed after loading the wavetable from the cache.
The second argument of this function can be used to clear out the directory (which might be helpful during development).
setErrorHandler
Sets up a function that will be executed when a error occurs during resynthesis. Edit on GitHub
WavetableController.setErrorHandler( var errorCallback)
setPostFXProcessors
Sets up a chain of post FX processors that will be applied to the loaded wavetable.
WavetableController.setPostFXProcessors( var postFXData)
This function can be used to add post-processing steps to the wavetable synthesiser. These are simple math functions with a single parameter that are applied on every wavetable after they have been loaded and can be used to customize the shape of the waveform:
wavetable_out = f(wavetable_in, parameter)
The functions are baked into the wavetables and are properly band limited using the same mip-map technique as when loading a normal wavetable, so these functions are not subject to realtime-manipulation, but will yield a alias-free sound.
Post processing function definition
You can use multiple post processing steps at once and they will be serially processed in order of definition. This function expects an array of JSON objects that describe every function. These are the supported properties:
Property | Type | Description |
Type
|
String | The type of FX from a predefined list (see below). |
min
|
double | the parameter value for the lowest table index. |
max
|
double | the parameter value for the highest table index. |
middlePosition
|
double | the parameter value for the middle table index. |
TableProcessor
|
String | The name of the HISE module that provides the Table (see below) |
TableIndex
|
int | the index of the table that should be used for the parameter lookup table. |
As you can see, with the exception of the Type
parameter that defines the function, all other properties are related to how the single parameter
will change for different table indexes, which allows you to create a dynamic function curve that you then can modulate through the TableIndex
parameter of the wavetable synth: for each cycle in the wavetable it will:
- normalize the cycle position (so that if the wavetable has 100 cycles, the 50th cycle will have the position
0.5
) - Apply the Table (if defined) so that you can fully customize the curve if desired.
- Scale it to the range provided by the
min
,max
andmiddlePosition
attributes - Apply the function to the entire cycle of the wavetable with the calculated
parameter
value
Here are a few examples that demonstrate the different use cases:
// A wavefold FX with a constant parameter of 0.8
{
Type: "Fold",
min: 0.8,
max: 0.8
}
// A hard sync effect with a parameter of 0 at the first table
// and a parameter value of 0.5 at the last table
{
Type: "Sync",
min: 0.0,
max: 0.5
}
// A FM effect using a sine wave with the same frequency as carrier
// and the first table of the main UI defining the curve from 0.0 to 16.0
{
Type: "FM1",
min: 0.0,
max: 16.0,
TableProcessor: "Interface",
TableIndex: 0
}
Available post processing functions
These are the available post processing functions. They are all shown with a basic sine wave as starting point.
"Sin"
This is a sinusoidal waveshaper that multiplies the amplitude with a sine wave. It can be used to quickly add harmonics without introducing too much distortion. The parameter
defines the amplitude of the sine wave (a value of 0.0
will not change the waveform)

"Warp"
This function skews the waveform to the start or end of the cycle and introduces very harsh harmonics. The parameter
defines how much the waveform is skewed towards either end (0.0
= left, 1.0
= right, 0.5
no change)

"Fold"
This function folds the amplitude of the waveform at the parameter
value (so that every value that lies above the parameter
value is folded back).

"Clip"
This function hard clips the waveform to the given amount. Note that this does not scale up the waveform, so if you want to clip it at 1.0, use the "Normalise"
step afterwards.

"Tanh"
This function applies a soft-clipping waveshaper (the standard tanh function) to the waveform.

"Bitcrush"
This function applies a amplitude quantisation (aka bitcrusher) FX on the waveform

"SampleAndHold"
This function applies a amplitude quantisation (aka samplerate downsampler) FX on the waveform

"Sync";
This function will apply a hard-sync effect to the waveform. The parameter will define how much of the original period length should be used (0.0 = no effect, 1.0 = almost zero length)

"Phase"
This function shifts the phase of the waveform. This will not change the harmonics of the cycle, but it will introduce phaseshifts when you start modulating the table index which will translate into subtle pitch changes.

"FM1" / "FM2" / "FM3" / "FM4"
These functions will apply a frequency modulation with a sinewave as carrier oscillator. The amount will define the amplitude of the carrier oscillator. The frequency of the carrier will be a multiple of the base frequency:
"FM1"
will use the root frequency of the waveform:

"FM2"
will use the first harmonic frequency of the waveform:

"FM3"
will use the second harmonic frequency of the waveform:

"FM4"
will use the third harmonic frequency of the waveform:

"Root"
This function simply adds a sine wave with the base frequency and zero phase to the waveform so that you can change the ratio between the root frequency and the harmonics. The parameter
is the amplitude of the root frequency that's added to the waveform (and by supplying a negative value you can subtract the root frequency from the waveform granted that the phase is zero).
The example now uses a saw wave as adding a sine wave to a sine wave would not yield an interesting graph:

"Normalise"
This function normalises the waveform to flatten out gain changes between the tables. Note that the final wavetable set is again normalised but this can be used to change the dynamics between the cycles. The parameter
value is a percentage of how strong the normalisation should be applied (0.0
= no change, 1.0
= full normalisation).
For the example, we'll use a sine wave with a "Clip"
post processor and a clip value of 0.8
so that you can see the difference:

setResynthesisOptions
Sets the current resynthesis options.
WavetableController.setResynthesisOptions( var optionData)
This will set the resynthesis options of the wavetable synthesiser based on the provided JSON object.
Property | Type | Description |
PhaseMode | String | One of three modes that define how to process the phase information (see below). |
MipMapSize | int | the amount of semitones that is used for the mip map (default is 12=1 octave). The wavetable will be internally recalculated and band limited based on this setting. If you are mainly working with organic material, you could increase this a bit to save memory. |
CycleMultiplier | int | the amount of cycles that is used to calculate a single wavetable. Increasing this value will "smooth" the spectrum, but you'll loose a bit of high frequency material. If you are using Loris this setting will not have any effect. |
UseTransientMode | bool | If enabled, this will turn off the cycle multiplier for the first 4 cycles to allow a non-smoothed resynthesis of the transient of the sample. This preserves the high frequency content of the transient and might be useful for some sounds. |
NumCycles | int | the number of cycles to create. If this is -1
, then it will create as much cycles as the provided audio material contains, but you can set this to a fixed size. |
ForceResynthesis | bool | This is more of a debugging property and it forces the resynthesis algorithm to always process the incoming audio material - if you load in wave files that already have a power of two cycle length, then it will skip the entire process and directly create the wavetables. With this property you can deactivate this to enforce a resynthesis every time. |
UseLoris | bool | If enabled (and HISE / your plugin is compiled with HISE_INCLUDE_LORIS
), then you can use the Loris library for resynthesis which offers a much better sound quality for organic material. Note that the Loris library is GPL licensed, so you cannot include this in a proprietary plugin without the explicit consent of the authors of this library! |
ReverseOrder | bool | If enabled, it will reverse the order of the cycles of each wavetable which allows you to apply some modulation tricks that are not possible with the default order. |
PhaseMode
The PhaseMode
property defines how the resynthesis should cope with the phase information and has three options:
ZeroPhase
will ignore any phase information and treat every harmonic as sine wave starting at the zero position.StaticPhase
will calculate the phase information of the very first cycle and then apply this to every cycle in the wavetable. This preserves the stereo field of the wavetable as well as the appearance of the waveform but removes all phase changes which can cause some pitch wobbling if the table index is automatedDynamicPhase
preserves the phase information of every cycle which is the best option for very organic material. It might sound a bit weird with some samples, so only use it ifStaticPhase
doesn't suit your material.
Note that calling will not cause a resynthesis of the currently loaded wavetable. If you want to do this, follow up this call with a call to resynthesise() .