Files
smack-a-brother/src/audio.cpp
T
shweetandClaude Opus 5.5 465f75f6d7 Split the simulation from presentation into an SDL-free library
Sim holds one whole game (tick, snapshots, checksum) and talks to the screen only
through Cues, which the client's Scene plays and the server ignores. All drawing
moved to scene.cpp. Match takes this screen's wishes as request flags instead of
reading the menu, input and settings. The sim library builds without SDL and the
server links it, ready to run games itself.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-08 01:46:07 -05:00

171 lines
3.7 KiB
C++

#include "audio.h"
#include "renderer.h"
#include <SDL3/SDL.h>
#include <stb_vorbis.c>
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]);
}