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