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