#if BACKEND == SDL3 #include "backends/backend.h" #include "external/stb_vorbis.h" #include "other/log.h" #include #include #include // structs i variables // ============================= namespace backend { namespace audio { static SDL_AudioSpec audioSpec { SDL_AUDIO_S16, 2, 48000 }; SDL_AudioDeviceID sdlAudioDevice { 0 }; SDL_TimerID timerID { 0 }; namespace music { struct music_t { SDL_AudioSpec spec { SDL_AUDIO_S16, 2, 48000 }; SDL_AudioStream* stream { nullptr }; const uint8_t* ogg_data { nullptr }; uint32_t ogg_length { 0 }; stb_vorbis* vorbis { nullptr }; stb_vorbis_info info {}; int times { 0 }; music::state state { music::state::invalid }; }; static int current { -1 }; static std::vector musics; static float volume { 1.0f }; static bool enabled { true }; namespace fade { static bool fading = false; static int start_time; static int duration; static int initial_volume; } } namespace sound { struct sound_t { SDL_AudioSpec spec { SDL_AUDIO_S16, 2, 48000 }; Uint32 length { 0 }; Uint8 *buffer { NULL }; }; static std::vector sounds; static float volume { 0.5f }; static bool enabled { true }; namespace channel { struct channel_t { int sound { -1 }; int pos { 0 }; int times { 0 }; SDL_AudioStream *stream { nullptr }; channel::state state { channel::state::free }; }; static std::vector channels; } } } } // Funcions // ================== namespace backend { namespace audio { static void updateMusic() { if (!music::enabled) return; if (music::current < 0) return; auto& m = music::musics[music::current]; if (m.state != music::state::playing) return; // Fade-out if (music::fade::fading) { int time = SDL_GetTicks(); if (time > (music::fade::start_time + music::fade::duration)) { music::fade::fading = false; music::stop(); return; } else { float percent = float(time - music::fade::start_time) / float(music::fade::duration); SDL_SetAudioStreamGain(m.stream, 1.0f - percent); } } // ¿Hay suficiente audio en el stream? if (SDL_GetAudioStreamAvailable(m.stream) > 48000) return; // 1 segundo // Decodificar un bloque const int SAMPLES = 4096; static int16_t temp[SAMPLES * 2]; int n = stb_vorbis_get_samples_short_interleaved( m.vorbis, m.info.channels, temp, SAMPLES ); if (n == 0) { if (m.times != 0) { stb_vorbis_seek_start(m.vorbis); if (m.times > 0) m.times--; return; } else { music::stop(); return; } } SDL_PutAudioStreamData(m.stream, temp, n * m.info.channels * sizeof(int16_t)); } static void updateSound() { if (sound::enabled) { for (int i=0; i < static_cast(sound::channel::channels.size()); ++i) { auto &c = sound::channel::channels[i]; if (c.state == sound::channel::state::playing) { if (c.times != 0) { auto &s = sound::sounds[c.sound]; if (SDL_GetAudioStreamAvailable(c.stream) < static_cast(s.length/2)) SDL_PutAudioStreamData(c.stream, s.buffer, s.length); if (c.times>0) c.times--; } } else { if (SDL_GetAudioStreamAvailable(c.stream) == 0) sound::channel::stop(i); } } } } Uint32 updateCallback(void *userdata, SDL_TimerID timerID, Uint32 interval) { updateMusic(); updateSound(); return 30; } void init() { #ifdef DEBUG SDL_SetLogPriority(SDL_LOG_CATEGORY_APPLICATION, SDL_LOG_PRIORITY_DEBUG); #endif audioSpec = {SDL_AUDIO_S16, 1, 48000 }; if (!sdlAudioDevice) SDL_CloseAudioDevice(sdlAudioDevice); sdlAudioDevice = SDL_OpenAudioDevice(SDL_AUDIO_DEVICE_DEFAULT_PLAYBACK, &audioSpec); if (!sdlAudioDevice) { log_msg(LOG_FAIL, "Failed to initialize SDL audio: %s\n", SDL_GetError()); } else { log_msg(LOG_OK, "Audio subsytem initialized\n"); } //SDL_PauseAudioDevice(sdlAudioDevice); timerID = SDL_AddTimer(30, updateCallback, nullptr); } void quit() { if (timerID) SDL_RemoveTimer(timerID); for (int i=0; i(music::musics.size());++i) music::destroy(i); music::musics.clear(); for (int i=0; i(sound::channel::channels.size());++i) sound::channel::stop(i); sound::channel::channels.clear(); for (int i=0; i(sound::sounds.size());++i) sound::destroy(i); sound::sounds.clear(); if (!sdlAudioDevice) SDL_CloseAudioDevice(sdlAudioDevice); sdlAudioDevice = 0; } float setVolume(float vol) { sound::setVolume(music::setVolume(vol) / 2.0f); return music::volume; } namespace music { int load(const uint8_t* buffer, uint32_t length) { int idx = 0; while (idx < (int)musics.size() && musics[idx].state != state::invalid) idx++; if (idx == (int)musics.size()) musics.emplace_back(); auto& m = musics[idx]; m.ogg_data = buffer; m.ogg_length = length; int error; m.vorbis = stb_vorbis_open_memory(buffer, length, &error, nullptr); if (!m.vorbis) { log_msg(LOG_FAIL, "stb_vorbis_open_memory failed: %d\n", error); return -1; } m.info = stb_vorbis_get_info(m.vorbis); m.spec.channels = m.info.channels; m.spec.freq = m.info.sample_rate; m.spec.format = SDL_AUDIO_S16; m.state = state::stopped; return idx; } int load(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 -1; fclose(f); int music = load(buffer, fsize); free(buffer); return music; } void play(int mus, int loop) { stop(); current = mus; auto& m = musics[mus]; m.times = loop; m.state = state::playing; stb_vorbis_seek_start(m.vorbis); m.stream = SDL_CreateAudioStream(&m.spec, &audioSpec); SDL_SetAudioStreamGain(m.stream, volume); SDL_BindAudioStream(sdlAudioDevice, m.stream); } void pause() { if (!music::enabled || (current<0)) return; if (current>static_cast(musics.size())) { log_msg(LOG_FAIL, "music::pause: Illegal music handle: %i\n", current); return; } auto &m = musics[current]; if (m.state == state::invalid) { log_msg(LOG_FAIL, "music::pause: Invalidated music handle: %i\n", current); return; } m.state = state::paused; //SDL_PauseAudioStreamDevice(current->stream); SDL_UnbindAudioStream(m.stream); } void resume() { if (!music::enabled || (current<0)) return; if (current>static_cast(musics.size())) { log_msg(LOG_FAIL, "music::resume: Illegal music handle: %i\n", current); return; } auto &m = musics[current]; if (m.state == state::invalid) { log_msg(LOG_FAIL, "music::resume: Invalidated music handle: %i\n", current); return; } m.state = state::playing; //SDL_ResumeAudioStreamDevice(current->stream); SDL_BindAudioStream(sdlAudioDevice, m.stream); } void stop() { if (current < 0) return; auto& m = musics[current]; m.state = state::stopped; if (m.stream) SDL_DestroyAudioStream(m.stream); m.stream = nullptr; } void fadeOut(int milliseconds) { if (!music::enabled || (current<0)) return; if (current>static_cast(musics.size())) { log_msg(LOG_FAIL, "music::fadeOut: Illegal music handle: %i\n", current); return; } auto &m = musics[current]; if (m.state == state::invalid) { log_msg(LOG_FAIL, "music::fadeOut: Invalidated music handle: %i\n", current); return; } fade::fading = true; fade::start_time = SDL_GetTicks(); fade::duration = milliseconds; fade::initial_volume = volume; } music::state getState() { if (!music::enabled) return music::state::disabled; if (current<0 || current>static_cast(musics.size())) return state::invalid; return musics[current].state; } void destroy(int mus) { if (mus < 0 || mus >= (int)musics.size()) return; auto& m = musics[mus]; if (m.stream) SDL_DestroyAudioStream(m.stream); if (m.vorbis) stb_vorbis_close(m.vorbis); m.stream = nullptr; m.vorbis = nullptr; m.state = state::invalid; } float setVolume(float vol) { volume = SDL_clamp( vol, 0.0f, 1.0f ); if (current<0 || current>static_cast(musics.size())) return vol; auto &m = musics[current]; if (m.state == state::invalid) return vol; SDL_SetAudioStreamGain(m.stream, volume); return volume; } void setPosition(float value) { auto& m = musics[current]; int sample = int(value * m.info.sample_rate); if (stb_vorbis_seek(m.vorbis, sample)) { SDL_ClearAudioStream(m.stream); // importante } } float getPosition() { if (!music::enabled) return 0; if (current<0 || current>static_cast(musics.size())) { log_msg(LOG_FAIL, "music::getPosition: Illegal music handle: %i\n", current); return 0; } auto &m = musics[current]; if (m.state == state::invalid) { log_msg(LOG_FAIL, "music::getPosition: Invalidated music handle: %i\n", current); return 0; } int sample = stb_vorbis_get_sample_offset(m.vorbis); return float(sample) / float(m.info.sample_rate); } void enable(bool value) { if (!value && music::enabled && current>=0 && current(musics.size()) && musics[current].state==state::playing) stop(); music::enabled = value; } bool isEnabled() { return enabled; } } namespace sound { int create(uint8_t* buffer, uint32_t length) { int snd = 0; while (snd < static_cast(sounds.size()) && sounds[snd].buffer) { snd++; } if (snd == static_cast(sounds.size())) sounds.emplace_back(); auto &s = sounds[snd]; s.buffer = buffer; s.length = length; return snd; } int load(uint8_t* buffer, uint32_t size) { int snd = 0; while (snd < static_cast(sounds.size()) && sounds[snd].buffer) { snd++; } if (snd == static_cast(sounds.size())) sounds.emplace_back(); auto &s = sounds[snd]; SDL_LoadWAV_IO(SDL_IOFromMem(buffer, size),1, &s.spec, &s.buffer, &s.length); return snd; } int load(const char* filename) { int snd = 0; while (snd < static_cast(sounds.size()) && sounds[snd].buffer) { snd++; } if (snd == static_cast(sounds.size())) sounds.emplace_back(); auto &s = sounds[snd]; SDL_LoadWAV(filename, &s.spec, &s.buffer, &s.length); return snd; } int play(int snd, int loop) { if (!sound::enabled) return -1; if (snd<0 || snd>static_cast(sounds.size())) { log_msg(LOG_FAIL, "sound::play: Illegal sound handle: %i\n", snd); return -1; } auto &s = sounds[snd]; if (!s.buffer) { log_msg(LOG_FAIL, "sound::play: Invalid sound: %i\n", snd); return -1; } int chan = 0; while (chan < static_cast(channel::channels.size()) && channel::channels[chan].state != channel::state::free) { chan++; } if (chan == static_cast(channel::channels.size())) channel::channels.emplace_back(); channel::stop(chan); auto &c = channel::channels[chan]; c.sound = snd; c.times = loop; c.pos = 0; c.state = channel::state::playing; c.stream = SDL_CreateAudioStream(&s.spec, &audioSpec); SDL_PutAudioStreamData(c.stream, s.buffer, s.length); SDL_SetAudioStreamGain(c.stream, volume); SDL_BindAudioStream(sdlAudioDevice, c.stream); return chan; } int playOnChannel(int snd, int chan, int loop) { if (!sound::enabled) return -1; if (snd<0 || snd>static_cast(sounds.size())) { log_msg(LOG_FAIL, "sound::playOnChannel: Illegal sound handle: %i\n", snd); return -1; } auto &s = sounds[snd]; if (!s.buffer) { log_msg(LOG_FAIL, "sound::playOnChannel: Invalid sound: %i\n", snd); return -1; } if (chan<0 || chan>static_cast(channel::channels.size())) { log_msg(LOG_FAIL, "sound::playOnChannel: Illegal channel handle: %i\n", chan); return -1; } channel::stop(chan); auto &c = channel::channels[chan]; c.sound = snd; c.times = loop; c.pos = 0; c.state = channel::state::playing; c.stream = SDL_CreateAudioStream(&s.spec, &audioSpec); SDL_PutAudioStreamData(c.stream, s.buffer, s.length); SDL_SetAudioStreamGain(c.stream, volume); SDL_BindAudioStream(sdlAudioDevice, c.stream); return chan; } void destroy(int snd) { for (int i = 0; i < static_cast(channel::channels.size()); i++) { if (channel::channels[i].sound == snd) channel::stop(i); } if (snd<0 || snd>static_cast(sounds.size())) { log_msg(LOG_FAIL, "sound::destroy: Illegal sound handle: %i\n", snd); return; } auto &s = sounds[snd]; SDL_free(s.buffer); } float setVolume(float vol) { volume = SDL_clamp( vol, 0.0f, 1.0f ); for (int i = 0; i < static_cast(channel::channels.size()); i++) { auto &c = channel::channels[i]; if ( (c.state == channel::state::playing) || (c.state == channel::state::paused) ) SDL_SetAudioStreamGain(c.stream, volume); } return volume; } void enable(bool value) { if (!value && sound::enabled) { for (int i = 0; i < static_cast(channel::channels.size()); i++) { if (channel::channels[i].state == channel::state::playing) channel::stop(i); } } enabled = value; } bool isEnabled() { return enabled; } namespace channel { void pause(const int chan) { if (!sound::enabled) return; if (chan == -1) { for (int i = 0; i < static_cast(channels.size()); i++) if (channels[i].state == state::playing) { channels[i].state = state::paused; //SDL_PauseAudioStreamDevice(channels[i].stream); SDL_UnbindAudioStream(channels[i].stream); } } else if (chan >= 0 && chan < static_cast(channels.size())) { if (channels[chan].state == state::playing) { channels[chan].state = state::paused; //SDL_PauseAudioStreamDevice(channels[channel].stream); SDL_UnbindAudioStream(channels[chan].stream); } } } void resume(int chan) { if (!sound::enabled) return; if (chan == -1) { for (int i = 0; i < static_cast(channels.size()); i++) if (channels[i].state == state::paused) { channels[i].state = state::playing; //SDL_ResumeAudioStreamDevice(channels[i].stream); SDL_BindAudioStream(sdlAudioDevice, channels[i].stream); } } else if (chan >= 0 && chan < static_cast(channels.size())) { if (channels[chan].state == state::paused) { channels[chan].state = state::playing; //SDL_ResumeAudioStreamDevice(channels[channel].stream); SDL_BindAudioStream(sdlAudioDevice, channels[chan].stream); } } } void stop(int chan) { if (!sound::enabled) return; if (chan == -1) { for (int i = 0; i < static_cast(channels.size()); i++) { if (channels[i].state != state::free) SDL_DestroyAudioStream(channels[i].stream); channels[i].stream = nullptr; channels[i].state = state::free; channels[i].pos = 0; channels[i].sound = -1; } } else if (chan >= 0 && chan < static_cast(channels.size())) { if (channels[chan].state != state::free) SDL_DestroyAudioStream(channels[chan].stream); channels[chan].stream = nullptr; channels[chan].state = state::free; channels[chan].pos = 0; channels[chan].sound = -1; } } channel::state getState(int chan) { if (!sound::enabled) return state::disabled; if (chan < 0 || chan >= static_cast(channels.size())) return state::invalid; return channels[chan].state; } } } } } #endif