2

My goal is to create an SDL window plotting different waveforms and playing an indefinite sound of this wave. By pressing specific keys, the parameters of the wave, like the amplitude, frequency or waveform can be modified.

The problem is that even a simple sine wave which looks nice when plotted, sounds noisy. I don't understand why.

Code:

#include "Graph.h"
#include <thread>
#include <iostream>
#include <sstream>
#include <string>


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

    Graph* g = new Graph();

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

int graphThreadFunc(void *pointer){
    Graph* grid = (Graph*)pointer;
    grid->init();

    return 0;
}



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

    Graph* graph = (Graph*)data;


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

        if (graph->voice.audioLength <= 0)
            stream[i] = graph->getSpec()->silence;      // 128 is silence in a uint8 stream
        else
        {
            stream[i] = graph->voice.getSample();
            graph->voice.audioPosition++;


            // Fill the graphBuffer with the first 1000 bytes of the wave for plotting
            if (graph->graphPointer < 999)
                graph->graphBuffer[graph->graphPointer++] = stream[i];

        }


    }
}


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

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


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

    desiredDeviceSpec.freq = 44100;         // 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{
        voice.waveForm = Graph::Voice::WaveForm::SINE;
        voice.amp = 120;
        voice.frequency = 440;
        SDL_PauseAudioDevice(dev, 1);        // play
        graphPointer = 0;

        voice.audioLength = 44100;
        voice.audioPosition = 0;

        SDL_PauseAudioDevice(dev, 0);        // play
        SDL_Delay(200);

        drawGraph();


        mainLoop();
        return;
    }
}

