642 lines
23 KiB
C++
642 lines
23 KiB
C++
#ifndef JA_USESDLMIXER
|
|
#include "jail_audio.h"
|
|
#include "external/stb_vorbis.h"
|
|
#include "other/log.h"
|
|
|
|
#include <SDL3/SDL.h>
|
|
|
|
#include <stdio.h>
|
|
#include <vector>
|
|
|
|
// structs i variables
|
|
// =============================
|
|
namespace jail
|
|
{
|
|
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 };
|
|
Uint32 length { 0 };
|
|
Uint8 *buffer { nullptr };
|
|
|
|
int pos { 0 };
|
|
int times { 0 };
|
|
SDL_AudioStream *stream { nullptr };
|
|
music::state state { music::state::invalid };
|
|
};
|
|
|
|
static int current { -1 };
|
|
static std::vector<music_t> 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<sound_t> 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<channel_t> channels;
|
|
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Funcions
|
|
// ==================
|
|
namespace jail
|
|
{
|
|
namespace audio
|
|
{
|
|
static void updateMusic()
|
|
{
|
|
if (!music::enabled) return;
|
|
if (music::current < 0 || music::current > music::musics.size()) return;
|
|
auto &m = music::musics[music::current];
|
|
if (m.state != music::state::playing) return;
|
|
|
|
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 {
|
|
const int time_passed = time - music::fade::start_time;
|
|
const float percent = (float)time_passed / (float)music::fade::duration;
|
|
SDL_SetAudioStreamGain(m.stream, 1.0 - percent);
|
|
}
|
|
}
|
|
|
|
if (m.times != 0)
|
|
{
|
|
if (SDL_GetAudioStreamAvailable(m.stream) < int(m.length/2)) {
|
|
SDL_PutAudioStreamData(m.stream, m.buffer, m.length);
|
|
}
|
|
if (m.times>0) m.times--;
|
|
}
|
|
else
|
|
{
|
|
if (SDL_GetAudioStreamAvailable(m.stream) == 0) music::stop();
|
|
}
|
|
}
|
|
|
|
static void updateSound()
|
|
{
|
|
if (sound::enabled)
|
|
{
|
|
for (int i=0; i < 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) < int(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 music = 0;
|
|
while (music < musics.size() && musics[music].state != state::invalid) { music++; }
|
|
if (music == musics.size()) musics.emplace_back();
|
|
|
|
auto &m = musics[music];
|
|
|
|
int chan, samplerate;
|
|
short *output;
|
|
m.length = stb_vorbis_decode_memory(buffer, length, &chan, &samplerate, &output) * chan * 2;
|
|
|
|
m.spec.channels = chan;
|
|
m.spec.freq = samplerate;
|
|
m.spec.format = SDL_AUDIO_S16;
|
|
m.buffer = (uint8_t*)SDL_malloc(m.length);
|
|
SDL_memcpy(m.buffer, output, m.length);
|
|
free(output);
|
|
m.pos = 0;
|
|
m.state = state::stopped;
|
|
|
|
return music;
|
|
}
|
|
|
|
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 NULL;
|
|
fclose(f);
|
|
|
|
int music = load(buffer, fsize);
|
|
|
|
free(buffer);
|
|
|
|
return music;
|
|
}
|
|
|
|
void play(int mus, int loop)
|
|
{
|
|
if (!music::enabled) return;
|
|
stop();
|
|
if (mus < 0 || mus >= musics.size()) {
|
|
log_msg(LOG_FAIL, "music::play: Illegal music handle: %i\n", mus);
|
|
return;
|
|
}
|
|
|
|
current = mus;
|
|
auto &m = musics[current];
|
|
m.pos = 0;
|
|
m.state = state::playing;
|
|
m.times = loop;
|
|
m.stream = SDL_CreateAudioStream(&m.spec, &audioSpec);
|
|
if (!SDL_PutAudioStreamData(m.stream, m.buffer, m.length)) log_msg(LOG_FAIL, "SDL_PutAudioStreamData failed!\n");
|
|
SDL_SetAudioStreamGain(m.stream, volume);
|
|
if (!SDL_BindAudioStream(sdlAudioDevice, m.stream)) log_msg(LOG_FAIL, "SDL_BindAudioStream failed!\n");
|
|
//SDL_ResumeAudioStreamDevice(current->stream);
|
|
}
|
|
|
|
void pause()
|
|
{
|
|
if (!music::enabled) return;
|
|
if (current<0 || current>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) return;
|
|
if (current<0 || current>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 (!music::enabled) return;
|
|
if (current<0 || current>musics.size()) {
|
|
log_msg(LOG_FAIL, "music::stop: Illegal music handle: %i\n", current);
|
|
return;
|
|
}
|
|
auto &m = musics[current];
|
|
if (m.state == state::invalid) {
|
|
log_msg(LOG_FAIL, "music::stop: Invalidated music handle: %i\n", current);
|
|
return;
|
|
}
|
|
|
|
m.pos = 0;
|
|
m.state = state::stopped;
|
|
//SDL_PauseAudioStreamDevice(current->stream);
|
|
SDL_DestroyAudioStream(m.stream);
|
|
m.stream = nullptr;
|
|
}
|
|
|
|
void fadeOut(int milliseconds)
|
|
{
|
|
if (!music::enabled) return;
|
|
if (current<0 || current>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>musics.size()) return state::invalid;
|
|
return musics[current].state;
|
|
}
|
|
|
|
void destroy(int mus)
|
|
{
|
|
if (current == mus) current = -1;
|
|
if (mus<0 || mus>musics.size()) {
|
|
log_msg(LOG_FAIL, "music::destroy: Illegal music handle: %i\n", mus);
|
|
return;
|
|
}
|
|
auto &m = musics[mus];
|
|
SDL_free(m.buffer);
|
|
m.buffer = nullptr;
|
|
if (m.stream) SDL_DestroyAudioStream(m.stream);
|
|
m.stream = nullptr;
|
|
m.state = state::invalid;
|
|
}
|
|
|
|
float setVolume(float vol)
|
|
{
|
|
volume = SDL_clamp( vol, 0.0f, 1.0f );
|
|
|
|
if (current<0 || current>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)
|
|
{
|
|
if (!music::enabled) return;
|
|
if (current<0 || current>musics.size()) {
|
|
log_msg(LOG_FAIL, "music::setPosition: Illegal music handle: %i\n", current);
|
|
return;
|
|
}
|
|
auto &m = musics[current];
|
|
if (m.state == state::invalid) {
|
|
log_msg(LOG_FAIL, "music::setPosition: Invalidated music handle: %i\n", current);
|
|
return;
|
|
}
|
|
|
|
m.pos = value * m.spec.freq;
|
|
}
|
|
|
|
float getPosition()
|
|
{
|
|
if (!music::enabled) return 0;
|
|
if (current<0 || current>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;
|
|
}
|
|
|
|
return float(m.pos)/float(m.spec.freq);
|
|
}
|
|
|
|
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 < sounds.size() && sounds[snd].buffer) { snd++; }
|
|
if (snd == 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 < sounds.size() && sounds[snd].buffer) { snd++; }
|
|
if (snd == 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 < sounds.size() && sounds[snd].buffer) { snd++; }
|
|
if (snd == 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>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 < channel::channels.size() && channel::channels[chan].state != channel::state::free) { chan++; }
|
|
if (chan == 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>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>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 < channel::channels.size(); i++) {
|
|
if (channel::channels[i].sound == snd) channel::stop(i);
|
|
}
|
|
|
|
if (snd<0 || snd>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 < 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 < 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 < 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 < 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 < 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 < 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 < 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 < 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 >= channels.size()) return state::invalid;
|
|
return channels[chan].state;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
#endif |