#include <SDL.h>
#include <SDL_mixer.h>
#include "private.h"
#include "sound.h"
#define HELI_SOUND_CHANNELS 64
typedef struct _HeliSoundInstance {
char *path;
Mix_Chunk *chunk;
int refcount;
} HeliSoundInstance;
typedef struct _HeliSoundTrashItem {
Mix_Chunk *chunk;
unsigned int time;
} HeliSoundTrashItem;
struct _Sound {
HeliSoundInstance *instance;
};
static int initialized;
static int ready;
static HeliArray cache;
static Sound channels[HELI_SOUND_CHANNELS];
static HeliSoundTrashItem *trash;
static size_t trashSize;
static int memSoundIndex = 0;
static int less(unsigned int a, unsigned int b)
{ return (b - a) < (1u << 31); }
static void trashAdd(Mix_Chunk *chunk) {
if (!chunk) return;
unsigned int time = SDL_GetTicks() + 1000u;
for(int i = 0; i < trashSize; ++i)
if (!trash[i].chunk)
{ trash[i].chunk = chunk; trash[i].time = time; return; }
size_t prevSize = trashSize;
trashSize += trashSize/4 + 32;
trash = realloc(trash, trashSize*sizeof(*trash));
memset(&trash[prevSize], 0, (trashSize - prevSize)*sizeof(*trash));
trash[prevSize].chunk = chunk;
trash[prevSize].time = time;
}
static void trashProcess() {
unsigned int time = SDL_GetTicks();
for(int i = 0; i < trashSize; ++i)
if (trash[i].chunk && !less(time, trash[i].time))
{ Mix_FreeChunk(trash[i].chunk); trash[i].chunk = NULL; }
}
static void trashDestroy() {
for(int i = 0; i < trashSize; ++i)
if (trash[i].chunk) Mix_FreeChunk(trash[i].chunk);
trashSize = 0;
free(trash);
trash = NULL;
}
static void init() {
if (initialized) return;
if (SDL_Init(SDL_INIT_AUDIO) == 0) {
int loaded = Mix_Init(MIX_INIT_OGG | MIX_INIT_FLAC | MIX_INIT_MP3);
if (!(loaded & MIX_INIT_OGG )) fprintf(stderr, "helianthus: cannot initialize OGG support\n");
if (!(loaded & MIX_INIT_FLAC)) fprintf(stderr, "helianthus: cannot initialize FLAC support\n");
if (!(loaded & MIX_INIT_MP3 )) fprintf(stderr, "helianthus: cannot initialize MP3 support\n");
ready = Mix_OpenAudio(44100, MIX_DEFAULT_FORMAT, 2, 1024) == 0;
if (!ready) fprintf(stderr, "helianthus: cannot open audio device\n");
}
initialized = TRUE;
}
static void deinit() {
if (!initialized) return;
heliArrayDestroy(&cache);
if (ready) Mix_CloseAudio();
trashDestroy();
Mix_Quit();
SDL_Quit();
for(int i = 0; i < HELI_SOUND_CHANNELS; ++i) channels[i] = NULL;
ready = FALSE;
initialized = FALSE;
}
static HeliSoundInstance* load(const char *path) {
HeliSoundInstance *s = calloc(1, sizeof(*s));
s->path = heliStringCopy(path);
if (ready) {
if (strncmp(path, "helimem:", 8) == 0) {
void *data = NULL;
int size = 0;
s->chunk = NULL;
if (sscanf(path, "helimem:%p:%d:", &data, &size) != EOF && data && size > 0) {
SDL_RWops *rw = SDL_RWFromMem((void*)data, size);
if (rw) s->chunk = Mix_LoadWAV_RW(rw, SDL_TRUE);
}
if (!s->chunk)
fprintf(stderr, "helianthus: cannot load sound from memory\n");
} else {
s->chunk = Mix_LoadWAV(path);
if (!s->chunk)
fprintf(stderr, "helianthus: cannot load sound file: %s\n", path);
}
}
return s;
}
static void unload(HeliSoundInstance *s) {
assert(!s->refcount);
free(s->path);
trashAdd(s->chunk);
free(s);
}
Sound createSound(const char *path) {
if (!heliInitialized) return NULL;
init();
HeliPair *item = heliStringmapGet(&cache, path);
if (!item) item = heliStringmapAdd(&cache, path, load(path), (HeliFreeCallback)&unload);
HeliSoundInstance *s = (HeliSoundInstance*)item->value;
++s->refcount;
Sound sound = calloc(1, sizeof(*sound));
sound->instance = s;
heliObjectRegister(sound, (HeliFreeCallback)&soundDestroy);
return sound;
}
Sound createSoundFromMemory(const void *data, int size) {
char buffer[1024] = {};
sprintf(buffer, "helimem:%p:%d:%d", data, size, ++memSoundIndex);
return createSound(buffer);
}
Sound soundClone(Sound sound) {
if (!sound) return NULL;
return createSound(sound->instance ? sound->instance->path : NULL);
}
void soundDestroy(Sound sound) {
heliObjectUnregister(sound);
soundStop(sound);
if (--sound->instance->refcount <= 0)
heliStringmapRemove(&cache, sound->instance->path);
free(sound);
}
void soundPlay(Sound sound, int loop) {
if (!ready) return;
if (sound->instance->chunk) {
int channel = Mix_PlayChannel(-1, sound->instance->chunk, loop ? -1 : 0);
assert(channel < HELI_SOUND_CHANNELS);
if (channel >= 0 && channel < HELI_SOUND_CHANNELS)
channels[channel] = sound;
}
}
void soundStop(Sound sound) {
for(int i = 0; i < HELI_SOUND_CHANNELS; ++i) {
if (channels[i] == sound) {
Mix_Pause(i);
Mix_HaltChannel(i);
channels[i] = NULL;
}
}
}
void heliSoundUpdate()
{ trashProcess(); }
void heliSoundFinish()
{ deinit(); }