Added lib/cmixer and new audio system

This commit is contained in:
rxi
2017-05-06 08:53:59 +01:00
parent 834f78a0e0
commit c096249beb
7 changed files with 1002 additions and 1 deletions

727
src/lib/cmixer/cmixer.c Normal file
View File

@@ -0,0 +1,727 @@
/*
** Copyright (c) 2017 rxi
**
** Permission is hereby granted, free of charge, to any person obtaining a copy
** of this software and associated documentation files (the "Software"), to
** deal in the Software without restriction, including without limitation the
** rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
** sell copies of the Software, and to permit persons to whom the Software is
** furnished to do so, subject to the following conditions:
**
** The above copyright notice and this permission notice shall be included in
** all copies or substantial portions of the Software.
**
** THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
** IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
** FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
** AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
** LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
** FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
** IN THE SOFTWARE.
**/
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include "cmixer.h"
#define UNUSED(x) ((void) (x))
#define CLAMP(x, a, b) ((x) < (a) ? (a) : (x) > (b) ? (b) : (x))
#define MIN(a, b) ((a) < (b) ? (a) : (b))
#define MAX(a, b) ((a) > (b) ? (a) : (b))
#define FX_BITS (12)
#define FX_UNIT (1 << FX_BITS)
#define FX_MASK (FX_UNIT - 1)
#define FX_FROM_FLOAT(f) ((f) * FX_UNIT)
#define FX_LERP(a, b, p) ((a) + ((((b) - (a)) * (p)) >> FX_BITS))
#define BUFFER_SIZE (512)
#define BUFFER_MASK (BUFFER_SIZE - 1)
struct cm_Source {
cm_Source *next; /* Next source in list */
cm_Int16 buffer[BUFFER_SIZE]; /* Internal buffer with raw stereo PCM */
cm_EventHandler handler; /* Event handler */
void *udata; /* Stream's udata (from cm_SourceInfo) */
int samplerate; /* Stream's native samplerate */
int length; /* Stream's length in frames */
int end; /* End index for the current play-through */
int state; /* Current state (playing|paused|stopped) */
cm_Int64 position; /* Current playhead position (fixed point) */
int lgain, rgain; /* Left and right gain (fixed point) */
int rate; /* Playback rate (fixed point) */
int nextfill; /* Next frame idx where the buffer needs to be filled */
int loop; /* Whether the source will loop when `end` is reached */
int rewind; /* Whether the source will rewind before playing */
int active; /* Whether the source is part of `sources` list */
double gain; /* Gain set by `cm_set_gain()` */
double pan; /* Pan set by `cm_set_pan()` */
};
static struct {
const char *lasterror; /* Last error message */
cm_EventHandler lock; /* Event handler for lock/unlock events */
cm_Source *sources; /* Linked list of active (playing) sources */
cm_Int32 buffer[BUFFER_SIZE]; /* Internal master buffer */
int samplerate; /* Master samplerate */
int gain; /* Master gain (fixed point) */
} cmixer;
static void dummy_handler(cm_Event *e) {
UNUSED(e);
}
static void lock(void) {
cm_Event e;
e.type = CM_EVENT_LOCK;
cmixer.lock(&e);
}
static void unlock(void) {
cm_Event e;
e.type = CM_EVENT_UNLOCK;
cmixer.lock(&e);
}
const char* cm_get_error(void) {
const char *res = cmixer.lasterror;
cmixer.lasterror = NULL;
return res;
}
static const char* error(const char *msg) {
cmixer.lasterror = msg;
return msg;
}
void cm_init(int samplerate) {
cmixer.samplerate = samplerate;
cmixer.lock = dummy_handler;
cmixer.sources = NULL;
cmixer.gain = FX_UNIT;
}
void cm_set_lock(cm_EventHandler lock) {
cmixer.lock = lock;
}
void cm_set_master_gain(double gain) {
cmixer.gain = FX_FROM_FLOAT(gain);
}
static void rewind_source(cm_Source *src) {
cm_Event e;
e.type = CM_EVENT_REWIND;
e.udata = src->udata;
src->handler(&e);
src->position = 0;
src->rewind = 0;
src->end = src->length;
src->nextfill = 0;
}
static void fill_source_buffer(cm_Source *src, int offset, int length) {
cm_Event e;
e.type = CM_EVENT_SAMPLES;
e.udata = src->udata;
e.buffer = src->buffer + offset;
e.length = length;
src->handler(&e);
}
static void process_source(cm_Source *src, int len) {
int i, n, a, b, p;
int frame, count;
cm_Int32 *dst = cmixer.buffer;
/* Do rewind if flag is set */
if (src->rewind) {
rewind_source(src);
}
/* Process audio */
while (len > 0) {
/* Get current position frame */
frame = src->position >> FX_BITS;
/* Fill buffer if required */
if (frame + 3 >= src->nextfill) {
fill_source_buffer(src, (src->nextfill*2) & BUFFER_MASK, BUFFER_SIZE/2);
src->nextfill += BUFFER_SIZE / 4;
}
/* Handle reaching the end of the playthrough */
if (frame >= src->end) {
/* As streams continiously fill the raw buffer in a loop we simply
** increment the end idx by one length and continue reading from it for
** another play-through */
src->end = frame + src->length;
/* Set state and stop processing if we're not set to loop */
if (!src->loop) {
src->state = CM_STATE_STOPPED;
break;
}
}
/* Work out how many frames we should process in the loop */
n = MIN(src->nextfill - 2, src->end) - frame;
count = (n << FX_BITS) / src->rate;
count = MAX(count, 1);
count = MIN(count, len / 2);
len -= count * 2;
/* Add audio to master buffer */
if (src->rate == FX_UNIT) {
/* Add audio to buffer -- basic */
n = frame * 2;
for (i = 0; i < count; i++) {
dst[0] += (src->buffer[(n ) & BUFFER_MASK] * src->lgain) >> FX_BITS;
dst[1] += (src->buffer[(n + 1) & BUFFER_MASK] * src->rgain) >> FX_BITS;
n += 2;
dst += 2;
}
src->position += count * FX_UNIT;
} else {
/* Add audio to buffer -- interpolated */
for (i = 0; i < count; i++) {
n = (src->position >> FX_BITS) * 2;
p = src->position & FX_MASK;
a = src->buffer[(n ) & BUFFER_MASK];
b = src->buffer[(n + 2) & BUFFER_MASK];
dst[0] += (FX_LERP(a, b, p) * src->lgain) >> FX_BITS;
n++;
a = src->buffer[(n ) & BUFFER_MASK];
b = src->buffer[(n + 2) & BUFFER_MASK];
dst[1] += (FX_LERP(a, b, p) * src->rgain) >> FX_BITS;
src->position += src->rate;
dst += 2;
}
}
}
}
void cm_process(cm_Int16 *dst, int len) {
int i;
cm_Source **s;
/* Process in chunks of BUFFER_SIZE if `len` is larger than BUFFER_SIZE */
while (len > BUFFER_SIZE) {
cm_process(dst, BUFFER_SIZE);
dst += BUFFER_SIZE;
len -= BUFFER_SIZE;
}
/* Zeroset internal buffer */
memset(cmixer.buffer, 0, len * sizeof(cmixer.buffer[0]));
/* Process active sources */
lock();
s = &cmixer.sources;
while (*s) {
process_source(*s, len);
/* Remove source from list if it is no longer playing */
if ((*s)->state != CM_STATE_PLAYING) {
(*s)->active = 0;
*s = (*s)->next;
} else {
s = &(*s)->next;
}
}
unlock();
/* Copy internal buffer to destination and clip */
for (i = 0; i < len; i++) {
int x = (cmixer.buffer[i] * cmixer.gain) >> FX_BITS;
dst[i] = CLAMP(x, -32768, 32767);
}
}
cm_Source* cm_new_source(const cm_SourceInfo *info) {
cm_Source *src = calloc(1, sizeof(*src));
if (!src) {
error("allocation failed");
return NULL;
}
src->handler = info->handler;
src->length = info->length;
src->samplerate = info->samplerate;
src->udata = info->udata;
cm_set_gain(src, 1);
cm_set_pan(src, 0);
cm_set_pitch(src, 1);
cm_set_loop(src, 0);
cm_stop(src);
return src;
}
static const char* wav_init(cm_SourceInfo *info, void *data, int len, int ownsdata);
#ifdef CM_USE_STB_VORBIS
static const char* ogg_init(cm_SourceInfo *info, void *data, int len, int ownsdata);
#endif
static int check_header(void *data, int size, char *str, int offset) {
int len = strlen(str);
return (size >= offset + len) && !memcmp((char*) data + offset, str, len);
}
static cm_Source* new_source_from_mem(void *data, int size, int ownsdata) {
const char *err;
cm_SourceInfo info;
if (check_header(data, size, "WAVE", 8)) {
err = wav_init(&info, data, size, ownsdata);
if (err) {
return NULL;
}
return cm_new_source(&info);
}
#ifdef CM_USE_STB_VORBIS
if (check_header(data, size, "OggS", 0)) {
err = ogg_init(&info, data, size, ownsdata);
if (err) {
return NULL;
}
return cm_new_source(&info);
}
#endif
error("unknown format or invalid data");
return NULL;
}
static void* load_file(const char *filename, int *size) {
FILE *fp;
void *data;
int n;
fp = fopen(filename, "rb");
if (!fp) {
return NULL;
}
/* Get size */
fseek(fp, 0, SEEK_END);
*size = ftell(fp);
rewind(fp);
/* Malloc, read and return data */
data = malloc(*size);
if (!data) {
fclose(fp);
return NULL;
}
n = fread(data, 1, *size, fp);
fclose(fp);
if (n != *size) {
free(data);
return NULL;
}
return data;
}
cm_Source* cm_new_source_from_file(const char *filename) {
int size;
cm_Source *src;
void *data;
/* Load file into memory */
data = load_file(filename, &size);
if (!data) {
error("could not load file");
return NULL;
}
/* Try to load and return */
src = new_source_from_mem(data, size, 1);
if (!src) {
free(data);
return NULL;
}
return src;
}
cm_Source* cm_new_source_from_mem(void *data, int size) {
return new_source_from_mem(data, size, 0);
}
void cm_destroy_source(cm_Source *src) {
cm_Event e;
lock();
if (src->active) {
cm_Source **s = &cmixer.sources;
while (*s) {
if (*s == src) {
*s = src->next;
break;
}
}
}
unlock();
e.type = CM_EVENT_DESTROY;
e.udata = src->udata;
src->handler(&e);
free(src);
}
double cm_get_length(cm_Source *src) {
return src->length / (double) src->samplerate;
}
double cm_get_position(cm_Source *src) {
return ((src->position >> FX_BITS) % src->length) / (double) src->samplerate;
}
int cm_get_state(cm_Source *src) {
return src->state;
}
static void recalc_source_gains(cm_Source *src) {
double l, r;
double pan = src->pan;
l = src->gain * (pan <= 0. ? 1. : 1. - pan);
r = src->gain * (pan >= 0. ? 1. : 1. + pan);
src->lgain = FX_FROM_FLOAT(l);
src->rgain = FX_FROM_FLOAT(r);
}
void cm_set_gain(cm_Source *src, double gain) {
src->gain = gain;
recalc_source_gains(src);
}
void cm_set_pan(cm_Source *src, double pan) {
src->pan = pan;
recalc_source_gains(src);
}
void cm_set_pitch(cm_Source *src, double pitch) {
double rate = src->samplerate / (double) cmixer.samplerate * pitch;
src->rate = FX_FROM_FLOAT(rate);
}
void cm_set_loop(cm_Source *src, int loop) {
src->loop = loop;
}
void cm_play(cm_Source *src) {
lock();
src->state = CM_STATE_PLAYING;
if (!src->active) {
src->active = 1;
src->next = cmixer.sources;
cmixer.sources = src;
}
unlock();
}
void cm_pause(cm_Source *src) {
src->state = CM_STATE_PAUSED;
}
void cm_stop(cm_Source *src) {
src->state = CM_STATE_STOPPED;
src->rewind = 1;
}
/*============================================================================
** Wav stream
**============================================================================*/
typedef struct {
void *data;
int bitdepth;
int samplerate;
int channels;
int length;
} Wav;
typedef struct {
Wav wav;
void *data;
int idx;
} WavStream;
static char* find_subchunk(char *data, int len, char *id, int *size) {
/* TODO : Error handling on malformed wav file */
int idlen = strlen(id);
char *p = data + 12;
next:
*size = *((cm_UInt32*) (p + 4));
if (memcmp(p, id, idlen)) {
p += 8 + *size;
if (p > data + len) return NULL;
goto next;
}
return p + 8;
}
static const char* read_wav(Wav *w, void *data, int len) {
int bitdepth, channels, samplerate, format;
int sz;
char *p = data;
memset(w, 0, sizeof(*w));
/* Check header */
if (memcmp(p, "RIFF", 4) || memcmp(p + 8, "WAVE", 4)) {
return error("bad wav header");
}
/* Find fmt subchunk */
p = find_subchunk(data, len, "fmt", &sz);
if (!p) {
return error("no fmt subchunk");
}
/* Load fmt info */
format = *((cm_UInt16*) (p));
channels = *((cm_UInt16*) (p + 2));
samplerate = *((cm_UInt32*) (p + 4));
bitdepth = *((cm_UInt16*) (p + 14));
if (format != 1) {
return error("unsupported format");
}
if (channels == 0 || samplerate == 0 || bitdepth == 0) {
return error("bad format");
}
/* Find data subchunk */
p = find_subchunk(data, len, "data", &sz);
if (!p) {
return error("no data subchunk");
}
/* Init struct */
w->data = (void*) p;
w->samplerate = samplerate;
w->channels = channels;
w->length = (sz / (bitdepth / 8)) / channels;
w->bitdepth = bitdepth;
/* Done */
return NULL;
}
#define WAV_PROCESS_LOOP(X) \
while (n--) { \
X \
dst += 2; \
s->idx++; \
}
static void wav_handler(cm_Event *e) {
int x, n;
cm_Int16 *dst;
WavStream *s = e->udata;
int len;
switch (e->type) {
case CM_EVENT_DESTROY:
free(s->data);
free(s);
break;
case CM_EVENT_SAMPLES:
dst = e->buffer;
len = e->length / 2;
fill:
n = MIN(len, s->wav.length - s->idx);
len -= n;
if (s->wav.bitdepth == 16 && s->wav.channels == 1) {
WAV_PROCESS_LOOP({
dst[0] = dst[1] = ((cm_Int16*) s->wav.data)[s->idx];
});
} else if (s->wav.bitdepth == 16 && s->wav.channels == 2) {
WAV_PROCESS_LOOP({
x = s->idx * 2;
dst[0] = ((cm_Int16*) s->wav.data)[x ];
dst[1] = ((cm_Int16*) s->wav.data)[x + 1];
});
} else if (s->wav.bitdepth == 8 && s->wav.channels == 1) {
WAV_PROCESS_LOOP({
dst[0] = dst[1] = (((cm_UInt8*) s->wav.data)[s->idx] - 128) << 8;
});
} else if (s->wav.bitdepth == 8 && s->wav.channels == 2) {
WAV_PROCESS_LOOP({
x = s->idx * 2;
dst[0] = (((cm_UInt8*) s->wav.data)[x ] - 128) << 8;
dst[1] = (((cm_UInt8*) s->wav.data)[x + 1] - 128) << 8;
});
}
/* Loop back and continue filling buffer if we didn't fill the buffer */
if (len > 0) {
s->idx = 0;
goto fill;
}
break;
case CM_EVENT_REWIND:
s->idx = 0;
break;
}
}
static const char* wav_init(cm_SourceInfo *info, void *data, int len, int ownsdata) {
WavStream *stream;
Wav wav;
const char *err = read_wav(&wav, data, len);
if (err != NULL) {
return err;
}
if (wav.channels > 2 || (wav.bitdepth != 16 && wav.bitdepth != 8)) {
return error("unsupported wav format");
}
stream = calloc(1, sizeof(*stream));
if (!stream) {
return error("allocation failed");
}
stream->wav = wav;
if (ownsdata) {
stream->data = data;
}
stream->idx = 0;
info->udata = stream;
info->handler = wav_handler;
info->samplerate = wav.samplerate;
info->length = wav.length;
/* Return NULL (no error) for success */
return NULL;
}
/*============================================================================
** Ogg stream
**============================================================================*/
#ifdef CM_USE_STB_VORBIS
#define STB_VORBIS_HEADER_ONLY
#include "stb_vorbis.c"
typedef struct {
stb_vorbis *ogg;
void *data;
} OggStream;
static void ogg_handler(cm_Event *e) {
int n, len;
OggStream *s = e->udata;
cm_Int16 *buf;
switch (e->type) {
case CM_EVENT_DESTROY:
stb_vorbis_close(s->ogg);
free(s->data);
free(s);
break;
case CM_EVENT_SAMPLES:
len = e->length;
buf = e->buffer;
fill:
n = stb_vorbis_get_samples_short_interleaved(s->ogg, 2, buf, len);
n *= 2;
/* rewind and fill remaining buffer if we reached the end of the ogg
** before filling it */
if (len != n) {
stb_vorbis_seek_start(s->ogg);
buf += n;
len -= n;
goto fill;
}
break;
case CM_EVENT_REWIND:
stb_vorbis_seek_start(s->ogg);
break;
}
}
static const char* ogg_init(cm_SourceInfo *info, void *data, int len, int ownsdata) {
OggStream *stream;
stb_vorbis *ogg;
stb_vorbis_info ogginfo;
int err;
ogg = stb_vorbis_open_memory(data, len, &err, NULL);
if (!ogg) {
return error("invalid ogg data");
}
stream = calloc(1, sizeof(*stream));
if (!stream) {
stb_vorbis_close(ogg);
return error("allocation failed");
}
stream->ogg = ogg;
if (ownsdata) {
stream->data = data;
}
ogginfo = stb_vorbis_get_info(ogg);
info->udata = stream;
info->handler = ogg_handler;
info->samplerate = ogginfo.sample_rate;
info->length = stb_vorbis_stream_length_in_samples(ogg);
/* Return NULL (no error) for success */
return NULL;
}
#endif

