Skip to content

Instantly share code, notes, and snippets.

@catsocks
Last active January 28, 2021 00:58
Show Gist options
  • Save catsocks/ebed6c0395affa99c5cc0f0e5b12c1aa to your computer and use it in GitHub Desktop.
Save catsocks/ebed6c0395affa99c5cc0f0e5b12c1aa to your computer and use it in GitHub Desktop.
A very simple square wave tone generator I made to create sound effects for a game using the SDL cross-platform multimedia library, with no dynamic allocations or VLAs.
// A very simple square wave tone generator I made to create sound effects for
// a game using the SDL cross-platform multimedia library, with no dynamic
// allocations or VLAs.
//
// cc tonegen.c -std=c99 -Wall -Wextra -pedantic -lm $(sdl2-config --cflags
// --libs)
#include <SDL.h>
// Unnecessary if you're compiling with GNU extensions or MSVC with
// _USE_MATH_DEFINES.
#ifndef M_PI
#define M_PI 3.14159265358979323846
#endif
#define TONEGEN_SAMPLING_RATE 44100 // samples per second
#define TONEGEN_FORMAT_SIZE sizeof(int16_t) // sample format
#define TONEGEN_BUF_MAX_LENGTH (TONEGEN_SAMPLING_RATE / 10)
static const int FORMAT_MAX_VALUE = INT16_MAX; // determ. by TONEGEN_FORMAT_SIZE
static const int AMPLITUDE = 0.025 * FORMAT_MAX_VALUE; // volume
struct tonegen_tone {
int freq;
int duration; // in ms
};
struct tonegen {
int freq;
int sample_idx;
int remaining_samples; // samples yet to be generated
int16_t buf[TONEGEN_BUF_MAX_LENGTH];
size_t buf_size;
};
void tonegen_set_tone(struct tonegen *gen, struct tonegen_tone tone);
void tonegen_generate(struct tonegen *gen);
void tonegen_queue(struct tonegen *gen, SDL_AudioDeviceID device_id);
int main() {
if (SDL_Init(SDL_INIT_AUDIO) < 0) {
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
"Couldn't initialize SDL: %s", SDL_GetError());
return EXIT_FAILURE;
}
SDL_AudioDeviceID device_id =
SDL_OpenAudioDevice(NULL, 0,
&(SDL_AudioSpec){.freq = TONEGEN_SAMPLING_RATE,
.format = AUDIO_S16SYS,
.channels = 1,
.samples = 2048},
NULL, 0);
if (device_id == 0) {
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
"Couldn't open an audio device: %s", SDL_GetError());
return EXIT_FAILURE;
}
// Unpause the audio device because it is paused by default.
SDL_PauseAudioDevice(device_id, 0);
// Some tones from the original Pong game.
struct tonegen_tone score_tone = {240, 500};
struct tonegen_tone paddle_hit_tone = {480, 35};
struct tonegen_tone wall_hit_tone = {240, 20};
struct tonegen gen = {0};
tonegen_set_tone(&gen, score_tone);
// Queues all three tunes, pausing for 1 second after each tune is
// completely queued.
int tone_idx = 0;
while (tone_idx < 3) {
tonegen_generate(&gen);
tonegen_queue(&gen, device_id);
if (gen.remaining_samples == 0) {
SDL_Delay(1000);
if (tone_idx == 0) {
tonegen_set_tone(&gen, paddle_hit_tone);
} else if (tone_idx == 1) {
tonegen_set_tone(&gen, wall_hit_tone);
}
tone_idx++;
}
}
SDL_CloseAudioDevice(device_id);
SDL_Quit();
return EXIT_SUCCESS;
}
void tonegen_set_tone(struct tonegen *gen, struct tonegen_tone tone) {
gen->freq = tone.freq;
gen->sample_idx = 0;
gen->remaining_samples = (tone.duration / 1000.0) * TONEGEN_SAMPLING_RATE;
}
static int square_wave_sample(int idx, int freq) {
if (sin(2.0 * M_PI * freq * (idx / (double)TONEGEN_SAMPLING_RATE)) >= 0.0) {
return AMPLITUDE;
}
return -AMPLITUDE;
}
void tonegen_generate(struct tonegen *gen) {
int len = gen->remaining_samples;
if (len > TONEGEN_BUF_MAX_LENGTH) {
len = TONEGEN_BUF_MAX_LENGTH;
}
gen->remaining_samples -= len;
gen->buf_size = len * TONEGEN_FORMAT_SIZE;
for (int i = 0; i < len; i++) {
gen->buf[i] = square_wave_sample(gen->sample_idx + i, gen->freq);
}
gen->sample_idx += len;
}
void tonegen_queue(struct tonegen *gen, SDL_AudioDeviceID device_id) {
if (gen->buf_size > 0) {
if (SDL_QueueAudio(device_id, gen->buf, gen->buf_size) < 0) {
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
"Couldn't queue audio: %s", SDL_GetError());
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment