diff options
| author | Ludvig Gunne Lindström <ludviggunnelindstrom@gmail.com> | 2026-07-17 11:43:24 +0200 |
|---|---|---|
| committer | Ludvig Gunne Lindström <ludviggunnelindstrom@gmail.com> | 2026-11-08 14:15:04 +0100 |
| commit | 039847a29868d335c08fe0a587260dffc71ca5c4 (patch) | |
| tree | e9d775090fef45ca51db576779fb373040d50e6a /mod/playback.c | |
| download | melisma-modules-039847a29868d335c08fe0a587260dffc71ca5c4.tar.gz | |
Diffstat (limited to 'mod/playback.c')
| -rw-r--r-- | mod/playback.c | 342 |
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; +} |
