summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorsanine <sanine.not@pm.me>2022-08-28 11:25:44 -0500
committersanine <sanine.not@pm.me>2022-08-28 11:25:44 -0500
commit0d6ece00397ebb9215ccf1af06cce22c3a94197e (patch)
tree022cc796822a998a59d8bf8896f02599ce6d3700
parenta4dd0ad63c00f4dee3b86dfd3075d1d61b2b3180 (diff)
begin plibsys refactor
-rw-r--r--include/mossrose.h8
-rw-r--r--src/CMakeLists.txt11
-rw-r--r--src/channel.c83
-rw-r--r--src/channel.h20
-rw-r--r--src/channel.test.c6
-rw-r--r--src/mossrose.c132
-rw-r--r--src/mutex.c58
-rw-r--r--src/mutex.h28
-rw-r--r--src/sound.c44
-rw-r--r--src/sound.h9
-rw-r--r--src/sound.test.c31
-rw-r--r--src/test/lily-test.c365
-rw-r--r--src/test/lily-test.h263
-rw-r--r--src/test/mossrose-test.c8
-rw-r--r--src/test/mossrose-test.h13
15 files changed, 762 insertions, 317 deletions
diff --git a/include/mossrose.h b/include/mossrose.h
index fb7d088..5ca454a 100644
--- a/include/mossrose.h
+++ b/include/mossrose.h
@@ -3,10 +3,16 @@
#include <stddef.h>
+struct mossrose_sound_t {
+ float *left;
+ float *right;
+ size_t len;
+};
+
int mossrose_init(double sample_rate, int n_channels);
int mossrose_terminate();
-int mossrose_play(float *left, float *right, size_t n_samples, int channel);
+int mossrose_play(struct mossrose_sound_t *sound, int channel);
#endif
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
index 069d00a..cca9610 100644
--- a/src/CMakeLists.txt
+++ b/src/CMakeLists.txt
@@ -2,6 +2,15 @@ project(mossrose)
target_sources(mossrose PUBLIC
${CMAKE_CURRENT_LIST_DIR}/mossrose.c
- ${CMAKE_CURRENT_LIST_DIR}/mutex.c
${CMAKE_CURRENT_LIST_DIR}/channel.c
)
+
+
+if (MOSSROSE_BUILD_TESTS)
+ target_sources(test PUBLIC
+ ${CMAKE_CURRENT_LIST_DIR}/test/lily-test.c
+ ${CMAKE_CURRENT_LIST_DIR}/test/mossrose-test.c
+ ${CMAKE_CURRENT_LIST_DIR}/channel.test.c
+ ${CMAKE_CURRENT_LIST_DIR}/sound.test.c
+ )
+endif()
diff --git a/src/channel.c b/src/channel.c
index 169b245..fd6741b 100644
--- a/src/channel.c
+++ b/src/channel.c
@@ -1,85 +1,8 @@
#include <stdlib.h>
#include <string.h>
-#include "mutex.h"
+#include <plibsys.h>
#include "channel.h"
-void mossrose_channel_init(struct mossrose_channel_t *chan)
-{
- mossrose_mutex_init(&(chan->mutex));
- chan->left = NULL;
- chan->right = NULL;
- chan->n_samples = 0;
- chan->pos = 0;
-}
-
-
-int mossrose_channel_set(struct mossrose_channel_t *chan, float *left, float *right, size_t len, int force)
-{
- int result = 0;
- mossrose_mutex_lock(&(chan->mutex));
- if (chan->n_samples != 0 && !force) {
- /* channel is still playing! */
- result = 1; goto unlock;
- }
-
- chan->n_samples = len;
- chan->pos = 0;
-
- /* left channel */
- if (chan->left != NULL) free(chan->left);
- if (left == NULL)
- chan->left = NULL;
- else {
- chan->left = malloc(len * sizeof(float));
- if (chan->left == NULL) { result = 2; goto unlock; }
- memcpy(chan->left, left, len * sizeof(float));
- }
-
- /* right channel */
- if (chan->right != NULL) free(chan->right);
- if (right == NULL)
- chan->right = NULL;
- else {
- chan->right = malloc(len * sizeof(float));
- if (chan->right == NULL) { result = 3; goto unlock; }
- memcpy(chan->right, right, len * sizeof(float));
- }
-
- unlock:
- mossrose_mutex_unlock(&(chan->mutex));
- return result;
-}
-
-
-void mossrose_channel_reset(struct mossrose_channel_t *chan)
-{
- chan->n_samples = 0;
- chan->pos = 0;
-}
-
-
-int mossrose_channel_advance(float *left, float *right, struct mossrose_channel_t *chan)
-{
- if (chan->pos >= chan->n_samples) return 1;
- if (chan->left != NULL)
- *left = chan->left[chan->pos];
- else
- *left = 0;
- if (chan->right != NULL)
- *right = chan->right[chan->pos];
- else
- *right = 0;
- chan->pos += 1;
- return 0;
-}
-
-
-void mossrose_channel_destroy(struct mossrose_channel_t *chan)
-{
- mossrose_mutex_destroy(&(chan->mutex));
- if (chan->left != NULL) free(chan->left);
- if (chan->right != NULL) free(chan->right);
-}
-
-
+struct channel_t {
+};
diff --git a/src/channel.h b/src/channel.h
index 23c11d6..6266a10 100644
--- a/src/channel.h
+++ b/src/channel.h
@@ -2,25 +2,7 @@
#define MOSSROSE_CHANNEL_H
#include <stddef.h>
-#include "mutex.h"
+#include <plibsys.h>
-struct mossrose_channel_t {
- mossrose_mutex_t mutex;
- float *left;
- float *right;
- size_t n_samples;
- size_t pos;
-};
-
-
-void mossrose_channel_init(struct mossrose_channel_t *chan);
-
-int mossrose_channel_set(struct mossrose_channel_t *chan, float *left, float *right, size_t len, int force);
-
-void mossrose_channel_reset(struct mossrose_channel_t *chan);
-
-int mossrose_channel_advance(float *left, float *right, struct mossrose_channel_t *chan);
-
-void mossrose_channel_destroy(struct mossrose_channel_t *chan);
#endif
diff --git a/src/channel.test.c b/src/channel.test.c
new file mode 100644
index 0000000..96b7364
--- /dev/null
+++ b/src/channel.test.c
@@ -0,0 +1,6 @@
+#include "test/mossrose-test.h"
+
+
+void suite_channel()
+{
+}
diff --git a/src/mossrose.c b/src/mossrose.c
index 4f31711..40f4393 100644
--- a/src/mossrose.c
+++ b/src/mossrose.c
@@ -1,149 +1,21 @@
#include <stdio.h>
#include <stdlib.h>
+#include <plibsys.h>
#include <portaudio.h>
#include <mossrose.h>
-#include "mutex.h"
#include "channel.h"
-/* ~~~~~~~~~~~~~~~~ type definitions ~~~~~~~~~~~~~~~~ */
-
-/* audio output */
-struct audio_output_t {
- float l;
- float r;
-};
-
-
-
-/* ~~~~~~~~~~~~~~~~ globals ~~~~~~~~~~~~~~~~ */
-
-struct mossrose_globals_t {
- PaStream *stream;
- struct mossrose_channel_t *channels;
- int n_channels;
-} mossrose_global;
-
-
-struct audio_output_t build_sample()
-{
- struct audio_output_t out;
- out.l = 0; out.r = 0;
-
- /* loop variables */
- struct mossrose_channel_t *chan;
- float chan_l, chan_r;
-
- for (int i=0; i<mossrose_global.n_channels; i++) {
- chan = mossrose_global.channels + i;
- if (mossrose_mutex_trylock(&(chan->mutex)) != 0) {
- /* can't lock the mutex, this channel is being modified */
- printf("can't lock channel %d\n", i);
- continue;
- }
-
- if (chan->n_samples == 0) {
- /* channel is not currently in use, skip */
- }
- else {
- if (mossrose_channel_advance(&chan_l, &chan_r, chan) != 0)
- /* channel is done playing, reset */
- mossrose_channel_reset(chan);
- else {
- out.l += chan_l;
- out.r += chan_r;
- }
- }
-
- mossrose_mutex_unlock(&(chan->mutex));
- }
-
- return out;
-}
-
-
-static int callback(
- const void *input,
- void *output,
- unsigned long frame_count,
- const PaStreamCallbackTimeInfo *time_info,
- void *userdata)
-{
- struct audio_output_t *out = output;
- struct audio_output_t sample;
-
- for (int i=0; i<frame_count; i++) {
- sample = build_sample();
- out[i].l = sample.l;
- out[i].r = sample.r;
- }
- return 0;
-}
-
-
int mossrose_init(double sample_rate, int n_channels)
{
- /* initialize channels */
- mossrose_global.n_channels = n_channels;
- mossrose_global.channels = malloc(n_channels * sizeof(struct mossrose_channel_t));
- if (mossrose_global.channels == NULL) {
- fprintf(stderr, "failed to allocate memory for %d channels", n_channels);
- return 1;
- }
- for (int i=0; i<n_channels; i++) {
- mossrose_channel_init(mossrose_global.channels + i);
- }
-
- PaError err;
-
- /* initialize portaudio */
- err = Pa_Initialize();
- if (err != paNoError) {
- fprintf(stderr, "failed to initialize PortAudio!\n");
- return 1;
- }
-
- /* open stream */
- err = Pa_OpenDefaultStream(&(mossrose_global.stream), 0, 2, paFloat32, sample_rate, 0, callback, NULL);
- if (err != paNoError) {
- fprintf(stderr, "failed to open audio stream!\n");
- return 1;
- }
-
- /* start stream */
- err = Pa_StartStream(mossrose_global.stream);
- if (err != paNoError) {
- fprintf(stderr, "failed to start audio stream!\n");
- return 1;
- }
-
- return 0;
};
-int mossrose_play(float *left, float *right, size_t len, int channel)
+int mossrose_play(struct mossrose_sound_t *sound, int channel)
{
- if (channel > 0) {
- if (mossrose_channel_set(mossrose_global.channels+channel, left, right, len, 1) == 0)
- return channel;
- else
- return -1;
- }
- else {
- struct mossrose_channel_t *chan;
- for (int i=0; i<mossrose_global.n_channels; i++) {
- chan = mossrose_global.channels + i;
- if (mossrose_channel_set(chan, left, right, len, 0) == 0) return i;
- }
- return -1;
- }
}
int mossrose_terminate(double sample_rate, int n_channels)
{
- Pa_AbortStream(mossrose_global.stream);
- Pa_CloseStream(mossrose_global.stream);
- Pa_Terminate();
- return 0;
}
diff --git a/src/mutex.c b/src/mutex.c
deleted file mode 100644
index 4a51fa8..0000000
--- a/src/mutex.c
+++ /dev/null
@@ -1,58 +0,0 @@
-#include "mutex.h"
-
-#ifdef WIN32
-#include <windows.h>
-void mossrose_mutex_init(mossrose_mutex_t *mutex)
-{
- *mutex = CreateMutex(NULL, false, NULL);
-}
-
-void mossrose_mutex_lock(mossrose_mutex_t *mutex)
-{
- WaitForSingleObject(*mutex, INFINITE);
-}
-
-int mossrose_mutex_trylock(mossrose_mutex_t *mutex)
-{
- int result = WaitForSingleObject(*mutex, 0);
- return result != WAIT_OBJECT_0;
-}
-
-void mossrose_mutex_unlock(mossrose_mutex_t *mutex)
-{
- ReleaseMutex(*mutex);
-}
-
-void mossrose_mutex_destroy(mossrose_mutex_t *mutex)
-{
- ReleaseMutex(*mutex);
-}
-
-
-#else
-#include <pthread.h>
-void mossrose_mutex_init(mossrose_mutex_t *mutex)
-{
- pthread_mutex_init(mutex, NULL);
-}
-
-void mossrose_mutex_lock(mossrose_mutex_t *mutex)
-{
- pthread_mutex_lock(mutex);
-}
-
-int mossrose_mutex_trylock(mossrose_mutex_t *mutex)
-{
- return pthread_mutex_trylock(mutex);
-}
-
-void mossrose_mutex_unlock(mossrose_mutex_t *mutex)
-{
- pthread_mutex_unlock(mutex);
-}
-
-void mossrose_mutex_destroy(mossrose_mutex_t *mutex)
-{
- pthread_mutex_destroy(mutex);
-}
-#endif
diff --git a/src/mutex.h b/src/mutex.h
deleted file mode 100644
index 84d1f63..0000000
--- a/src/mutex.h
+++ /dev/null
@@ -1,28 +0,0 @@
-#ifndef MOSSROSE_MUTEX_H
-#define MOSSROSE_MUTEX_H
-
-
-#ifdef WIN32
-#include <windows.h>
-typedef HANDLE mossrose_mutex_t;
-#else
-#include <pthread.h>
-typedef pthread_mutex_t mossrose_mutex_t;
-#endif
-
-/* initialize a mutex */
-void mossrose_mutex_init(mossrose_mutex_t *mutex);
-
-/* lock a mutex, hanging until locked */
-void mossrose_mutex_lock(mossrose_mutex_t *mutex);
-
-/* attempt to lock a mutex. returns 0 on success and 1 otherwise */
-int mossrose_mutex_trylock(mossrose_mutex_t *mutex);
-
-/* unlock a mutex */
-void mossrose_mutex_unlock(mossrose_mutex_t *mutex);
-
-/* destroy a mutex */
-void mossrose_mutex_destroy(mossrose_mutex_t *mutex);
-
-#endif
diff --git a/src/sound.c b/src/sound.c
new file mode 100644
index 0000000..36c1e5b
--- /dev/null
+++ b/src/sound.c
@@ -0,0 +1,44 @@
+#include <stdlib.h>
+#include <string.h>
+#include <mossrose.h>
+#include "sound.h"
+
+
+int sound_copy(struct mossrose_sound_t *dest, struct mossrose_sound_t *src)
+{
+ sound_free_audio(dest);
+ dest->len = src->len;
+
+ /* left channel */
+ if (src->left != NULL) {
+ dest->left = malloc(src->len * sizeof(float));
+ if (dest->left == NULL) return 1;
+ memcpy(dest->left, src->left, src->len * sizeof(float));
+ }
+ else
+ dest->left = NULL;
+
+ /* right channel */
+ if (src->right != NULL) {
+ dest->right = malloc(src->len * sizeof(float));
+ if (dest->right == NULL) return 1;
+ memcpy(dest->right, src->right, src->len * sizeof(float));
+ }
+ else
+ dest->right = NULL;
+
+ return 0;
+}
+
+
+void sound_free_audio(struct mossrose_sound_t *sound)
+{
+ if (sound->left == NULL) {
+ free(sound->left);
+ sound->left = NULL;
+ }
+ if (sound->right == NULL) {
+ free(sound->right);
+ sound->right = NULL;
+ }
+}
diff --git a/src/sound.h b/src/sound.h
new file mode 100644
index 0000000..282441b
--- /dev/null
+++ b/src/sound.h
@@ -0,0 +1,9 @@
+#ifndef MOSSROSE_SOUND_H
+#define MOSSROSE_SOUND_H
+
+#include <mossrose.h>
+
+int sound_copy(struct mossrose_sound_t *dest, struct mossrose_sound_t *src);
+void sound_free_audio(struct mossrose_sound_t *sound);
+
+#endif
diff --git a/src/sound.test.c b/src/sound.test.c
new file mode 100644
index 0000000..60dcdd1
--- /dev/null
+++ b/src/sound.test.c
@@ -0,0 +1,31 @@
+#include "test/mossrose-test.h"
+
+#include "sound.c"
+
+
+void test_sound_copy()
+{
+ struct mossrose_sound_t src, dest;
+ float left[] = { 0.1, 0.2, 0.3 };
+ float right[] = { 0.0, 0.5, 1.0 };
+ src.left = left;
+ src.right = right;
+ src.len = 3;
+
+ dest.left = NULL;
+ dest.right = NULL;
+
+ sound_copy(&dest, &src);
+
+ lily_assert_int_equal(dest.len, 3);
+ lily_assert_not_null(dest.left);
+ lily_assert_memory_equal(dest.left, left, 3*sizeof(float));
+ lily_assert_not_null(dest.right);
+ lily_assert_memory_equal(dest.right, right, 3*sizeof(float));
+}
+
+
+void suite_sound()
+{
+ lily_run_test(test_sound_copy);
+}
diff --git a/src/test/lily-test.c b/src/test/lily-test.c
new file mode 100644
index 0000000..2f43a28
--- /dev/null
+++ b/src/test/lily-test.c
@@ -0,0 +1,365 @@
+/****************************************************************
+ *
+ * ======== lily-test ========
+ *
+ * a midsize C unit testing library
+ * copyright (c) 2022 kate swanson (sanine)
+ *
+ * This is anti-capitalist software, released for free use by individuals and
+ * organizations that do not operate by capitalist principles.
+ *
+ * Permission is hereby granted, free of charge, to any person or
+ * organization (the "User") obtaining a copy of this software and associated
+ * documentation files (the "Software"), to use, copy, modify, merge,
+ * distribute, and/or sell copies of the Software, subject to the following
+ * conditions:
+ *
+ * 1. The above copyright notice and this permission notice shall be included
+ * in all copies or modified versions of the Software.
+ *
+ * 2. The User is one of the following:
+ * a. An individual person, laboring for themselves
+ * b. A non-profit organization
+ * c. An educational institution
+ * d. An organization that seeks shared profit for all of its members, and
+ * allows non-members to set the cost of their labor
+ *
+ * 3. If the User is an organization with owners, then all owners are workers
+ * and all workers are owners with equal equity and/or equal vote.
+ *
+ * 4. If the User is an organization, then the User is not law enforcement or
+ * military, or working for or under either.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT EXPRESS OR IMPLIED WARRANTY OF
+ * ANY KIND, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS 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.
+ *
+ * https://anticapitalist.software/
+ * https://sanine.net
+ *
+ ****************************************************************/
+
+
+#include <stdarg.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include "lily-test.h"
+
+struct lily_globals_t _lily_globals = { {0}, 0, NULL, "" };
+
+/* run an individual test */
+void _lily_run_test(const char *name, lily_test_t test)
+{
+ printf(" %s: ", name);
+
+ _lily_globals.error_msg = NULL;
+ _lily_globals.error_location = "";
+ int val = setjmp(_lily_globals.env);
+
+ if (val) {
+ /* test failed */
+ printf("FAIL - %s (%s)\n",
+ _lily_globals.error_msg,
+ _lily_globals.error_location);
+ free(_lily_globals.error_msg); /* error message was allocated! */
+ return;
+ }
+
+ test();
+
+ /* test succeeded */
+ printf("OK\n");
+}
+
+/* run a suite */
+void _lily_run_suite(const char *name, lily_test_t suite)
+{
+ printf("=== %s ===\n", name);
+ suite();
+ printf("\n");
+}
+
+
+/* ======== ASSERTIONS ======== */
+
+static void _lily_assert_msg(bool statement, const char *location,
+ const char *format_string, va_list args)
+{
+ if (statement) {
+ va_end(args);
+ return; // no error, return
+ }
+
+ _lily_globals.error_location = location;
+
+ va_list args_len;
+ va_copy(args_len, args);
+ size_t length = vsnprintf(NULL, 0, format_string, args_len);
+ va_end(args_len);
+
+ if (_lily_globals.error_msg_len < length+1 ||
+ _lily_globals.error_msg == NULL) {
+ if (_lily_globals.error_msg != NULL)
+ free(_lily_globals.error_msg);
+
+ char *msg = malloc((length+2) * sizeof(char));
+
+ if (msg == NULL) {
+ fprintf(stderr, "WARNING: failed to allocate memory for failed test message!\n");
+ _lily_globals.error_msg = NULL;
+ va_end(args);
+ longjmp(_lily_globals.env, 1);
+ return;
+ }
+ else {
+ _lily_globals.error_msg = msg;
+ _lily_globals.error_msg_len = length+1;
+ }
+ }
+
+ vsnprintf(_lily_globals.error_msg, length+1, format_string, args);
+
+ va_end(args);
+ longjmp(_lily_globals.env, 1);
+}
+
+
+void lily_assert_msg(bool statement, const char *location,
+ const char *format_string, ...)
+{
+ va_list args;
+ va_start(args, format_string);
+ // _lily_assert_msg may long jump, so it takes calls va_end(args) for you
+ _lily_assert_msg(statement, location, format_string, args);
+}
+
+
+void _lily_assert_true(const char *statement, bool value, const char *location)
+{
+ lily_assert_msg(value, location,
+ "%s is not true",
+ statement);
+}
+
+
+void _lily_assert_false(const char *statement, bool value, const char *location)
+{
+ lily_assert_msg(!value, location,
+ "%s is not false",
+ statement);
+}
+
+
+void _lily_assert_not_null(const char *name, void *ptr, const char *location)
+{
+ lily_assert_msg(ptr != NULL, location,
+ "%s is NULL",
+ name);
+}
+
+
+void _lily_assert_null(const char *name, void *ptr, const char *location)
+{
+ lily_assert_msg(ptr == NULL, location,
+ "%s (%p) is not NULL",
+ name, ptr);
+}
+
+
+void _lily_assert_ptr_equal(const char *name_a, const char *name_b,
+ void *a, void *b, const char *location)
+{
+ lily_assert_msg(a == b, location,
+ "%s (%p) is not equal to %s (%p)",
+ name_a, a, name_b, b);
+}
+
+
+void _lily_assert_ptr_not_equal(const char *name_a, const char *name_b,
+ void *a, void *b, const char *location)
+{
+ lily_assert_msg(a != b, location,
+ "%s (%p) is equal to %s",
+ name_a, a, name_b);
+}
+
+
+void _lily_assert_int_equal(const char *name_a, const char *name_b,
+ intmax_t a, intmax_t b, const char *location)
+{
+ lily_assert_msg(a == b, location,
+ "%s (%d) is not equal to %s (%d)",
+ name_a, a, name_b, b);
+}
+
+
+void _lily_assert_int_not_equal(const char *name_a, const char *name_b,
+ intmax_t a, intmax_t b, const char *location)
+{
+ lily_assert_msg(a != b, location,
+ "%s (%d) is equal to %s",
+ name_a, a, name_b);
+}
+
+
+void _lily_assert_float_equal(const char *name_a, const char *name_b,
+ double a, double b, double epsilon, const char *location)
+{
+ lily_assert_msg(abs(a - b) <= epsilon,
+ "%s (%f) is not equal to %s (%f) (epsilon: %f)",
+ name_a, a, name_b, b, epsilon);
+}
+
+
+void _lily_assert_float_not_equal(const char *name_a, const char *name_b,
+ double a, double b, double epsilon, const char *location)
+{
+ lily_assert_msg(abs(a - b) > epsilon,
+ "%s (%f) is equal to %s (%f) (epsilon: %f)",
+ name_a, a, name_b, b, epsilon);
+}
+
+
+void _lily_assert_string_equal(const char *name_a, const char *name_b,
+ char *a, char *b, const char *location)
+{
+ lily_assert_msg(strcmp(a, b) == 0,
+ "%s ('%s') is not equal to %s ('%s')",
+ name_a, a, name_b, b);
+}
+
+
+void _lily_assert_string_not_equal(const char *name_a, const char *name_b,
+ char *a, char *b, const char *location)
+{
+ lily_assert_msg(strcmp(a, b) != 0,
+ "%s ('%s') is equal to %s",
+ name_a, a, name_b);
+}
+
+
+void _lily_assert_memory_equal(const char *name_a, const char *name_b,
+ void *a, void *b, size_t size, const char *location)
+{
+ lily_assert_msg(memcmp(a, b, size) == 0,
+ "%s and %s contain different data", name_a, name_b);
+}
+
+
+void _lily_assert_memory_not_equal(const char *name_a, const char *name_b,
+ void *a, void *b, size_t size, const char *location)
+{
+ lily_assert_msg(memcmp(a, b, size) == 0,
+ "%s contains the same data s %s", name_a, name_b);
+}
+
+
+/* ======== MOCKS ======== */
+
+lily_queue_t* lily_queue_create()
+{
+ const size_t size = 256 * sizeof(uint8_t);
+
+ lily_queue_t *q = malloc(sizeof(lily_queue_t));
+ q->buf_size = size;
+ q->buf = malloc(size);
+ q->front = q->buf;
+ q->back = q->front;
+
+ return q;
+}
+
+
+void lily_queue_destroy(lily_queue_t *q)
+{
+ free(q->buf);
+ free(q);
+}
+
+
+void _lily_enqueue(lily_queue_t *q, size_t size, uint8_t *data)
+{
+ size_t used = q->back - q->buf;
+ size_t remaining = q->buf_size - used;
+
+ if (remaining < size) {
+ /* re-allocate bigger buffer */
+ size_t size_new = 2 * q->buf_size;
+ uint8_t *buf_new = realloc(q->buf, size_new);
+ if (buf_new == NULL) {
+ fprintf(stderr, "failed to allocated %lu bytes for queue %p!\n",
+ size_new, q);
+ return;
+ }
+ q->buf = buf_new;
+ }
+
+ memcpy(q->back, data, size);
+ q->back += size;
+}
+
+
+void _lily_dequeue(lily_queue_t *q, size_t size, uint8_t *ptr)
+{
+ size_t dist = q->back - q->front;
+ if (dist < size) {
+ fprintf(stderr, "attempted to read %lu bytes out of queue %p, "
+ "which has %lu bytes presently queued\n", size, q, dist);
+ return;
+ }
+
+ memcpy(ptr, q->front, size);
+ q->front += size;
+}
+
+
+lily_mock_t * lily_mock_create()
+{
+ lily_mock_t *m = malloc(sizeof(lily_mock_t));
+ m->n_calls = 0;
+ m->arguments = lily_queue_create();
+ m->values = lily_queue_create();
+ return m;
+}
+
+void lily_mock_destroy(lily_mock_t *m)
+{
+ lily_queue_destroy(m->arguments);
+ lily_queue_destroy(m->values);
+ free(m);
+}
+
+void lily_mock_use(lily_mock_t **m)
+{
+ if (*m != NULL) lily_mock_destroy(*m);
+ *m = lily_mock_create();
+}
+
+void _lily_mock_call(lily_mock_t *m, struct lily_mock_arg_t *args, size_t n_args)
+{
+ m->n_calls += 1;
+
+ for (int i=0; i<n_args; i++) {
+ _lily_enqueue(m->arguments, args[i].size, args[i].var);
+ }
+}
+
+void _lily_get_call(lily_mock_t *m,
+ struct lily_mock_arg_t *args,
+ size_t n_args,
+ unsigned int call_num)
+{
+ size_t stride = 0;
+ for (int i=0; i<n_args; i++) {
+ stride += args[i].size;
+ }
+
+ m->arguments->front = m->arguments->buf + (call_num * stride);
+ for (int i=0; i<n_args; i++) {
+ _lily_dequeue(m->arguments, args[i].size, args[i].var);
+ }
+}
diff --git a/src/test/lily-test.h b/src/test/lily-test.h
new file mode 100644
index 0000000..b5f380c
--- /dev/null
+++ b/src/test/lily-test.h
@@ -0,0 +1,263 @@
+/****************************************************************
+ *
+ * ======== lily-test ========
+ *
+ * a midsize C unit testing library
+ * copyright (c) 2022 kate swanson (sanine)
+ *
+ * This is anti-capitalist software, released for free use by individuals and
+ * organizations that do not operate by capitalist principles.
+ *
+ * Permission is hereby granted, free of charge, to any person or
+ * organization (the "User") obtaining a copy of this software and associated
+ * documentation files (the "Software"), to use, copy, modify, merge,
+ * distribute, and/or sell copies of the Software, subject to the following conditions:
+ *
+ * 1. The above copyright notice and this permission notice shall be included
+ * in all copies or modified versions of the Software.
+ *
+ * 2. The User is one of the following:
+ * a. An individual person, laboring for themselves
+ * b. A non-profit organization
+ * c. An educational institution
+ * d. An organization that seeks shared profit for all of its members, and
+ * allows non-members to set the cost of their labor
+ *
+ * 3. If the User is an organization with owners, then all owners are workers
+ * and all workers are owners with equal equity and/or equal vote.
+ *
+ * 4. If the User is an organization, then the User is not law enforcement or
+ * military, or working for or under either.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT EXPRESS OR IMPLIED WARRANTY OF
+ * ANY KIND, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS 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.
+ *
+ * https://anticapitalist.software/
+ * https://sanine.net
+ *
+ ****************************************************************/
+
+
+#ifndef LILY_TEST_H
+#define LILY_TEST_H
+
+#define LILY_VERSION_MAJOR 1
+#define LILY_VERSION_MINOR 0
+#define LILY_VERSION_PATCH 0
+
+#include <stdbool.h>
+#include <stddef.h>
+#include <stdint.h>
+#include <setjmp.h>
+
+#define STR_IMP(x) #x
+#define STR(x) STR_IMP(x)
+/* define SOURCE_PATH_SIZE to strip away the
+ leading parts of the full compilation path */
+#ifndef SOURCE_PATH_SIZE
+#define LILY_LOCATION (__FILE__ ":" STR(__LINE__))
+#else
+#define LILY_LOCATION ((__FILE__ ":" STR(__LINE__)) + SOURCE_PATH_SIZE)
+#endif
+
+/** a few nasty globals that make everything clean for the end user */
+struct lily_globals_t {
+ jmp_buf env;
+ size_t error_msg_len;
+ char *error_msg;
+ const char *error_location;
+};
+extern struct lily_globals_t _lily_globals;
+
+typedef void (*lily_test_t)(void);
+
+/** run a single test */
+#define lily_run_test(test) _lily_run_test(#test, test)
+void _lily_run_test(const char *name, lily_test_t test);
+
+/** run a suite */
+#define lily_run_suite(suite) _lily_run_suite(#suite, suite)
+void _lily_run_suite(const char *name, lily_test_t suite);
+
+
+/* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ *
+ * assertions
+ *
+ * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ */
+
+/** basic assertion function, mostly used by the other assertions */
+void lily_assert_msg(bool statement, const char *location,
+ const char *format_string, ...);
+
+#define lily_assert_true(statement) _lily_assert_true(#statement, statement, LILY_LOCATION)
+void _lily_assert_true(const char *statement, bool value, const char *location);
+
+
+#define lily_assert_false(statement) _lily_assert_false(#statement, statement, LILY_LOCATION)
+void _lily_assert_false(const char *statement, bool value, const char *location);
+
+
+#define lily_assert_not_null(ptr) _lily_assert_not_null(#ptr, ptr, LILY_LOCATION)
+void _lily_assert_not_null(const char *name, void *ptr, const char *location);
+
+
+#define lily_assert_null(ptr) _lily_assert_null(#ptr, ptr, LILY_LOCATION)
+void _lily_assert_null(const char *name, void *ptr, const char *location);
+
+
+#define lily_assert_ptr_equal(a, b) _lily_assert_ptr_equal(#a, #b, a, b, LILY_LOCATION)
+void _lily_assert_ptr_equal(const char *name_a, const char *name_b,
+ void *a, void *b, const char *location);
+
+
+#define lily_assert_ptr_not_equal(a, b) _lily_assert_ptr_not_equal(#a, #b, a, b, LILY_LOCATION)
+void _lily_assert_ptr_not_equal(const char *name_a, const char *name_b,
+ void *a, void *b, const char *location);
+
+
+#define lily_assert_int_equal(a, b) _lily_assert_int_equal(#a, #b, a, b, LILY_LOCATION)
+void _lily_assert_int_equal(const char *name_a, const char *name_b,
+ intmax_t a, intmax_t b, const char *location);
+
+
+#define lily_assert_int_not_equal(a, b) _lily_assert_int_not_equal(#a, #b, a, b, LILY_LOCATION)
+void _lily_assert_int_not_equal(const char *name_a, const char *name_b,
+ intmax_t a, intmax_t b, const char *location);
+
+
+#define lily_assert_float_equal(a, b, epsilon) \
+ _lily_assert_float_equal(#a, #b, a, b, epsilon, LILY_LOCATION)
+void _lily_assert_float_equal(const char *name_a, const char *name_b,
+ double a, double b, double epsilon, const char *location);
+
+
+#define lily_assert_float_not_equal(a, b, epsilon) \
+ _lily_assert_float_not_equal(#a, #b, a, b, epsilon, LILY_LOCATION)
+void _lily_assert_float_not_equal(const char *name_a, const char *name_b,
+ double a, double b, double epsilon, const char *location);
+
+
+#define lily_assert_string_equal(a, b) _lily_assert_string_equal(#a, #b, a, b, LILY_LOCATION)
+void _lily_assert_string_equal(const char *name_a, const char *name_b,
+ char *a, char *b, const char *location);
+
+
+#define lily_assert_string_not_equal(a, b) _lily_assert_string_not_equal(#a, #b, a, b, LILY_LOCATION)
+void _lily_assert_string_not_equal(const char *name_a, const char *name_b,
+ char *a, char *b, const char *location);
+
+
+#define lily_assert_memory_equal(a, b, size) \
+ _lily_assert_memory_equal(#a, #b, a, b, size, LILY_LOCATION)
+void _lily_assert_memory_equal(const char *name_a, const char *name_b,
+ void *a, void *b, size_t size, const char *location);
+
+
+#define lily_assert_memory_not_equal(a, b, size) \
+ _lily_assert_memory_not_equal(#a, #b, a, b, size, LILY_LOCATION)
+void _lily_assert_memory_not_equal(const char *name_a, const char *name_b,
+ void *a, void *b, size_t size, const char *location);
+
+
+/* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ *
+ * mocks
+ *
+ * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ */
+
+
+/** queue structure capable of containing arbitrary data */
+typedef struct lily_queue_t {
+ size_t buf_size;
+ uint8_t *buf;
+ uint8_t *front, *back;
+} lily_queue_t;
+
+
+/** create a queue */
+lily_queue_t* lily_queue_create();
+
+/** destroy a queue */
+void lily_queue_destroy(lily_queue_t *q);
+
+
+/** enqueue a value
+ *
+ * q - the queue to append to
+ * type - the type of the value to append
+ * value - the value to append
+ */
+#define lily_enqueue(q, type, value) \
+ do { \
+ type _var = value; \
+ _lily_enqueue(q, sizeof(type), (uint8_t*)(&_var)); \
+ } while(0)
+void _lily_enqueue(lily_queue_t *q, size_t size, uint8_t *data);
+
+
+/** pop a value from the queue
+ *
+ * q - the queue to pop from
+ * type - the type of the value to pop
+ * ptr - the location to store the popped value
+ */
+#define lily_dequeue(q, type, ptr) \
+ _lily_dequeue(q, sizeof(type), (uint8_t*) ptr)
+void _lily_dequeue(lily_queue_t *q, size_t size, uint8_t *ptr);
+
+
+struct lily_mock_arg_t {
+ size_t size;
+ void *var;
+};
+
+#define LILY_NARGS(args) (sizeof(args)/sizeof(struct lily_mock_arg_t))
+
+/** structure to store data for mock functions */
+typedef struct lily_mock_t {
+ unsigned int n_calls;
+ lily_queue_t *arguments;
+ lily_queue_t *values;
+} lily_mock_t;
+
+/** setup mock function storage */
+lily_mock_t * lily_mock_create();
+/** tear down mock function storage */
+void lily_mock_destroy(lily_mock_t *m);
+/** automatically re-create mock function storage */
+void lily_mock_use(lily_mock_t **m);
+
+/** store a call to a mock function */
+#define lily_mock_store_call(m, args) \
+ _lily_mock_call(m, args, LILY_NARGS(args))
+/* lily_mock_call is deprecated!! do not use it in new programs */
+#define lily_mock_call(m, args) \
+ _lily_mock_call(m, args, LILY_NARGS(args))
+void _lily_mock_call(lily_mock_t *m, struct lily_mock_arg_t *args, size_t n_args);
+
+/** retrieve a call to a mock function */
+#define lily_mock_get_call(m, args, call_num) \
+ _lily_get_call(m, args, LILY_NARGS(args), call_num)
+/* lily_get_call is deprecated!! do not use it in new programs */
+#define lily_get_call(m, args, call_num) \
+ _lily_get_call(m, args, LILY_NARGS(args), call_num)
+void _lily_get_call(lily_mock_t *m,
+ struct lily_mock_arg_t *args,
+ size_t n_args,
+ unsigned int call_num);
+
+/** store a value in a mock structure */
+#define lily_store_value(m, type, value) \
+ lily_enqueue(m->values, type, value)
+/** retrieve a value from a mock structure */
+#define lily_get_value(m, type, ptr) \
+ lily_dequeue(m->values, type, ptr)
+
+#endif
diff --git a/src/test/mossrose-test.c b/src/test/mossrose-test.c
new file mode 100644
index 0000000..88ce5ff
--- /dev/null
+++ b/src/test/mossrose-test.c
@@ -0,0 +1,8 @@
+#include "mossrose-test.h"
+
+
+int main()
+{
+ RUN_TESTS()
+ return 0;
+}
diff --git a/src/test/mossrose-test.h b/src/test/mossrose-test.h
new file mode 100644
index 0000000..201a51b
--- /dev/null
+++ b/src/test/mossrose-test.h
@@ -0,0 +1,13 @@
+#ifndef MOSSROSE_TEST_H
+#define MOSSROSE_TEST_H
+
+#include "lily-test.h"
+
+void suite_channel();
+void suite_sound();
+
+#define RUN_TESTS() \
+ lily_run_suite(suite_channel); \
+ lily_run_suite(suite_sound); \
+
+#endif