Created
July 11, 2026 19:15
-
-
Save jweinst1/31782d4ccdc315d7d610c97fa736e8f3 to your computer and use it in GitHub Desktop.
audio sounds for games in SDL
This file contains hidden or 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
| #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