diff options
| author | Ludvig Gunne Lindström <ludviggunnelindstrom@gmail.com> | 2026-07-17 11:43:24 +0200 |
|---|---|---|
| committer | Ludvig Gunne Lindström <ludviggunnelindstrom@gmail.com> | 2026-11-08 14:15:04 +0100 |
| commit | 039847a29868d335c08fe0a587260dffc71ca5c4 (patch) | |
| tree | e9d775090fef45ca51db576779fb373040d50e6a /mod/sample.c | |
| download | melisma-modules-main.tar.gz | |
Diffstat (limited to 'mod/sample.c')
| -rw-r--r-- | mod/sample.c | 180 |
1 files changed, 180 insertions, 0 deletions
diff --git a/mod/sample.c b/mod/sample.c new file mode 100644 index 0000000..6a3a24b --- /dev/null +++ b/mod/sample.c @@ -0,0 +1,180 @@ +/* + * 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 <https://www.gnu.org/licenses/>. + */ + +#include <stdlib.h> +#include <string.h> +#include <lauxlib.h> +#include <sndfile.h> +#include <samplerate.h> +#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; +} |
