/*
* 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 "melisma.h"
#include "mutil.h"
#define BUFFER_TYPENAME "buffer"
enum mode {CAPTURE,PLAYBACK,IDLE};
struct buffer
{
ml_channel *chan;
enum mode mode;
double phase;
double speed;
size_t len;
size_t cap;
float data[];
};
static size_t capture(struct buffer *b, const float *in, size_t n)
{
size_t i = 0;
for (; i < n; i++)
{
b->data[b->len++] = in[i];
if (b->len == b->cap)
{
b->mode = IDLE;
b->phase = 0;
break;
}
}
return i;
}
static size_t playback(struct buffer *b, float *out, size_t n)
{
size_t i = 0;
for (; i < n; i++)
{
float t = b->phase*b->len;
size_t i0 = (size_t) t;
size_t i1 = i0 + 1;
float ip = t - i0;
i0 %= b->len;
i1 %= b->len;
float s0 = b->data[i0];
float s1 = b->data[i1];
out[i] = s0 + ip*(s1 - s0);
b->phase += b->speed/b->len;
if (b->phase >= 1)
{
b->phase = 0;
b->mode = IDLE;
}
}
return i;
}
static size_t idle(struct buffer *b, float *out, size_t n)
{
memset(out,0,sizeof(*out)*n);
return n;
}
static void proc(const ml_proc_args *args)
{
struct buffer *b = args->userp;
const float *in = args->in[0];
float *out = args->out[0];
size_t n = args->nframes;
while (n > 0)
{
size_t d;
switch (b->mode)
{
case CAPTURE:
d = capture(b,in,n);
break;
case PLAYBACK:
d = playback(b,out,n);
break;
case IDLE:
d = idle(b,out,n);
break;
}
in += d;
out += d;
n -= d;
}
}
static int l_init(lua_State *s)
{
float t = luaL_checknumber(s,1);
ml_channel *src = mutil_get_channel(s,2);
if (t <= 0)
{
luaL_error(s,"invalid buffer capacity %.2f\n",t);
}
size_t sr = ml_sample_rate();
size_t cap = t*sr;
struct buffer *b;
size_t size = sizeof(*b) + sizeof(*b->data)*cap;
b = calloc(1,size);
ml_node *node = ml_create_node(proc,b);
ml_label_node(node,"buffer");
ml_channel *dst = ml_add_input(node);
ml_connect(src,dst);
b->mode = IDLE;
b->len = 0;
b->phase = 0;
b->cap = cap;
b->chan = ml_add_output(node);
*(void**) lua_newuserdata(s,sizeof(void*)) = b;
luaL_setmetatable(s,BUFFER_TYPENAME);
return 1;
}
static int l_setmode(lua_State *s)
{
int mode_i = lua_upvalueindex(1);
struct buffer *b = *(void**) luaL_checkudata(s,1,BUFFER_TYPENAME);
enum mode mode = lua_tointeger(s,mode_i);
b->phase = 0;
b->mode = mode;
if (mode == CAPTURE)
{
b->len = 0;
}
return 0;
}
static int l_setspeed(lua_State *s)
{
struct buffer *b = *(void**) luaL_checkudata(s,1,BUFFER_TYPENAME);
double speed = luaL_checknumber(s,2);
if (speed <= 0)
{
luaL_error(s,"invalid speed %.2f",speed);
}
b->speed = speed;
return 0;
}
static int l_getlength(lua_State *s)
{
struct buffer *b = *(void**) luaL_checkudata(s,1,BUFFER_TYPENAME);
size_t sr = ml_sample_rate();
lua_pushnumber(s,(float)b->len / (float)sr / b->speed);
return 1;
}
static int l_out(lua_State *s)
{
struct buffer *b = *(void**) luaL_checkudata(s,1,BUFFER_TYPENAME);
*(void**) lua_newuserdata(s,sizeof(void*)) = b->chan;
luaL_setmetatable(s,MELISMA_OUTPUT_CHANNEL_TYPENAME);
return 1;
}
int luaopen_melisma_buffer(lua_State *s)
{
/*** Audio buffer for capture/playback.
local mbuffer = require 'melisma.buffer'
local mevent = require 'melisma.event'
local input = ...
-- Create a buffer with capacity of 5 seconds
-- Capture stops automatically when buffer is full.
local len = 5
local buffer = mbuffer.init(input, len)
-- Capture audio
buffer:capture()
-- Playback after capture 3 seconds
mevent.schedule(3, buffer.playback, buffer)
@classmod buffer
*/
lua_newtable(s);
int tbl = lua_absindex(s,lua_gettop(s));
/*** Create an audio buffer.
@static
@function buffer.init
@tparam channel input Input signal.
@tparam number len Buffer capacity in seconds.
@treturn buffer The newly created audio buffer.
*/
lua_pushcfunction(s,l_init);
lua_setfield(s,tbl,"init");
/*** Set playback speed.
@function buffer.speed
@tparam number speed Playback speed.
*/
lua_pushcfunction(s,l_setspeed);
lua_setfield(s,tbl,"speed");
/*** Get length of captured audio.
@function buffer.length
@treturn number Length of captured audio in seconds.
*/
lua_pushcfunction(s,l_getlength);
lua_setfield(s,tbl,"length");
/*** Start playback mode.
@function buffer.playback
*/
lua_pushinteger(s,PLAYBACK);
lua_pushcclosure(s,l_setmode,1);
lua_setfield(s,tbl,"playback");
/*** Start capture mode.
@function buffer.capture
*/
lua_pushinteger(s,CAPTURE);
lua_pushcclosure(s,l_setmode,1);
lua_setfield(s,tbl,"capture");
lua_pushcfunction(s,l_out);
lua_setfield(s,tbl,MUTIL_OUTPUT_CHANNEL_FUNCTION);
luaL_newmetatable(s,BUFFER_TYPENAME);
int mt = lua_absindex(s,lua_gettop(s));
lua_pushvalue(s,tbl);
lua_setfield(s,mt,"__index");
lua_settop(s,tbl);
return 1;
}