#include "audio.h" #include "renderer.h" #include #include constexpr float PITCH_VARIANCE = 0.06f; constexpr Uint64 SOUND_GAP = 40 * SDL_NS_PER_MS; constexpr const char* SOUND_NAMES[SOUND_COUNT] = { "beam", "blast", "boomerang", "boomerang_heavy", "bounce", "bow", "bow_heavy", "break", "cast", "charge", "chat", "crawl", "dash", "death", "detonate", "detonate_empty", "duck", "echo", "equip", "explosion", "fall", "finish", "fireball", "flail", "flail_heavy", "flail_land", "flame", "grapple", "grapple_heavy", "hit_blunt", "hit_blunt_heavy", "hit_bullet", "hit_bullet_heavy", "hit_energy", "hit_energy_heavy", "hit_explosive", "hit_explosive_heavy", "hit_fire", "hit_fire_heavy", "hit_lash", "hit_lash_heavy", "hit_magic", "hit_magic_heavy", "hit_pierce", "hit_pierce_heavy", "hit_slash", "hit_slash_heavy", "impact_arrow", "impact_blaster", "impact_boomerang", "impact_bullet", "impact_mine", "jump", "land", "latch", "level_down", "level_up", "mine", "poke", "poke_heavy", "pound", "punch", "ready", "ricochet", "rocket", "rocket_heavy", "roll", "select", "shotgun", "shotgun_heavy", "slam", "slash", "slash_heavy", "step", "swap", "swing", "swing_heavy", "teleport", "teleport_warp", "unduck", "unequip", "uppercut", "uzi", "uzi_heavy", "whip", "whip_heavy", "wisp", "zap", }; void Audio::init() { if (SDL_InitSubSystem(SDL_INIT_AUDIO)) device = SDL_OpenAudioDevice(SDL_AUDIO_DEVICE_DEFAULT_PLAYBACK, nullptr); if (!device) { SDL_Log("Failed to open audio: %s", SDL_GetError()); return; } for (int i = 0; i < SOUND_COUNT; i++) { std::string path = path_get("sfx/") + SOUND_NAMES[i] + ".ogg"; Sound& sound = sounds[i]; sound.frames = stb_vorbis_decode_filename(path.c_str(), &sound.channels, &sound.rate, &sound.samples); if (sound.frames <= 0) SDL_Log("Failed to load %s", path.c_str()); } SDL_AudioSpec deviceSpec = {}; SDL_GetAudioDeviceFormat(device, &deviceSpec, nullptr); for (SDL_AudioStream*& voice : voices) { voice = SDL_CreateAudioStream(&deviceSpec, &deviceSpec); SDL_BindAudioStream(device, voice); } } // Plays a sound with a slight random pitch and returns the voice used, or -1 if it was skipped. int Audio::play(SoundId id) { if (id == SOUND_NONE) return -1; Sound& sound = sounds[id]; Uint64 now = SDL_GetTicksNS(); if (!device || sound.frames <= 0 || now - sound.played < SOUND_GAP) return -1; sound.played = now; int voice = voiceNext; for (int i = 0; i < VOICE_COUNT; i++) { if (SDL_GetAudioStreamQueued(voices[i]) == 0) { voice = i; break; } } voiceNext = (voice + 1) % VOICE_COUNT; SDL_AudioStream* stream = voices[voice]; SDL_AudioSpec spec = {SDL_AUDIO_S16, sound.channels, sound.rate}; SDL_ClearAudioStream(stream); SDL_SetAudioStreamFormat(stream, &spec, nullptr); SDL_SetAudioStreamFrequencyRatio(stream, random_get(1 - PITCH_VARIANCE, 1 + PITCH_VARIANCE)); SDL_PutAudioStreamData(stream, sound.samples, sound.frames * sound.channels * sizeof(short)); return voice; } // Sets the volume of everything played, from silent at 0 to full at 1. void Audio::gain_set(float gain) { if (device) SDL_SetAudioDeviceGain(device, gain); } void Audio::stop(int voice) { if (voice >= 0) SDL_ClearAudioStream(voices[voice]); }