/*
* 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 PAN_TYPENAME "pan"
struct pan
{
ml_channel *l_out;
ml_channel *r_out;
float val;
bool map;
};
static void proc(const ml_proc_args *args)
{
struct pan *pan = args->userp;
float val,*in,*l_out,*r_out,*map;
in = args->in[0];
l_out = args->out[0];
r_out = args->out[1];
map = pan->map ? args->in[1] : NULL;
for (size_t i = 0; i < args->nframes; i++)
{
val = map ? map[i] : pan->val;
l_out[i] = val*in[i];
r_out[i] = (1 - val)*in[i];
}
}
static int l_init(lua_State *s)
{
ml_channel *dst,*src,*map_dst,*map_src = NULL;
float val = 0;
if (lua_isuserdata(s,1))
{
map_src = mutil_get_channel(s,1);
}
else
{
val = luaL_checknumber(s,1);
}
src = mutil_get_channel(s,2);
struct pan *pan = calloc(1,sizeof(*pan));
pan->val = val;
pan->map = map_src != NULL;
ml_node *node = ml_create_node(proc,pan);
ml_label_node(node,"pan");
dst = ml_add_input(node);
pan->l_out = ml_add_output(node);
pan->r_out = ml_add_output(node);
ml_connect(src,dst);
if (pan->map)
{
map_dst = ml_add_input(node);
ml_connect(map_src,map_dst);
}
*(void**) lua_newuserdata(s,sizeof(void*)) = pan;
luaL_setmetatable(s,PAN_TYPENAME);
return 1;
}
static int l_left(lua_State *s)
{
struct pan *pan = *(void**) luaL_checkudata(s,1,PAN_TYPENAME);
*(void**) lua_newuserdata(s,sizeof(void*)) = pan->l_out;
luaL_setmetatable(s,MELISMA_OUTPUT_CHANNEL_TYPENAME);
return 1;
}
static int l_right(lua_State *s)
{
struct pan *pan = *(void**) luaL_checkudata(s,1,PAN_TYPENAME);
*(void**) lua_newuserdata(s,sizeof(void*)) = pan->r_out;
luaL_setmetatable(s,MELISMA_OUTPUT_CHANNEL_TYPENAME);
return 1;
}
static int l_set(lua_State *s)
{
struct pan *pan = *(void**) luaL_checkudata(s,1,PAN_TYPENAME);
pan->val = luaL_checknumber(s,2);
return 0;
}
int luaopen_melisma_pan(lua_State *s)
{
ml_check_version();
/*** Panning
local mpan = require 'melisma.pan'
local mexpression = require 'melisma.expression'
local moscil = require 'melisma.oscil'
local wt = moscil.wavetable.sin()
local lfo_oscil = moscil.init(wt,1,1)
local lfo = (1 + mexpression.input(lfo_oscil,0) / 2):build()
local oscil = moscil.init(wt,440,0.5)
local pan = mpan.init(lfo,oscil)
local left = pan:left()
local right = pan:right()
@classmod pan
*/
lua_newtable(s);
int tbl = lua_gettop(s);
/*** Initialize a panning unit.
A panning value ranges from 0 to 1, where a value of 0
will pan everything to the left.
@static
@function init
@tparam number|channel x Constant pan or a controlling signal.
@tparam channel sig Input signal.
@treturn pan The newly created panning unit.
*/
lua_pushcfunction(s,l_init);
lua_setfield(s,tbl,"init");
/*** Get the left output signal.
@function left
@treturn channel
*/
lua_pushcfunction(s,l_left);
lua_setfield(s,tbl,"left");
/*** Get the right output signal.
@function right
@treturn channel
*/
lua_pushcfunction(s,l_right);
lua_setfield(s,tbl,"right");
/*** Set the panning value.
@function set
@tparam number x
*/
lua_pushcfunction(s,l_set);
lua_setfield(s,tbl,"set");
luaL_newmetatable(s,PAN_TYPENAME);
int mt = lua_gettop(s);
lua_pushvalue(s,tbl);
lua_setfield(s,mt,"__index");
lua_settop(s,tbl);
return 1;
}