Search code examples
c++mathaudiosdlwaveform

Playing a Sine Wave with changable parameters - how to do the phase shifting?


I am generating a Sine wave and send it to the SDL Audio Buffer to generate sound. All the parameter like the amplitude and frequency can be changed with the arrow keys of the keyboard.

Now, the problem is that when I change the frequency, I hear a "scratch". I understand why this is happening: I'm getting a totally wrong value when I just continue iterating my x in f(x) when the function itself has changed. But I fail to see or understand how I can fix this problem by phase shifting.

Any tips how to start?

#include "WaveGenerator.h"
#include <thread>
#include <iostream>
#include <sstream>
#include <string>
#include <algorithm>    // std::min


int main(int argc, char* argv[]){

    WaveGenerator* wg = new WaveGenerator();

    int i;
    std::cin >> i;
    return 0;
}

int graphThreadFunc(void *pointer){
    WaveGenerator* wg = (WaveGenerator*)pointer;
    wg->init();

    return 0;
}



// SDL calls this function whenever it wants its buffer to be filled with samples
// length = 2048
void SDLAudioCallback(void *data, Uint8 *buffer, int length){
    uint8_t *stream = (uint8_t*)buffer;

    WaveGenerator* wg = (WaveGenerator*)data;   // pointer to our WaveGenerator object where the voice data is stored

    for (int i = 0; i < length; i++){

        if (wg->voice.audioLength <= 0)
            stream[i] = wg->getSpec()->silence;      // 128 is silence in a uint8 stream
        else
        {
            stream[i] = wg->voice.getSample();      // calculate the current sample value

        }

        wg->voice.audioPosition++;
    }
}


WaveGenerator::WaveGenerator()
{
    // spawn thread
    SDL_Thread *refresh_thread = SDL_CreateThread(graphThreadFunc, NULL, this);
}

SDL_AudioSpec* WaveGenerator::getSpec(){
    return &this->spec;
}


void WaveGenerator::init()
{
    // Init SDL & SDL_ttf
    SDL_Init(SDL_INIT_VIDEO | SDL_INIT_AUDIO | SDL_INIT_TIMER);

    SDL_zero(desiredDeviceSpec);

    desiredDeviceSpec.freq = SAMPLING_RATE;     // Sample Rate
    desiredDeviceSpec.format = AUDIO_U8;        // Unsigned 8-Bit Samples
    desiredDeviceSpec.channels = 1;             // Mono
    desiredDeviceSpec.samples = 2048;           // The size of the Audio Buffer (in number of samples, eg: 2048 * 1 Byte (AUDIO_U8)
    desiredDeviceSpec.callback = SDLAudioCallback;
    desiredDeviceSpec.userdata = this;


    dev = SDL_OpenAudioDevice(NULL, 0, &desiredDeviceSpec, &spec, SDL_AUDIO_ALLOW_FREQUENCY_CHANGE);
    if (dev == 0) {
        printf("\nFailed to open audio: %s\n", SDL_GetError());
    }
    else {
        SDL_PauseAudioDevice(dev, 1); /* pause! */
        SDL_PauseAudio(1);
    }

     //Create an application window with the following settings:
        window = SDL_CreateWindow(
            WINDOW_TITLE.c_str(),              // window title
            SDL_WINDOWPOS_UNDEFINED,           // initial x position
            SDL_WINDOWPOS_UNDEFINED,           // initial y position
            WINDOW_WIDTH,                      // width, in pixels
            WINDOW_HEIGHT,                     // height, in pixels
            SDL_WINDOW_SHOWN                  // flags - see below
            );

        // Check if the window was successfully created
        if (window == NULL) {
            // In case the window could not be created...
            printf("Could not create window: %s\n", SDL_GetError());
            return;
        }
        else{
            // Initial wave parameters
            voice.waveForm = WaveGenerator::Voice::WaveForm::SINE;
            voice.amp = 120;
            voice.frequency = 440;
            SDL_PauseAudioDevice(dev, 1);        // pause       
            voice.audioLength = SAMPLING_RATE;
            voice.audioPosition = 0;

            SDL_PauseAudioDevice(dev, 0);        // play
            SDL_Delay(SAMPLING_RATE / voice.audioLength * 1000);    // 44100 / length of the audio  * 1000 (to get milliseconds)

            mainLoop();
        }
    return;

}

