/* * 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 . */ #include #include #include #include #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; }