Last active
January 28, 2021 00:58
-
-
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.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// 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