aboutsummaryrefslogtreecommitdiff
path: root/mod/sample.c
diff options
context:
space:
mode:
authorLudvig Gunne Lindström <ludviggunnelindstrom@gmail.com>2026-07-17 11:43:24 +0200
committerLudvig Gunne Lindström <ludviggunnelindstrom@gmail.com>2026-11-08 14:15:04 +0100
commit039847a29868d335c08fe0a587260dffc71ca5c4 (patch)
treee9d775090fef45ca51db576779fb373040d50e6a /mod/sample.c
downloadmelisma-modules-039847a29868d335c08fe0a587260dffc71ca5c4.tar.gz
Diffstat (limited to 'mod/sample.c')
-rw-r--r--mod/sample.c180
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;
+}