Audio¶
AudioManager provides simple playback for sound effects and background music.
Supported APIs:
- Desktop: OpenAL (vendored OpenAL Soft preferred when available)
- Web: Emscripten OpenAL
- iOS: statically linked vendored OpenAL Soft
- Android: statically linked vendored OpenAL Soft with its Oboe backend
Supported file formats:
- .wav
- .ogg
- .mp3
- .flac
Sample Usage¶
auto* audio = context()->audioManager();
if (audio) {
audio->playCooldown = 0.08f; // optional cooldown per sound path
const auto sfx = audio->play("audio/cat_meow.ogg");
const auto loopSfx = audio->play("audio/wind.ogg", true); // looping SFX instance
audio->playBgm("audio/bgm.mp3", 0.8f, 1.0f); // volume, pitch
audio->setBgmPlaybackSpeed(1.1f);
audio->mute();
audio->unmute();
audio->stop(sfx);
audio->stop(loopSfx); // stop a specific SFX instance
if (audio->isBgmPlaying()) {
audio->stopBgm();
}
}
playCooldown throttles repeated play() calls for the same path (in seconds). Set it to 0.0f to disable.
play() returns an AudioHandle; when playback does not start it returns AudioManager::kInvalidAudioHandle.
mute() silences all audio without interrupting playback. unmute() restores audio output.
Layered Mute State¶
Snowpulse tracks independent mute reasons so one subsystem cannot accidentally unmute another:
AudioMuteSource::Application—mute()/unmute()and game settingsAudioMuteSource::Advertisement— game-side ad flowAudioMuteSource::Portal— portal audio preferenceAudioMuteSource::PortalPause— temporary portal pauseAudioMuteSource::SystemLifecycle— Activity/app suspensionAudioMuteSource::AudioFocus— Android focus loss
audio->setMuted(snowpulse::AudioMuteSource::Advertisement, true);
// ...show an ad...
audio->setMuted(snowpulse::AudioMuteSource::Advertisement, false);
if (audio->isMuted(snowpulse::AudioMuteSource::Portal)) {
// The host portal still requires silence.
}
isMuted() without an argument reports whether any source is active.
mute() and unmute() only change the Application source. CrazyGames,
Playgama, and YouTube portal adapters update their own portal sources.
Loading, Unloading, and Output Latency¶
play() and playBgm() load files on demand. Use load(path) to warm the
decoded-audio cache, unload(path) to release one entry, and unloadAll() to
clear it. Stop any active playback that depends on an entry before unloading
it.
preferredOutputBufferFrames(frames) requests a backend buffer size and must
be called before AudioManager::init(). Call outputBufferFrames() after
initialization to inspect the effective size:
bool init() override {
context()->audioManager()->preferredOutputBufferFrames(256);
if (!snowpulse::App::init()) {
return false;
}
std::printf("Audio buffer: %d frames\n",
context()->audioManager()->outputBufferFrames());
return true;
}
The backend may choose a different effective value. This is a latency/CPU tuning control, not a portable guarantee.
On Android, audio is loaded from packaged assets rather than extracted files.
Short effects use cached OpenAL buffers and BGM feeds queued streaming buffers
from a seekable decoder. Activity pause/resume and Android audio focus are
independent mute sources; Oboe reopens output across Bluetooth/headset route
changes. Keep long audio extensions in Gradle's noCompress list.
Physical volume buttons are handled by Android rather than Snowpulse input.
While the game Activity is foregrounded, they target the media/music stream
and display the normal system volume overlay. This device volume is independent
of AudioManager::mute(), per-sound gain, and the layered mute sources above.
Desktop Recording¶
On macOS 13 or newer, the desktop MP4 recorder captures the final process audio produced by OpenAL, including BGM, sound effects, volume, pitch, looping, and source stopping. It encodes that mix as 48 kHz stereo AAC. This uses ScreenCaptureKit and therefore requests macOS Screen Recording permission on the first audio-enabled recording.
Audio capture is enabled inside an opt-in recorder configuration. Disable it when video-only output or permission-free recording is preferred:
auto recorder = context()->desktopRecorderConfig();
recorder.captureAudio = false;
context()->desktopRecorderConfig(recorder);
Denied permission and macOS 11-12 automatically fall back to video-only MP4.