aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorLudvig Gunne Lindström <ludviggunnelindstrom@gmail.com>2026-11-08 13:30:11 +0100
committerLudvig Gunne Lindström <ludviggunnelindstrom@gmail.com>2026-11-08 13:36:29 +0100
commit2ffb6d05f4519d2185425e45a5d85f32282ca031 (patch)
tree0e3126f6a4891135b5818534a67df99b7cdd3285
parent9810b318a743321d0813ae13a8d094aa2924b843 (diff)
downloadmelisma-examples-main.tar.gz
add synth exampleHEADmain
-rw-r--r--synth/main.lua119
1 files changed, 119 insertions, 0 deletions
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)