/*
* 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"
#define GAIN_TYPENAME "gain"
struct gain
{
ml_node *node;
float value;
int mapped;
int db;
};
static void proc(const ml_proc_args *args)
{
struct gain *gain = args->userp;
float *in = args->in[0];
float *out = args->out[0];
float *mapped = gain->mapped ? args->in[1] : NULL;
float v;
for (size_t i = 0; i < args->nframes; i++)
{
v = mapped ? mapped[i] : gain->value;
if (gain->db)
{
v = powf(10,v/20.f);
}
out[i] = v*in[i];
}
}
static int l_init(lua_State *s)
{
int db_i = lua_upvalueindex(1);
int db = lua_tointeger(s,db_i);
ml_channel *audio_src, *mapped_src = NULL;
ml_channel *audio_dst, *mapped_dst;
float value = 0;
if (lua_isuserdata(s,1))
{
mapped_src = mutil_get_channel(s,1);
}
else
{
value = luaL_checknumber(s,1);
}
audio_src = mutil_get_channel(s,2);
struct gain *gain = calloc(1,sizeof(*gain));
gain->value = value;
gain->mapped = mapped_src != NULL;
gain->db = db;
ml_node *node = ml_create_node(proc,gain);
ml_label_node(node,"gain");
gain->node = node;
ml_add_output(node);
audio_dst = ml_add_input(node);
ml_connect(audio_src,audio_dst);
if (mapped_src)
{
mapped_dst = ml_add_input(node);
ml_connect(mapped_src,mapped_dst);
}
*(void**) lua_newuserdata(s,sizeof(void*)) = gain;
luaL_setmetatable(s,GAIN_TYPENAME);
return 1;
}
static int l_out(lua_State *s)
{
struct gain *gain = *(void**) luaL_checkudata(s,1,GAIN_TYPENAME);
*(void**) lua_newuserdata(s,sizeof(void*)) = ml_get_output(gain->node,0);
luaL_setmetatable(s,MELISMA_OUTPUT_CHANNEL_TYPENAME);
return 1;
}
int luaopen_melisma_gain(lua_State *s)
{
ml_check_version();
/*** Apply gain to signals.
local mgain = require 'melisma.gain'
-- Interprets `cntl` as decibels and applies it to `input`
local input = ...
local cntl = ...
local gain = mgain.db(input,cntl)
@classmod gain
*/
lua_newtable(s);
int tbl = lua_gettop(s);
/*** Creates a linear gain unit.
@static
@function linear
@tparam channel input Input signal.
@tparam number|channel gain Constant gain or a controlling signal.
@treturn gain The newly created gain unit.
*/
lua_pushinteger(s,0);
lua_pushcclosure(s,l_init,1);
lua_setfield(s,tbl,"linear");
/*** Creates a decibel gain unit.
@static
@function db
@tparam channel input Input signal.
@tparam number|channel gain Constant gain in decibels or a controlling signal.
@treturn gain The newly created gain unit.
*/
lua_pushinteger(s,1);
lua_pushcclosure(s,l_init,1);
lua_setfield(s,tbl,"db");
lua_pushcfunction(s,l_out);
lua_setfield(s,tbl,MUTIL_OUTPUT_CHANNEL_FUNCTION);
luaL_newmetatable(s,GAIN_TYPENAME);
int mt = lua_gettop(s);
lua_pushvalue(s,tbl);
lua_setfield(s,mt,"__index");
lua_settop(s,tbl);
return 1;
}