From 2ffb6d05f4519d2185425e45a5d85f32282ca031 Mon Sep 17 00:00:00 2001 From: Ludvig Gunne Lindström Date: Sun, 8 Nov 2026 13:30:11 +0100 Subject: add synth example --- synth/main.lua | 119 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 119 insertions(+) create mode 100644 synth/main.lua diff --git a/synth/main.lua b/synth/main.lua new file mode 100644 index 0000000..42fd82b --- /dev/null +++ b/synth/main.lua @@ -0,0 +1,119 @@ +local ml = require 'melisma.lazy' + +ml.util.openmodule(ml.functional) + +local argv = ml.internal.argv +local device = argv[1] + +if not device then + print('Usage: melisma -C synth main.lua DEVICE') + print('Available devices are:') + + for _,source in ipairs(ml.port.midi.sources()) do + print('\t'..source) + end + + os.exit(1) +end + +local mport = ml.port.midi.input.open('midi-in') + +print(string.format('Connecting device \'%s\'', device)) +mport:connect(device) + +local wt = ml.oscil.wavetable.saw() +wt = wt + wt:scale(2) + +local size = 8 +local active = replicate(false, size) +local allocs = replicate(nil, 128) +local oscil = replicate(bind(ml.oscil.init, wt, 0, 0), size) +local interp = replicate(bind(ml.interp.init, 0), size) + +zip(ml.oscil.mapa, oscil, interp) + +local mix = ml.mixer.init(table.unpack(oscil)) +local delay = ml.delay.init(mix) + :delay(0.2) + :feedback(0.4) + :wetdrymix(0.5) + :gain(0.8) +local lowpass = ml.biquad.lowpass.init(delay) + :cutoff(4000) + :resonance(0.7) +local aport = ml.port.audio.output.open('audio-out', lowpass) +local dests = ml.port.audio.destinations() + +fmap(bind(aport.connect, aport), dests) + +local attack = 0.025 +local release = 0.05 + +local function handle_on(key, vel) + print('on', key) + + -- Check if this note is already on + if allocs[key] then return end + + -- Find unused oscillator + local i = nil + for j = 1,size do + if not active[j] then + i = j + break + end + end + if not i then return end + + -- Play the note + print('activate oscillator', i) + active[i] = true + allocs[key] = i + interp[i]:linear(vel / 127.0, attack) + + local f = 440 * 2^((key - 57) / 12.0) + print('frequency', f) + oscil[i]:setf(f) +end + +local function free(args) + -- Mark the oscillator as unused after the note is released + local i, key = table.unpack(args) + print('free oscillator', i) + active[i] = false + allocs[key] = nil +end + +local function handle_off(key) + print('off', key) + + -- Find the oscillator + local i = allocs[key] + if not i then return end + + -- Stop playing the note + print('deactivate oscillator', i) + interp[i]:linear(0, release) + ml.event.schedule(release, free, { i, key }) +end + +mport:callback(function(msg) + local status = msg.status & 0xf0 + local key = msg.data1 + local vel = msg.data2 + + print('received', status, key, vel) + + local off = 0x80 + local on = 0x90 + + if status == off then + handle_off(key) + return + end + + if status == on then + handle_on(key, vel) + return + end +end) -- cgit v1.3.1