Previous topic

Complex Sinusoids

Next topic

Reactions

This Page

Rates and Domains

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;
}