/*
* 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
#include "melisma.h"
#include "sample.h"
static struct sample *alloc_sample(size_t len)
{
struct sample *s = malloc(sizeof(*s) + len*sizeof(s->data[0]));
s->len = len;
return s;
}
static struct sample *resample(struct sample *old, unsigned long from, unsigned long to)
{
double ratio = (double)to / (double)from;
size_t new_len = ratio * old->len;
struct sample *new = alloc_sample(new_len);
SRC_DATA src_data = {
.data_in = old->data,
.data_out = new->data,
.input_frames = old->len,
.output_frames = new->len,
.src_ratio = ratio,
};
src_simple(&src_data,SRC_SINC_MEDIUM_QUALITY,1);
new->path = old->path;
free(old);
return new;
}
static int l_load(lua_State *s)
{
const char *path = luaL_checkstring(s,1);
SF_INFO info;
SNDFILE *f;
f = sf_open(path,SFM_READ,&info);
if (f == NULL)
{
luaL_error(s,"failed to open audio file '%s': %s",path,sf_strerror(f));
}
size_t len = info.frames*info.channels;
float *data = malloc(sizeof(*data)*len);
size_t off = 0;
while (off < len)
{
ssize_t res = sf_read_float(f,&data[off],len - off);
if (res < 0)
{
luaL_error(s,"failed to open audio file '%s': %s",path,sf_strerror(f));
}
off += res;
}
sf_close(f);
size_t samplerate = ml_sample_rate();
for (size_t c = 0; c < info.channels; c++)
{
struct sample *smp = alloc_sample(info.frames);
smp->path = strdup(path);
for (size_t i = 0; i < info.frames; i++)
{
smp->data[i] = data[i*info.channels + c];
}
if (info.samplerate != samplerate)
{
smp = resample(smp,info.samplerate,samplerate);
}
*(void**) lua_newuserdata(s,sizeof(void*)) = smp;
luaL_setmetatable(s,SAMPLE_TYPENAME);
}
free(data);
return info.channels;
}
static int l_length(lua_State *s)
{
struct sample *smp = *(void**) luaL_checkudata(s,1,SAMPLE_TYPENAME);
unsigned long sr = ml_sample_rate();
double l = smp->len / (double)sr;
lua_pushnumber(s,l);
return 1;
}
static int l_path(lua_State *s)
{
struct sample *smp = *(void**) luaL_checkudata(s,1,SAMPLE_TYPENAME);
lua_pushstring(s,smp->path);
return 1;
}
int luaopen_melisma_sample(lua_State *s)
{
/*** Load audio samples.
local msample = require 'melisma.sample'
local smp = msample.load('sample.wav')
print(smp:length())
print(smp:path())
@classmod sample
*/
lua_newtable(s);
int tbl = lua_gettop(s);
/*** Load an audio sample.
If necessary, the audio will be resampled to the current sample rate.
@static
@function load
@tparam string path
@treturn sample
*/
lua_pushcfunction(s,l_load);
lua_setfield(s,tbl,"load");
/*** Get sample length.
@function length
@treturn number The length of the sample in seconds.
*/
lua_pushcfunction(s,l_length);
lua_setfield(s,tbl,"length");
/*** Get sample path.
@function path
@treturn string The path that the sample was loaded from.
*/
lua_pushcfunction(s,l_path);
lua_setfield(s,tbl,"path");
luaL_newmetatable(s,SAMPLE_TYPENAME);
int mt = lua_gettop(s);
lua_pushvalue(s,tbl);
lua_setfield(s,mt,"__index");
lua_settop(s,tbl);
return 1;
}