Having considerable difficulty getting a pure sine wave with SDL (it sounds very close to a sine wave, but there's a subtle secondary freqency)


#1

I’ve been attempting to get a pure sine wave playing back over the past few days, and it’s proving much more difficult than I had anticipated. The issue is that I’ve tried several different algorithms for producing a sine wave, but every time, the playback isn’t quite right. It initially sounds correct, but if you turn the volume up, there’s definitely something off – almost like a subtle square wave is being played beneath it

I recorded the sound: https://www.youtube.com/watch?v=-V2IMhK2Zis&feature=youtu.be

And after a considerable amount of effort, I found an oscilloscope I could run the audio through: https://i.stack.imgur.com/34xCr.png

As you can see, the image looks like a sine wave initially, but on the left side of the oscilloscope, something weird is going on. I thought this just meant I was using the wrong algorithm, but after using probably 4-5 different found algorithms (and still getting the issue), I’m not sure what else to try. Is it maybe an incorrect implicit cast, or something specific to SDL2?

Here is the (roughly) minimal code to reproduce the problem:

#include "x:\SDL2-2.0.10\include\SDL.h"
#include <stdint.h>

#define Pi32 3.14159265359f
#define Tau32 (2.0f * Pi32)

#define INITIAL_SCREEN_WIDTH (8*80)
#define INITIAL_SCREEN_HEIGHT (8*60)

typedef struct audio_user_data audio_user_data;
struct audio_user_data
{
    int SamplesPerSecond;
    int BytesPerSample;
    int SampleIndex;
    int ToneHz;
    int ToneVolume;
    int WavePeriod;
    uint32_t FileLength;
    uint16_t* BufferLocation;
};

void
AudioCallback(void* UserData, uint8_t* Stream, int Length)
{
    audio_user_data* AudioUserData = (audio_user_data*)UserData;
    static uint32_t Count = 0;

    uint16_t* SampleBuffer = (uint16_t*)Stream;
    int SamplesToWrite = Length / AudioUserData->BytesPerSample;
    for(int SampleIndex = 0; SampleIndex < SamplesToWrite; SampleIndex++)
    {
       uint16_t ToneValue = round((AudioUserData->ToneVolume * sin(Tau32 * (float)Count / (float)AudioUserData->WavePeriod)));
        *SampleBuffer++ = ToneValue;
        *SampleBuffer++ = ToneValue;
        ++Count;
    }
}

int
main(int argc, char* argv[])
{
    SDL_Init(SDL_INIT_VIDEO|SDL_INIT_AUDIO);

    SDL_Window* Window = SDL_CreateWindow("Spell Checker", SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, INITIAL_SCREEN_WIDTH*2, INITIAL_SCREEN_HEIGHT*2, 0);
    SDL_Renderer* Renderer = SDL_CreateRenderer(Window, 0, SDL_RENDERER_SOFTWARE);
    SDL_PixelFormat* Format = SDL_AllocFormat(SDL_PIXELFORMAT_RGB888);
    SDL_Texture* Screen = SDL_CreateTexture(Renderer, Format->format, SDL_TEXTUREACCESS_STREAMING, INITIAL_SCREEN_WIDTH, INITIAL_SCREEN_HEIGHT);

    audio_user_data AudioUserData = {0};
    AudioUserData.SamplesPerSecond = 44100;
    AudioUserData.BytesPerSample = 2 * sizeof(int16_t);
    AudioUserData.SampleIndex = 0;
    AudioUserData.ToneVolume = 3000;
    AudioUserData.ToneHz = 440;
    AudioUserData.WavePeriod = AudioUserData.SamplesPerSecond / AudioUserData.ToneHz;

    SDL_AudioSpec Want, Have;
    SDL_AudioDeviceID AudioDeviceID;
    Want.freq = AudioUserData.SamplesPerSecond;
    Want.format = AUDIO_S16;
    Want.channels = 2;
    Want.samples = 4096;
    Want.callback = &AudioCallback;
    Want.userdata = &AudioUserData;
    AudioDeviceID = SDL_OpenAudioDevice(0, 0, &Want, &Have, 0);

    SDL_PauseAudioDevice(AudioDeviceID, 0); // Start playing

    uint32_t* PixelMap = calloc(INITIAL_SCREEN_WIDTH * INITIAL_SCREEN_HEIGHT, sizeof(PixelMap));

    int PixelMapLocation = 0;
    int Running = 1;
    while(Running)
    {

        SDL_Event Event;
        while(SDL_PollEvent(&Event))
        {
            if(Event.type == SDL_QUIT)
            {
                Running = 0;
                break;
            }
        }

        // Test colors
    //  {
            PixelMapLocation = 0;
            for(int Row = 0; Row < INITIAL_SCREEN_WIDTH; ++Row)
            {
                for(int Col = 0; Col < INITIAL_SCREEN_HEIGHT; ++Col)
                {
                    PixelMap[PixelMapLocation++] = 0xFF00FF;
                }
            }
            for(int Row = 0; Row < INITIAL_SCREEN_WIDTH; ++Row)
            {
                for(int Col = 0; Col < INITIAL_SCREEN_HEIGHT; ++Col)
                {
                    PixelMap[PixelMapLocation++] = 0x00FFFF;
                }
            }
    //  }

        SDL_UpdateTexture(Screen, 0, PixelMap, INITIAL_SCREEN_WIDTH * sizeof(PixelMap));
        SDL_RenderClear(Renderer);
        SDL_RenderCopy(Renderer, Screen, 0, 0);
        SDL_RenderPresent(Renderer);
    }

    return(0);
}

Anyone know what might be going on here? I am truly perplexed O_O

Thanks for reading! :slight_smile:


#2

Your rounding method does not work for negative numbers (1.2 is rounded to 1 but -1.2 is rounded to 0). Use round instead.


#3

@es5804 Thanks so much for the response!

Great eye – I changed the code to:

uint16_t ToneValue = round((AudioUserData->ToneVolume * sin(Tau32 * (float)Count / (float)AudioUserData->WavePeriod)));

Unfortunately the problem persists, though. Something still tells me it’s a precision error of some kind, but I can’t seem to track it down


#4

It may be a precision issue, try using AUDIO_F32 instead of AUDIO_S16 to see if it solve your problem.


#5

I’m not sure if this is a problem, but you declare signed 16bits audio, and the callback produces unsigned 16bits data.


#6

I think your volume is too low and this is a problem for the precision of your sinus wave. Try multiply it by 10 or 20


#7

@es5804 @Sanette

Thank you both for the suggestions! I wound up posting this code in a few other places and chatting with several people about the problem.

It turns out that this line was the problem:

AudioUserData.SamplesPerSecond = 44100;

I’m still not sure why, but changing the value to 48000 completely fixed the problem!

That said, I definitely do need to correct the signed/unsigned thing even if it doesn’t fix anything – it’s better to be consistent. And I will keep the AUDIO_F32 thing in mind in case something happens later

Thank you both for the help! :slight_smile:


#8

Did you check your Have?

https://wiki.libsdl.org/SDL_OpenAudioDevice does not always give you want you Want.