aboutsummaryrefslogtreecommitdiff
path: root/mod/playback.c
diff options
context:
space:
mode:
authorLudvig Gunne Lindström <ludviggunnelindstrom@gmail.com>2026-07-17 11:43:24 +0200
committerLudvig Gunne Lindström <ludviggunnelindstrom@gmail.com>2026-11-08 14:15:04 +0100
commit039847a29868d335c08fe0a587260dffc71ca5c4 (patch)
treee9d775090fef45ca51db576779fb373040d50e6a /mod/playback.c
downloadmelisma-modules-main.tar.gz
Diffstat (limited to 'mod/playback.c')
-rw-r--r--mod/playback.c342
1 files changed, 342 insertions, 0 deletions
diff --git a/mod/playback.c b/mod/playback.c
new file mode 100644
index 0000000..f9d22ca
--- /dev/null
+++ b/mod/playback.c
@@ -0,0 +1,342 @@
+/*
+ * melisma - extensible and scriptable audio engine
+ * Copyright (C) 2026 Ludvig Gunne Lindström
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include <stdlib.h>
+#include <string.h>
+#include <stdbool.h>
+#include <lauxlib.h>
+#include "melisma.h"
+#include "mutil.h"
+#include "sample.h"
+
+#define PLAYBACK_TYPENAME "playback"
+
+struct playback
+{
+ ml_node *node;
+ struct sample *smp;
+ double speed;
+ double phase;
+ bool active;
+ bool loop;
+ int fn;
+ int arg;
+};
+
+static void proc(const ml_proc_args *args)
+{
+ struct playback *pb = args->userp;
+
+ if (pb->smp == NULL)
+ {
+ memset(args->out[0],0,sizeof(*args->out[0])*args->nframes);
+ return;
+ }
+
+ unsigned long sr = ml_sample_rate();
+ double phase = pb->phase;
+ float *dst = args->out[0];
+ size_t len = pb->smp->len;
+ float *data = pb->smp->data;
+
+ for (size_t i = 0; i < args->nframes; i++)
+ {
+ if (!pb->active)
+ {
+ memset(&dst[i],0,(args->nframes - i)*sizeof(*dst));
+ return;
+ }
+
+ float t = phase*len;
+
+ size_t i0 = (size_t) t;
+ size_t i1 = i0 + 1;
+ float ip = t - i0;
+
+ i0 %= len;
+ i1 %= len;
+
+ float s0 = data[i0];
+ float s1 = data[i1];
+
+ dst[i] = s0 + ip*(s1 - s0);
+
+ phase += pb->speed/len;
+
+ while (phase > 1)
+ {
+ phase -= 1;
+
+ if (pb->fn != LUA_NOREF)
+ {
+ ml_schedule_event(0,pb->fn,pb->arg);
+
+ }
+
+ if (!pb->loop)
+ {
+ pb->active = false;
+ pb->phase = 0;
+ }
+ }
+
+ while (phase < 0)
+ {
+ phase += 1;
+
+ if (pb->fn != LUA_NOREF)
+ {
+ ml_schedule_event(0,pb->fn,pb->arg);
+
+ }
+
+ if (!pb->loop)
+ {
+ pb->active = false;
+ pb->phase = 0;
+ }
+ }
+ }
+
+ pb->phase = phase;
+}
+
+static int l_init(lua_State *s)
+{
+ struct sample *smp = NULL;
+
+ if (!lua_isnoneornil(s,1))
+ {
+ smp = *(void**) luaL_checkudata(s,1,SAMPLE_TYPENAME);
+ }
+
+ struct playback *pb = calloc(1,sizeof(*pb));
+
+ pb->smp = smp;
+ pb->speed = 1;
+ pb->fn = LUA_NOREF;
+ pb->arg = LUA_NOREF;
+ pb->node = ml_create_node(proc,pb);
+ ml_label_node(pb->node,"playback (%s)",smp ? smp->path : "NULL");
+
+ ml_add_output(pb->node);
+
+ *(void**) lua_newuserdata(s,sizeof(void*)) = pb;
+ luaL_setmetatable(s,PLAYBACK_TYPENAME);
+
+ return 1;
+}
+
+static int l_set_sample(lua_State *s)
+{
+ struct playback *pb = *(void**) luaL_checkudata(s,1,PLAYBACK_TYPENAME);
+ struct sample *smp = *(void**) luaL_checkudata(s,2,SAMPLE_TYPENAME);
+ pb->smp = smp;
+ return 0;
+}
+
+static int l_play(lua_State *s)
+{
+ struct playback *pb = *(void**) luaL_checkudata(s,1,PLAYBACK_TYPENAME);
+ pb->active = true;
+ return 0;
+}
+
+static int l_pause(lua_State *s)
+{
+ struct playback *pb = *(void**) luaL_checkudata(s,1,PLAYBACK_TYPENAME);
+ pb->active = false;
+ return 0;
+}
+
+static int l_reset(lua_State *s)
+{
+ struct playback *pb = *(void**) luaL_checkudata(s,1,PLAYBACK_TYPENAME);
+ pb->phase = 0;
+ return 0;
+}
+
+static int l_stop(lua_State *s)
+{
+ struct playback *pb = *(void**) luaL_checkudata(s,1,PLAYBACK_TYPENAME);
+ pb->phase = 0;
+ pb->active = false;
+ return 0;
+}
+
+static int l_speed(lua_State *s)
+{
+ struct playback *pb = *(void**) luaL_checkudata(s,1,PLAYBACK_TYPENAME);
+
+ if (!lua_isnil(s,2))
+ {
+ pb->speed = luaL_checknumber(s,2);
+ }
+
+ lua_pushnumber(s,pb->speed);
+ return 1;
+}
+
+static int l_loop(lua_State *s)
+{
+ struct playback *pb = *(void**) luaL_checkudata(s,1,PLAYBACK_TYPENAME);
+
+ if (!lua_isnil(s,2))
+ {
+ luaL_checktype(s,2,LUA_TBOOLEAN);
+ pb->loop = lua_toboolean(s,2);
+ }
+
+ lua_pushboolean(s,pb->loop);
+ return 1;
+}
+
+static int l_phase(lua_State *s)
+{
+ struct playback *pb = *(void**) luaL_checkudata(s,1,PLAYBACK_TYPENAME);
+
+ if (!lua_isnil(s,2))
+ {
+ double phase = luaL_checknumber(s,2);
+
+ if (phase > 1)
+ {
+ phase = 1;
+ }
+
+ if (phase < 0)
+ {
+ phase = 0;
+ }
+
+ pb->phase = phase;
+ }
+
+ lua_pushnumber(s,pb->phase);
+ return 1;
+}
+
+static int l_out(lua_State *s)
+{
+ struct playback *pb = *(void**) luaL_checkudata(s,1,PLAYBACK_TYPENAME);
+ *(void**) lua_newuserdata(s,sizeof(void*)) = ml_get_output(pb->node,0);
+ luaL_setmetatable(s,MELISMA_OUTPUT_CHANNEL_TYPENAME);
+ return 1;
+}
+
+int luaopen_melisma_playback(lua_State *s)
+{
+/*** Audio sample playback.
+
+ local msample = require 'melisma.sample'
+ local mplayback = require 'melisma.playback'
+
+ local sample = msample.load('sample.wav')
+ local playb = mplayback.init(sample)
+
+ playb:loop(true)
+ playb:spped(2)
+ playb:play()
+
+@classmod playback
+*/
+ lua_newtable(s);
+ int tbl = lua_gettop(s);
+
+/*** Intialize a playback unit.
+@static
+@function init
+@tparam sample|nil smp
+@treturn playback
+*/
+ lua_pushcfunction(s,l_init);
+ lua_setfield(s,tbl,"init");
+
+/*** Set playback sample.
+@function set_sample
+@tparam sample smp
+*/
+ lua_pushcfunction(s,l_set_sample);
+ lua_setfield(s,tbl,"set_sample");
+
+/*** Start playback.
+@function play
+*/
+ lua_pushcfunction(s,l_play);
+ lua_setfield(s,tbl,"play");
+
+/*** Pause playback.
+@function pause
+*/
+ lua_pushcfunction(s,l_pause);
+ lua_setfield(s,tbl,"pause");
+
+/*** Stop playback.
+
+Equivalent to @{playback.pause}; @{playback.reset}.
+
+@function stop
+*/
+ lua_pushcfunction(s,l_stop);
+ lua_setfield(s,tbl,"stop");
+
+
+/*** Reset playback.
+
+Equivalent to @{playback.phase}(0).
+
+@function reset
+*/
+ lua_pushcfunction(s,l_reset);
+ lua_setfield(s,tbl,"reset");
+
+/*** Set playback speed.
+
+A negative speed will play the sample in reverse.
+
+@function reset
+*/
+ lua_pushcfunction(s,l_speed);
+ lua_setfield(s,tbl,"speed");
+
+/*** Enable/disable automatic looping.
+@function loop
+@tparam bool b
+*/
+ lua_pushcfunction(s,l_loop);
+ lua_setfield(s,tbl,"loop");
+
+/*** Set playback phase.
+@function phase
+@tparam number x Phase ranging from 0 to 1.
+*/
+ lua_pushcfunction(s,l_phase);
+ lua_setfield(s,tbl,"phase");
+
+ lua_pushcfunction(s,l_out);
+ lua_setfield(s,tbl,MUTIL_OUTPUT_CHANNEL_FUNCTION);
+
+ luaL_newmetatable(s,PLAYBACK_TYPENAME);
+ int mt = lua_gettop(s);
+
+ lua_pushvalue(s,tbl);
+ lua_setfield(s,mt,"__index");
+
+ lua_settop(s,tbl);
+ return 1;
+}