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