Blame src/sound.c

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