Skip to content

Instantly share code, notes, and snippets.

@jweinst1
Created July 11, 2026 19:15
Show Gist options
  • Select an option

  • Save jweinst1/31782d4ccdc315d7d610c97fa736e8f3 to your computer and use it in GitHub Desktop.

Select an option

Save jweinst1/31782d4ccdc315d7d610c97fa736e8f3 to your computer and use it in GitHub Desktop.
audio sounds for games in SDL
#include <SDL3/SDL.h>
#include <SDL3/SDL_main.h>
#include <cmath>
#include <iostream>
#include <cstdlib> // For rand()
// Global settings
const float SAMPLE_RATE = 48000.0f;
const float PI = 3.14159265f;
// Sound types available in our skilling RPG
enum SoundType { NONE, JUMP, WATER_DROP, MINING_HIT };
// Thread-safe state structure shared between Main thread and Audio thread
struct AudioEngine {
SoundType active_type = NONE;
float elapsed_time = 0.0f;
float duration = 0.0f;
bool is_playing = false;
// Mutex to safely pass triggers from main loop to audio thread
SDL_Mutex* mutex = nullptr;
// Sound customization variables (Fiddle with these in main!)
float start_freq = 440.0f;
float end_freq = 880.0f;
} g_AudioEngine;
// The core "Fragment Shader" for audio. Runs once per audio sample frame.
float GenerateSample(float sample_rate) {
if (!g_AudioEngine.is_playing) return 0.0f;
// Advance our internal clock (Delta Time per sample)
g_AudioEngine.elapsed_time += (1.0f / sample_rate);
// Life-cycle check (Kill the sound if its lifespan ends)
if (g_AudioEngine.elapsed_time >= g_AudioEngine.duration) {
g_AudioEngine.is_playing = false;
return 0.0f;
}
float progress = g_AudioEngine.elapsed_time / g_AudioEngine.duration; // Normalized 0.0 to 1.0
float sample = 0.0f;
// Linearly interpolate current frequency over time (Pitch Sweep)
float current_freq = g_AudioEngine.start_freq + (progress * (g_AudioEngine.end_freq - g_AudioEngine.start_freq));
// Standard phase calculation based on changing frequency
float phase = g_AudioEngine.elapsed_time * current_freq * 2.0f * PI;
if (g_AudioEngine.active_type == JUMP) {
// Retro Buzz (Square Wave): If positive, output max height, else min height.
float square_wave = (std::sin(phase) >= 0.0f) ? 1.0f : -1.0f;
// Linear fade-out curve (Volume)
float volume = 1.0f - progress;
sample = square_wave * volume * 0.15f; // Hard limit volume to 15%
}
else if (g_AudioEngine.active_type == WATER_DROP) {
// Smooth organic droplet (Sine Wave)
float sine_wave = std::sin(phase);
// Exponential volume drop: rapid decay curve (Sharp falloff)
float volume = std::pow(1.0f - progress, 3.0f);
sample = sine_wave * volume * 0.30f;
}
else if (g_AudioEngine.active_type == MINING_HIT) {
// Metallic clink (High pitch Square Wave)
float square_wave = (std::sin(phase) >= 0.0f) ? 1.0f : -1.0f;
// Pickaxe crunch: Generate direct White Noise (Random numbers between -1.0 and 1.0)
float white_noise = ((float)std::rand() / (float)RAND_MAX) * 2.0f - 1.0f;
// Mix 40% clean metal ring + 60% gravel crunch texture
float texture_mix = (square_wave * 0.4f) + (white_noise * 0.6f);
// Steep exponential decay curve for heavy physical impact
float volume = std::pow(1.0f - progress, 4.0f);
sample = texture_mix * volume * 0.25f;
}
return sample;
}
// SDL3 Callback: Feeds raw PCM buffer data into the hardware
void AudioCallback(void* userdata, SDL_AudioStream* stream, int additional_amount, int total_amount) {
int samplesNeeded = additional_amount / sizeof(float);
float* temp_buffer = new float[samplesNeeded];
// Thread Safety: Lock state variables while processing this block
SDL_LockMutex(g_AudioEngine.mutex);
for (int i = 0; i < samplesNeeded; i += 2) {
float mono_sample = GenerateSample(SAMPLE_RATE);
// Interleave stream channels (Duplicate mono signal to Left & Right stereo)
temp_buffer[i] = mono_sample; // Left
temp_buffer[i + 1] = mono_sample; // Right
}
SDL_UnlockMutex(g_AudioEngine.mutex);
SDL_PutAudioStreamData(stream, temp_buffer, additional_amount);
delete[] temp_buffer;
}
// Main thread function to trigger sounds
void TriggerSound(SoundType type, float duration, float start_f, float end_f) {
SDL_LockMutex(g_AudioEngine.mutex);
g_AudioEngine.active_type = type;
g_AudioEngine.duration = duration;
g_AudioEngine.start_freq = start_f;
g_AudioEngine.end_freq = end_f;
g_AudioEngine.elapsed_time = 0.0f;
g_AudioEngine.is_playing = true;
SDL_UnlockMutex(g_AudioEngine.mutex);
}
int main(int argc, char* argv[]) {
if (!SDL_Init(SDL_INIT_AUDIO)) {
std::cerr << "SDL Audio Init Failed: " << SDL_GetError() << std::endl;
return -1;
}
g_AudioEngine.mutex = SDL_CreateMutex();
SDL_AudioSpec spec;
spec.format = SDL_AUDIO_F32;
spec.channels = 2;
spec.freq = (int)SAMPLE_RATE;
SDL_AudioStream* stream = SDL_OpenAudioDeviceStream(SDL_AUDIO_DEVICE_DEFAULT_PLAYBACK, &spec, AudioCallback, nullptr);
if (!stream) {
std::cerr << "Stream Open Failed: " << SDL_GetError() << std::endl;
SDL_Quit();
return -1;
}
SDL_ResumeAudioStreamDevice(stream);
std::cout << "=== Procedural Audio Sandbox ===" << std::endl;
std::cout << "[1] Trigger Mario Jump" << std::endl;
std::cout << "[2] Trigger RPG Water Drop" << std::endl;
std::cout << "[3] Trigger Mining Pickaxe Hit" << std::endl;
std::cout << "[Q] Quit Application" << std::endl;
char choice = ' ';
while (choice != 'q' && choice != 'Q') {
std::cout << "\nEnter Command: ";
std::cin >> choice;
if (choice == '1') {
// Jump: Slides UP from low to high over 0.25 seconds
TriggerSound(JUMP, 0.25f, 150.0f, 700.0f);
}
else if (choice == '2') {
// Water Drop: Fast curve up, very brief (0.08 seconds)
TriggerSound(WATER_DROP, 0.08f, 600.0f, 1600.0f);
}
else if (choice == '3') {
// Mining Hit: Fast slide DOWN to mimic heavy iron resonance hitting rock
TriggerSound(MINING_HIT, 0.18f, 300.0f, 60.0f);
}
}
// Clean execution tear down
SDL_DestroyAudioStream(stream);
SDL_DestroyMutex(g_AudioEngine.mutex);
SDL_Quit();
return 0;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment