aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorkdx <kikoodx@paranoici.org>2023-01-15 00:59:15 +0100
committerkdx <kikoodx@paranoici.org>2023-01-15 01:02:34 +0100
commit8fbf9e2ef66d6b33c8eb1d357417465a3853c473 (patch)
tree13f8aef930877895b1f8ef60ac2463f9f32a3d5e
parent278949bacaa12237f5c21ce7dc64e81b09f69c48 (diff)
downloadlzr-8fbf9e2ef66d6b33c8eb1d357417465a3853c473.tar.gz
drop SDL2_mixer for rxi/cmixer
-rw-r--r--Makefile2
-rw-r--r--Tupfile6
-rw-r--r--cmixer.c757
-rw-r--r--cmixer.h70
-rw-r--r--lzr.c140
-rw-r--r--lzr.h10
6 files changed, 921 insertions, 64 deletions
diff --git a/Makefile b/Makefile
index 66592c0..506beaf 100644
--- a/Makefile
+++ b/Makefile
@@ -3,7 +3,7 @@ LD := $(CC)
CFLAGS := -Wall -Wextra -std=c99 -pedantic \
-D_POSIX_C_SOURCE=200809L $(shell sdl2-config --cflags)
# -D_POSIX_C_SOURCE is only used by DEVMODE
-LDFLAGS := $(shell sdl2-config --libs) -lSDL2_gfx -lSDL2_image -lSDL2_mixer
+LDFLAGS := $(shell sdl2-config --libs) -lSDL2_gfx -lSDL2_image
SRC := $(wildcard *.c)
OBJ := $(patsubst %.c,%.o,$(SRC))
NAME := lzr
diff --git a/Tupfile b/Tupfile
deleted file mode 100644
index 3131268..0000000
--- a/Tupfile
+++ /dev/null
@@ -1,6 +0,0 @@
-CFLAGS = -Wall -Wextra -std=c99 -pedantic `sdl2-config --cflags`
-LDFLAGS = -ldx `sdl2-config --libs` -lSDL2_gfx -lSDL2_image -lSDL2_mixer
-
-.gitignore
-: foreach *.c |> gcc $(CFLAGS) -c -o %o %f |> %B.o
-: *.o |> gcc -o %o %f $(LDFLAGS) |> lzr
diff --git a/cmixer.c b/cmixer.c
new file mode 100644
index 0000000..add9ac7
--- /dev/null
+++ b/cmixer.c
@@ -0,0 +1,757 @@
+/*
+** 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 <stdio.h>
+#include <stdlib.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);
+ }
+
+ /* Don't process if not playing */
+ if (src->state != CM_STATE_PLAYING) {
+ return;
+ }
+
+ /* 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 = CLAMP(pan, -1.0, 1.0);
+ recalc_source_gains(src);
+}
+
+void cm_set_pitch(cm_Source *src, double pitch)
+{
+ double rate;
+ if (pitch > 0.) {
+ rate = src->samplerate / (double)cmixer.samplerate * pitch;
+ } else {
+ rate = 0.001;
+ }
+ 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
diff --git a/cmixer.h b/cmixer.h
new file mode 100644
index 0000000..6ca680b
--- /dev/null
+++ b/cmixer.h
@@ -0,0 +1,70 @@
+/*
+** 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.1"
+
+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
diff --git a/lzr.c b/lzr.c
index 6548f16..ee4d1e4 100644
--- a/lzr.c
+++ b/lzr.c
@@ -8,7 +8,7 @@
# include <SDL2/SDL_image.h>
#endif
#ifdef LZR_ENABLE_MIXER
-# include <SDL2/SDL_mixer.h>
+# include "cmixer.h"
#endif
#ifdef LZR_ENABLE_DEVMODE
# include <sys/stat.h>
@@ -51,9 +51,24 @@ static int mouse_y = 0;
#ifdef LZR_ENABLE_MIXER
static struct {
- Mix_Chunk *ptr;
+ cm_Source *ptr;
} sounds[LZR_MAX_SOUNDS] = {0};
-static Mix_Music *music = NULL;
+static SDL_mutex *audio_mutex = NULL;
+static SDL_AudioDeviceID audio_dev = 0;
+
+static void _lock_handler(cm_Event *e)
+{
+ if (e->type == CM_EVENT_LOCK)
+ SDL_LockMutex(audio_mutex);
+ else
+ SDL_UnlockMutex(audio_mutex);
+}
+
+static void _audio_callback(void *udata, Uint8 *stream, int size)
+{
+ (void)udata;
+ cm_process((void *)stream, size / 2);
+}
#endif
static char *_lzrstrdup(const char *str)
@@ -176,8 +191,7 @@ int LZR_Init(LZR_Config cfg)
(float)config.display_width / (float)config.display_height;
config.ratio /= ratio;
}
-
- if (SDL_Init(SDL_INIT_VIDEO) < 0) {
+ if (SDL_Init(SDL_INIT_VIDEO | SDL_INIT_AUDIO) < 0) {
SDL_Log("%s", SDL_GetError());
return -1;
}
@@ -188,14 +202,26 @@ int LZR_Init(LZR_Config cfg)
}
#endif
#ifdef LZR_ENABLE_MIXER
- if (Mix_Init(MIX_INIT_OGG) != MIX_INIT_OGG) {
- SDL_Log("%s", Mix_GetError());
+ audio_mutex = SDL_CreateMutex();
+ if (audio_mutex == NULL) {
+ SDL_Log("%s", SDL_GetError());
return -1;
}
- if (Mix_OpenAudio(44100, MIX_DEFAULT_FORMAT, 8, 1024) < 0) {
- SDL_Log("%s", Mix_GetError());
+ SDL_AudioSpec fmt = {.freq = 44100,
+ .format = AUDIO_S16,
+ .channels = 2,
+ .samples = 1024,
+ .callback = _audio_callback};
+ SDL_AudioSpec got;
+ audio_dev = SDL_OpenAudioDevice(NULL, 0, &fmt, &got,
+ SDL_AUDIO_ALLOW_FREQUENCY_CHANGE);
+ if (audio_dev == 0) {
+ SDL_Log("%s", SDL_GetError());
return -1;
}
+ cm_init(44100);
+ cm_set_lock(_lock_handler);
+ SDL_PauseAudioDevice(audio_dev, 0);
#endif
basepath = SDL_GetBasePath();
if (basepath == NULL) {
@@ -242,16 +268,25 @@ int LZR_Init(LZR_Config cfg)
void LZR_Quit(void)
{
- LZR_StopMusic();
#ifdef LZR_ENABLE_MIXER
- for (int i = 0; i < LZR_MAX_SOUNDS; i++)
+ for (int i = LZR_MAX_SOUNDS - 1; i >= 0; i--)
+ if (sounds[i].ptr != NULL)
+ cm_stop(sounds[i].ptr);
+ SDL_Delay(100);
+ for (int i = LZR_MAX_SOUNDS - 1; i >= 0; i--)
if (sounds[i].ptr != NULL) {
- Mix_FreeChunk(sounds[i].ptr);
+ cm_destroy_source(sounds[i].ptr);
sounds[i].ptr = NULL;
SDL_Log("destroyed sound %d", i);
}
- Mix_CloseAudio();
- Mix_Quit();
+ if (audio_dev != 0) {
+ SDL_CloseAudioDevice(audio_dev);
+ audio_dev = 0;
+ }
+ if (audio_mutex != NULL) {
+ SDL_DestroyMutex(audio_mutex);
+ audio_mutex = NULL;
+ }
#endif
for (int i = 0; i < LZR_MAX_IMAGES; i++) {
if (images[i].tex != NULL) {
@@ -399,13 +434,13 @@ int LZR_SoundLoad(const char *path, float volume)
SDL_Log("LZR_PathPrefix failed");
return -1;
}
- Mix_Chunk *const chunk = Mix_LoadWAV(apath);
+ cm_Source *const chunk = cm_new_source_from_file(apath);
free(apath);
if (chunk == NULL) {
- SDL_Log("%s: %s", path, Mix_GetError());
+ SDL_Log("%s: %s", path, cm_get_error());
return -1;
}
- Mix_VolumeChunk(chunk, volume * MIX_MAX_VOLUME);
+ cm_set_gain(chunk, volume);
sounds[i].ptr = chunk;
return i;
#else
@@ -781,7 +816,7 @@ int LZR_DrawTile(int id, int tile, int x, int y, double rot, int flip)
return 0;
}
-int LZR_PlaySound(int id)
+int LZR_PlaySound(int id, int loops)
{
#ifdef LZR_ENABLE_MIXER
if (id < 0) {
@@ -792,70 +827,71 @@ int LZR_PlaySound(int id)
SDL_Log("no sound with id %d", id);
return -1;
}
- if (Mix_PlayChannel(-1, sounds[id].ptr, 0) < 0) {
- SDL_Log("%s", Mix_GetError());
- return -1;
- }
+ cm_stop(sounds[id].ptr);
+ cm_set_loop(sounds[id].ptr, loops);
+ cm_play(sounds[id].ptr);
return 0;
#else
- (void)id;
+ (void)id, (void)loops;
SDL_Log("LZR MIXER module is disabled");
return -1;
#endif
}
-int LZR_SetMusicVolume(float volume)
+void LZR_StopSound(int id)
{
#ifdef LZR_ENABLE_MIXER
- if (Mix_VolumeMusic(volume * MIX_MAX_VOLUME) < 0) {
- SDL_Log("%s", Mix_GetError());
- return -1;
+ if (id < 0) {
+ SDL_Log("id is negative");
+ return;
}
- return 0;
+ if (id >= LZR_MAX_SOUNDS || sounds[id].ptr == NULL) {
+ SDL_Log("no sound with id %d", id);
+ return;
+ }
+ cm_stop(sounds[id].ptr);
#else
- (void)volume;
- SDL_Log("LZR MIXER module is disabled");
- return -1;
+ (void)id;
#endif
}
-int LZR_PlayMusic(const char *path, int loops)
+int LZR_SetSoundVolume(int id, float volume)
{
#ifdef LZR_ENABLE_MIXER
- LZR_StopMusic();
- char *const apath = LZR_PathPrefix(path);
- if (apath == NULL) {
- SDL_Log("LZR_PathPrefix failed");
- return -1;
- }
- music = Mix_LoadMUS(apath);
- free(apath);
- if (music == NULL) {
- SDL_Log("%s: %s", path, Mix_GetError());
+ if (id < 0) {
+ SDL_Log("id is negative");
return -1;
}
- if (Mix_PlayMusic(music, loops) < 0) {
- SDL_Log("%s", Mix_GetError());
+ if (id >= LZR_MAX_SOUNDS || sounds[id].ptr == NULL) {
+ SDL_Log("no sound with id %d", id);
return -1;
}
- Mix_RewindMusic();
+ cm_set_gain(sounds[id].ptr, volume);
return 0;
#else
- (void)path, (void)loops;
+ (void)id, (void)volume;
SDL_Log("LZR MIXER module is disabled");
return -1;
#endif
}
-void LZR_StopMusic(void)
+int LZR_SetSoundPan(int id, float pan)
{
#ifdef LZR_ENABLE_MIXER
- if (Mix_PlayingMusic())
- Mix_HaltMusic();
- if (music != NULL) {
- Mix_FreeMusic(music);
- music = NULL;
+ if (id < 0) {
+ SDL_Log("id is negative");
+ return -1;
+ }
+ if (id >= LZR_MAX_SOUNDS || sounds[id].ptr == NULL) {
+ SDL_Log("no sound with id %d", id);
+ return -1;
}
+ cm_set_pan(sounds[id].ptr, pan);
+ return 0;
+#else
+ (void)id, (void)pan;
+ SDL_Log("LZR MIXER module is disabled");
+ return -1;
#endif
}
diff --git a/lzr.h b/lzr.h
index 220d13e..12f2bb5 100644
--- a/lzr.h
+++ b/lzr.h
@@ -22,7 +22,7 @@ extern "C" {
# define LZR_ENABLE_DEVMODE
#endif
-#define LZR_MAX_IMAGES 32
+#define LZR_MAX_IMAGES 64
#define LZR_MAX_SOUNDS 64
#define LZR_BUTTON(btn) LZR_ButtonDown(LZR_BUTTON_##btn)
#define LZR_IMAGE(img) LZR_ImageLoad(img)
@@ -93,10 +93,10 @@ int LZR_DrawPolygon(bool fill, int *vx, int *vy, int n);
int LZR_DrawImage(int id, int x, int y);
int LZR_DrawImageEx(int id, int x, int y, LZR_ImageDrawSettings stg);
int LZR_DrawTile(int id, int tile, int x, int y, double rot, int flip);
-int LZR_PlaySound(int id);
-int LZR_SetMusicVolume(float volume);
-int LZR_PlayMusic(const char *path, int loops);
-void LZR_StopMusic(void);
+int LZR_PlaySound(int id, int loops);
+void LZR_StopSound(int id);
+int LZR_SetSoundVolume(int id, float volume);
+int LZR_SetSoundPan(int id, float pan);
void LZR_ToggleFullscreen(void);
uint64_t LZR_GetTick(void);
void LZR_ScreenTransform(int *x, int *y);