/* * 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" #define SPRING_TYPENAME "spring" struct spring { ml_node *node; float target; float value; float freq; float damping; float velocity; bool mapped; }; static void proc(const ml_proc_args *args) { struct spring *spring = args->userp; float *out = args->out[0]; float *mapped = spring->mapped ? args->in[0] : NULL; float dt = 1.f/ml_sample_rate(); for (size_t i = 0; i < args->nframes; i++) { float target = mapped ? mapped[i] : spring->target; float w = 2*M_PI*spring->freq; float f = (target - spring->value)*w*w; float d = 2*w*spring->velocity*spring->damping; float a = f - d; spring->velocity += a*dt; spring->value += spring->velocity*dt; out[i] = spring->value; } } static int l_damping(lua_State *s) { struct spring *spring = *(void**) luaL_checkudata(s,1,SPRING_TYPENAME); float x = luaL_checknumber(s,2); spring->damping = x; return 0; } static int l_freq(lua_State *s) { struct spring *spring = *(void**) luaL_checkudata(s,1,SPRING_TYPENAME); float x = luaL_checknumber(s,2); spring->freq = x; return 0; } static int l_target(lua_State *s) { struct spring *spring = *(void**) luaL_checkudata(s,1,SPRING_TYPENAME); float x = luaL_checknumber(s,2); spring->target = x; return 0; } static int l_map(lua_State *s) { struct spring *spring = *(void**) luaL_checkudata(s,1,SPRING_TYPENAME); if (spring->mapped) { luaL_error(s,"already mapped"); } ml_channel *src = *(void**) luaL_checkudata(s,2,MELISMA_OUTPUT_CHANNEL_TYPENAME); ml_channel *dst = ml_add_input(spring->node); ml_connect(src,dst); spring->mapped = true; return 0; } static int l_sample(lua_State *s) { struct spring *spring = *(void**) luaL_checkudata(s,1,SPRING_TYPENAME); lua_pushnumber(s,spring->value); return 1; } static int l_call(lua_State *s) { struct spring *spring = *(void**) luaL_checkudata(s,1,SPRING_TYPENAME); *(void**) lua_newuserdata(s,sizeof(void*)) = ml_get_output(spring->node,0); luaL_setmetatable(s,MELISMA_OUTPUT_CHANNEL_TYPENAME); return 1; } static int l_init(lua_State *s) { float value = luaL_checknumber(s,1); float freq = luaL_checknumber(s,2); float damping = luaL_checknumber(s,3); struct spring *spring = calloc(1,sizeof(*spring)); spring->value = value; spring->freq = freq; spring->damping = damping; spring->mapped = false; spring->node = ml_create_node(proc,spring); ml_add_output(spring->node); *(void**) lua_newuserdata(s,sizeof(void*)) = spring; luaL_setmetatable(s,SPRING_TYPENAME); return 1; } int luaopen_melisma_spring(lua_State *s) { lua_newtable(s); int tbl = lua_gettop(s); lua_pushcfunction(s,l_init); lua_setfield(s,tbl,"init"); lua_pushcfunction(s,l_freq); lua_setfield(s,tbl,"freq"); lua_pushcfunction(s,l_damping); lua_setfield(s,tbl,"damping"); lua_pushcfunction(s,l_target); lua_setfield(s,tbl,"target"); lua_pushcfunction(s,l_sample); lua_setfield(s,tbl,"sample"); lua_pushcfunction(s,l_map); lua_setfield(s,tbl,"map"); luaL_newmetatable(s,SPRING_TYPENAME); int mt = lua_gettop(s); lua_pushvalue(s,tbl); lua_setfield(s,mt,"__index"); lua_pushcfunction(s,l_call); lua_setfield(s,mt,"__call"); lua_settop(s,tbl); return 1; }