Blob Blame Raw

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