Crates.io | surgeosc-window |
lib.rs | surgeosc-window |
version | 0.2.12-alpha.0 |
source | src |
created_at | 2021-10-26 00:10:09.50656 |
updated_at | 2023-04-07 22:22:23.598358 |
description | surge synthesizer -- window oscillator |
homepage | |
repository | https://github.com/klebz/surge-rs |
max_upload_size | |
id | 471461 |
size | 64,979 |
A window oscillator component for the Surge synthesizer system.
surgeosc-window
is a Rust crate that provides
a window oscillator component as a subcomponent of
the Surge synthesizer system. The window
oscillator is responsible for generating audio
signals based on a given waveform and various
control parameters. It supports multiple control
types, modulation, and different value types for
a flexible and powerful audio synthesis
experience.
The crate contains tokens that represent various aspects of the oscillator's behavior, such as control groups, value ranges, and processing functions. Some of the tokens have mathematical ideas associated with them, which are explained below.
control_group
: A group of related control
parameters.
control_type
: The type of control applied to
the oscillator (e.g., frequency, amplitude).
default_value
: The default value for a control
parameter.
max_value
: The maximum value for a control
parameter.
min_value
: The minimum value for a control
parameter.
modulateable
: Whether a control parameter can
be modulated by external signals.
moverate
: The rate at which a control
parameter changes (e.g., the frequency of an
LFO).
snap
: Whether a control parameter should snap
to specific values (e.g., quantization).
value_type
: The type of value used for
a control parameter (e.g., integer, float).
get_size_mask
: Calculates the size mask for
the oscillator, which is a bitwise mask used to
determine the size of the waveform buffer.
size_mask = 2^N - 1
where N
is the number of bits needed to
represent the buffer size.
get_size_mask_win
: Similar to get_size_mask
,
but for the window buffer.
get_table
: Retrieves the waveform table for
a specific control type.
get_wave
: Retrieves the waveform data for the
oscillator.
get_win
: Retrieves the window data for the
oscillator.
get_window
: Retrieves the window function used
to shape the oscillator's output.
create_wetblock
: Creates a new wetblock, which
is a buffer for storing the output of the
oscillator before it's sent to the next
processing stage.
process_sub_osc
: Processes a single
sub-oscillator within the window oscillator.
block_update_mono_out
: Updates the
oscillator's mono output buffer with new audio
data.
block_update_stereo_out
: Updates the
oscillator's stereo output buffer with new audio
data.
process_sub_oscs
: Processes all the
sub-oscillators within the window oscillator.
clear
: Clears the oscillator's audio buffers.
WindowOscillator
: The main struct representing
the window oscillator.
set_pitch
: Sets the pitch of the
oscillator. The frequency f
is related to the
pitch p
by the following equation:
f = 440 * 2^((p - 69) / 12)
where p
is in MIDI note numbers.
init
: Initializes the window oscillator.
get_formant_mul
: Retrieves the formant
multiplier for the oscillator, which is used to
scale the frequencies of the oscillator's
harmonics.
process_block
: Processes a block of audio
samples for the window oscillator.
SubOscProcessBlockCfg
: Configuration struct
for processing a sub-oscillator block.
SubOscProcessCfg
: Configuration struct for
processing a sub-oscillator.
process_oscillator_block
: Processes a block of
audio samples for a single oscillator.
2> thanks -- what is a window oscillator?
A window oscillator is a type of audio oscillator used in digital synthesis that combines multiple waveforms or "windows" to generate complex and evolving sounds. The basic concept is to take segments (windows) of different waveforms and blend them together in a controllable manner. This allows the synthesis of a wide variety of sounds by combining and modulating these windows in real-time.
Window oscillators can be used in various synthesis techniques, such as wavetable synthesis, granular synthesis, or additive synthesis. In wavetable synthesis, for example, a window oscillator may be used to smoothly transition between different waveforms stored in a table. By modulating the window position or the window size, new and interesting timbres can be created.
In the context of the surgeosc-window
crate, the
window oscillator is a component of the Surge
synthesizer system that is responsible for
generating audio signals based on the chosen
waveform and various control parameters. It
supports multiple control types, modulation, and
different value types for a flexible and powerful
audio synthesis experience.
3> could you explain the window oscillator struct?
The WindowOscillator
struct represents the state
and configuration of a window oscillator in the
surgeosc-window
crate. It contains various
fields that store the oscillator's properties and
configuration, as well as buffers for the
generated audio signals.
Here's a breakdown of the fields in the
WindowOscillator
struct:
drift
: A field representing the frequency
drift of the oscillator, which can be used to
simulate the natural pitch instability found in
analog oscillators.
master_osc
: A pointer to the master
oscillator's buffer, which is used for frequency
modulation (FM) purposes.
out
: An OscillatorOut
struct that contains
the output buffers for the oscillator's
generated audio signals.
pos
, sub_pos
, ratio
, table
,
formant_mul
: Arrays representing various
oscillator parameters, such as position,
sub-oscillator position, frequency ratio,
waveform table index, and formant multiplier.
dispatch_delay
: An array representing the
sample delay for each sub-oscillator before
playback should start, allowing for per-sample
scheduling.
gain
: A 2D array representing the gain for
each sub-oscillator.
drift_lfo
: A 2D array representing the
low-frequency oscillation (LFO) values for
frequency drift.
fm_ratio
: A 2D array representing the
frequency modulation ratios for each
sub-oscillator.
params
: A WindowOscillatorParamArrayRT
struct that contains the real-time parameters
for the window oscillator.
osc_params
: An OscillatorParamArrayRT
struct
that contains the real-time parameters for the
oscillator.
fm_depth
: An array representing the frequency
modulation depth for each sub-oscillator.
out_attenuation
: A field representing the
output attenuation of the oscillator.
detune_bias
, detune_offset
: Fields
representing detuning bias and offset, which can
be used to control the detuning of
sub-oscillators relative to the main oscillator.
active_sub_oscs
: An integer representing the
number of active sub-oscillators.
window_wavetable
, wave_wavetable
:
WaveTableBase<i16>
structs representing the
wavetables for the window and the waveform,
respectively.
tables
: A TablesHandle
struct that provides
access to shared wavetable data.
tuner
: A TunerHandle
struct that provides
access to the tuner, which is used to calculate
the oscillator's frequency based on its pitch.
srunit
: A SampleRateHandle
struct that
provides access to the sample rate, which is
used to convert frequency values to sample-based
values.
The WindowOscillator
struct also implements the
SetPitch
trait, which allows setting the
oscillator's pitch. The set_pitch
function
updates the active number of sub-oscillators to
1 when the is_display
flag is set.
4> what about how we process the sub oscillators?
The WindowOscillator
implementation contains the
process_sub_oscs
function, which is responsible
for processing all the active sub-oscillators
within the window oscillator. The function takes
two boolean parameters: stereo
and fm
, which
indicate whether the oscillator should produce
stereo output and whether frequency modulation
(FM) should be applied.
Here's a step-by-step breakdown of what the
process_sub_oscs
function does:
Retrieve the morph parameter value using
self.pvalf(WindowOscillatorParam::Morph)
. The
morph parameter is used to control the blend
between different waveforms or windows.
Call self.get_table(morph)
to get the table
index for the given morph value.
Retrieve the formant parameter value using
self.pvalf(WindowOscillatorParam::Formant)
,
which is used to control the formant shaping of
the oscillator's output. Then, call
self.get_formant_mul(formant as f64)
to
calculate the formant multiplier.
Retrieve the size masks for the oscillator and
window buffers using self.get_size_mask()
and
self.get_size_mask_win()
, respectively. These
masks are used to wrap the buffer indices
during processing.
Call self.get_window()
to get the current
window function index.
Retrieve the number of active sub-oscillators
using self.active_sub_oscs as usize
.
Create a SubOscProcessCfg
struct with the
calculated values for table, formant
multiplier, size masks, window index, and the
input flags stereo
and fm
. This struct is
used to configure the processing of each
sub-oscillator.
Iterate over the active sub-oscillators in
a loop, calling self.process_sub_osc(so, &cfg)
for each one. This function processes
each sub-oscillator individually, generating
audio signals based on their configuration and
the shared configuration passed in the cfg
struct.
In summary, the process_sub_oscs
function sets
up the configuration for processing the
sub-oscillators, iterates over the active
sub-oscillators, and processes them individually
to generate the audio output. The function is
designed to handle both mono and stereo output, as
well as applying frequency modulation if desired.
5> what about each individual sub oscillator?
The process_sub_osc
function in the
WindowOscillator
implementation processes
a single sub-oscillator with a given index so
and shared configuration subosc_cfg
. The main
purpose of this function is to generate the audio
output for a single sub-oscillator based on its
specific configuration and the shared
configuration.
Here's a step-by-step breakdown of what the
process_sub_osc
function does:
Extract the table
, formant_mul
, window
,
and fm
fields from the subosc_cfg
struct.
Retrieve the current position of the sub-oscillator in the waveform buffer.
Calculate the frequency ratio ratio_a
for the
sub-oscillator. If frequency modulation (FM) is
enabled, ratio_a
is set to the value from the
fm_ratio
array.
Calculate the indices mipmap_a
and mipmap_b
for selecting the appropriate mipmap levels in
the window and waveform wavetables,
respectively. These indices are used to adapt
the wavetable resolution based on the frequency
ratio and formant multiplier.
Get the pointers wave_adr
and win_adr
to
the data in the waveform and window wavetables
at the calculated mipmap levels and table
indices.
Create a SubOscProcessBlockCfg
struct with
the calculated values for ratio_a
,
mipmap_a
, mipmap_b
, wave_mipmap_idx
,
wave_adr
, and win_adr
. This struct is used
to configure the processing of the
sub-oscillator in the subsequent loop.
Iterate over the audio block samples in
a loop. For each sample, call the
self.process_oscillator_block
function, which
processes a single block of the sub-oscillator
using the current position, shared
configuration, and sub-oscillator-specific
configuration.
Update the position of the sub-oscillator in the waveform buffer.
In summary, the process_sub_osc
function
calculates the necessary parameters for processing
a single sub-oscillator, gets the pointers to the
data in the wavetables, and iterates over the
audio block samples to generate the output. This
function is called by the process_sub_oscs
function for each active sub-oscillator in the
window oscillator.
6> what about how we manage scratch space?
The create_wetblock
function in the
WindowOscillator
implementation is responsible
for converting a 128-bit integer input (__m128i
)
into a WetBlock1
structure with 4 floating-point
values. The main purpose of this function is to
preprocess the input data before it's used in the
oscillator processing.
Here's a step-by-step breakdown of what the
create_wetblock
function does:
Create a mutable WetBlock1
structure with
4 elements, initialized to their default
values.
Check if the code is compiled for macOS using
the cfg![macos]
macro. The implementation
differs based on the target platform:
On macOS: Use _mm_cvtsi128_si32
and
_mm_shuffle_epi32
SSE2 intrinsics to
extract the 32-bit integer values from the
input __m128i
and convert them to
f32
. Store the converted values in the
wetblock.buf
array.
On other platforms: Use _mm_store_si128
SSE2 intrinsic to store the input __m128i
directly into the wetblock.buf
array as
128-bit integer data.
Calculate the sum of the 4 floating-point
values in the wetblock.buf
array and divide
the sum by 2^13. Store the result back in the
first element of the wetblock.buf
array.
Return the wetblock
structure containing the
4 processed floating-point values.
In summary, the create_wetblock
function takes
a 128-bit integer input and converts it into
a WetBlock1
structure with 4 floating-point
values. The function uses platform-specific SSE2
intrinsics for efficient data conversion and
performs some preprocessing on the data before it
is used in the oscillator processing.
7> which custom data structures do we use?
These are custom data structures used in the
WindowOscillator
implementation to represent
specific configurations for sub-oscillator
processing.
WINDOW_OSCILLATOR_NUM_SUBOSCS
: This constant
specifies the number of sub-oscillators in
a window oscillator. In this case, the value is
set to 2, meaning there are two sub-oscillators
for each window oscillator.
SubOscProcessCfg
: This structure holds the
shared configuration for processing all
sub-oscillators in the window oscillator. It
includes the following fields:
table
: The index of the wavetable to be
used.
formant_mul
: The formant multiplier, which
affects the frequency content of the output
waveform.
size_mask
: A bitmask used to determine the
size of the wavetable for the oscillator.
size_mask_win
: A bitmask used to determine
the size of the window wavetable.
window
: The index of the window function to
be used.
stereo
: A boolean flag indicating whether
the output should be in stereo or mono.
fm
: A boolean flag indicating whether
frequency modulation (FM) is enabled.
SubOscProcessBlockCfg
: This structure holds
the specific configuration for processing
a single sub-oscillator. It is used in the
process_oscillator_block
function and
includes the following fields:
ratio_a
: The frequency ratio of the
sub-oscillator, which determines its pitch.
mipmap_a
: The mipmap index for selecting
the appropriate resolution level in the
window wavetable.
mipmap_b
: The mipmap index for selecting
the appropriate resolution level in the
waveform wavetable.
wave_mipmap_idx
: The index of the mipmap
level in the waveform wavetable.
wave_adr
: A pointer to the data in the
waveform wavetable at the specified mipmap
level and table index.
win_adr
: A pointer to the data in the
window wavetable at the specified mipmap
level and table index.
These structures help organize the processing of
sub-oscillators within the
WindowOscillator
. They store configuration data
that is either shared among all sub-oscillators or
specific to each sub-oscillator, allowing for
efficient and modular processing.
8> how do we process each oscillator block?
The process_oscillator_block
function is a part
of the WindowOscillator
implementation. This
function processes a single block of samples for
a specific sub-oscillator. It takes the following
parameters:
so
: The index of the sub-oscillator being
processed.
block_idx
: The index of the current sample
block.
pos
: A mutable reference to the current
position within the wavetable.
subosc_cfg
: A reference to the shared
SubOscProcessCfg
configuration for all
sub-oscillators in the window oscillator.
subosc_block_cfg
: A reference to the specific
SubOscProcessBlockCfg
configuration for the
current sub-oscillator.
The function first updates the current position in the wavetable based on whether frequency modulation (FM) is enabled or not. Then, it checks if the current position is out of the valid window wavetable range and, if necessary, updates the formant multiplier and table index for the current sub-oscillator and adjusts the position accordingly.
Next, the function calculates the positions within the window and waveform wavetables, taking into account the mipmap levels for better performance and memory usage.
Using the calculated positions, the function
fetches the window and waveform data by calling
get_wave
and get_win
functions, which return
SIMD vector types __m128i
. Then, it converts the
SIMD vectors into intermediate "wet block"
representations with the create_wetblock
function.
Finally, depending on whether the output is stereo
or mono, the function calls either
block_update_stereo_out
or
block_update_mono_out
to update the output
buffer with the calculated data from the window
and waveform "wet blocks".
Overall, this function is responsible for
calculating the correct sample values for
a sub-oscillator at a specific block index,
considering the configuration data provided in
subosc_cfg
and subosc_block_cfg
.
9> what about the other processing?
The process_block
function is an implementation
of the OscillatorProcess
trait for the
WindowOscillator
struct. This function processes
a single block of audio samples for the entire
window oscillator, considering its configuration
provided by the OscillatorProcessBlockCfg
structure.
The function initializes some variables such as
detune
, fmstrength
, and then iterates through
all active sub-oscillators
(self.active_sub_oscs
). For each sub-oscillator,
it calculates a frequency ratio based on the
provided pitch, drift, and detune. If frequency
modulation (FM) is enabled, the function also
adjusts the frequency ratio based on the input FM
signal, updating the fm_ratio
for the current
sub-oscillator.
After processing all sub-oscillators, the function
calls process_sub_oscs
to process the audio
output for each sub-oscillator and sum them up
into the final output.
Finally, the function scales and converts the final output from integer values to floating-point values. If stereo processing is enabled, the function scales and converts both left and right channels; otherwise, it only scales and converts the left channel.
In summary, the process_block
function is
responsible for processing a block of audio
samples for the entire window oscillator, taking
into account the provided configuration (pitch,
drift, stereo, and frequency modulation) and
updating the oscillator's internal state.
10> which parameters do we use?
The WindowOscillatorParam
is an enumerated type
representing the parameters of the
WindowOscillator
. The enum contains the
following parameters:
Morph: Controls the morphing between wavetables.
Formant: Controls the formant shifting of the oscillator.
Window: Determines the windowing function applied to the wavetable.
UniSpread: Controls the spread (detuning) of the unison voices.
UniCount: Determines the number of unison voices.
The WindowOscillatorParam
enum implements the
Param
trait, which provides methods to configure
the parameters. These methods include:
control_group: Returns the control group to which the parameter belongs.
snap: Indicates whether the parameter value should be snapped to the nearest integer.
control_type: Returns the control type associated with the parameter.
default_value: Returns the default value of the parameter.
min_value: Returns the minimum allowed value for the parameter.
max_value: Returns the maximum allowed value for the parameter.
value_type: Returns the value type of the parameter (integer or float).
moverate: Returns the rate at which the parameter value changes when moved.
modulateable: Indicates whether the parameter can be modulated or not.
Additionally, the WindowOscillatorParam
enum
provides a new_runtime
method to create
a runtime instance of the parameters, which is an
array of WindowOscillatorParamRT
elements. This
method initializes a new runtime array with the
respective runtime parameter objects.
11> how do we initialize?
The WindowOscillator
struct implements the
Init
trait, which provides an init
method. This method initializes the
WindowOscillator
with the necessary default
values and settings:
Clears the oscillator state by calling the
clear
method.
Sets the active_sub_oscs
value based on the
UniCount
parameter, limited to the range
between 1 and `WINDOW_OSCILLATOR_NUM_SUBOSCS
Calculates the output attenuation based on the square root of the number of active sub oscillators.
Sets the detune_bias
, detune_offset
, and
initial position and gain for each sub
oscillator, depending on the number of active
sub oscillators.
If there's only one active sub oscillator, it sets the gain to unity and the position to the start of the window wavetable.
If there are multiple active sub oscillators, it calculates the detune bias and offset, and then initializes the gain and position for each sub oscillator based on their index and a few other factors like panning and retriggering.
The initialization process takes into account various settings and configurations of the oscillator parameters to ensure that the oscillator is set up correctly and ready for use.
12> what about formant processing?
The WindowOscillator
struct has a method
get_formant_mul
which takes a formant
parameter of type f64
as input and returns an
i32
value. The purpose of this method is to
calculate the formant multiplier based on the
input formant value and the characteristics of/
the window and wave wavetables. Here's how it
works:
Calculate the formant_mul
value by converting
the input formant
value using the
n2p_tuningctr
method of the tuner
object. Then, multiply the result by 65536 and
cast it to an i32
.
Calculate the difference in the number of
samples per table between the window wavetable
and the wave wavetable as a power of 2. This is
done by subtracting the
num_samples_per_table_po2()
values of the two
wavetables and storing the result as an i32
.
Depending on whether the window_vs_wave_po2
value is negative or not, the method shifts the
formant_mul
value either to the left (if
negative) or to the right (if positive) by the
absolute value of window_vs_wave_po2
. The
maxi
function is used to ensure that the
resulting value is at least 1.
The get_formant_mul
method calculates a formant
multiplier value based on the input formant and
the characteristics of the window and wave
wavetables. This value is used in the oscillator's
processing to adjust the formant frequencies.
13> how do we reset this module?
The clear
method in the WindowOscillator
struct is used to reset its internal state. It
initializes all the arrays to their default
values, effectively clearing any previous
data. Here's what happens in the method:
pos
array is filled with zeros.sub_pos
array is filled with zeros.ratio
array is filled with zeros.table
array is filled with zeros.formant_mul
array is filled with zeros.dispatch_delay
array is filled with zeros.gain
array is filled with zeros.drift_lfo
array is filled with 0.0.fm_ratio
array is filled with zeros.The clear
method is generally called during the
initialization process or when resetting the
oscillator's state is necessary.
14> how do we update each block?
The block_update_mono_out
and
block_update_stereo_out
methods in the
WindowOscillator
struct are used to update the
oscillator's output in mono and stereo
configurations respectively, for a given block
index. They perform calculations based on the
input WetBlock1
structs i_win
and
i_wave
. Here's a brief explanation of what each
method does:
block_update_mono_out
:
Takes block_idx
, i_win
, and i_wave
as
input/ parameters.
Calculates the product of the first buffer
elements of i_win
and i_wave
.
Shifts the resulting product right by 6 bits
and casts it to f32
.
Adds the result to the left output channel of
the oscillator at the given block_idx
.
block_update_stereo_out
:
Takes so
, block_idx
, i_win
, and
i_wave
as input parameters.
Calculates the product of the first buffer
elements of i_win
and i_wave
.
Shifts the resulting product right by 7 bits
and casts it to i32
.
For both left and right output channels:
Multiplies the result by the corresponding gain value.
Shifts the product right by 6 bits and
casts it to f32
.
Adds the result to the output channel of
the oscillator at the given block_idx
.
These methods are used to update the oscillator's output buffer based on the input waveforms and their corresponding gains. They are typically called during the processing loop when generating audio output.
15> how do we access the various fields?
The WindowOscillator
implementation provided
here includes several methods to access various
components and perform specific tasks related to
the oscillator. Here's a brief explanation of what
each method does:
get_table
:
Takes morph
as an input parameter.
Calculates the table index based on the
number of tables and the morph
value.
Returns the limited table index within the valid range.
get_size_mask
:
get_size_mask_win
:
get_window
:
Gets the value of the
WindowOscillatorParam::Window
parameter and
limits it to the range [0, 8].
Returns the limited value as an i8
.
get_wave
:
Takes ms_pos
, m_pos
, and wave_adr
as
input parameters.
Loads the appropriate values from memory
using SIMD intrinsics (__m128i
).
Performs a multiply and add operation on the
loaded values and returns the result as
__m128i
.
get_win
:
Takes win_s_pos
, win_pos
, and win_adr
as input parameters.
Loads the appropriate values from memory
using SIMD intrinsics (__m128i
).
Performs a multiply and add operation on the
loaded values and returns the result as
__m128i
.
These methods are used for accessing and processing various components of the oscillator, such as wavetables, windows, and other parameters. They help in the overall functioning of the oscillator and are used in the synthesis process.
16> thanks -- what else can you teach me about this system? could you summarize the most important functionality in mathematical depth? how can it be improved?
This system appears to be a part of an audio
synthesis system, specifically implementing an
oscillator called WindowOscillator
. It seems to
be using wavetables for synthesis, which allows
efficient, high-quality sound generation. The
primary components of this oscillator are the
wave_wavetable
and window_wavetable
. Here's
a summary of the main functionality in
mathematical depth:
Initialization (init
): Initializes the
oscillator's parameters, including the number
of active sub-oscillators, output attenuation,
gain, and positions for each
sub-oscillator. Detune bias and detune offset
are also calculated during this step.
Formant Multiplier (get_formant_mul
):
Calculates the formant multiplier based on the
given formant frequency. This involves tuning
and adjusting the multiplier based on the
wavetable sizes.
Clearing (clear
): Resets the internal state
of the oscillator, clearing all arrays and
values associated with the current oscillator
state.
Block Updates: Updates the output of the
oscillator either in mono
(block_update_mono_out
) or stereo
(block_update_stereo_out
) mode. This involves
calculating the output value by multiplying
window and wave values and then adding them to
the output buffer with appropriate gain
adjustments.
Accessor Methods: Several accessor methods are provided for getting specific oscillator components, such as wavetables, windows, and size masks. Some of these methods also involve SIMD operations for efficient processing.
To improve this system, consider the following:
Optimizations: Investigate further SIMD optimizations to make the audio processing more efficient.
Parallelization: If the system runs on multicore hardware, consider parallelizing the processing of different sub-oscillators or separate audio blocks.
Higher-Order Interpolation: The system appears to be using linear interpolation. Implementing higher-order interpolation techniques, such as cubic or spline interpolation, can improve audio quality.
Adaptive Tuning: Implement an adaptive tuning mechanism that adjusts the oscillator's behavior based on the input signal or user interaction to enhance the audio synthesis process.
Additional Oscillator Features: Explore the implementation of more oscillator features, such as frequency modulation, amplitude modulation, or other synthesis techniques, to expand the system's capabilities.
Remember that the provided code is just a part of a larger system. Identifying improvements and optimizations may require a deeper understanding of the entire audio synthesis system and its specific use cases.
17> thanks. what is the key algorithm by which this oscillator produces its output signal?
The key algorithm used by this WindowOscillator
to produce its output signal is wavetable
synthesis. Wavetable synthesis generates audio by
using precomputed waveforms stored in tables
(wavetables) and reading through them at various
speeds to produce different frequencies.
In this specific implementation, there are two
wavetables involved: the wave_wavetable
and the
window_wavetable
. The oscillator generates
output by combining the values from these two
wavetables. Here's a step-by-step description of
the algorithm:
Initialize the oscillator and set up its parameters like the number of active sub-oscillators, detune values, and gain values.
For each sub-oscillator, read values from the
wave_wavetable
and window_wavetable
based
on the current position and the desired morph
value (for wavetable selection).
Multiply the retrieved values from the
wave_wavetable
and window_wavetable
to
create an intermediate result.
Apply gain values specific to the sub-oscillator and channel (left or right) to the intermediate result.
Add the computed value from step 4 to the corresponding output buffer (left or right channel).
Update the position within the wavetables based on the oscillator's frequency and other factors like formant and pitch.
The process repeats for each sub-oscillator and
each audio sample in the output buffer. By
combining multiple sub-oscillators with different
gains, positions, and possibly detuning, the
WindowOscillator
can produce a rich and complex
output signal.