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 /lib | |
| download | melisma-modules-039847a29868d335c08fe0a587260dffc71ca5c4.tar.gz | |
Diffstat (limited to 'lib')
| -rw-r--r-- | lib/distrib.lua | 38 | ||||
| -rw-r--r-- | lib/functional.lua | 171 | ||||
| -rw-r--r-- | lib/graphviz.lua | 48 | ||||
| -rw-r--r-- | lib/ladspa_interface.lua | 63 | ||||
| -rw-r--r-- | lib/lazy.lua | 24 | ||||
| -rw-r--r-- | lib/midimap.lua | 34 | ||||
| -rw-r--r-- | lib/osc.lua | 76 | ||||
| -rw-r--r-- | lib/util.lua | 139 |
8 files changed, 593 insertions, 0 deletions
diff --git a/lib/distrib.lua b/lib/distrib.lua new file mode 100644 index 0000000..e49ff88 --- /dev/null +++ b/lib/distrib.lua @@ -0,0 +1,38 @@ +-- +-- 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/>. +-- + +local M = {} + +function M.uniform(lo,hi) + return lo + math.random()*(hi - lo) +end + +function M.normal(mean,dev) + local u,v,s,t,z0,z1 + repeat + u = M.uniform(-1,1) + v = M.uniform(-1,1) + s = u^2 + v^2 + until s > 0 and s < 1 + t = math.sqrt(-2*math.log(s) / s) + z0 = mean + u*t*dev + z1 = mean + v*t*dev + return z0,z1 +end + +return M diff --git a/lib/functional.lua b/lib/functional.lua new file mode 100644 index 0000000..9f50b7e --- /dev/null +++ b/lib/functional.lua @@ -0,0 +1,171 @@ +-- +-- 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/>. +-- + +local m_util = require 'melisma.util' + +local M = {} + +function M.fmap(f,l) + local res = {} + for k,v in pairs(l) do res[k] = f(v) end + return res +end + +function M.compose(f,g,...) + local tail = table.pack(...) + if g == nil then return f end + return function(x) return f(M.compose(g,table.unpack(tail))(x)) end +end + +function M.pipeline(...) + local args = table.pack(...) + return function(x) + for _,f in ipairs(args) do x = f(x) end + return x + end +end + +function M.bind(f,...) + local bound = table.pack(...) + return function(...) + local free = table.pack(...) + local all = M.concat(bound,free) + return f(table.unpack(all)) + end +end + +function M.bind1(f,...) + local bound = table.pack(...) + return function(x) + return f(x,table.unpack(bound)) + end +end + +function M.constval(v) return function(...) return v end end + +function M.constfunc(f) return function(...) f() end end + +function M.call(f,...) return f(...) end + +function M.replicate(f,n) + local res = {} + for i=1,n do + if type(f) == 'function' then + table.insert(res,f()) + else + table.insert(res,f) + end + end + return res +end + +function M.id(...) return ... end + +function M.access(...) + local args = table.pack(...) + local x = args[#args] + args[#args] = nil + + for _,key in ipairs(args) do + x = x[key] + end + + return x +end + +function M.filter(pred,list) + local result = {} + + for _,elem in ipairs(list) do + if pred(elem) then + table.insert(result,elem) + end + end + + return result +end + +function M.void(...) end + +function M.zip(f,lhs,rhs) + local result = {} + local n = math.min(#lhs,#rhs) + + for i = 1,n do + local v = f(lhs[i],rhs[i]) + table.insert(result,v) + end + + return result +end + +function M.tap(f) + return function(...) + f(...) + return ... + end +end + +function M.fold(mempty,mappend,tbl) + local result = mempty + for i = 1,#tbl do result = mappend(result,tbl) end + return result +end + +function M.flatten(tbl) + if type(tbl) ~= 'table' then return tbl end + return M.fold({}, M.concat, fmap(M.flatten, tbl)) +end + +function M.concat(lhs,rhs) + local result = {} + for i = 1,#lhs do result[i] = lhs[i] end + for i = 1,#rhs do result[i + #lhs] = rhs[i] end + return result +end + +function M.find(list,pred) + for i = 1,#list do + if pred(list[i]) then return list[i],i end + end + return nil,nil +end + +function M.range(f,t,s) + local res = {} + for x = f,t,s or 1 do table.insert(res,x) end + return res +end + +function M.image(a,b,c,d,x) return (x - a)/(b - a)*(d - c) + c end + +function M.apply(f,...) return f(...) end + +function M.once(f) + local ret + local called = false + return function(...) + if not called then + ret = f(...) + called = true + return ret + end + end +end + +return M diff --git a/lib/graphviz.lua b/lib/graphviz.lua new file mode 100644 index 0000000..173612d --- /dev/null +++ b/lib/graphviz.lua @@ -0,0 +1,48 @@ +-- +-- 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/>. +-- + +local module = {} + +function module.render(layout) + local lines = {} + + local function append(format,...) + local line = string.format(format,...) + table.insert(lines,line) + end + + append('digraph {') + append('rankdir=BT') + append('node [shape=rect]') + + for id,node in pairs(layout) do + append('"%s" [label="%s"]',id,node.label or '?') + end + + for id,node in pairs(layout) do + for _,input in ipairs(node.inputs) do + append('"%s" -> "%s"',input,id) + end + end + + append('}') + + return table.concat(lines,'\n') +end + +return module diff --git a/lib/ladspa_interface.lua b/lib/ladspa_interface.lua new file mode 100644 index 0000000..794f431 --- /dev/null +++ b/lib/ladspa_interface.lua @@ -0,0 +1,63 @@ +-- +-- 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/>. +-- + +local m_ladspa = require 'melisma.ladspa' + +local M = {} + +function M.init(spec,...) + local interface = {} + local info = m_ladspa.info(spec) + local instance = m_ladspa.load(spec,...) + local audio_index = 1 + local control_index = 1 + + for _,port in ipairs(info.ports) do + if port.type == 'control' and port.direction == 'input' then + local control_index_copy = control_index + + interface[port.name] = function(self,value) + if port.hints.upper_bound and value > port.hints.upper_bound then + value = port.hints.upper_bound + end + + if port.hints.lower_bound and value < port.hints.lower_bound then + value = port.hints.lower_bound + end + + instance:control(control_index_copy,value) + return self + end + + if port.hints.default then + interface[port.name](nil,port.hints.default) + end + + control_index = control_index + 1 + end + + if port.type == 'audio' and port.direction == 'output' then + interface[port.name] = instance(audio_index) + audio_index = audio_index + 1 + end + end + + return interface +end + +return M diff --git a/lib/lazy.lua b/lib/lazy.lua new file mode 100644 index 0000000..144ea3a --- /dev/null +++ b/lib/lazy.lua @@ -0,0 +1,24 @@ +-- +-- 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/>. +-- + +return setmetatable({}, { + __index = function(tbl,key) + tbl[key] = require('melisma.'..key) + return tbl[key] + end +}) diff --git a/lib/midimap.lua b/lib/midimap.lua new file mode 100644 index 0000000..f9f7a1d --- /dev/null +++ b/lib/midimap.lua @@ -0,0 +1,34 @@ +-- +-- 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/>. +-- + +local M = {} + +local function optget(map,key) + if map ~= nil then return nil end + if map[key] ~= nil then key = 'default' end + return map[key] +end + +function M.lookup(map,msg) + map = optget(map,msg.status) + map = optget(map,msg.data1) + map = optget(map,msg.data2) + return map +end + +return M diff --git a/lib/osc.lua b/lib/osc.lua new file mode 100644 index 0000000..66f378a --- /dev/null +++ b/lib/osc.lua @@ -0,0 +1,76 @@ +-- +-- 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/>. +-- + +local M = {} + +function M.decode(data) + local addr,ttag,offset = string.unpack('!4 z Xs4 z Xs4',data) + if offset == nil then error('invalid osc message') end + + ttag = string.sub(ttag,2) + data = string.sub(data,offset) + + local args = {} + for i = 1,#ttag do + local format + local c = string.sub(ttag,i,i) + + if c == 's' then format = '!4 z Xs4' + elseif c == 'i' then format = '>i4' + elseif c == 'f' then format = '>f' + else error('invalid osc message') end + + local arg,offset = string.unpack(format,data) + if offset == nil then error('invalid osc message') end + + table.insert(args,arg) + data = string.sub(data,offset) + end + + return {addr=addr, args=args} +end + +function M.encode(msg) + local addr = msg.addr + local args = msg.args + local data = '' + local ttag = ',' + + for _,arg in pairs(args) do + if type(arg) == 'string' then + ttag = ttag..'s' + data = data..string.pack('!4 z Xs4',arg) + elseif type(arg) == 'number' then + if math.type(arg) == 'integer' then + ttag = ttag..'i' + data = data..string.pack('>i4',arg) + elseif math.type(arg) == 'float' then + ttag = ttag..'f' + data = data..string.pack('>f',arg) + else + error('unsupported argument type '..math.type(arg)) + end + else + error('unsupported argument type '..type(arg)) + end + end + + return string.pack('!4 z Xs4',addr)..string.pack('!4 z Xs4',ttag)..data +end + +return M diff --git a/lib/util.lua b/lib/util.lua new file mode 100644 index 0000000..3ceb4cf --- /dev/null +++ b/lib/util.lua @@ -0,0 +1,139 @@ +-- +-- 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/>. +-- + +local M = {} + +function M.deepstring(value,visited,depth,is_key) + if not is_key and type(value) == 'string' then return '\'' .. value .. '\'' end + if type(value) ~= 'table' then return tostring(value) end + if next(value) == nil then return '{}' end + + visited = visited or {} + visited[value] = true + + depth = depth or 0 + local indent = string.rep(' ',depth) + + local str = {} + local function append(...) table.insert(str,string.format(...)) end + + append('{ ') + for k,v in pairs(value) do + if visited[k] then k='{...}' else k=M.deepstring(k,visited,depth+1,true) end + if visited[v] then v='{...}' else v=M.deepstring(v,visited,depth+1) end + append(indent .. ' ' .. '%s = %s, ',k,v) + end + + append(indent .. '}') + return table.concat(str,'\n') +end + +function M.deepcopy(x) + if type(x) ~= 'table' then return x end + local copy = {} + for k,v in pairs(x) do copy[k] = M.deepcopy(v) end + return copy +end + +function M.size(x) + local s = 0 + for _,_ in pairs(x) do + s = s + 1 + end + return s +end + +function M.image(a,b,c,d,x) return (x - a)/(b - a)*(d - c) + c end + +function M.select(list,prompt) + for i,entry in ipairs(list) do + print(i,entry) + end + + while true do + io.write(prompt .. '> ') + io.flush() + + local input = io.read('*l') + local i = tonumber(input) + + if i == nil then + print() + error() + end + + if i >= 1 and i <= #list then + return list[i] + end + end +end + +function M.splitcmd(str) + local words = {} + local word = '' + local state = 'skip' + + for i = 1,#str do + local c = string.sub(str,i,i) + + if state == 'skip' then + if c == '"' then + state = 'string' + elseif c ~= ' ' then + state = 'take' + word = word .. c + end + elseif state == 'take' then + if c == ' ' then + state = 'skip' + table.insert(words,word) + word = '' + elseif c == '"' then + state = 'string' + else + word = word .. c + end + elseif state == 'string' then + if c == '"' then + state = 'take' + else + word = word .. c + end + end + end + + if state ~= 'skip' then + table.insert(words,word) + end + + return words +end + +function M.openmodule(module) + if module.lazy_require_getmodule ~= nil then + module = module.lazy_require_getmodule() + end + + for k,v in pairs(module) do + if string.sub(k,1,2) ~= '__' then + _G[k] = v + end + end +end + +return M |
