signal-processingvdsp

Efficiently generate a Sine wave in IOS


What is the most efficient way of generating a sine wave for a device running IOS. For the purposes of the exercise assume a frequency of 440Hz and a sampling rate of 44100Hz and 1024 samples.

A vanilla C implementation looks something like.

#define SAMPLES 1024
#define TWO_PI (3.14159 * 2)
#define FREQUENCY 440
#define SAMPLING_RATE 44100

int main(int argc, const char * argv[]) {
    float samples[SAMPLES];

    float phaseIncrement = TWO_PI * FREQUENCY / SAMPLING_RATE;
    float currentPhase = 0.0;
    for (int i = 0; i < SAMPLES; i ++){
        samples[i] = sin(currentPhase);
        currentPhase += phaseIncrement;
    }

    return 0;
}

To take advantage of the Accelerate Framework and the vecLib vvsinf function the loop can be changed to only do the addition.

#define SAMPLES 1024
#define TWO_PI (3.14159 * 2)
#define FREQUENCY 440
#define SAMPLING_RATE 44100

int main(int argc, const char * argv[]) {
    float samples[SAMPLES] __attribute__ ((aligned));
    float results[SAMPLES] __attribute__ ((aligned));

    float phaseIncrement = TWO_PI * FREQUENCY / SAMPLING_RATE;
    float currentPhase = 0.0;
    for (int i = 0; i < SAMPLES; i ++){
        samples[i] = currentPhase;
        currentPhase += phaseIncrement;
    }
    vvsinf(results, samples, SAMPLES);

    return 0;
}

But is just applying the vvsinf function as far as I should go in terms of efficiency?

I don't really understand the Accelerate framework well enough to know if I can also replace the loop. Is there a vecLib or vDSP function I can use?

For that matter is it possible to use an entirely different alogrithm to fill a buffer with a sine wave?


Solution

  • If efficiency is required, you could pre-load a 440hz (44100 / 440) sine waveform look-up table and loop around it without further mapping or pre-load a 1hz (44100 / 44100) sine waveform look-up table and loop around by skipping samples to reach 440hz just as you did by incrementing a phase counter. Using look-up tables should be faster than computing sin().

    Method A (using 440hz sine waveform):

    #define SAMPLES 1024
    #define FREQUENCY 440
    #define SAMPLING_RATE 44100
    #define WAVEFORM_LENGTH (SAMPLING / FREQUENCY)
    
    int main(int argc, const char * argv[]) {
        float waveform[WAVEFORM_LENGTH];
        LoadSinWaveForm(waveform);
    
        float samples[SAMPLES] __attribute__ ((aligned));
        float results[SAMPLES] __attribute__ ((aligned));
    
        for (int i = 0; i < SAMPLES; i ++){
            samples[i] = waveform[i % WAVEFORM_LENGTH];
        }
    
        vvsinf(results, samples, SAMPLES);
    
        return 0;
    }
    

    Method B (using 1hz sine waveform):

    #define SAMPLES 1024
    #define FREQUENCY 440
    #define TWO_PI (3.14159 * 2)
    #define SAMPLING_RATE 44100
    #define WAVEFORM_LENGTH SAMPLING_RATE // since it's 1hz
    
    int main(int argc, const char * argv[]) {
        float waveform[WAVEFORM_LENGTH];
        LoadSinWaveForm(waveform);
    
        float samples[SAMPLES] __attribute__ ((aligned));
        float results[SAMPLES] __attribute__ ((aligned));
    
        float phaseIncrement = TWO_PI * FREQUENCY / SAMPLING_RATE;
        float currentPhase = 0.0;
        for (int i = 0; i < SAMPLES; i ++){
            samples[i] = waveform[floor(currentPhase) % WAVEFORM_LENGTH];
            currentPhase += phaseIncrement;
        }
    
        vvsinf(results, samples, SAMPLES);
    
        return 0;
    }
    

    Please note that: