Playing a Sine Wave 3
A Looping Callback
Playing a sine wave by calling sin(double x)
from within the callback is all well and good, but it's rather wasteful, considering that sine waves are periodic. A better approach is to precompute a segment of a sine wave, with the last value leading directly into the first, and then looping this segment. To do that we'll need an audio callback that can loop over audio data.
#define FREQ 200 /* the frequency we want */
Sint16 *sinebuf = NULL; /* where our precomputed values will go */
Uint32 length; /* how many samples are in sinebuf? */
Uint32 pos; /* what sample we are up to */
int loop; /* how many loops? */
/* Play the sound, with looping if present */
void MyAudioCallback(void* userdata, Uint8* stream, int len) {
len /= 2; /* 16 bit */
if(!sinebuf) return;
int i = 0, llen = 0, rAudio, rBuffer;
Sint16 *buf = (Sint16*)stream;
do {
rAudio = length - pos; /* how much of our audio data is left */
rBuffer = len - i; /* how much buffer needs to be filled */
llen += rAudio < rBuffer ? rAudio : rBuffer; /* how far into the buffer we copy data */
while(i < llen) /* write sound data */
buf[i++] = sinebuf[pos++];
if(i < len) { /* if buffer is not filled yet */
if(loop) { /* looping */
pos = 0;
if(loop>0) loop--;
} else { /* not looping, write silence and exit */
SDL_memset(buf + i, 0, (len - llen)*2);
i = len;
}
}
} while(i < len); /* loop until buffer is filled */
return;
}
We initialize as usual, and open an audio device allowing only frequency change (the frequency still doesn't matter... yet). Then we fill in some values, allocate our audio buffer, and calculate it.
/*
* Initialize audio device as mono S16
*/
/* calculate how long one cycles is, allocate a buffer for ~ 1 seconds worth */
unsigned int samples = have.freq / FREQ; /* samples per period */
unsigned int bufsz = 2 * samples; /* size of 1 cycle as Sint16 */
bufsz *= 1 * FREQ; /* 1 second length */
sinebuf = malloc(bufsz);
if(!sinebuf) {
printf("Failed to allocate audio buffer\n");
SDL_CloseAudioDevice(dev);
SDL_Quit();
return 1;
}
/* fill the buffer with our sine wave */
for(i = 0; i < bufsz / 2; i++)
sinebuf[i] = 6000 * sin((i % samples) * 2 * M_PI / samples);
length = bufsz / 2; /* 16 bit samples */
pos = 0;
loop = 9; /* play 9 + 1 times */
SDL_PauseAudioDevice(dev, 0); /* play */
while(loop || pos < length)
SDL_Delay(500);
/*
* Cleanup
*/
The 10th time it plays, loop will be 0, so we might exit in the middle of the final playthrough if the check is performed then.
Exercises / Questions
- What happens if you set
loop = -1;
? - The calculation
unsigned int samples = have.freq / FREQ;
will round the number of samples per period down, so the played frequency might be higher than requested. How could you get the frequency closer to requested while still having the precomputed audio be loopable?
References
https://wiki.libsdl.org/CategoryAudio- Uses javascript SyntaxHighlighter for syntax highlighting.
- Uses javascript MathJax for LateX rendering.
To the extent possible under law,
Craig Hughes
has waived all copyright and related or neighboring rights to
the text and source code in this example/tutorial.