aboutsummaryrefslogtreecommitdiffstats
path: root/alv-lib/rhythm.moon
blob: 582483f4e02eb74ad873369e1f612b307f7b8dda (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 Constant, Op, Input, T, sig, evt from require 'alv.base'

-- slower reference implementation
bjorklund = (n, k) ->
  full = ['1' for i=1,k]
  rest = ['0' for i=k,n-1]
  while #rest > 1
    next_full = {}
    while full[1] and rest[1]
      a = table.remove full
      b = table.remove rest
      table.insert next_full, a .. b
    while rest[1]
      table.insert full, table.remove rest
    full, rest = next_full, full

  if rest[1]
    table.insert full, rest[1]
  table.concat full, ''

-- faster implementation
bjorklund2 = (n, k) ->
  a, as = '1', k
  b, bs = '0', n - k

  while true
    if as < 2 or bs < 2
      break
    elseif as < bs
      a, b = a .. b, b
      as, bs = as, bs - as
    else
      a, b = a .. b, a
      as, bs = bs, as - bs

  (string.rep a, as) .. (string.rep b, bs)

euclid = Constant.meta
  meta:
    name: 'euclid'
    summary: "Generate euclidean rhythms."
    examples: { '(euclid trig! n k)', '(euclid i n k)' }
    description: "Generates bangs according to the Euclidean algorithm.

When fed a bang! trigger, steps forward to the next step on each trigger.
When fed a num~ or num! stream, outputs a bang if the corresponding step is on."

  value: class extends Op
    pattern = (evt.bang / sig.num / evt.num) + sig.num + sig.num
    setup: (inputs) =>
      { trig, n, k } = pattern\match inputs

      @out or= T.bang\mk_evt!

      if trig\type! == T.bang
        @state or= 1
      else
        @state = nil

      super
        trig: Input.hot trig
        n: Input.cold n
        k: Input.cold k

    tick: =>
      { :trig, :n, :k } = @unwrap_all!
      n = math.floor n
      k = math.floor k

      if @inputs.trig\type! == T.bang
        @state += 1
        if @state >= n
          @state -= n

      i = 1 + (@state or trig % n)
      pat = bjorklund2 n, k
      if '1' == pat\sub i, i
        @out\set true

trigseq = Constant.meta
  meta:
    name: 'trigseq'
    summary: "Generate rhythms based on a trigger-sequence"
    examples: { '(trigseq trig! s1 s2…)', '(trigseq i s1 s2…)' }
    description: "Generates bangs according to the sequence `s1`, `s2`, …

Each step should be a bool~ that determines whether a bang should be emitted on
that step or not.

When fed a bang! trigger, steps forward to the next step on each trigger.
When fed a num~ or num! stream, outputs a bang if the corresponding step is on."

  value: class extends Op
    pattern = (evt.bang / sig.num / evt.num) + sig.bool*0
    setup: (inputs) =>
      { trig, steps } = pattern\match inputs

      @out or= T.bang\mk_evt!

      super
        trig: Input.hot trig
        steps: [Input.cold s for s in *steps]

      if @inputs.trig\type! == T.bang
        @state or= 1
      else
        @state = nil

    tick: =>
      n = #@inputs.steps
      if @inputs.trig\type! == T.bang
        @state += 1
        if @state >= n
          @state -= n

      i = 1 + (@state or trig % n)
      if @inputs.steps[i]!
        @out\set true

Constant.meta
  meta:
    name: 'rhythm'
    summary: "Rhythm-generation and sequencing."

  value:
    :euclid
    :trigseq