-
Notifications
You must be signed in to change notification settings - Fork 2
/
Copy pathchanges.lua
208 lines (170 loc) · 4.23 KB
/
changes.lua
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
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
-- Changes
-- 1.0.0 @markeats
-- llllllll.co/t/changes
--
-- Eight connected sine wave
-- LFOs over MIDI.
--
-- E2 : Left time
-- E3 : Right time
-- K2 : Reset phase
--
local ControlSpec = require "controlspec"
local Formatters = require "formatters"
local SCREEN_FRAMERATE = 15
local screen_dirty = true
local NUM_LFOS = 8
local LFO_MIN_TIME = 1 -- Secs
local LFO_MAX_TIME = 60 * 60 * 24
local LFO_UPDATE_FREQ = 128
local LFO_RESOLUTION = 128 -- MIDI CC resolution
local lfo_freqs = {}
local lfo_progress = {}
local lfo_values = {}
local midi_out_device
local midi_out_channel
local specs = {}
specs.TIME_L = ControlSpec.new(LFO_MIN_TIME, LFO_MAX_TIME, "exp", 0, 4, "s")
specs.TIME_R = ControlSpec.new(LFO_MIN_TIME, LFO_MAX_TIME, "exp", 0, 300, "s")
local function reset_phase()
for i = 1, NUM_LFOS do
lfo_progress[i] = math.pi * 1.5
end
end
local function update_freqs()
for i = 1, NUM_LFOS do
lfo_freqs[i] = 1 / util.linexp(1, NUM_LFOS, params:get("time_l"), params:get("time_r"), i)
end
end
-- Metro callbacks
local function lfo_update()
local delta = (1 / LFO_UPDATE_FREQ) * 2 * math.pi
for i = 1, NUM_LFOS do
lfo_progress[i] = lfo_progress[i] + delta * lfo_freqs[i]
local value = util.round(util.linlin(-1, 1, 0, LFO_RESOLUTION - 1, math.sin(lfo_progress[i])))
if value ~= lfo_values[i] then
lfo_values[i] = value
midi_out_device:cc(i - 1 + params:get("midi_cc_start"), value, midi_out_channel)
screen_dirty = true
end
end
end
local function screen_update()
if screen_dirty then
screen_dirty = false
redraw()
end
end
-- Encoder input
function enc(n, delta)
if n == 2 then
params:delta("time_l", delta * 0.1)
elseif n == 3 then
params:delta("time_r", delta * 0.1)
end
end
-- Key input
function key(n, z)
if z == 1 then
if n == 2 then
reset_phase()
end
end
end
function init()
midi_out_device = midi.connect(1)
-- Add params
params:add_separator()
params:add {
type = "number",
id = "midi_out_device",
name = "MIDI Out Device",
min = 1,
max = 4,
default = 1,
action = function(value)
midi_out_device = midi.connect(value)
end
}
params:add {
type = "number",
id = "midi_out_channel",
name = "MIDI Out Channel",
min = 1,
max = 16,
default = 1,
action = function(value)
midi_out_channel = value
end
}
params:add {
type = "number",
id = "midi_cc_start",
name = "MIDI CC Range",
min = 0,
max = 128 - NUM_LFOS,
default = 1,
formatter = function(param)
return param:get() .. "-" .. param:get() + NUM_LFOS - 1
end
}
params:add_separator("LFOs")
params:add {
type = "control",
id = "time_l",
name = "Left Time",
controlspec = specs.TIME_L,
formatter = Formatters.format_secs,
action = function(value)
update_freqs()
screen_dirty = true
end
}
params:add {
type = "control",
id = "time_r",
name = "Right Time",
controlspec = specs.TIME_R,
formatter = Formatters.format_secs,
action = function(value)
update_freqs()
screen_dirty = true
end
}
midi_out_channel = params:get("midi_out_channel")
reset_phase()
update_freqs()
lfo_update()
metro.init(lfo_update, 1 / LFO_UPDATE_FREQ):start()
metro.init(screen_update, 1 / SCREEN_FRAMERATE):start()
end
function redraw()
screen.clear()
screen.aa(1)
local BAR_W, BAR_H = 1, 41
local MARGIN_H, MARGIN_V = 6, 6
local gutter = (128 - MARGIN_H * 2 - BAR_W * NUM_LFOS) / (NUM_LFOS - 1)
-- Draw bars
for i = 1, NUM_LFOS do
local row_x = util.round(MARGIN_H + (gutter + BAR_W) * (i - 1))
-- Dotted
for y = 0, BAR_H - 1, 2 do
screen.rect(row_x, MARGIN_V + y, BAR_W, 1)
screen.level(1)
screen.fill()
end
-- Fills
local filled_height = util.linlin(0, LFO_RESOLUTION - 1, 0, BAR_H, lfo_values[i])
screen.rect(row_x, MARGIN_V + BAR_H - filled_height, BAR_W, filled_height)
screen.level(15)
screen.fill()
end
-- Draw text
screen.level(3)
screen.move(MARGIN_H, 64 - 5)
screen.text("\u{25C0} " .. params:string("time_l"))
screen.move(128 - MARGIN_H, 64 - 5)
screen.text_right(params:string("time_r") .. " \u{25B6}")
screen.fill()
screen.update()
end