Blob Blame Raw

#include <glib.h>

#include <SDL.h>
#include <SDL_mixer.h>

#include "private.h"
#include "world.h"


#define HELI_SOUND_CHANNELS 64


typedef struct _HeliSoundInstance {
	char *path;
	Mix_Chunk *chunk;
	int refcount;
} HeliSoundInstance;

typedef struct _HeliSoundTrashItem {
	Mix_Chunk *chunk;
	gint64 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 void trashAdd(Mix_Chunk *chunk) {
	if (!chunk) return;
	
	gint64 time = g_get_monotonic_time() + 1000000;
	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() {
	gint64 time = g_get_monotonic_time();
	for(int i = 0; i < trashSize; ++i)
		if (trash[i].chunk && trash[i].time <= 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) {
		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;
}

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(); }