76
src/lib/cmixer/cmixer.h Normal file
View File

@@ -0,0 +1,76 @@
/*
** Copyright (c) 2017 rxi
**
** This library is free software; you can redistribute it and/or modify it
** under the terms of the MIT license. See `cmixer.c` for details.
**/
#ifndef CMIXER_H
#define CMIXER_H
#define CM_VERSION "0.1.0"
typedef short cm_Int16;
typedef int cm_Int32;
typedef long long cm_Int64;
typedef unsigned char cm_UInt8;
typedef unsigned short cm_UInt16;
typedef unsigned cm_UInt32;
typedef struct cm_Source cm_Source;
typedef struct {
int type;
void *udata;
const char *msg;
cm_Int16 *buffer;
int length;
} cm_Event;
typedef void (*cm_EventHandler)(cm_Event *e);
typedef struct {
cm_EventHandler handler;
void *udata;
int samplerate;
int length;
} cm_SourceInfo;
enum {
CM_STATE_STOPPED,
CM_STATE_PLAYING,
CM_STATE_PAUSED
};
enum {
CM_EVENT_LOCK,
CM_EVENT_UNLOCK,
CM_EVENT_DESTROY,
CM_EVENT_SAMPLES,
CM_EVENT_REWIND
};
const char* cm_get_error(void);
void cm_init(int samplerate);
void cm_set_lock(cm_EventHandler lock);
void cm_set_master_gain(double gain);
void cm_process(cm_Int16 *dst, int len);
cm_Source* cm_new_source(const cm_SourceInfo *info);
cm_Source* cm_new_source_from_file(const char *filename);
cm_Source* cm_new_source_from_mem(void *data, int size);
void cm_destroy_source(cm_Source *src);
double cm_get_length(cm_Source *src);
double cm_get_position(cm_Source *src);
int cm_get_state(cm_Source *src);
void cm_set_gain(cm_Source *src, double gain);
void cm_set_pan(cm_Source *src, double pan);
void cm_set_pitch(cm_Source *src, double pitch);
void cm_set_loop(cm_Source *src, int loop);
void cm_play(cm_Source *src);
void cm_pause(cm_Source *src);
void cm_stop(cm_Source *src);
#endif

