diff options
| author | s-ol <s-ol@users.noreply.github.com> | 2020-02-11 09:49:42 +0000 |
|---|---|---|
| committer | s-ol <s-ol@users.noreply.github.com> | 2020-02-11 09:49:42 +0000 |
| commit | d9ed2848c4709aa5f3757333dab0ec8de5833aef (patch) | |
| tree | 4d92c21b69170be9b14f74fd01cbf350b874a415 | |
| parent | envelopes (diff) | |
| download | alive-d9ed2848c4709aa5f3757333dab0ec8de5833aef.tar.gz alive-d9ed2848c4709aa5f3757333dab0ec8de5833aef.zip | |
add MIDI input ops
| -rw-r--r-- | core/cell.moon | 2 | ||||
| -rw-r--r-- | lib/envelope.moon | 2 | ||||
| -rw-r--r-- | lib/midi.moon | 125 | ||||
| -rw-r--r-- | lib/util.moon | 11 | ||||
| -rw-r--r-- | test.alv | 21 |
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_ @@ -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)))) |
