aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authors-ol <s-ol@users.noreply.github.com>2020-02-11 09:49:42 +0000
committers-ol <s-ol@users.noreply.github.com>2020-02-11 09:49:42 +0000
commitd9ed2848c4709aa5f3757333dab0ec8de5833aef (patch)
tree4d92c21b69170be9b14f74fd01cbf350b874a415
parentenvelopes (diff)
downloadalive-d9ed2848c4709aa5f3757333dab0ec8de5833aef.tar.gz
alive-d9ed2848c4709aa5f3757333dab0ec8de5833aef.zip
add MIDI input ops
-rw-r--r--core/cell.moon2
-rw-r--r--lib/envelope.moon2
-rw-r--r--lib/midi.moon125
-rw-r--r--lib/util.moon11
-rw-r--r--test.alv21
5 files changed, 146 insertions, 15 deletions
diff --git a/core/cell.moon b/core/cell.moon
index ae6eea4..8f31f27 100644
--- a/core/cell.moon
+++ b/core/cell.moon
@@ -10,6 +10,8 @@ class Cell
head: => @children[1]
tail: => [c for c in *@children[2,]]
+ destroy: =>
+
-- AST interface
eval: (scope, registry) =>
head = @head!\eval scope, registry
diff --git a/lib/envelope.moon b/lib/envelope.moon
index 0129eb4..8e8fafe 100644
--- a/lib/envelope.moon
+++ b/lib/envelope.moon
@@ -20,7 +20,7 @@ goes from 0 to 1 in attack seconds, holds while gate is on, then goes back to 0
@r\update dt
@gate\update dt
- slope = if @gate\get! then @a\getc! else -@r\getc!
+ slope = if @gate\get! then (@a\get! or 0.1) else -(@r\get! or 0.5)
@value = math.min 1, math.max 0, @value + dt / slope
diff --git a/lib/midi.moon b/lib/midi.moon
new file mode 100644
index 0000000..5aebc0b
--- /dev/null
+++ b/lib/midi.moon
@@ -0,0 +1,125 @@
+import Const, Op from require 'core'
+import RtMidiIn, RtMidiOut, RtMidi from require 'luartmidi'
+import band, rshift from require 'bit32'
+
+MIDI = {
+ [0x9]: 'note-on'
+ [0x8]: 'note-off'
+
+ [0xa]: 'after-key'
+ [0xd]: 'after-channel'
+
+ [0xb]: 'control-change'
+ [0xe]: 'pitch-bend'
+ [0xc]: 'program-change'
+}
+
+class Dispatcher
+ new: (name) =>
+ @input = RtMidiIn RtMidi.Api.UNIX_JACK
+
+ id = nil
+ for port=1,@input\getportcount!
+ if name == @input\getportname port
+ id = port
+ break
+
+ @input\openport id
+
+ @listeners = {}
+
+ tick: =>
+ while true
+ delta, bytes = @input\getmessage!
+ break unless delta
+
+ { status, a, b } = bytes
+ chan = band status, 0xf
+ status = MIDI[rshift status, 4]
+ @dispatch status, chan, a, b
+
+ dispatch: (status, chan, a, b) =>
+ L\trace "dispatching MIDI event #{status} CH#{chan} #{a} #{b}"
+ for mask, handler in pairs @listeners
+ match = true
+ match and= status == mask.status if mask.status
+ match and= chan == mask.chan if mask.chan
+ match and= a == mask.a if mask.a
+ if match
+ handler status, chan, a, b
+
+ -- register a handler
+ -- mask is { :status, :chan, :a } (all keys optional)
+ attach: (mask, handler) =>
+ @listeners[mask] = handler
+
+ detach: (mask) =>
+ @listeners[mask] = nil
+
+dispatch = Dispatcher 'system:midi_capture_2'
+
+class gate extends Op
+ @doc: "(midi/gate note [chan]) - gate from note-on and note-off messages"
+
+ new: (...) =>
+ super ...
+
+ destroy: =>
+ dispatch\detach @mask if @mask
+
+ setup: (note, chan) =>
+ dispatch\detach @mask if @mask
+
+ note = note\getc 'num'
+ chan = chan and chan\getc 'num'
+ @value = false
+
+ @mask = dispatch\attach { :chan, a: note }, (status) ->
+ if status == 'note-on'
+ @value = true
+ else if status == 'note-off'
+ @value = false
+
+ update: (dt) => dispatch\tick!
+
+class cc extends Op
+ @doc: "(midi/cc cc [chan [range]]) - MIDI CC to number
+
+range can be one of:
+- 'raw' [ 0 - 128[
+- 'uni' [ 0 - 1[ (default)
+- 'bip' [-1 - 1[
+- 'rad' [ 0 - tau[
+- (num) [ 0 - num["
+
+ destroy: =>
+ dispatch\detach @mask if @mask
+
+ setup: (cc, chan, @range=Const.str'uni') =>
+ dispatch\detach @mask if @mask
+
+ cc = cc\getc 'num'
+ chan = chan and chan\getc 'num'
+
+ @mask = dispatch\attach { status: 'control-change', :chan, a: cc }, (_, _, _, val) -> @apply val
+
+ update: (dt) => dispatch\tick!
+
+ apply: (val) =>
+ @value = if @range.type == 'str'
+ switch @range\get!
+ when 'raw' then val
+ when 'uni' then val / 128
+ when 'bip' then val / 64 - 1
+ when 'rad' then val / 64 * math.pi
+ else
+ error "unknown range #{@range}"
+ elseif @range.type == 'num'
+ val / 128 * @range\get!
+ else
+ error "range has to be a string or number"
+
+{
+ :gate
+ :cc
+}
diff --git a/lib/util.moon b/lib/util.moon
index 849466c..6da3698 100644
--- a/lib/util.moon
+++ b/lib/util.moon
@@ -1,4 +1,4 @@
-import Op from require 'core'
+import Const, Op from require 'core'
class switch_ extends Op
@doc: "(switch i v0 [v1 v2...]) - switch between multiple inputs
@@ -52,17 +52,18 @@ like (switch ...) except that the unused inputs are paused."
active\get!
class keep extends Op
- @doc: "(keep value) - keep the last non-nil value
+ @doc: "(keep value [default]) - keep the last non-nil value
-always reproduces the last non-nil value the input produced"
+always reproduces the last non-nil value the input produced or default.
+default defaults to zero."
- setup: (@i) =>
+ setup: (@i, @default=Const.num 0) =>
update: (dt) =>
@i\update dt
next = @i\get!
- @value = next or @value
+ @value = next or @value or @default\get!
{
'switch': switch_
diff --git a/test.alv b/test.alv
index 1db7293..7d51b9b 100644
--- a/test.alv
+++ b/test.alv
@@ -1,13 +1,16 @@
-([1]import* math time string)
-([18]import osc)
+([1]import* math time string util)
+([2]import osc envelope midi)
-([7]def make-lfo ([8]fn (type)
- ([11]fn ([9]f) ([10]lfo ([31]* f 0.5) type))))
+([3]defn make-lfo (type)
+ ([8]fn ([5]f) ([7]lfo ([6]* f 0.5) type)))
-([12]def sin-lfo ([13]make-lfo 'sin'))
+([9]def sin-lfo ([10]make-lfo 'sin'))
-([20]defn send (name value)
- ([22]osc/out '127.0.0.1' 9000 ([19].. '/param/' name '/set') value))
+([11]defn send (name value)
+ ([13]osc/out '127.0.0.1' 9000
+ ([12].. '/param/' name '/set')
+ value))
-([14]send 'radius' ([15]sin-lfo 1.05))
-([16]send 'offset' ([17]sin-lfo 0.2))
+([23]send 'radius' ([25]([24]envelope/ar ([14]midi/cc 0) ([15]midi/cc 1)) ([27]midi/gate 36)))
+
+([19]send 'offset' ([20]sin-lfo ([16]keep ([26]midi/cc 24 0 4))))