AudioRate
is the sampling rate value of the AudioDomain
domain.
ControlRate
is the sampling rate value of the ControlDomain
domain.
AudioDomain
abstracts the audioCallback () and ControlDomain
abstracts the controlCallbacK ().
For the following examples ControlIn
is a single block and not a block bundle.
The following is the declaration of the SinOsc ()
module, which will be used in the following sections.
The C++ code shown is informal, simply used to convey the concepts in the examples.
module SinOsc {
input: none
output: Output
propertyName: 'frequency'
propertyDirection: in
propertyBlock: signal Frequency { default: 440. rate: none domain: GetDomain ( port: 'frequency' ) }
internalBlock: [
signal Output { default: 0. rate: streamRate domain: streamDomain },
signal Phase { default: 0. rate: none domain: streamDomain },
signal PhaseIncrement { default: 0. rate: none domain: GetDomain ( port: 'frequency' ) }
]
streams: [
2.0 * M_PI * Frequency / streamRate >> PhaseIncremet;
Phase >> Sin() >> Output;
Phase + PhaseIncremet >> Phase;
]
}
A beginner user would type the following code and it would produce the desired effect. However, this code is far from optimized since all computations are being performed at AudioRate
.
ControlIn
>> Map ( minimum: 55. maximum: 880. )
>> FrequencyValue;
SinOsc ( frequency: FrequencyValue )
>> AudioOut;
Since FrequencyValue
was not explicityly declared, it gets declared by the backend as a signal
block with default values.
signal FrequencyValue { default: 0.0 rate: AudioRate domain: AudioDomain }
Code produced by a backend could be:
AtomicFloat ControlValue = 0.0;
void controlCallbacK ( float input ) {
ControlValue = input;
}
void audioCallbacK ( float &output ) {
static float Phase = 0.0;
static float FrequencyValue = 0.0;
static float PhaseIncrement = 0.0;
FrequencyValue = Map ( ControlValue, 0., 1., 55., 880. );
PhaseIncrement = 2 * M_PI * FrequencyValue / AudioRate;
output = Sin ( Phase );
Phase += PhaseIncrement;
}
The first improvement the user could do is to explicitly declare FrequencyValue
and change its rate
. This will reduce the number of computatiosn performed to calculate the phase incremet of the sine oscillator. If the chosen rate is less than ControlRate
, which is the rate of ControlIn
, then ControlIn
will be downsampled (The signal will be aliased).
signal FrequencyValue { default: 0.0 rate: 1024. domain: AudioDomain }
ControlIn
>> Map ( minimum: 55. maximum: 880. )
>> FrequencyValue;
SinOsc ( frequency: FrequencyValue )
>> AudioOut;
Code produced by a backend could be:
AtomicFloat ControlValue = 0.0;
Timer timer ( 1024. / AudioRate );
void controlCallbacK ( float input ) {
ControlValue = input;
}
void audioCallbacK ( float &output ) {
static float Phase = 0.0;
static float FrequencyValue = 0.0;
static float PhaseIncrement = 0.0;
if ( timer () ) {
FrequencyValue = Map ( ControlValue, 0., 1., 55., 880. );
PhaseIncrement = 2 * M_PI * FrequencyValue / AudioRate;
}
output = Sin ( Phase );
Phase += PhaseIncrement;
}
The first change one needs to do to improve performace is to add an OnChange ()
module after ControlIn
. However, this change alone is not sufficient since it actually adds computation.
signal FrequencyValue { default: 0.0 rate: 1024. domain: AudioDomain }
ControlIn
>> OnChange ()
>> Map ( minimum: 55. maximum: 880. )
>> FrequencyValue;
SinOsc ( frequency: FrequencyValue )
>> AudioOut;
Code produced by a backend could be:
AtomicFloat ControlValue = 0.0;
Timer timer ( 1024. / AudioRate );
void controlCallbacK ( float input ) {
ControlValue = input;
}
void audioCallbacK ( float &output ) {
static float Phase = 0.0;
static float FrequencyValue = 0.0;
static float PhaseIncrement = 0.0;
static float PreviousValue = 0.0;
if (ControlValue != PreviousValue) {
PreviousValue = ControlValue;
}
if ( timer () ) {
FrequencyValue = Map ( PreviousValue, 0., 1., 55., 880. );
PhaseIncrement = 2 * M_PI * FrequencyValue / AudioRate;
}
output = Sin ( Phase );
Phase += PhaseIncrement;
}
To achieve the desired improvement FrequencyValue
should be set to run in asynchronous mode by changing its rate
to none
.
signal FrequencyValue { default: 0.0 rate: none domain: AudioDomain }
ControlIn
>> OnChange ()
>> Map ( minimum: 55. maximum: 880. )
>> FrequencyValue;
SinOsc ( frequency: FrequencyValue )
>> AudioOut;
Code produced by a backend could be:
AtomicFloat ControlValue = 0.0;
void controlCallbacK ( float input ) {
ControlValue = input;
}
void audioCallbacK ( float &output ) {
static float Phase = 0.0;
static float FrequencyValue = 0.0;
static float PhaseIncrement = 0.0;
static float PreviousValue = 0.0;
if ( ControlValue != PreviousValue ) {
FrequencyValue = Map ( ControlValue, 0., 1., 55., 880. );
PhaseIncrement = 2 * M_PI * FrequencyValue / AudioRate;
PreviousValue = ControlValue;
}
output = Sin ( Phase );
Phase += PhaseIncrement;
}
The user also has a choice of moving computations from one domain to the other. That is, computations can be moved from one thread to another. By changing the domain
of FrequencyValue
from AudioDomain
to ControlDomain
, the phase increment computation will be moved from audioCallback () to controlCallback ().
signal FrequencyValue { default: 0.0 rate: none domain: ControlDomain }
ControlIn
>> OnChange ()
>> Map ( minimum: 55. maximum: 880. )
>> FrequencyValue;
SinOsc ( frequency: FrequencyValue )
>> AudioOut;
Code produced by a backend could be:
AtomicFloat ControlValue = 0.0;
void controlCallbacK ( float input ) {
static float FrequencyValue = 0.0;
static float PhaseIncrement = 0.0;
static float PreviousValue = 0.0;
if ( input != PreviousValue ) {
FrequencyValue = Map ( input, 0., 1., 55., 880. );
ControlValue = 2 * M_PI * FrequencyValue / AudioRate;
PreviousValue = input;
}
}
void audioCallbacK ( float &output ) {
static float Phase = 0.0;
output = Sin ( Phase );
Phase += ControlValue;
}