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