void Graph::mainLoop()
{
    while (thread_exit == 0){
        SDL_Event event;
        bool hasChanged = false;

        while (SDL_PollEvent(&event)) {
            switch (event.type)
            {
            case SDL_KEYDOWN:
            {
                hasChanged = true;

                if (event.key.keysym.scancode == SDL_SCANCODE_SPACE){
                    //pause_thread = !pause_thread;
                    switch (voice.waveForm){
                    case Voice::SINE:
                    {
                        voice.waveForm = Graph::Voice::WaveForm::TRIANGLE;
                        break;
                    }
                    case Voice::TRIANGLE:
                    {
                        voice.waveForm = Graph::Voice::WaveForm::RECT;
                        break;
                    }
                    case Voice::RECT:
                    {
                        voice.waveForm = Graph::Voice::WaveForm::SAWTOOTH;
                        break;
                    }
                    case Voice::SAWTOOTH:
                    {
                        voice.waveForm = Graph::Voice::WaveForm::SINE;
                        break;
                    }
                    default:
                        break;
                    }
                }
                else if (event.key.keysym.scancode == SDL_SCANCODE_ESCAPE){
                    exit();
                }
                else if (event.key.keysym.scancode == SDL_SCANCODE_RETURN){

                }
                else if (event.key.keysym.scancode == SDL_SCANCODE_LEFT){
                    voice.frequency -= 2;
                }
                else if (event.key.keysym.scancode == SDL_SCANCODE_RIGHT){
                    voice.frequency += 2;
                }
                else if (event.key.keysym.scancode == SDL_SCANCODE_UP){
                    voice.amp += 2;
                }
                else if (event.key.keysym.scancode == SDL_SCANCODE_DOWN){
                    voice.amp -= 2;
                }
                else{

                }

                break;
            }

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

        if (!pause_thread && hasChanged)
        {

            //SDL_PauseAudioDevice(dev, 1);      // play
            graphPointer = 0;

            voice.audioLength = 44100;
            voice.audioPosition = 0;

            SDL_PauseAudioDevice(dev, 0);        // play
            SDL_Delay(200);

            drawGraph();
        }


        //voice.waveForm = Voice::WaveForm::TRIANGLE;       

        //SDL_Delay(n);                      // delay the program to prevent the voice to be overridden before it has been played to the end
        //SDL_PauseAudioDevice(dev, 1);      // pause   

        SDL_Delay(REFRESH_INTERVAL);
        //SDL_PauseAudioDevice(dev, 1);      // pause   
    }


    return;
}

void Graph::drawGraph()
{

    SDL_Renderer *renderer = SDL_GetRenderer(window);
    if (renderer == nullptr)
        renderer = SDL_CreateRenderer(window, 0, SDL_RENDERER_ACCELERATED);

    // Set background color
    SDL_SetRenderDrawColor(renderer, 255, 255, 255, 255);

    // Clear winow
    SDL_RenderClear(renderer);


    SDL_SetRenderDrawColor(renderer, 22, 22, 22, 255);
    for (int x = 0; x < WINDOW_WIDTH; x++){
        uint8_t y = graphBuffer[x];
        SDL_RenderDrawPoint(renderer, x, WINDOW_HEIGHT - y);
    }


    SDL_RenderPresent(renderer);


    return;
}

void Graph::exit(){
    thread_exit = 1;
    // Close and destroy the window
    SDL_DestroyWindow(window);
    // Clean up
    SDL_Quit();
}


uint8_t Graph::Voice::getSample(){
    switch (waveForm){
    case SINE:
    {
        float sineStep = 2 * M_PI * audioPosition * frequency / 44100;
        return (amp * sin(sineStep)) + 128;
        break;
    }
    case RECT:
        break;

    case SAWTOOTH:
        break;

    case TRIANGLE:
        break;

    default:
        return 0;
    }
}

And the header file:

#ifndef GRAPH_H
#define GRAPH_H
#include "SDL.h"
#include "SDL_audio.h"
#include <stdio.h>
#include <cmath>
#include <string>
#include <stack>

/* Constants */
const int REFRESH_INTERVAL = 50;                // mseconds
const int WINDOW_WIDTH = 1000;
const int WINDOW_HEIGHT = 255;
const std::string WINDOW_TITLE = "Wave Graph";

class Graph
{
private:
    SDL_Window *window;          // Declare a pointer

    // SDL audio stuff
    SDL_AudioSpec desiredDeviceSpec;
    SDL_AudioSpec spec;
    SDL_AudioDeviceID dev;

    int thread_exit = 0;
    bool pause_thread = false;

public:
    Graph();
    void init();
    void mainLoop();
    void drawGraph();

    void exit();
    SDL_AudioSpec* getSpec();

    struct Voice{
        int frequency;              // the frequency of the voice
        int amp;                    // the amplitude of the voice

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

        enum WaveForm{
            SINE = 0, RECT = 1, SAWTOOTH = 2, TRIANGLE = 3
        } waveForm;


        uint8_t getSample();
    } voice;
    int graphPointer = 0;
    uint8_t graphBuffer[1000];


};


#endif
user66875
  • 2,772
  • 3
  • 29
  • 55
  • 1
    I think the amplitude of your sine wave might be too low. It looks like voice.amp is 1 so your samples are going to only vary between 126 and 128. Try `(amp * sin(sineStep) * 128) + 127;` – jaket Oct 22 '15 at 06:27
  • Hi jaket, you are right I forgot to mention that I initially set the amplitude to a higher value (between 100 and 128), however, the noise is still there. – user66875 Oct 22 '15 at 06:47
  • actually you probably want to use 127. when `sin` returns -1 then -1*128+127 would be -1 unsigned and cause clipping. If that's not it maybe you should describe the noise you are hearing. The normal definition of it can be heard here: https://en.wikipedia.org/wiki/White_noise. Is it something like that with a sine wave on top or is it more like glitches? If glitches are they at regular intervals or random? – jaket Oct 22 '15 at 06:57
  • I would start with measuring the output (for example with [this](http://stackoverflow.com/a/21658139/2521214)) to actually see what is wrong. What sound API SDL uses? Each has it quirks may be you just have not enough or too small buffers and hit the latency wall... you either send wrong data or do not append buffers correctly or stress the sound API too much or too less – Spektre Oct 22 '15 at 07:32
  • I really don't know what to do, I tried different codes doing almost exactly the same and it still doesn't sound good. I tried different approaches, different buffer sizes, AUDIO_S8... I wrote all the values to a file and plotted it in excel - it's a perfectly fine sine wave. I just doesn't want to work. Is there anybody who succeed in getting a nice 8bit sine sound with SDL2? – user66875 Oct 22 '15 at 15:39

1 Answers1

3

Your SDLAudioCallback() is writing an extra byte off the end of buffer:

void SDLAudioCallback(void *data, Uint8 *buffer, int length)
{
    ...
    for (int i = 0; i <= length; i++)
    //                ^^ huh?
    {
        ...
    }
}

Changing the <= to just < fixes the crackles on my system.

Generally C-style "byte pointer + length" APIs expect a left-closed, right-open interval: [0, length). I.e., you can access buffer[length - 1] but not buffer[length].

genpfault
  • 51,148
  • 11
  • 85
  • 139