aboutsummaryrefslogtreecommitdiffstats
path: root/lib/midi/launchctl.moon
blob: 495743b9d7f3d3258456673b64b0c26b7b29d32b (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
import Value, Op, ValueInput, EventInput, IOInput, match
  from require 'core.base'
import apply_range from require 'lib.midi.core'
import bor, lshift from require 'bit32'

unpack or= table.unpack
color = (r, g) -> bor 12, r, (lshift g, 4)

class cc_seq extends Op
  @doc: "(launctl/cc-seq port i start chan [steps [range]]) - CC-Sequencer

returns the value for the i-th step steps (buttons starting from start).
steps defaults to 8.

range can be one of:
- 'raw' [ 0 - 128[
- 'uni' [ 0 - 1[ (default)
- 'bip' [-1 - 1[
- 'rad' [ 0 - tau[
- 'deg' [ 0 - 360[
- (num) [ 0 - num["

  new: =>
    super 'num'
    @steps = {}

  setup: (inputs) =>
    { port, i, start,
      chan, steps, range } = match 'midi/port num num num num? any?', inputs

    super
      port:  IOInput port
      i:     ValueInput i
      start: ValueInput start
      chan:  ValueInput chan
      steps: ValueInput steps or Value.num 8
      range: ValueInput range or Value.str 'uni'

    if not @out\unwrap!
      @out\set apply_range @inputs.range, 0

  tick: =>
    { :port, :i, :start, :chan, :steps, :range } = @inputs

    if steps\dirty!
      while steps! > #@steps
        table.insert @steps, 0
      while steps! < #@steps
        table.remove @steps

    curr_i = i! % #@steps
    if port\dirty!
      changed = false
      for msg in port!\receive!
        if msg.status == 'control-change' and msg.chan == chan!
          rel_i = msg.a - start!
          if rel_i >= 0 and rel_i < #@steps
            @steps[rel_i+1] = msg.b
            changed = rel_i == curr_i
      @out\set apply_range range, @steps[curr_i+1] if changed
    else
      @out\set apply_range range, @steps[curr_i+1]

class gate_seq extends Op
  @doc: "(launctl/gate-seq port i start chan [steps]) - Gate-Sequencer

returns true or false for the i-th step steps (buttons starting from start).
steps defaults to 8."

  new: =>
    super 'bool', false
    @steps = {}

  setup: (inputs) =>
    { port, i, start, chan, steps } = match 'midi/port num num num num?', inputs

    super
      port:  IOInput port
      i:     ValueInput i
      start: ValueInput start
      chan:  ValueInput chan
      steps: ValueInput steps or Value.num 8

  light = (set, active) ->
    set = if set then 'S' else ' '
    active = if active then 'A' else ' '
    color switch set .. active
      when '  ' then 0, 0
      when ' A' then 1, 1
      when 'S ' then 1, 0
      when 'SA' then 3, 1

  display: (i, active) =>
    { :port, :start, :chan } = @unwrap_all!
    port\send 'note-on', chan, (start + i), light @steps[i+1], active

  tick: =>
    { :port, :i, :start, :chan, :steps } = @inputs

    if steps\dirty!
      while steps! > #@steps
        table.insert @steps, false
      while steps! < #@steps
        table.remove @steps

    curr_i = i! % #@steps

    if port\dirty!
      for msg in port!\receive!
        if msg.status == 'note-on' and msg.chan == chan!
          rel_i = msg.a - start!
          if rel_i >= 0 and rel_i < #@steps
            @steps[rel_i+1] = not @steps[rel_i+1]
            @display rel_i, rel_i == curr_i

    if i\dirty!
      prev_i = (curr_i - 1) % #@steps

      @display curr_i, true
      @display prev_i, false

      @out\set @steps[curr_i+1]

{
  'gate-seq': gate_seq
  'cc-seq': cc_seq
}