Lephisto/src/music.c

318 lines
8.0 KiB
C

#include <AL/al.h>
#include <AL/alc.h>
#include <vorbis/vorbisfile.h>
#include <SDL.h>
#include "lephisto.h"
#include "log.h"
#include "pack.h"
#include "music.h"
#define MUSIC_STOPPED (1<<1)
#define MUSIC_PLAYING (1<<2)
#define MUSIC_KILL (1<<9)
#define music_is(f) (music_state & f)
#define music_set(f) (music_state |= f)
#define music_rm(f) (music_state ^= f)
#define MUSIC_PREFIX "../snd/music/"
#define MUSIC_SUFFIX ".ogg"
#define BUFFER_SIZE (4096 * 8)
// Gobal sound mutex.
extern SDL_mutex* sound_lock;
// Saves the music to ram in this structure.
typedef struct alMusic_ {
char name[32]; // Name.
Packfile file;
OggVorbis_File stream;
vorbis_info* info;
ALenum format;
} alMusic;
// Song currently playing.
static SDL_mutex* music_vorbis_lock;
static alMusic music_vorbis;
static ALuint music_buffer[2]; // Front and back buffer.
static ALuint music_source = 0;
// What is available?
static char** music_selection = NULL;
static int nmusic_selection = 0;
// Volume
static ALfloat mvolume = 1.;
// Vorbis suff.
static size_t ovpack_read(void* ptr, size_t size,
size_t nmemb, void* datasource) {
return (ssize_t) pack_read(datasource, ptr, size*nmemb);
}
static int ovpack_retneg(void) { return -1; } // Must return -1.
static int ovpack_retzero(void) { return 0; } // Must return 0.
ov_callbacks ovcall = {
.read_func = ovpack_read,
.seek_func = (int(*)(void*, ogg_int64_t, int)) ovpack_retneg,
.close_func = (int(*)(void*))ovpack_retzero,
.tell_func = (long(*)(void*))ovpack_retneg
};
static int stream_loadBuffer(ALuint buffer);
static int music_find(void);
static int music_loadOGG(const char* filename);
static void music_free(void);
// The thread.
static unsigned int music_state = 0;
int music_thread(void* unused) {
(void)unused;
int active; // Active buffer.
ALint stat;
// Main loop.
while(!music_is(MUSIC_KILL)) {
if(music_is(MUSIC_PLAYING)) {
if(music_vorbis.file.end == 0)
music_rm(MUSIC_PLAYING);
else {
music_rm(MUSIC_STOPPED);
SDL_mutexP(music_vorbis_lock); // Lock the mutex.
SDL_mutexP(sound_lock);
// Start playing current song.
active = 0; // Load first buffer.
if(stream_loadBuffer(music_buffer[active])) music_rm(MUSIC_PLAYING);
alSourceQueueBuffers(music_source, 1, &music_buffer[active]);
// Start playing with buffer laoaded.
alSourcePlay(music_source);
active = 1; // Load the second buffer.
if(stream_loadBuffer(music_buffer[active])) music_rm(MUSIC_PLAYING);
alSourceQueueBuffers(music_source, 1, &music_buffer[active]);
SDL_mutexV(sound_lock);
active = 0;
}
while(music_is(MUSIC_PLAYING)) {
SDL_mutexP(sound_lock);
alGetSourcei(music_source, AL_BUFFERS_PROCESSED, &stat);
if(stat > 0) {
// Refill active buffer.
alSourceUnqueueBuffers(music_source, 1, &music_buffer[active]);
if(stream_loadBuffer(music_buffer[active])) music_rm(MUSIC_PLAYING);
alSourceQueueBuffers(music_source, 1, &music_buffer[active]);
active = 1 - active;
}
SDL_mutexV(sound_lock);
SDL_Delay(0);
}
SDL_mutexP(sound_lock);
alSourceStop(music_source);
alSourceUnqueueBuffers(music_source, 2, music_buffer);
SDL_mutexV(sound_lock);
SDL_mutexV(music_vorbis_lock);
}
music_set(MUSIC_STOPPED);
SDL_Delay(0); // We must not kill resources.
}
return 0;
}
static int stream_loadBuffer(ALuint buffer) {
int size, section, result;
char data[BUFFER_SIZE]; // Buffer to hold the data.
size = 0;
while(size < BUFFER_SIZE) {
// Fill up the entire data buffer.
result = ov_read(
&music_vorbis.stream, // Stream.
data + size, // Data.
BUFFER_SIZE - size, // Amount to read.
0, // Big endian?.
2, // 16 bit.
1, // Signed.
&section); // Current bitstream.
if(result == 0) return 1;
else if(result == OV_HOLE) {
WARN("OGG: Vorbis hole detected in music!");
return 0;
}
else if(result == OV_EBADLINK) {
WARN("OGG: Invalid stream section or corrupt link in music!");
return -1;
}
size += result;
if(size == BUFFER_SIZE) break; // Buffer is full.
}
// Load the buffer.
alBufferData(buffer, music_vorbis.format, data, BUFFER_SIZE,
music_vorbis.info->rate);
return 0;
}
// Init/Exit.
int music_init(void) {
music_vorbis_lock = SDL_CreateMutex();
music_find();
music_vorbis.file.end = 0; // Indication that it's not loaded..
SDL_mutexP(sound_lock);
alGenBuffers(2, music_buffer);
alGenSources(1, &music_source);
alSourcef(music_source, AL_GAIN, mvolume);
alSourcef(music_source, AL_ROLLOFF_FACTOR, 0.);
alSourcei(music_source, AL_SOURCE_RELATIVE, AL_TRUE);
SDL_mutexV(sound_lock);
return 0;
}
void music_exit(void) {
int i;
// Free the music.
alDeleteBuffers(2, music_buffer);
alDeleteSources(1, &music_source);
music_free();
// Free selection
for(i = 0; i < nmusic_selection; i++)
free(music_selection[i]);
free(music_selection);
SDL_DestroyMutex(music_vorbis_lock);
}
// Internal music loading ruitines.
static int music_loadOGG(const char* filename) {
// Free currently loaded ogg.
music_free();
SDL_mutexP(music_vorbis_lock);
// Load the new ogg.
pack_open(&music_vorbis.file, DATA, filename);
ov_open_callbacks(&music_vorbis.file, &music_vorbis.stream, NULL, 0, ovcall);
music_vorbis.info = ov_info(&music_vorbis.stream, -1);
if(music_vorbis.info->channels == 1) music_vorbis.format = AL_FORMAT_MONO16;
else music_vorbis.format = AL_FORMAT_STEREO16;
SDL_mutexV(music_vorbis_lock);
return 0;
}
static int music_find(void) {
char** files;
uint32_t nfiles, i;
char tmp[64];
int len;
// Get the file list.
files = pack_listfiles(data, &nfiles);
// Load the profiles.
for(i = 0; i < nfiles; i++)
if((strncmp(files[i], MUSIC_PREFIX, strlen(MUSIC_PREFIX))==0) &&
(strncmp(files[i] + strlen(files[i]) - strlen(MUSIC_SUFFIX),
MUSIC_SUFFIX, strlen(MUSIC_SUFFIX))==0)) {
// Grow the selection size.
music_selection = realloc(music_selection,++nmusic_selection*sizeof(char*));
// Remove the prefix and suffix.
len = strlen(files[i]) - strlen(MUSIC_SUFFIX MUSIC_PREFIX);
strncpy(tmp, files[i] + strlen(MUSIC_PREFIX), len);
tmp[len] = '\0';
music_selection[nmusic_selection-1] = strdup(tmp);
}
// Free the char* allocated by pack.
for(i = 0; i < nfiles; i++)
free(files[i]);
free(files);
DEBUG("Loaded %d song%c", nmusic_selection, (nmusic_selection==1)?' ':'s');
return 0;
}
static void music_free(void) {
SDL_mutexP(music_vorbis_lock);
if(music_vorbis.file.end != 0) {
ov_clear(&music_vorbis.stream);
pack_close(&music_vorbis.file);
music_vorbis.file.end = 0; // Somewhat ended.
}
SDL_mutexV(music_vorbis_lock);
}
void music_volume(const double vol) {
if(sound_lock == NULL) return;
// Sanity check!
ALfloat fvol = ABS(vol);
if(fvol > 1.) fvol = 1.;
mvolume = fvol;
// Only needed if playing!
if(music_set(MUSIC_PLAYING)) {
SDL_mutexP(sound_lock);
alSourcef(music_source, AL_GAIN, fvol);
SDL_mutexV(sound_lock);
}
}
// Music control functions.
void music_load(const char* name) {
if(sound_lock == NULL) return;
int i;
char tmp[64];
music_stop();
while(!music_is(MUSIC_STOPPED)) SDL_Delay(0);
for(i = 0; i < nmusic_selection; i++)
if(strcmp(music_selection[i], name)==0) {
snprintf(tmp, 64, MUSIC_PREFIX"%s"MUSIC_SUFFIX, name);
music_loadOGG(tmp);
return;
}
WARN("Requested load song '%s' but it can't be found in the music stack",name);
}
void music_play(void) {
if(!music_is(MUSIC_PLAYING)) music_set(MUSIC_PLAYING);
}
void music_stop(void) {
if(music_is(MUSIC_PLAYING)) music_rm(MUSIC_PLAYING);
}
void music_kill(void) {
if(!music_is(MUSIC_KILL)) music_set(MUSIC_KILL);
}