View File

@@ -25,6 +25,7 @@ typedef struct {
#define LUAOBJ_TYPE_IMAGE (1 << 0)
#define LUAOBJ_TYPE_QUAD (1 << 1)
#define LUAOBJ_TYPE_FONT (1 << 2)
#define LUAOBJ_TYPE_SOURCE (1 << 3)
int luaobj_newclass(lua_State *L, const char *name, const char *extends,

View File

@@ -12,6 +12,7 @@
#include <dos.h>
#include "lib/dmt/dmt.h"
#include "lib/cmixer/cmixer.h"
#include "vga.h"
#include "luaobj.h"
#include "keyboard.h"
@@ -46,6 +47,22 @@ static int onLuaPanic(lua_State *L) {
}
static short audioBuffer[SOUNDBLASTER_SAMPLES_PER_BUFFER * 2];
static const short* audioCallback(void) {
/* For the moment the soundblaster code expects mono audio while the cmixer
** library outputs stereo -- we process to a stereo buffer, then copy the left
** channel to the start of the buffer */
int i;
int len = SOUNDBLASTER_SAMPLES_PER_BUFFER;
cm_process(audioBuffer, len * 2);
for (i = 0; i < len; i++) {
audioBuffer[i] = audioBuffer[i * 2];
}
return audioBuffer;
}
int luaopen_love(lua_State *L);
int main(int argc, char **argv) {
@@ -57,7 +74,8 @@ int main(int argc, char **argv) {
/* Init everything */
atexit(deinit);
// soundblaster_init(mixer_getNextBlock);
cm_init(soundblaster_getSampleRate());
soundblaster_init(audioCallback);
vga_init();
palette_init();
keyboard_init();

20
src/modules/l_audio.c Normal file
View File

@@ -0,0 +1,20 @@
/**
* Copyright (c) 2017 rxi
*
* This library is free software; you can redistribute it and/or modify it
* under the terms of the MIT license. See LICENSE for details.
*/
#include "luaobj.h"
int l_source_new(lua_State *L);
int luaopen_audio(lua_State *L) {
luaL_Reg reg[] = {
{ "newSource", l_source_new },
{ 0, 0 },
};
luaL_newlib(L, reg);
return 1;
}

View File

@@ -19,10 +19,12 @@ int l_love_getVersion(lua_State *L) {
int luaopen_image(lua_State *L);
int luaopen_quad(lua_State *L);
int luaopen_font(lua_State *L);
int luaopen_source(lua_State *L);
int luaopen_system(lua_State *L);
int luaopen_event(lua_State *L);
int luaopen_filesystem(lua_State *L);
int luaopen_graphics(lua_State *L);
int luaopen_audio(lua_State *L);
int luaopen_timer(lua_State *L);
int luaopen_keyboard(lua_State *L);
int luaopen_mouse(lua_State *L);
@@ -36,6 +38,7 @@ int luaopen_love(lua_State *L) {
luaopen_image,
luaopen_quad,
luaopen_font,
luaopen_source,
NULL,
};
for (i = 0; classes[i]; i++) {
@@ -56,6 +59,7 @@ int luaopen_love(lua_State *L) {
{ "event", luaopen_event },
{ "filesystem", luaopen_filesystem },
{ "graphics", luaopen_graphics },
{ "audio", luaopen_audio },
{ "timer", luaopen_timer },
{ "keyboard", luaopen_keyboard },
{ "mouse", luaopen_mouse },

155
src/modules/l_source.c Normal file
View File

@@ -0,0 +1,155 @@
/**
* Copyright (c) 2017 rxi
*
* This library is free software; you can redistribute it and/or modify it
* under the terms of the MIT license. See LICENSE for details.
*/
#include <string.h>
#include "lib/cmixer/cmixer.h"
#include "filesystem.h"
#include "luaobj.h"
#define CLASS_TYPE LUAOBJ_TYPE_SOURCE
#define CLASS_NAME "Source"
typedef struct {
cm_Source *source;
void *data;
} source_t;
int l_source_new(lua_State *L) {
const char *filename = luaL_checkstring(L, 1);
/* Create object */
source_t *self = luaobj_newudata(L, sizeof(*self));
luaobj_setclass(L, CLASS_TYPE, CLASS_NAME);
memset(self, 0, sizeof(*self));
/* Load file */
int size;
self->data = filesystem_read(filename, &size);
if (!self->data) {
luaL_error(L, "could not open file");
}
/* Init source */
self->source = cm_new_source_from_mem(self->data, size);
if (!self->source) {
luaL_error(L, "%s", cm_get_error());
}
/* Return object */
return 1;
}
int l_source_gc(lua_State *L) {
source_t *self = luaobj_checkudata(L, 1, CLASS_TYPE);
if (self->source) cm_destroy_source(self->source);
if (self->data) filesystem_free(self->data);
return 0;
}
int l_source_setVolume(lua_State *L) {
source_t *self = luaobj_checkudata(L, 1, CLASS_TYPE);
double n = luaL_checknumber(L, 2);
cm_set_gain(self->source, n);
return 0;
}
int l_source_setPitch(lua_State *L) {
source_t *self = luaobj_checkudata(L, 1, CLASS_TYPE);
double n = luaL_checknumber(L, 2);
cm_set_pitch(self->source, n);
return 0;
}
int l_source_setLooping(lua_State *L) {
source_t *self = luaobj_checkudata(L, 1, CLASS_TYPE);
int enable = lua_toboolean(L, 2);
cm_set_loop(self->source, enable);
return 0;
}
int l_source_getDuration(lua_State *L) {
source_t *self = luaobj_checkudata(L, 1, CLASS_TYPE);
double n = cm_get_length(self->source);
lua_pushnumber(L, n);
return 1;
}
int l_source_isPlaying(lua_State *L) {
source_t *self = luaobj_checkudata(L, 1, CLASS_TYPE);
lua_pushboolean(L, cm_get_state(self->source) == CM_STATE_PLAYING);
return 1;
}
int l_source_isPaused(lua_State *L) {
source_t *self = luaobj_checkudata(L, 1, CLASS_TYPE);
lua_pushboolean(L, cm_get_state(self->source) == CM_STATE_PAUSED);
return 1;
}
int l_source_isStopped(lua_State *L) {
source_t *self = luaobj_checkudata(L, 1, CLASS_TYPE);
lua_pushboolean(L, cm_get_state(self->source) == CM_STATE_STOPPED);
return 1;
}
int l_source_tell(lua_State *L) {
source_t *self = luaobj_checkudata(L, 1, CLASS_TYPE);
double n = cm_get_position(self->source);
lua_pushnumber(L, n);
return 1;
}
int l_source_play(lua_State *L) {
source_t *self = luaobj_checkudata(L, 1, CLASS_TYPE);
cm_play(self->source);
return 0;
}
int l_source_pause(lua_State *L) {
source_t *self = luaobj_checkudata(L, 1, CLASS_TYPE);
cm_pause(self->source);
return 0;
}
int l_source_stop(lua_State *L) {
source_t *self = luaobj_checkudata(L, 1, CLASS_TYPE);
cm_stop(self->source);
return 0;
}
int luaopen_source(lua_State *L) {
luaL_Reg reg[] = {
{ "new", l_source_new },
{ "__gc", l_source_gc },
{ "setVolume", l_source_setVolume },
{ "setPitch", l_source_setPitch },
{ "setLooping", l_source_setLooping },
{ "getDuration", l_source_getDuration },
{ "isPlaying", l_source_isPlaying },
{ "isPaused", l_source_isPaused },
{ "isStopped", l_source_isStopped },
{ "tell", l_source_tell },
{ "play", l_source_play },
{ "pause", l_source_pause },
{ "stop", l_source_stop },
{ 0, 0 },
};
luaobj_newclass(L, CLASS_NAME, NULL, l_source_new, reg);
return 1;
}