void WaveGenerator::mainLoop()
{
    bool waveHasChanged = false;

    // poll SDL events until we terminate the thread
    while (thread_exit == 0){
        SDL_Event event;

        while (SDL_PollEvent(&event)) {
            switch (event.type)
            {
            case SDL_KEYDOWN:
            {       
                if (event.key.keysym.scancode == SDL_SCANCODE_SPACE){
                    switch (voice.waveForm){
                    case Voice::SINE:
                    {
                        voice.waveForm = WaveGenerator::Voice::WaveForm::TRIANGLE;
                        break;
                    }
                    case Voice::TRIANGLE:
                    {
                        voice.waveForm = WaveGenerator::Voice::WaveForm::RECT;
                        break;
                    }
                    case Voice::RECT:
                    {
                        voice.waveForm = WaveGenerator::Voice::WaveForm::SAWTOOTH;
                        break;
                    }
                    case Voice::SAWTOOTH:
                    {
                        voice.waveForm = WaveGenerator::Voice::WaveForm::NOISE;
                        break;
                    }
                    case Voice::NOISE:
                    {
                        voice.waveForm = WaveGenerator::Voice::WaveForm::SINE;
                        break;
                    }
                    default:
                        break;
                    }
                    waveHasChanged = true;
                }
                else if (event.key.keysym.scancode == SDL_SCANCODE_ESCAPE){
                    exit();
                }
                else if (event.key.keysym.scancode == SDL_SCANCODE_LEFT){               
                    voice.frequency -= 10;
                    waveHasChanged = true;
                }
                else if (event.key.keysym.scancode == SDL_SCANCODE_RIGHT){                  
                    voice.frequency += 10;
                    waveHasChanged = true;
                }
                else if (event.key.keysym.scancode == SDL_SCANCODE_UP){                 
                    voice.amp += 2;
                    waveHasChanged = true;
                }
                else if (event.key.keysym.scancode == SDL_SCANCODE_DOWN){                   
                    voice.amp -= 2;
                    waveHasChanged = true;
                }
                else{

                }

                break;
            }

            case SDL_QUIT:
            {
                exit();
                return;
                break;
            }
            default: /* unhandled event */
                break;
            }
        }

        if (!pause_thread && waveHasChanged)
        {

            // calculate phase shifting?
        }


        SDL_Delay(50);
    }


    return;
}

void WaveGenerator::exit(){
    thread_exit = 1;
    // Clean up
    SDL_Quit();
}

WaveGenerator::Voice::Voice(){
}

uint8_t WaveGenerator::Voice::getSample(){

    switch (waveForm){
    case SINE:
    {
        return (amp * sin(2 * M_PI * audioPosition * frequency / SAMPLING_RATE)) + 128;
        break;
    }
    // .....
    default:
        return 0;
    }
}

and the header file:

#ifndef WAVEGENERATOR_H
#define WAVEGENERATOR_H
#include "SDL.h"
#include "SDL_audio.h"
#include <stdio.h>
#include <cmath>
#include <string>
#include <stack>
#include <io.h> // unistd.h for mac/linux, io.h for windows
#include <vector>
#include <fstream>

/* Window Constants */
const std::string WINDOW_TITLE = "Wave Graph";
const int WINDOW_WIDTH = 1980;
const int WINDOW_HEIGHT = 255;

/* Audio Constants */
const int SAMPLING_RATE = 44100;                // number of samples per second


class WaveGenerator
{
private:
    SDL_Window *window;

    // SDL Audio
    SDL_AudioSpec desiredDeviceSpec;
    SDL_AudioSpec spec;
    SDL_AudioDeviceID dev;

    int thread_exit = 0;
    bool pause_thread = false;


public:
    WaveGenerator();
    void init();
    void mainLoop();

    void exit();
    SDL_AudioSpec* getSpec();


    // SDL audio members
    struct Voice{
        Voice();

        // WaveForm parameters
        enum WaveForm{
            SINE = 0, RECT = 1, SAWTOOTH = 2, TRIANGLE = 3, NOISE = 4
        } waveForm;
        int frequency;              // the frequency of the voice
        int amp;                    // the amplitude of the voice


        // SDL buffer handling members
        int audioLength;            // number of samples to be played, eg: 1.2 seconds * 44100 samples per second
        int audioPosition = 0;      // counter

        uint8_t getSample();


    } voice;

};

#endif

Solution

  • The easiest way to change frequencies without a jump in phase by removing audioPosition from the equation:

    class WaveGenerator
    {
    private:
        double m_sinePhase;
        double m_sinePhaseInc;
    
    uint8_t WaveGenerator::Voice::getSample(){
        switch (waveForm){
        case SINE:
        {
            uint8_t sample = (amp * sin(2 * M_PI * m_sinePhase)) + 128;
            m_sinePhase += m_sinePhaseInc;
            return sample;
        }
    }
    

    And then when you change the frequency just recompute the phase increment

    m_sinePhaseInc = freq/sampleRate;