Compare commits
2 Commits
dff5d6fab2
...
cec347a97c
| Author | SHA1 | Date | |
|---|---|---|---|
| cec347a97c | |||
| 4f844fe17e |
@@ -20,12 +20,13 @@ set(APP_SOURCES
|
||||
src/rendering/shader_backend.cpp
|
||||
src/rendering/opengl_shader_backend.cpp
|
||||
src/rendering/sdl3gpu/sdl3gpu_shader_backend.cpp
|
||||
src/audio/jail_audio.cpp
|
||||
)
|
||||
|
||||
# Fuentes de librerías de terceros
|
||||
set(EXTERNAL_SOURCES
|
||||
third_party/glad/src/glad.c
|
||||
third_party/jail_audio.cpp
|
||||
third_party/stb_vorbis_impl.cpp
|
||||
)
|
||||
|
||||
# Configuración de SDL3
|
||||
|
||||
3
Makefile
3
Makefile
@@ -39,8 +39,9 @@ APP_SOURCES := \
|
||||
src/rendering/shader_backend.cpp \
|
||||
src/rendering/opengl_shader_backend.cpp \
|
||||
src/rendering/sdl3gpu/sdl3gpu_shader_backend.cpp \
|
||||
src/audio/jail_audio.cpp \
|
||||
third_party/glad/src/glad.c \
|
||||
third_party/jail_audio.cpp
|
||||
third_party/stb_vorbis_impl.cpp
|
||||
|
||||
# Includes
|
||||
INCLUDES := -Isrc -Ithird_party/glad/include -Ithird_party
|
||||
|
||||
578
src/audio/jail_audio.cpp
Normal file
578
src/audio/jail_audio.cpp
Normal file
@@ -0,0 +1,578 @@
|
||||
#include "audio/jail_audio.hpp"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cassert>
|
||||
#include <cstdio>
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
#include <vector>
|
||||
|
||||
// Només declaracions de stb_vorbis: STB_VORBIS_HEADER_ONLY omet el bloc
|
||||
// d'implementació. Les definicions les aporta source/external/stb_vorbis_impl.cpp
|
||||
// (TU aïllat perquè clang-analyzer no dispari fals positius al nostre codi).
|
||||
#define STB_VORBIS_HEADER_ONLY
|
||||
// clang-format off
|
||||
// NOLINTNEXTLINE(bugprone-suspicious-include)
|
||||
#include "stb_vorbis.c"
|
||||
// clang-format on
|
||||
|
||||
namespace Ja {
|
||||
|
||||
// --- Streaming internals (file-scope constants) ---
|
||||
namespace {
|
||||
// Bytes-per-sample per canal (sempre s16)
|
||||
constexpr int MUSIC_BYTES_PER_SAMPLE = 2;
|
||||
// Quants shorts decodifiquem per crida a get_samples_short_interleaved.
|
||||
// 8192 shorts = 4096 samples/channel en estèreo ≈ 85ms de so a 48kHz.
|
||||
constexpr int MUSIC_CHUNK_SHORTS = 8192;
|
||||
// Umbral d'audio per davant del cursor de reproducció. Mantenim ≥ 0.5 s a
|
||||
// l'SDL_AudioStream per absorbir jitter de frame i evitar underruns.
|
||||
constexpr float MUSIC_LOW_WATER_SECONDS = 0.5F;
|
||||
} // namespace
|
||||
|
||||
// --- Engine::active_ storage ---
|
||||
Engine* Engine::active_ = nullptr;
|
||||
|
||||
auto Engine::active() noexcept -> Engine* { return active_; }
|
||||
|
||||
// --- Ctor/Dtor ---
|
||||
|
||||
Engine::Engine(const int freq, const SDL_AudioFormat format, const int num_channels) {
|
||||
assert(active_ == nullptr && "Ja::Engine: més d'una instància activa no està suportat");
|
||||
active_ = this;
|
||||
|
||||
audio_spec_ = {.format = format, .channels = num_channels, .freq = freq};
|
||||
sdl_audio_device_ = SDL_OpenAudioDevice(SDL_AUDIO_DEVICE_DEFAULT_PLAYBACK, &audio_spec_);
|
||||
if (sdl_audio_device_ == 0) { std::fprintf(stderr, "Ja::Engine: Failed to initialize SDL audio!\n"); }
|
||||
for (auto& channel : channels_) { channel.state = ChannelState::FREE; }
|
||||
}
|
||||
|
||||
Engine::~Engine() {
|
||||
if (outgoing_music_.stream != nullptr) {
|
||||
SDL_DestroyAudioStream(outgoing_music_.stream);
|
||||
outgoing_music_.stream = nullptr;
|
||||
}
|
||||
if (sdl_audio_device_ != 0) { SDL_CloseAudioDevice(sdl_audio_device_); }
|
||||
sdl_audio_device_ = 0;
|
||||
|
||||
if (active_ == this) { active_ = nullptr; }
|
||||
}
|
||||
|
||||
// --- Helpers stateless (no toquen membres d'Engine) ---
|
||||
namespace {
|
||||
|
||||
auto feedMusicChunk(Music* music) -> int {
|
||||
if (music == nullptr || music->vorbis == nullptr || music->stream == nullptr) { return 0; }
|
||||
|
||||
short chunk[MUSIC_CHUNK_SHORTS];
|
||||
const int NUM_CHANNELS = music->spec.channels;
|
||||
const int SAMPLES_PER_CHANNEL = stb_vorbis_get_samples_short_interleaved(
|
||||
music->vorbis,
|
||||
NUM_CHANNELS,
|
||||
static_cast<short*>(chunk),
|
||||
MUSIC_CHUNK_SHORTS);
|
||||
if (SAMPLES_PER_CHANNEL <= 0) { return 0; }
|
||||
|
||||
const int BYTES = SAMPLES_PER_CHANNEL * NUM_CHANNELS * MUSIC_BYTES_PER_SAMPLE;
|
||||
SDL_PutAudioStreamData(music->stream, static_cast<const void*>(chunk), BYTES);
|
||||
return SAMPLES_PER_CHANNEL;
|
||||
}
|
||||
|
||||
void pumpMusic(Music* music) {
|
||||
if (music == nullptr || music->vorbis == nullptr || music->stream == nullptr) { return; }
|
||||
|
||||
const int BYTES_PER_SECOND = music->spec.freq * music->spec.channels * MUSIC_BYTES_PER_SAMPLE;
|
||||
const int LOW_WATER_BYTES = static_cast<int>(MUSIC_LOW_WATER_SECONDS * static_cast<float>(BYTES_PER_SECOND));
|
||||
|
||||
while (SDL_GetAudioStreamAvailable(music->stream) < LOW_WATER_BYTES) {
|
||||
const int DECODED = feedMusicChunk(music);
|
||||
if (DECODED > 0) { continue; }
|
||||
|
||||
// EOF: si queden loops, rebobinar; si no, tallar i deixar drenar.
|
||||
if (music->times != 0) {
|
||||
stb_vorbis_seek_start(music->vorbis);
|
||||
if (music->times > 0) { music->times--; }
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void preFillOutgoing(Music* music, const int duration_ms) {
|
||||
if (music == nullptr || music->vorbis == nullptr || music->stream == nullptr) { return; }
|
||||
|
||||
const int BYTES_PER_SECOND = music->spec.freq * music->spec.channels * MUSIC_BYTES_PER_SAMPLE;
|
||||
const int NEEDED_BYTES = static_cast<int>((static_cast<std::int64_t>(duration_ms) * BYTES_PER_SECOND) / 1000);
|
||||
|
||||
while (SDL_GetAudioStreamAvailable(music->stream) < NEEDED_BYTES) {
|
||||
const int DECODED = feedMusicChunk(music);
|
||||
if (DECODED <= 0) { break; }
|
||||
}
|
||||
}
|
||||
|
||||
// Retorna el progrés lineal [0..1] d'un fade. 1.0 vol dir completat. Única
|
||||
// font de la corba del fade: si es vol canviar a logarítmica/quadràtica,
|
||||
// s'edita aquí i afecta fade-in i fade-out alhora.
|
||||
auto fadeProgress(const FadeState& fade) -> float {
|
||||
if (fade.duration_ms <= 0) { return 1.0F; }
|
||||
const Uint64 ELAPSED = SDL_GetTicks() - fade.start_time;
|
||||
if (ELAPSED >= static_cast<Uint64>(fade.duration_ms)) { return 1.0F; }
|
||||
return static_cast<float>(ELAPSED) / static_cast<float>(fade.duration_ms);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
void Engine::updateOutgoingFade() {
|
||||
if (outgoing_music_.stream == nullptr || !outgoing_music_.fade.active) { return; }
|
||||
|
||||
const float PROGRESS = fadeProgress(outgoing_music_.fade);
|
||||
if (PROGRESS >= 1.0F) {
|
||||
SDL_DestroyAudioStream(outgoing_music_.stream);
|
||||
outgoing_music_.stream = nullptr;
|
||||
outgoing_music_.fade.active = false;
|
||||
} else {
|
||||
SDL_SetAudioStreamGain(outgoing_music_.stream, outgoing_music_.fade.initial_volume * (1.0F - PROGRESS));
|
||||
}
|
||||
}
|
||||
|
||||
void Engine::updateIncomingFade() {
|
||||
if (!incoming_fade_.active) { return; }
|
||||
|
||||
const float PROGRESS = fadeProgress(incoming_fade_);
|
||||
if (PROGRESS >= 1.0F) {
|
||||
incoming_fade_.active = false;
|
||||
SDL_SetAudioStreamGain(current_music_->stream, music_volume_);
|
||||
} else {
|
||||
SDL_SetAudioStreamGain(current_music_->stream, music_volume_ * PROGRESS);
|
||||
}
|
||||
}
|
||||
|
||||
void Engine::updateCurrentMusic() {
|
||||
if (current_music_ == nullptr || current_music_->state != MusicState::PLAYING) { return; }
|
||||
|
||||
updateIncomingFade();
|
||||
|
||||
pumpMusic(current_music_);
|
||||
if (current_music_->times == 0 && SDL_GetAudioStreamAvailable(current_music_->stream) == 0) {
|
||||
// La pista ha acabat de drenar naturalment. L'aturem primer (deixa
|
||||
// l'engine en estat consistent) i llavors invoquem el callback;
|
||||
// així un eventual playMusic des del callback comença net.
|
||||
stopMusic();
|
||||
if (on_music_ended_) { on_music_ended_(); }
|
||||
}
|
||||
}
|
||||
|
||||
void Engine::updateSoundChannels() {
|
||||
for (int i = 0; i < MAX_SIMULTANEOUS_CHANNELS; ++i) {
|
||||
if (channels_[i].state != ChannelState::PLAYING) { continue; }
|
||||
|
||||
if (channels_[i].times != 0) {
|
||||
if (static_cast<Uint32>(SDL_GetAudioStreamAvailable(channels_[i].stream)) < (channels_[i].sound->length / 2)) {
|
||||
SDL_PutAudioStreamData(channels_[i].stream, channels_[i].sound->buffer.get(), channels_[i].sound->length);
|
||||
if (channels_[i].times > 0) { channels_[i].times--; }
|
||||
}
|
||||
} else if (SDL_GetAudioStreamAvailable(channels_[i].stream) == 0) {
|
||||
stopChannel(i);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Engine::stealCurrentIntoOutgoing(const int duration_ms) {
|
||||
if (outgoing_music_.stream != nullptr) {
|
||||
SDL_DestroyAudioStream(outgoing_music_.stream);
|
||||
outgoing_music_.stream = nullptr;
|
||||
outgoing_music_.fade.active = false;
|
||||
}
|
||||
|
||||
if (current_music_ == nullptr || current_music_->state != MusicState::PLAYING || current_music_->stream == nullptr) {
|
||||
return;
|
||||
}
|
||||
|
||||
preFillOutgoing(current_music_, duration_ms);
|
||||
outgoing_music_.stream = current_music_->stream;
|
||||
outgoing_music_.fade = {
|
||||
.active = true,
|
||||
.start_time = SDL_GetTicks(),
|
||||
.duration_ms = duration_ms,
|
||||
.initial_volume = music_volume_,
|
||||
};
|
||||
current_music_->stream = nullptr;
|
||||
current_music_->state = MusicState::STOPPED;
|
||||
if (current_music_->vorbis != nullptr) { stb_vorbis_seek_start(current_music_->vorbis); }
|
||||
}
|
||||
|
||||
template <typename Fn>
|
||||
void Engine::forEachTargetChannel(const int channel, Fn&& fn) {
|
||||
if (channel == -1) {
|
||||
for (auto& ch : channels_) { fn(ch); }
|
||||
} else if (channel >= 0 && channel < MAX_SIMULTANEOUS_CHANNELS) {
|
||||
fn(channels_[channel]);
|
||||
}
|
||||
}
|
||||
|
||||
// --- Engine public API ---
|
||||
|
||||
void Engine::update() {
|
||||
updateOutgoingFade();
|
||||
updateCurrentMusic();
|
||||
updateSoundChannels();
|
||||
}
|
||||
|
||||
void Engine::playMusic(Music* music, const int loop) {
|
||||
if (music == nullptr || music->vorbis == nullptr) { return; }
|
||||
|
||||
stopMusic();
|
||||
|
||||
current_music_ = music;
|
||||
current_music_->state = MusicState::PLAYING;
|
||||
current_music_->times = loop;
|
||||
|
||||
stb_vorbis_seek_start(current_music_->vorbis);
|
||||
|
||||
current_music_->stream = SDL_CreateAudioStream(¤t_music_->spec, &audio_spec_);
|
||||
if (current_music_->stream == nullptr) {
|
||||
std::fprintf(stderr, "Ja::Engine::playMusic: Failed to create audio stream!\n");
|
||||
current_music_->state = MusicState::STOPPED;
|
||||
return;
|
||||
}
|
||||
SDL_SetAudioStreamGain(current_music_->stream, music_volume_);
|
||||
|
||||
pumpMusic(current_music_);
|
||||
|
||||
if (!SDL_BindAudioStream(sdl_audio_device_, current_music_->stream)) {
|
||||
std::fprintf(stderr, "Ja::Engine::playMusic: SDL_BindAudioStream failed!\n");
|
||||
}
|
||||
}
|
||||
|
||||
void Engine::pauseMusic() {
|
||||
if (current_music_ == nullptr || current_music_->state != MusicState::PLAYING) { return; }
|
||||
|
||||
current_music_->state = MusicState::PAUSED;
|
||||
SDL_UnbindAudioStream(current_music_->stream);
|
||||
}
|
||||
|
||||
void Engine::resumeMusic() {
|
||||
if (current_music_ == nullptr || current_music_->state != MusicState::PAUSED) { return; }
|
||||
|
||||
current_music_->state = MusicState::PLAYING;
|
||||
SDL_BindAudioStream(sdl_audio_device_, current_music_->stream);
|
||||
}
|
||||
|
||||
void Engine::stopMusic() {
|
||||
if (outgoing_music_.stream != nullptr) {
|
||||
SDL_DestroyAudioStream(outgoing_music_.stream);
|
||||
outgoing_music_.stream = nullptr;
|
||||
outgoing_music_.fade.active = false;
|
||||
}
|
||||
incoming_fade_.active = false;
|
||||
|
||||
if (current_music_ == nullptr || current_music_->state == MusicState::INVALID || current_music_->state == MusicState::STOPPED) { return; }
|
||||
|
||||
current_music_->state = MusicState::STOPPED;
|
||||
if (current_music_->stream != nullptr) {
|
||||
SDL_DestroyAudioStream(current_music_->stream);
|
||||
current_music_->stream = nullptr;
|
||||
}
|
||||
if (current_music_->vorbis != nullptr) {
|
||||
stb_vorbis_seek_start(current_music_->vorbis);
|
||||
}
|
||||
}
|
||||
|
||||
void Engine::fadeOutMusic(const int milliseconds) {
|
||||
if (current_music_ == nullptr || current_music_->state != MusicState::PLAYING) { return; }
|
||||
|
||||
stealCurrentIntoOutgoing(milliseconds);
|
||||
incoming_fade_.active = false;
|
||||
}
|
||||
|
||||
void Engine::crossfadeMusic(Music* music, const int crossfade_ms, const int loop) {
|
||||
if (music == nullptr || music->vorbis == nullptr) { return; }
|
||||
|
||||
stealCurrentIntoOutgoing(crossfade_ms);
|
||||
|
||||
current_music_ = music;
|
||||
current_music_->state = MusicState::PLAYING;
|
||||
current_music_->times = loop;
|
||||
|
||||
stb_vorbis_seek_start(current_music_->vorbis);
|
||||
current_music_->stream = SDL_CreateAudioStream(¤t_music_->spec, &audio_spec_);
|
||||
if (current_music_->stream == nullptr) {
|
||||
std::fprintf(stderr, "Ja::Engine::crossfadeMusic: Failed to create audio stream!\n");
|
||||
current_music_->state = MusicState::STOPPED;
|
||||
return;
|
||||
}
|
||||
SDL_SetAudioStreamGain(current_music_->stream, 0.0F);
|
||||
pumpMusic(current_music_);
|
||||
SDL_BindAudioStream(sdl_audio_device_, current_music_->stream);
|
||||
|
||||
incoming_fade_ = {
|
||||
.active = true,
|
||||
.start_time = SDL_GetTicks(),
|
||||
.duration_ms = crossfade_ms,
|
||||
.initial_volume = 0.0F,
|
||||
};
|
||||
}
|
||||
|
||||
auto Engine::getMusicState() const -> MusicState {
|
||||
if (current_music_ == nullptr) { return MusicState::INVALID; }
|
||||
return current_music_->state;
|
||||
}
|
||||
|
||||
auto Engine::setMusicVolume(float volume) -> float {
|
||||
music_volume_ = SDL_clamp(volume, 0.0F, 1.0F);
|
||||
if (current_music_ != nullptr && current_music_->stream != nullptr) {
|
||||
SDL_SetAudioStreamGain(current_music_->stream, music_volume_);
|
||||
}
|
||||
return music_volume_;
|
||||
}
|
||||
|
||||
void Engine::setOnMusicEnded(std::function<void()> callback) {
|
||||
on_music_ended_ = std::move(callback);
|
||||
}
|
||||
|
||||
void Engine::onMusicDeleted(const Music* music) {
|
||||
if (music == nullptr) { return; }
|
||||
if (current_music_ == music) {
|
||||
stopMusic();
|
||||
current_music_ = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
// --- Sound ---
|
||||
|
||||
auto Engine::playSound(Sound* sound, const int loop, const int group) -> int {
|
||||
if (sound == nullptr) { return -1; }
|
||||
|
||||
int channel = 0;
|
||||
while (channel < MAX_SIMULTANEOUS_CHANNELS && channels_[channel].state != ChannelState::FREE) { channel++; }
|
||||
if (channel == MAX_SIMULTANEOUS_CHANNELS) {
|
||||
// No hay canal libre, reemplazamos el primero
|
||||
channel = 0;
|
||||
}
|
||||
|
||||
return playSoundOnChannel(sound, channel, loop, group);
|
||||
}
|
||||
|
||||
auto Engine::playSoundOnChannel(Sound* sound, const int channel, const int loop, const int group) -> int {
|
||||
if (sound == nullptr) { return -1; }
|
||||
if (channel < 0 || channel >= MAX_SIMULTANEOUS_CHANNELS) { return -1; }
|
||||
|
||||
stopChannel(channel);
|
||||
|
||||
channels_[channel].sound = sound;
|
||||
channels_[channel].times = loop;
|
||||
channels_[channel].pos = 0;
|
||||
channels_[channel].group = group;
|
||||
channels_[channel].state = ChannelState::PLAYING;
|
||||
channels_[channel].stream = SDL_CreateAudioStream(&channels_[channel].sound->spec, &audio_spec_);
|
||||
|
||||
if (channels_[channel].stream == nullptr) {
|
||||
std::fprintf(stderr, "Ja::Engine::playSoundOnChannel: Failed to create audio stream!\n");
|
||||
channels_[channel].state = ChannelState::FREE;
|
||||
return -1;
|
||||
}
|
||||
|
||||
SDL_PutAudioStreamData(channels_[channel].stream, channels_[channel].sound->buffer.get(), channels_[channel].sound->length);
|
||||
SDL_SetAudioStreamGain(channels_[channel].stream, sound_volume_[group]);
|
||||
SDL_BindAudioStream(sdl_audio_device_, channels_[channel].stream);
|
||||
|
||||
return channel;
|
||||
}
|
||||
|
||||
void Engine::pauseChannel(const int channel) {
|
||||
forEachTargetChannel(channel, [](Channel& ch) {
|
||||
if (ch.state == ChannelState::PLAYING) {
|
||||
ch.state = ChannelState::PAUSED;
|
||||
SDL_UnbindAudioStream(ch.stream);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void Engine::resumeChannel(const int channel) {
|
||||
const SDL_AudioDeviceID DEVICE = sdl_audio_device_;
|
||||
forEachTargetChannel(channel, [DEVICE](Channel& ch) {
|
||||
if (ch.state == ChannelState::PAUSED) {
|
||||
ch.state = ChannelState::PLAYING;
|
||||
SDL_BindAudioStream(DEVICE, ch.stream);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void Engine::stopChannel(const int channel) {
|
||||
forEachTargetChannel(channel, [this](Channel& ch) {
|
||||
if (ch.state != ChannelState::FREE) {
|
||||
if (ch.stream != nullptr) { SDL_DestroyAudioStream(ch.stream); }
|
||||
ch.stream = nullptr;
|
||||
ch.state = ChannelState::FREE;
|
||||
ch.pos = 0;
|
||||
ch.sound = nullptr;
|
||||
if (ch.has_effect) {
|
||||
ch.has_effect = false;
|
||||
if (effect_channels_active_ > 0) { --effect_channels_active_; }
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
auto Engine::setSoundVolume(float volume, const int group) -> float {
|
||||
const float V = SDL_clamp(volume, 0.0F, 1.0F);
|
||||
|
||||
if (group == -1) {
|
||||
std::fill(std::begin(sound_volume_), std::end(sound_volume_), V);
|
||||
} else if (group >= 0 && group < MAX_GROUPS) {
|
||||
sound_volume_[group] = V;
|
||||
} else {
|
||||
return V;
|
||||
}
|
||||
|
||||
for (auto& ch : channels_) {
|
||||
if ((ch.state == ChannelState::PLAYING) || (ch.state == ChannelState::PAUSED)) {
|
||||
if (group == -1 || ch.group == group) {
|
||||
if (ch.stream != nullptr) {
|
||||
SDL_SetAudioStreamGain(ch.stream, sound_volume_[ch.group]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return V;
|
||||
}
|
||||
|
||||
void Engine::onSoundDeleted(const Sound* sound) {
|
||||
if (sound == nullptr) { return; }
|
||||
for (int i = 0; i < MAX_SIMULTANEOUS_CHANNELS; ++i) {
|
||||
if (channels_[i].sound == sound) { stopChannel(i); }
|
||||
}
|
||||
}
|
||||
|
||||
auto Engine::playProcessedOnFreeChannel(const std::vector<std::uint8_t>& bytes, const SDL_AudioSpec& spec, const int group) -> int {
|
||||
// El cap de canals amb efecte es valida abans de reservar slot —
|
||||
// així evitem crear i destruir un stream només per descartar el play.
|
||||
if (effect_channels_active_ >= MAX_EFFECT_CHANNELS) { return -1; }
|
||||
|
||||
int channel = 0;
|
||||
while (channel < MAX_SIMULTANEOUS_CHANNELS && channels_[channel].state != ChannelState::FREE) { ++channel; }
|
||||
if (channel == MAX_SIMULTANEOUS_CHANNELS) { channel = 0; }
|
||||
|
||||
stopChannel(channel);
|
||||
|
||||
// El stream es crea contra l'spec del buffer processat (S16, ...)
|
||||
// perquè SDL faci el resampling cap a audio_spec_ del device.
|
||||
channels_[channel].stream = SDL_CreateAudioStream(&spec, &audio_spec_);
|
||||
if (channels_[channel].stream == nullptr) {
|
||||
std::fprintf(stderr, "Ja::Engine::playProcessedOnFreeChannel: Failed to create audio stream!\n");
|
||||
return -1;
|
||||
}
|
||||
|
||||
channels_[channel].sound = nullptr; // El buffer no és propietat de cap Ja::Sound.
|
||||
channels_[channel].times = 0;
|
||||
channels_[channel].pos = 0;
|
||||
const int CLAMPED_GROUP = (group >= 0 && group < MAX_GROUPS) ? group : 0;
|
||||
channels_[channel].group = CLAMPED_GROUP;
|
||||
channels_[channel].state = ChannelState::PLAYING;
|
||||
channels_[channel].has_effect = true;
|
||||
++effect_channels_active_;
|
||||
|
||||
SDL_PutAudioStreamData(channels_[channel].stream, bytes.data(), static_cast<int>(bytes.size()));
|
||||
SDL_SetAudioStreamGain(channels_[channel].stream, sound_volume_[CLAMPED_GROUP]);
|
||||
SDL_BindAudioStream(sdl_audio_device_, channels_[channel].stream);
|
||||
|
||||
return channel;
|
||||
}
|
||||
|
||||
// shadertoy build: AudioEffects is not bundled. Both echo/reverb entry
|
||||
// points are stubbed; if an effect is requested, fall back to playing
|
||||
// the dry sound so callers don't lose audio.
|
||||
auto Engine::playSoundWithEcho(const Sound* sound, const EchoParams& /*params*/, const int group) -> int {
|
||||
if (sound == nullptr) { return -1; }
|
||||
return playSound(const_cast<Sound*>(sound), 0, group);
|
||||
}
|
||||
|
||||
auto Engine::playSoundWithReverb(const Sound* sound, const ReverbParams& /*params*/, const int group) -> int {
|
||||
if (sound == nullptr) { return -1; }
|
||||
return playSound(const_cast<Sound*>(sound), 0, group);
|
||||
}
|
||||
|
||||
// --- Factories i destructors (permanents) ---
|
||||
|
||||
auto loadMusic(const Uint8* buffer, Uint32 length) -> Music* {
|
||||
if (buffer == nullptr || length == 0) { return nullptr; }
|
||||
|
||||
// Allocem el Music primer per aprofitar el seu `std::vector<Uint8>`
|
||||
// com a propietari del OGG comprimit. stb_vorbis guarda un punter
|
||||
// persistent al buffer; com que ací no el resize'jem, el .data() és
|
||||
// estable durant tot el cicle de vida del music.
|
||||
auto music = std::make_unique<Music>();
|
||||
music->ogg_data.assign(buffer, buffer + length);
|
||||
|
||||
int vorbis_error = 0;
|
||||
music->vorbis = stb_vorbis_open_memory(music->ogg_data.data(),
|
||||
static_cast<int>(length),
|
||||
&vorbis_error,
|
||||
nullptr);
|
||||
if (music->vorbis == nullptr) {
|
||||
std::fprintf(stderr, "Ja::loadMusic: stb_vorbis_open_memory failed (error %d)\n", vorbis_error);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
const stb_vorbis_info INFO = stb_vorbis_get_info(music->vorbis);
|
||||
music->spec.channels = static_cast<int>(INFO.channels);
|
||||
music->spec.freq = static_cast<int>(INFO.sample_rate);
|
||||
music->spec.format = SDL_AUDIO_S16;
|
||||
music->state = MusicState::STOPPED;
|
||||
|
||||
return music.release();
|
||||
}
|
||||
|
||||
// Overload amb filename. Resource::Cache l'usa per registrar el path dins
|
||||
// del propi Ja::Music (camp `filename`); la capa Audio l'usa per recuperar
|
||||
// el nom després d'un playMusic(Ja::Music*, ...) — veure PATCH-02.
|
||||
auto loadMusic(const Uint8* buffer, Uint32 length, const char* filename) -> Music* {
|
||||
Music* music = loadMusic(buffer, length);
|
||||
if (music != nullptr && filename != nullptr) { music->filename = filename; }
|
||||
return music;
|
||||
}
|
||||
|
||||
void deleteMusic(Music* music) {
|
||||
if (music == nullptr) { return; }
|
||||
// Notifiquem el motor actiu perquè pari la pista si és la current_music.
|
||||
// Si no hi ha motor (shutdown-order invertit), passem: els recursos
|
||||
// propis del Music es lliberen igualment a sota.
|
||||
if (Engine* eng = Engine::active()) { eng->onMusicDeleted(music); }
|
||||
|
||||
if (music->stream != nullptr) { SDL_DestroyAudioStream(music->stream); }
|
||||
if (music->vorbis != nullptr) { stb_vorbis_close(music->vorbis); }
|
||||
delete music;
|
||||
}
|
||||
|
||||
auto loadSound(std::uint8_t* buffer, std::uint32_t size) -> Sound* {
|
||||
auto sound = std::make_unique<Sound>();
|
||||
Uint8* raw = nullptr;
|
||||
if (!SDL_LoadWAV_IO(SDL_IOFromMem(buffer, size), true, &sound->spec, &raw, &sound->length)) {
|
||||
std::fprintf(stderr, "Ja::loadSound: Failed to load WAV from memory: %s\n", SDL_GetError());
|
||||
return nullptr;
|
||||
}
|
||||
sound->buffer.reset(raw); // adopta el SDL_malloc'd buffer
|
||||
return sound.release();
|
||||
}
|
||||
|
||||
void deleteSound(Sound* sound) {
|
||||
if (sound == nullptr) { return; }
|
||||
if (Engine* eng = Engine::active()) { eng->onSoundDeleted(sound); }
|
||||
// buffer es destrueix automàticament via RAII (SdlFreeDeleter).
|
||||
delete sound;
|
||||
}
|
||||
|
||||
} // namespace Ja
|
||||
|
||||
// --- stb_vorbis macro leak cleanup ---
|
||||
// stb_vorbis.c filtra noms curts (L, C, R i PLAYBACK_*) al TU que el compila.
|
||||
// Xocarien amb paràmetres de plantilla d'altres headers si aquestes definicions
|
||||
// s'escapessin. Els netegem al final del TU per tancar la porta.
|
||||
// clang-format off
|
||||
#undef L
|
||||
#undef C
|
||||
#undef R
|
||||
#undef PLAYBACK_MONO
|
||||
#undef PLAYBACK_LEFT
|
||||
#undef PLAYBACK_RIGHT
|
||||
// clang-format on
|
||||
229
src/audio/jail_audio.hpp
Normal file
229
src/audio/jail_audio.hpp
Normal file
@@ -0,0 +1,229 @@
|
||||
#pragma once
|
||||
|
||||
// --- Includes ---
|
||||
#include <SDL3/SDL.h>
|
||||
|
||||
#include <cstdint>
|
||||
#include <functional>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
// Forward-declaració del decoder de vorbis. La implementació viu a
|
||||
// jail_audio.cpp (únic TU que compila external/stb_vorbis.c). Qualsevol caller
|
||||
// només necessita `stb_vorbis*` per punter — mai per valor — així que el
|
||||
// forward decl n'hi ha prou i evita arrossegar el .c a tots els TU.
|
||||
// NOLINTNEXTLINE(readability-identifier-naming) — nom imposat per l'API de stb_vorbis
|
||||
struct stb_vorbis;
|
||||
|
||||
// Deleter stateless per a buffers reservats amb `SDL_malloc` / `SDL_LoadWAV*`.
|
||||
// Compatible amb `std::unique_ptr<Uint8[], SdlFreeDeleter>` — zero size overhead
|
||||
// gràcies a EBO, igual que un unique_ptr amb default_delete.
|
||||
struct SdlFreeDeleter {
|
||||
void operator()(Uint8* p) const noexcept {
|
||||
if (p != nullptr) { SDL_free(p); }
|
||||
}
|
||||
};
|
||||
|
||||
// Motor de baix nivell d'àudio del projecte jailgames: streaming OGG
|
||||
// (stb_vorbis) + N canals d'efectes (SDL3 audio). No depèn d'Options ni de cap
|
||||
// singleton del joc; només de SDL3 i stb_vorbis. La capa superior (Audio) li
|
||||
// passa recursos pel punter i fa el bookkeeping d'usuari.
|
||||
namespace Ja {
|
||||
|
||||
// --- Public Enums ---
|
||||
enum class ChannelState : std::uint8_t {
|
||||
FREE,
|
||||
PLAYING,
|
||||
PAUSED,
|
||||
};
|
||||
|
||||
enum class MusicState : std::uint8_t {
|
||||
INVALID, // Music carregat però mai play-ejat
|
||||
PLAYING,
|
||||
PAUSED,
|
||||
STOPPED,
|
||||
};
|
||||
|
||||
// --- Constants ---
|
||||
inline constexpr int MAX_SIMULTANEOUS_CHANNELS = 20;
|
||||
inline constexpr int MAX_GROUPS = 2;
|
||||
// Cap superior de canals que poden estar simultàniament reproduint un so
|
||||
// amb efecte (eco/reverb). Si està al límit, les noves crides amb efecte
|
||||
// cauen al camí sec — l'usuari sent el so igualment, sense la cua.
|
||||
inline constexpr int MAX_EFFECT_CHANNELS = 4;
|
||||
|
||||
// --- Paràmetres d'efectes ---
|
||||
// Els camps els fixa el caller (Audio) llegint sounds.yaml; el motor només
|
||||
// els passa a AudioEffects::applyEcho/applyReverb. Els defaults són
|
||||
// sensats però els presets els sobreescriuen.
|
||||
struct EchoParams {
|
||||
float delay_ms{220.0F}; // Temps fins al primer rebot.
|
||||
float feedback{0.45F}; // Reinjecció (0..0.95).
|
||||
float wet{0.35F}; // Mescla humida (0..1).
|
||||
};
|
||||
|
||||
struct ReverbParams {
|
||||
float room_size{0.7F}; // Mida percebuda (0..1).
|
||||
float damping{0.5F}; // Atenuació d'aguts per rebot (0..1).
|
||||
float wet{0.4F}; // Mescla humida (0..1).
|
||||
};
|
||||
|
||||
// Spec de fallback del dispositiu. S'aplica abans que l'Engine s'iniciï i
|
||||
// com a valor inicial de Sound/Music. L'spec real d'ús l'imposa el ctor
|
||||
// d'Engine, alimentat des de Defaults::Audio via Audio.
|
||||
inline constexpr SDL_AudioSpec DEFAULT_SPEC{SDL_AUDIO_S16, 2, 48000};
|
||||
|
||||
// --- Struct Definitions ---
|
||||
struct Sound {
|
||||
SDL_AudioSpec spec{DEFAULT_SPEC};
|
||||
Uint32 length{0};
|
||||
// Buffer descomprimit (PCM) propietat del sound. Reservat per SDL_LoadWAV
|
||||
// via SDL_malloc; el deleter `SdlFreeDeleter` allibera amb SDL_free.
|
||||
std::unique_ptr<Uint8[], SdlFreeDeleter> buffer;
|
||||
};
|
||||
|
||||
// L'ordre (punters primer, ints després, enum de 8 bits al final) minimitza
|
||||
// el padding a 64-bit (evita avisos de clang-analyzer-optin.performance.Padding).
|
||||
struct Channel {
|
||||
Sound* sound{nullptr};
|
||||
SDL_AudioStream* stream{nullptr};
|
||||
int pos{0};
|
||||
int times{0};
|
||||
int group{0};
|
||||
ChannelState state{ChannelState::FREE};
|
||||
// Marca si aquest canal va arrencar amb so processat per un efecte.
|
||||
// El motor compta canals actius amb efecte per fer complir
|
||||
// MAX_EFFECT_CHANNELS i alliberar el comptador en parar.
|
||||
bool has_effect{false};
|
||||
};
|
||||
|
||||
struct Music {
|
||||
SDL_AudioSpec spec{DEFAULT_SPEC};
|
||||
|
||||
// OGG comprimit en memòria. Propietat nostra; es copia des del buffer
|
||||
// d'entrada una sola vegada en loadMusic i es descomprimix en chunks
|
||||
// per streaming. Com que stb_vorbis guarda un punter persistent al
|
||||
// `.data()` d'aquest vector, no el podem resize'jar un cop establert
|
||||
// (una reallocation invalidaria el punter que el decoder conserva).
|
||||
std::vector<Uint8> ogg_data;
|
||||
stb_vorbis* vorbis{nullptr}; // handle del decoder, viu tot el cicle del Music
|
||||
|
||||
std::string filename;
|
||||
|
||||
int times{0}; // loops restants (-1 = infinit, 0 = un sol play)
|
||||
SDL_AudioStream* stream{nullptr};
|
||||
MusicState state{MusicState::INVALID};
|
||||
};
|
||||
|
||||
struct FadeState {
|
||||
bool active{false};
|
||||
Uint64 start_time{0};
|
||||
int duration_ms{0};
|
||||
float initial_volume{0.0F};
|
||||
};
|
||||
|
||||
struct OutgoingMusic {
|
||||
SDL_AudioStream* stream{nullptr};
|
||||
FadeState fade;
|
||||
};
|
||||
|
||||
// --- Engine ---
|
||||
// Encapsula tot l'estat que abans vivia com a globals inline. Un sol Engine
|
||||
// viu per procés (enforceat via assert al ctor contra `active_`). El ctor
|
||||
// obre el device SDL; el dtor el tanca (RAII). Els deleters
|
||||
// `Ja::deleteMusic`/`Ja::deleteSound` accedeixen al motor actiu via
|
||||
// `Engine::active()` per parar canals abans d'alliberar.
|
||||
class Engine {
|
||||
public:
|
||||
Engine(int freq, SDL_AudioFormat format, int num_channels);
|
||||
~Engine();
|
||||
Engine(const Engine&) = delete;
|
||||
auto operator=(const Engine&) -> Engine& = delete;
|
||||
Engine(Engine&&) = delete;
|
||||
auto operator=(Engine&&) -> Engine& = delete;
|
||||
|
||||
// Retorna el motor actiu o nullptr si cap ha estat construït. L'usen
|
||||
// els deleters de recursos perquè no els arriba cap referència directa.
|
||||
[[nodiscard]] static auto active() noexcept -> Engine*;
|
||||
|
||||
void update();
|
||||
|
||||
// --- Música ---
|
||||
void playMusic(Music* music, int loop = -1);
|
||||
void pauseMusic();
|
||||
void resumeMusic();
|
||||
void stopMusic();
|
||||
void fadeOutMusic(int milliseconds);
|
||||
void crossfadeMusic(Music* music, int crossfade_ms, int loop = -1);
|
||||
[[nodiscard]] auto getMusicState() const -> MusicState;
|
||||
auto setMusicVolume(float volume) -> float;
|
||||
// Registra un callback que es disparà quan la música actual acabi de
|
||||
// drenar naturalment (times == 0 + stream buit). Es crida DESPRÉS de
|
||||
// stopMusic, així que el callback pot invocar playMusic sense córrer.
|
||||
// S'executa al mateix thread que Engine::update (render loop); no fer
|
||||
// operacions blocants.
|
||||
void setOnMusicEnded(std::function<void()> callback);
|
||||
// Notifica al motor que un Music s'està destruint: si és el current_music
|
||||
// s'atura abans que els seus recursos (stream/vorbis) deixin de ser vàlids.
|
||||
void onMusicDeleted(const Music* music);
|
||||
|
||||
// --- So ---
|
||||
auto playSound(Sound* sound, int loop = 0, int group = 0) -> int;
|
||||
auto playSoundOnChannel(Sound* sound, int channel, int loop = 0, int group = 0) -> int;
|
||||
// Reproducció amb so processat per un efecte. Retorna el canal
|
||||
// assignat o -1 si no queden slots d'efecte (MAX_EFFECT_CHANNELS).
|
||||
// El sound original només s'usa per consultar el spec/buffer; el
|
||||
// canal manipula el buffer ja processat (no reapunta a `sound`).
|
||||
auto playSoundWithEcho(const Sound* sound, const EchoParams& params, int group = 0) -> int;
|
||||
auto playSoundWithReverb(const Sound* sound, const ReverbParams& params, int group = 0) -> int;
|
||||
void pauseChannel(int channel);
|
||||
void resumeChannel(int channel);
|
||||
void stopChannel(int channel);
|
||||
auto setSoundVolume(float volume, int group = -1) -> float;
|
||||
// Notifica al motor que un Sound s'està destruint: els canals que el
|
||||
// referenciïn es paren abans d'alliberar el buffer.
|
||||
void onSoundDeleted(const Sound* sound);
|
||||
|
||||
private:
|
||||
void stealCurrentIntoOutgoing(int duration_ms);
|
||||
void updateOutgoingFade();
|
||||
void updateIncomingFade();
|
||||
void updateCurrentMusic();
|
||||
void updateSoundChannels();
|
||||
// Empenta un buffer ja processat (S16) a un canal lliure i el deixa
|
||||
// sonar sense bucle. Camí comú dels dos overloads playSoundWith*.
|
||||
// Retorna el canal o -1 si no queden slots.
|
||||
auto playProcessedOnFreeChannel(const std::vector<std::uint8_t>& bytes, const SDL_AudioSpec& spec, int group) -> int;
|
||||
|
||||
template <typename Fn>
|
||||
void forEachTargetChannel(int channel, Fn&& fn);
|
||||
|
||||
Music* current_music_{nullptr};
|
||||
Channel channels_[MAX_SIMULTANEOUS_CHANNELS]{};
|
||||
SDL_AudioSpec audio_spec_{DEFAULT_SPEC};
|
||||
float music_volume_{1.0F};
|
||||
float sound_volume_[MAX_GROUPS]{};
|
||||
SDL_AudioDeviceID sdl_audio_device_{0};
|
||||
OutgoingMusic outgoing_music_;
|
||||
FadeState incoming_fade_;
|
||||
std::function<void()> on_music_ended_;
|
||||
// Comptador derivat de Channel::has_effect — evita haver-lo de
|
||||
// recalcular cada vegada que algú demana un play amb efecte.
|
||||
int effect_channels_active_{0};
|
||||
|
||||
// NOLINTNEXTLINE(readability-identifier-naming) — convenció projecte: private static amb sufix _
|
||||
static Engine* active_;
|
||||
};
|
||||
|
||||
// --- Factories i destructors (permanents) ---
|
||||
// No depenen de l'estat del motor: loadMusic/loadSound només construeixen
|
||||
// objectes, deleteMusic/deleteSound consulten Engine::active() per parar
|
||||
// canals abans d'alliberar (si el motor encara viu).
|
||||
[[nodiscard]] auto loadMusic(const Uint8* buffer, Uint32 length) -> Music*;
|
||||
[[nodiscard]] auto loadMusic(const Uint8* buffer, Uint32 length, const char* filename) -> Music*;
|
||||
void deleteMusic(Music* music);
|
||||
[[nodiscard]] auto loadSound(std::uint8_t* buffer, std::uint32_t size) -> Sound*;
|
||||
void deleteSound(Sound* sound);
|
||||
|
||||
} // namespace Ja
|
||||
90
src/main.cpp
90
src/main.cpp
@@ -10,8 +10,8 @@
|
||||
|
||||
#include <SDL3/SDL.h>
|
||||
|
||||
#include "audio/jail_audio.hpp"
|
||||
#include "defines.hpp"
|
||||
#include "jail_audio.h"
|
||||
#include "rendering/shader_backend.hpp"
|
||||
|
||||
struct Logger {
|
||||
@@ -51,9 +51,11 @@ static Uint32 fps_frame_count_ = 0;
|
||||
static Uint32 fps_last_update_ticks_ = 0;
|
||||
static float current_fps_ = 0.0f;
|
||||
|
||||
static std::vector<std::filesystem::path> music_list_;
|
||||
static std::unique_ptr<Ja::Engine> audio_engine_;
|
||||
static std::vector<Ja::Music*> music_list_;
|
||||
static std::vector<std::string> music_names_;
|
||||
static size_t current_music_index_ = 0;
|
||||
static JA_Music_t* current_music_ = nullptr;
|
||||
static bool music_muted_ = false;
|
||||
|
||||
static std::vector<ShaderEntry> scanShaderDirectory(const std::filesystem::path& directory) {
|
||||
std::vector<ShaderEntry> shaders;
|
||||
@@ -89,48 +91,48 @@ static std::vector<ShaderEntry> scanShaderDirectory(const std::filesystem::path&
|
||||
return shaders;
|
||||
}
|
||||
|
||||
static std::vector<std::filesystem::path> scanMusicDirectory(const std::filesystem::path& directory) {
|
||||
std::vector<std::filesystem::path> music_files;
|
||||
|
||||
static void preloadMusicDirectory(const std::filesystem::path& directory) {
|
||||
if (!std::filesystem::exists(directory) || !std::filesystem::is_directory(directory)) {
|
||||
Logger::info("Music directory does not exist: " + directory.string());
|
||||
return music_files;
|
||||
return;
|
||||
}
|
||||
|
||||
std::vector<std::filesystem::path> ogg_paths;
|
||||
for (const auto& entry : std::filesystem::directory_iterator(directory)) {
|
||||
if (entry.is_regular_file()) {
|
||||
const auto ext = entry.path().extension().string();
|
||||
if (ext == ".ogg") {
|
||||
music_files.push_back(entry.path());
|
||||
}
|
||||
if (entry.is_regular_file() && entry.path().extension() == ".ogg") {
|
||||
ogg_paths.push_back(entry.path());
|
||||
}
|
||||
}
|
||||
std::sort(ogg_paths.begin(), ogg_paths.end());
|
||||
|
||||
std::sort(music_files.begin(), music_files.end());
|
||||
for (const auto& path : ogg_paths) {
|
||||
std::size_t size = 0;
|
||||
void* raw = SDL_LoadFile(path.string().c_str(), &size);
|
||||
if (raw == nullptr || size == 0) {
|
||||
Logger::error("Failed to read music file: " + path.string());
|
||||
if (raw != nullptr) { SDL_free(raw); }
|
||||
continue;
|
||||
}
|
||||
Ja::Music* music = Ja::loadMusic(static_cast<Uint8*>(raw), static_cast<Uint32>(size),
|
||||
path.filename().string().c_str());
|
||||
SDL_free(raw);
|
||||
if (music == nullptr) {
|
||||
Logger::error("Failed to decode OGG: " + path.string());
|
||||
continue;
|
||||
}
|
||||
music_list_.push_back(music);
|
||||
music_names_.push_back(path.filename().string());
|
||||
}
|
||||
|
||||
Logger::info("Found " + std::to_string(music_files.size()) + " music file(s) in " + directory.string());
|
||||
return music_files;
|
||||
Logger::info("Preloaded " + std::to_string(music_list_.size()) + " music file(s) from " + directory.string());
|
||||
}
|
||||
|
||||
static void playRandomMusic() {
|
||||
if (music_list_.empty()) { return; }
|
||||
|
||||
if (current_music_ != nullptr) {
|
||||
JA_DeleteMusic(current_music_);
|
||||
current_music_ = nullptr;
|
||||
}
|
||||
if (music_list_.empty() || !audio_engine_) { return; }
|
||||
|
||||
current_music_index_ = static_cast<size_t>(rand()) % music_list_.size();
|
||||
|
||||
const auto& music_path = music_list_[current_music_index_];
|
||||
current_music_ = JA_LoadMusic(music_path.string().c_str());
|
||||
|
||||
if (current_music_ != nullptr) {
|
||||
JA_PlayMusic(current_music_, 0);
|
||||
Logger::info("Now playing: " + music_path.filename().string());
|
||||
} else {
|
||||
Logger::error("Failed to load music: " + music_path.string());
|
||||
}
|
||||
audio_engine_->playMusic(music_list_[current_music_index_], 0);
|
||||
Logger::info("Now playing: " + music_names_[current_music_index_]);
|
||||
}
|
||||
|
||||
static void updateWindowTitle() {
|
||||
@@ -288,6 +290,12 @@ void handleDebugEvents(const SDL_Event& event) {
|
||||
switch (event.key.key) {
|
||||
case SDLK_F3: { toggleFullscreen(); break; }
|
||||
case SDLK_F4: { toggleVSync(); break; }
|
||||
case SDLK_M: {
|
||||
music_muted_ = !music_muted_;
|
||||
if (audio_engine_) { audio_engine_->setMusicVolume(music_muted_ ? 0.0f : 1.0f); }
|
||||
Logger::info(music_muted_ ? "Music muted" : "Music unmuted");
|
||||
break;
|
||||
}
|
||||
case SDLK_LEFT: { switchShader(-1); break; }
|
||||
case SDLK_RIGHT: { switchShader(+1); break; }
|
||||
default: break;
|
||||
@@ -368,14 +376,15 @@ int main(int argc, char** argv) {
|
||||
setFullscreenMode();
|
||||
backend_->setVSync(Options_video.vsync);
|
||||
|
||||
JA_Init(48000, SDL_AUDIO_S16LE, 2);
|
||||
audio_engine_ = std::make_unique<Ja::Engine>(48000, SDL_AUDIO_S16, 2);
|
||||
audio_engine_->setOnMusicEnded([]() { playRandomMusic(); });
|
||||
|
||||
const std::string resources_dir = getResourcesDirectory();
|
||||
|
||||
srand(static_cast<unsigned int>(time(nullptr)));
|
||||
|
||||
const std::filesystem::path music_directory = std::filesystem::path(resources_dir) / "data" / "music";
|
||||
music_list_ = scanMusicDirectory(music_directory);
|
||||
preloadMusicDirectory(music_directory);
|
||||
|
||||
if (!music_list_.empty()) {
|
||||
playRandomMusic();
|
||||
@@ -456,11 +465,7 @@ int main(int argc, char** argv) {
|
||||
updateWindowTitle();
|
||||
}
|
||||
|
||||
JA_Update();
|
||||
|
||||
if (!music_list_.empty() && JA_GetMusicState() == JA_MUSIC_STOPPED) {
|
||||
playRandomMusic();
|
||||
}
|
||||
if (audio_engine_) { audio_engine_->update(); }
|
||||
|
||||
SDL_Event e;
|
||||
while (SDL_PollEvent(&e)) {
|
||||
@@ -493,11 +498,10 @@ int main(int argc, char** argv) {
|
||||
backend_->cleanup();
|
||||
backend_.reset();
|
||||
|
||||
if (current_music_ != nullptr) {
|
||||
JA_DeleteMusic(current_music_);
|
||||
current_music_ = nullptr;
|
||||
}
|
||||
JA_Quit();
|
||||
for (Ja::Music* m : music_list_) { Ja::deleteMusic(m); }
|
||||
music_list_.clear();
|
||||
music_names_.clear();
|
||||
audio_engine_.reset();
|
||||
|
||||
SDL_DestroyWindow(window_);
|
||||
SDL_Quit();
|
||||
|
||||
477
third_party/jail_audio.cpp
vendored
477
third_party/jail_audio.cpp
vendored
@@ -1,477 +0,0 @@
|
||||
#ifndef JA_USESDLMIXER
|
||||
#include "jail_audio.h"
|
||||
|
||||
#include <SDL3/SDL.h> // Para SDL_AudioFormat, SDL_BindAudioStream, SDL_SetAudioStreamGain, SDL_PutAudioStreamData, SDL_DestroyAudioStream, SDL_GetAudioStreamAvailable, Uint8, SDL_CreateAudioStream, SDL_UnbindAudioStream, Uint32, SDL_CloseAudioDevice, SDL_GetTicks, SDL_Log, SDL_free, SDL_AudioSpec, SDL_AudioStream, SDL_IOFromMem, SDL_LoadWAV, SDL_LoadWAV_IO, SDL_OpenAudioDevice, SDL_clamp, SDL_malloc, SDL_AUDIO_DEVICE_DEFAULT_PLAYBACK, SDL_AudioDeviceID, SDL_memcpy
|
||||
#include <stdint.h> // Para uint32_t, uint8_t
|
||||
#include <stdio.h> // Para NULL, fseek, printf, fclose, fopen, fread, ftell, FILE, SEEK_END, SEEK_SET
|
||||
#include <stdlib.h> // Para free, malloc
|
||||
#include <string.h> // Para strcpy, strlen
|
||||
|
||||
#include "stb_vorbis.h" // Para stb_vorbis_decode_memory
|
||||
|
||||
#define JA_MAX_SIMULTANEOUS_CHANNELS 20
|
||||
#define JA_MAX_GROUPS 2
|
||||
|
||||
struct JA_Sound_t
|
||||
{
|
||||
SDL_AudioSpec spec { SDL_AUDIO_S16, 2, 48000 };
|
||||
Uint32 length { 0 };
|
||||
Uint8 *buffer { NULL };
|
||||
};
|
||||
|
||||
struct JA_Channel_t
|
||||
{
|
||||
JA_Sound_t *sound { nullptr };
|
||||
int pos { 0 };
|
||||
int times { 0 };
|
||||
int group { 0 };
|
||||
SDL_AudioStream *stream { nullptr };
|
||||
JA_Channel_state state { JA_CHANNEL_FREE };
|
||||
};
|
||||
|
||||
struct JA_Music_t
|
||||
{
|
||||
SDL_AudioSpec spec { SDL_AUDIO_S16, 2, 48000 };
|
||||
Uint32 length { 0 };
|
||||
Uint8 *buffer { nullptr };
|
||||
char *filename { nullptr };
|
||||
|
||||
int pos { 0 };
|
||||
int times { 0 };
|
||||
SDL_AudioStream *stream { nullptr };
|
||||
JA_Music_state state { JA_MUSIC_INVALID };
|
||||
};
|
||||
|
||||
JA_Music_t *current_music { nullptr };
|
||||
JA_Channel_t channels[JA_MAX_SIMULTANEOUS_CHANNELS];
|
||||
|
||||
SDL_AudioSpec JA_audioSpec { SDL_AUDIO_S16, 2, 48000 };
|
||||
float JA_musicVolume { 1.0f };
|
||||
float JA_soundVolume[JA_MAX_GROUPS];
|
||||
bool JA_musicEnabled { true };
|
||||
bool JA_soundEnabled { true };
|
||||
SDL_AudioDeviceID sdlAudioDevice { 0 };
|
||||
//SDL_TimerID JA_timerID { 0 };
|
||||
|
||||
bool fading = false;
|
||||
int fade_start_time;
|
||||
int fade_duration;
|
||||
int fade_initial_volume;
|
||||
|
||||
|
||||
void JA_Update()
|
||||
{
|
||||
if (JA_musicEnabled && current_music && current_music->state == JA_MUSIC_PLAYING)
|
||||
{
|
||||
if (fading) {
|
||||
int time = SDL_GetTicks();
|
||||
if (time > (fade_start_time+fade_duration)) {
|
||||
fading = false;
|
||||
JA_StopMusic();
|
||||
return;
|
||||
} else {
|
||||
const int time_passed = time - fade_start_time;
|
||||
const float percent = (float)time_passed / (float)fade_duration;
|
||||
SDL_SetAudioStreamGain(current_music->stream, JA_musicVolume*(1.0 - percent));
|
||||
}
|
||||
}
|
||||
|
||||
if (current_music->times != 0)
|
||||
{
|
||||
if ((Uint32)SDL_GetAudioStreamAvailable(current_music->stream) < (current_music->length/2)) {
|
||||
SDL_PutAudioStreamData(current_music->stream, current_music->buffer, current_music->length);
|
||||
}
|
||||
if (current_music->times>0) current_music->times--;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (SDL_GetAudioStreamAvailable(current_music->stream) == 0) JA_StopMusic();
|
||||
}
|
||||
}
|
||||
|
||||
if (JA_soundEnabled)
|
||||
{
|
||||
for (int i=0; i < JA_MAX_SIMULTANEOUS_CHANNELS; ++i)
|
||||
if (channels[i].state == JA_CHANNEL_PLAYING)
|
||||
{
|
||||
if (channels[i].times != 0)
|
||||
{
|
||||
if ((Uint32)SDL_GetAudioStreamAvailable(channels[i].stream) < (channels[i].sound->length/2)) {
|
||||
SDL_PutAudioStreamData(channels[i].stream, channels[i].sound->buffer, channels[i].sound->length);
|
||||
if (channels[i].times>0) channels[i].times--;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (SDL_GetAudioStreamAvailable(channels[i].stream) == 0) JA_StopChannel(i);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
void JA_Init(const int freq, const SDL_AudioFormat format, const int num_channels)
|
||||
{
|
||||
#ifdef DEBUG
|
||||
SDL_SetLogPriority(SDL_LOG_CATEGORY_APPLICATION, SDL_LOG_PRIORITY_DEBUG);
|
||||
#endif
|
||||
|
||||
JA_audioSpec = {format, num_channels, freq };
|
||||
if (!sdlAudioDevice) SDL_CloseAudioDevice(sdlAudioDevice);
|
||||
sdlAudioDevice = SDL_OpenAudioDevice(SDL_AUDIO_DEVICE_DEFAULT_PLAYBACK, &JA_audioSpec);
|
||||
if (sdlAudioDevice==0) SDL_Log("Failed to initialize SDL audio!");
|
||||
for (int i=0; i<JA_MAX_SIMULTANEOUS_CHANNELS; ++i) channels[i].state = JA_CHANNEL_FREE;
|
||||
for (int i=0; i<JA_MAX_GROUPS; ++i) JA_soundVolume[i] = 0.5f;
|
||||
}
|
||||
|
||||
void JA_Quit()
|
||||
{
|
||||
if (!sdlAudioDevice) SDL_CloseAudioDevice(sdlAudioDevice);
|
||||
sdlAudioDevice = 0;
|
||||
}
|
||||
|
||||
JA_Music_t *JA_LoadMusic(Uint8* buffer, Uint32 length)
|
||||
{
|
||||
JA_Music_t *music = new JA_Music_t();
|
||||
|
||||
int chan, samplerate;
|
||||
short *output;
|
||||
music->length = stb_vorbis_decode_memory(buffer, length, &chan, &samplerate, &output) * chan * 2;
|
||||
|
||||
music->spec.channels = chan;
|
||||
music->spec.freq = samplerate;
|
||||
music->spec.format = SDL_AUDIO_S16;
|
||||
music->buffer = (Uint8*)SDL_malloc(music->length);
|
||||
SDL_memcpy(music->buffer, output, music->length);
|
||||
free(output);
|
||||
music->pos = 0;
|
||||
music->state = JA_MUSIC_STOPPED;
|
||||
|
||||
return music;
|
||||
}
|
||||
|
||||
JA_Music_t *JA_LoadMusic(const char* filename)
|
||||
{
|
||||
// [RZC 28/08/22] Carreguem primer el arxiu en memòria i després el descomprimim. Es algo més rapid.
|
||||
FILE *f = fopen(filename, "rb");
|
||||
fseek(f, 0, SEEK_END);
|
||||
long fsize = ftell(f);
|
||||
fseek(f, 0, SEEK_SET);
|
||||
Uint8 *buffer = (Uint8*)malloc(fsize + 1);
|
||||
if (fread(buffer, fsize, 1, f)!=1) return NULL;
|
||||
fclose(f);
|
||||
|
||||
JA_Music_t *music = JA_LoadMusic(buffer, fsize);
|
||||
music->filename = (char*)malloc(strlen(filename)+1);
|
||||
strcpy(music->filename, filename);
|
||||
|
||||
free(buffer);
|
||||
|
||||
return music;
|
||||
}
|
||||
|
||||
void JA_PlayMusic(JA_Music_t *music, const int loop)
|
||||
{
|
||||
if (!JA_musicEnabled) return;
|
||||
|
||||
JA_StopMusic();
|
||||
|
||||
current_music = music;
|
||||
current_music->pos = 0;
|
||||
current_music->state = JA_MUSIC_PLAYING;
|
||||
current_music->times = loop;
|
||||
|
||||
current_music->stream = SDL_CreateAudioStream(¤t_music->spec, &JA_audioSpec);
|
||||
if (!SDL_PutAudioStreamData(current_music->stream, current_music->buffer, current_music->length)) printf("[ERROR] SDL_PutAudioStreamData failed!\n");
|
||||
SDL_SetAudioStreamGain(current_music->stream, JA_musicVolume);
|
||||
if (!SDL_BindAudioStream(sdlAudioDevice, current_music->stream)) printf("[ERROR] SDL_BindAudioStream failed!\n");
|
||||
//SDL_ResumeAudioStreamDevice(current_music->stream);
|
||||
}
|
||||
|
||||
char *JA_GetMusicFilename(JA_Music_t *music)
|
||||
{
|
||||
if (!music) music = current_music;
|
||||
return music->filename;
|
||||
}
|
||||
|
||||
void JA_PauseMusic()
|
||||
{
|
||||
if (!JA_musicEnabled) return;
|
||||
if (!current_music || current_music->state == JA_MUSIC_INVALID) return;
|
||||
|
||||
current_music->state = JA_MUSIC_PAUSED;
|
||||
//SDL_PauseAudioStreamDevice(current_music->stream);
|
||||
SDL_UnbindAudioStream(current_music->stream);
|
||||
}
|
||||
|
||||
void JA_ResumeMusic()
|
||||
{
|
||||
if (!JA_musicEnabled) return;
|
||||
if (!current_music || current_music->state == JA_MUSIC_INVALID) return;
|
||||
|
||||
current_music->state = JA_MUSIC_PLAYING;
|
||||
//SDL_ResumeAudioStreamDevice(current_music->stream);
|
||||
SDL_BindAudioStream(sdlAudioDevice, current_music->stream);
|
||||
}
|
||||
|
||||
void JA_StopMusic()
|
||||
{
|
||||
if (!JA_musicEnabled) return;
|
||||
if (!current_music || current_music->state == JA_MUSIC_INVALID) return;
|
||||
|
||||
current_music->pos = 0;
|
||||
current_music->state = JA_MUSIC_STOPPED;
|
||||
//SDL_PauseAudioStreamDevice(current_music->stream);
|
||||
SDL_DestroyAudioStream(current_music->stream);
|
||||
current_music->stream = nullptr;
|
||||
free(current_music->filename);
|
||||
current_music->filename = nullptr;
|
||||
}
|
||||
|
||||
void JA_FadeOutMusic(const int milliseconds)
|
||||
{
|
||||
if (!JA_musicEnabled) return;
|
||||
if (current_music == NULL || current_music->state == JA_MUSIC_INVALID) return;
|
||||
|
||||
fading = true;
|
||||
fade_start_time = SDL_GetTicks();
|
||||
fade_duration = milliseconds;
|
||||
fade_initial_volume = JA_musicVolume;
|
||||
}
|
||||
|
||||
JA_Music_state JA_GetMusicState()
|
||||
{
|
||||
if (!JA_musicEnabled) return JA_MUSIC_DISABLED;
|
||||
if (!current_music) return JA_MUSIC_INVALID;
|
||||
|
||||
return current_music->state;
|
||||
}
|
||||
|
||||
void JA_DeleteMusic(JA_Music_t *music)
|
||||
{
|
||||
if (current_music == music) current_music = nullptr;
|
||||
SDL_free(music->buffer);
|
||||
if (music->stream) SDL_DestroyAudioStream(music->stream);
|
||||
delete music;
|
||||
}
|
||||
|
||||
float JA_SetMusicVolume(float volume)
|
||||
{
|
||||
JA_musicVolume = SDL_clamp( volume, 0.0f, 1.0f );
|
||||
if (current_music) SDL_SetAudioStreamGain(current_music->stream, JA_musicVolume);
|
||||
return JA_musicVolume;
|
||||
}
|
||||
|
||||
void JA_SetMusicPosition(float value)
|
||||
{
|
||||
if (!current_music) return;
|
||||
current_music->pos = value * current_music->spec.freq;
|
||||
}
|
||||
|
||||
float JA_GetMusicPosition()
|
||||
{
|
||||
if (!current_music) return 0;
|
||||
return float(current_music->pos)/float(current_music->spec.freq);
|
||||
}
|
||||
|
||||
void JA_EnableMusic(const bool value)
|
||||
{
|
||||
if ( !value && current_music && (current_music->state==JA_MUSIC_PLAYING) ) JA_StopMusic();
|
||||
|
||||
JA_musicEnabled = value;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
JA_Sound_t *JA_NewSound(Uint8* buffer, Uint32 length)
|
||||
{
|
||||
JA_Sound_t *sound = new JA_Sound_t();
|
||||
sound->buffer = buffer;
|
||||
sound->length = length;
|
||||
return sound;
|
||||
}
|
||||
|
||||
JA_Sound_t *JA_LoadSound(uint8_t* buffer, uint32_t size)
|
||||
{
|
||||
JA_Sound_t *sound = new JA_Sound_t();
|
||||
SDL_LoadWAV_IO(SDL_IOFromMem(buffer, size),1, &sound->spec, &sound->buffer, &sound->length);
|
||||
|
||||
return sound;
|
||||
}
|
||||
|
||||
JA_Sound_t *JA_LoadSound(const char* filename)
|
||||
{
|
||||
JA_Sound_t *sound = new JA_Sound_t();
|
||||
SDL_LoadWAV(filename, &sound->spec, &sound->buffer, &sound->length);
|
||||
|
||||
return sound;
|
||||
}
|
||||
|
||||
int JA_PlaySound(JA_Sound_t *sound, const int loop, const int group)
|
||||
{
|
||||
if (!JA_soundEnabled) return -1;
|
||||
|
||||
int channel = 0;
|
||||
while (channel < JA_MAX_SIMULTANEOUS_CHANNELS && channels[channel].state != JA_CHANNEL_FREE) { channel++; }
|
||||
if (channel == JA_MAX_SIMULTANEOUS_CHANNELS) channel = 0;
|
||||
JA_StopChannel(channel);
|
||||
|
||||
channels[channel].sound = sound;
|
||||
channels[channel].times = loop;
|
||||
channels[channel].pos = 0;
|
||||
channels[channel].state = JA_CHANNEL_PLAYING;
|
||||
channels[channel].stream = SDL_CreateAudioStream(&channels[channel].sound->spec, &JA_audioSpec);
|
||||
SDL_PutAudioStreamData(channels[channel].stream, channels[channel].sound->buffer, channels[channel].sound->length);
|
||||
SDL_SetAudioStreamGain(channels[channel].stream, JA_soundVolume[group]);
|
||||
SDL_BindAudioStream(sdlAudioDevice, channels[channel].stream);
|
||||
|
||||
return channel;
|
||||
}
|
||||
|
||||
int JA_PlaySoundOnChannel(JA_Sound_t *sound, const int channel, const int loop, const int group)
|
||||
{
|
||||
if (!JA_soundEnabled) return -1;
|
||||
|
||||
if (channel < 0 || channel >= JA_MAX_SIMULTANEOUS_CHANNELS) return -1;
|
||||
JA_StopChannel(channel);
|
||||
|
||||
channels[channel].sound = sound;
|
||||
channels[channel].times = loop;
|
||||
channels[channel].pos = 0;
|
||||
channels[channel].state = JA_CHANNEL_PLAYING;
|
||||
channels[channel].stream = SDL_CreateAudioStream(&channels[channel].sound->spec, &JA_audioSpec);
|
||||
SDL_PutAudioStreamData(channels[channel].stream, channels[channel].sound->buffer, channels[channel].sound->length);
|
||||
SDL_SetAudioStreamGain(channels[channel].stream, JA_soundVolume[group]);
|
||||
SDL_BindAudioStream(sdlAudioDevice, channels[channel].stream);
|
||||
|
||||
return channel;
|
||||
}
|
||||
|
||||
void JA_DeleteSound(JA_Sound_t *sound)
|
||||
{
|
||||
for (int i = 0; i < JA_MAX_SIMULTANEOUS_CHANNELS; i++) {
|
||||
if (channels[i].sound == sound) JA_StopChannel(i);
|
||||
}
|
||||
SDL_free(sound->buffer);
|
||||
delete sound;
|
||||
}
|
||||
|
||||
void JA_PauseChannel(const int channel)
|
||||
{
|
||||
if (!JA_soundEnabled) return;
|
||||
|
||||
if (channel == -1)
|
||||
{
|
||||
for (int i = 0; i < JA_MAX_SIMULTANEOUS_CHANNELS; i++)
|
||||
if (channels[i].state == JA_CHANNEL_PLAYING)
|
||||
{
|
||||
channels[i].state = JA_CHANNEL_PAUSED;
|
||||
//SDL_PauseAudioStreamDevice(channels[i].stream);
|
||||
SDL_UnbindAudioStream(channels[i].stream);
|
||||
}
|
||||
}
|
||||
else if (channel >= 0 && channel < JA_MAX_SIMULTANEOUS_CHANNELS)
|
||||
{
|
||||
if (channels[channel].state == JA_CHANNEL_PLAYING)
|
||||
{
|
||||
channels[channel].state = JA_CHANNEL_PAUSED;
|
||||
//SDL_PauseAudioStreamDevice(channels[channel].stream);
|
||||
SDL_UnbindAudioStream(channels[channel].stream);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void JA_ResumeChannel(const int channel)
|
||||
{
|
||||
if (!JA_soundEnabled) return;
|
||||
|
||||
if (channel == -1)
|
||||
{
|
||||
for (int i = 0; i < JA_MAX_SIMULTANEOUS_CHANNELS; i++)
|
||||
if (channels[i].state == JA_CHANNEL_PAUSED)
|
||||
{
|
||||
channels[i].state = JA_CHANNEL_PLAYING;
|
||||
//SDL_ResumeAudioStreamDevice(channels[i].stream);
|
||||
SDL_BindAudioStream(sdlAudioDevice, channels[i].stream);
|
||||
}
|
||||
}
|
||||
else if (channel >= 0 && channel < JA_MAX_SIMULTANEOUS_CHANNELS)
|
||||
{
|
||||
if (channels[channel].state == JA_CHANNEL_PAUSED)
|
||||
{
|
||||
channels[channel].state = JA_CHANNEL_PLAYING;
|
||||
//SDL_ResumeAudioStreamDevice(channels[channel].stream);
|
||||
SDL_BindAudioStream(sdlAudioDevice, channels[channel].stream);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void JA_StopChannel(const int channel)
|
||||
{
|
||||
if (!JA_soundEnabled) return;
|
||||
|
||||
if (channel == -1)
|
||||
{
|
||||
for (int i = 0; i < JA_MAX_SIMULTANEOUS_CHANNELS; i++) {
|
||||
if (channels[i].state != JA_CHANNEL_FREE) SDL_DestroyAudioStream(channels[i].stream);
|
||||
channels[i].stream = nullptr;
|
||||
channels[i].state = JA_CHANNEL_FREE;
|
||||
channels[i].pos = 0;
|
||||
channels[i].sound = NULL;
|
||||
}
|
||||
}
|
||||
else if (channel >= 0 && channel < JA_MAX_SIMULTANEOUS_CHANNELS)
|
||||
{
|
||||
if (channels[channel].state != JA_CHANNEL_FREE) SDL_DestroyAudioStream(channels[channel].stream);
|
||||
channels[channel].stream = nullptr;
|
||||
channels[channel].state = JA_CHANNEL_FREE;
|
||||
channels[channel].pos = 0;
|
||||
channels[channel].sound = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
JA_Channel_state JA_GetChannelState(const int channel)
|
||||
{
|
||||
if (!JA_soundEnabled) return JA_SOUND_DISABLED;
|
||||
|
||||
if (channel < 0 || channel >= JA_MAX_SIMULTANEOUS_CHANNELS) return JA_CHANNEL_INVALID;
|
||||
|
||||
return channels[channel].state;
|
||||
}
|
||||
|
||||
float JA_SetSoundVolume(float volume, const int group)
|
||||
{
|
||||
const float v = SDL_clamp( volume, 0.0f, 1.0f );
|
||||
for (int i = 0; i < JA_MAX_GROUPS; ++i) {
|
||||
if (group==-1 || group==i) JA_soundVolume[i]=v;
|
||||
}
|
||||
|
||||
for (int i = 0; i < JA_MAX_SIMULTANEOUS_CHANNELS; i++)
|
||||
if ( ((channels[i].state == JA_CHANNEL_PLAYING) || (channels[i].state == JA_CHANNEL_PAUSED)) &&
|
||||
((group==-1) || (channels[i].group==group)) )
|
||||
SDL_SetAudioStreamGain(channels[i].stream, JA_soundVolume[i]);
|
||||
|
||||
return v;
|
||||
}
|
||||
|
||||
void JA_EnableSound(const bool value)
|
||||
{
|
||||
for (int i = 0; i < JA_MAX_SIMULTANEOUS_CHANNELS; i++)
|
||||
{
|
||||
if (channels[i].state == JA_CHANNEL_PLAYING) JA_StopChannel(i);
|
||||
}
|
||||
JA_soundEnabled = value;
|
||||
}
|
||||
|
||||
float JA_SetVolume(float volume)
|
||||
{
|
||||
JA_SetSoundVolume(JA_SetMusicVolume(volume) / 2.0f);
|
||||
|
||||
return JA_musicVolume;
|
||||
}
|
||||
|
||||
#endif
|
||||
43
third_party/jail_audio.h
vendored
43
third_party/jail_audio.h
vendored
@@ -1,43 +0,0 @@
|
||||
#pragma once
|
||||
#include <SDL3/SDL.h>
|
||||
|
||||
enum JA_Channel_state { JA_CHANNEL_INVALID, JA_CHANNEL_FREE, JA_CHANNEL_PLAYING, JA_CHANNEL_PAUSED, JA_SOUND_DISABLED };
|
||||
enum JA_Music_state { JA_MUSIC_INVALID, JA_MUSIC_PLAYING, JA_MUSIC_PAUSED, JA_MUSIC_STOPPED, JA_MUSIC_DISABLED };
|
||||
|
||||
struct JA_Sound_t;
|
||||
struct JA_Music_t;
|
||||
|
||||
void JA_Update();
|
||||
|
||||
void JA_Init(const int freq, const SDL_AudioFormat format, const int num_channels);
|
||||
void JA_Quit();
|
||||
|
||||
JA_Music_t *JA_LoadMusic(const char* filename);
|
||||
JA_Music_t *JA_LoadMusic(Uint8* buffer, Uint32 length);
|
||||
void JA_PlayMusic(JA_Music_t *music, const int loop = -1);
|
||||
char *JA_GetMusicFilename(JA_Music_t *music = nullptr);
|
||||
void JA_PauseMusic();
|
||||
void JA_ResumeMusic();
|
||||
void JA_StopMusic();
|
||||
void JA_FadeOutMusic(const int milliseconds);
|
||||
JA_Music_state JA_GetMusicState();
|
||||
void JA_DeleteMusic(JA_Music_t *music);
|
||||
float JA_SetMusicVolume(float volume);
|
||||
void JA_SetMusicPosition(float value);
|
||||
float JA_GetMusicPosition();
|
||||
void JA_EnableMusic(const bool value);
|
||||
|
||||
JA_Sound_t *JA_NewSound(Uint8* buffer, Uint32 length);
|
||||
JA_Sound_t *JA_LoadSound(Uint8* buffer, Uint32 length);
|
||||
JA_Sound_t *JA_LoadSound(const char* filename);
|
||||
int JA_PlaySound(JA_Sound_t *sound, const int loop = 0, const int group=0);
|
||||
int JA_PlaySoundOnChannel(JA_Sound_t *sound, const int channel, const int loop = 0, const int group=0);
|
||||
void JA_PauseChannel(const int channel);
|
||||
void JA_ResumeChannel(const int channel);
|
||||
void JA_StopChannel(const int channel);
|
||||
JA_Channel_state JA_GetChannelState(const int channel);
|
||||
void JA_DeleteSound(JA_Sound_t *sound);
|
||||
float JA_SetSoundVolume(float volume, const int group=0);
|
||||
void JA_EnableSound(const bool value);
|
||||
|
||||
float JA_SetVolume(float volume);
|
||||
7
third_party/stb_vorbis_impl.cpp
vendored
Normal file
7
third_party/stb_vorbis_impl.cpp
vendored
Normal file
@@ -0,0 +1,7 @@
|
||||
// Isolated TU for the stb_vorbis implementation.
|
||||
// jail_audio.cpp defines STB_VORBIS_HEADER_ONLY before including stb_vorbis.c
|
||||
// (it only sees declarations there); this TU provides the definitions and
|
||||
// the linker resolves them.
|
||||
|
||||
// NOLINTNEXTLINE(bugprone-suspicious-include)
|
||||
#include "stb_vorbis.c"
|
||||
Reference in New Issue
Block a user