Skip to content
Permalink
Newer
Older
100644 305 lines (252 sloc) 8.13 KB
1
--- clock coroutines
2
-- @module clock
3
4
local clock = {}
5
6
clock.threads = {}
8
local clock_id_counter = 1
9
local function new_id()
10
local id = clock_id_counter
11
clock_id_counter = clock_id_counter + 1
12
return id
13
end
14
15
--- create a coroutine to run but do not immediately run it;
16
-- @tparam function f
17
-- @treturn integer : coroutine ID that can be used to resume/stop it later
18
clock.create = function(f)
19
local coro = coroutine.create(f)
20
local coro_id = new_id()
21
clock.threads[coro_id] = coro
22
return coro_id
23
end
24
25
--- create a coroutine from the given function and immediately run it;
26
-- the function parameter is a task that will suspend when clock.sleep and clock.sync are called inside it and will wake up again after specified time.
27
-- @tparam function f
28
-- @treturn integer : coroutine ID that can be used to stop it later
30
local coro_id = clock.create(f)
32
return coro_id
35
--- stop execution of a coroutine started using clock.run.
36
-- @tparam integer coro_id : coroutine ID
37
clock.cancel = function(coro_id)
38
_norns.clock_cancel(coro_id)
39
clock.threads[coro_id] = nil
40
end
41
42
local SLEEP = 0
43
local SYNC = 1
44
local SUSPEND = 2
45
46
--- yield and schedule waking up the coroutine in s seconds;
47
-- must be called from within a coroutine started with clock.run.
48
-- @tparam float s : seconds
49
clock.sleep = function(...)
50
return coroutine.yield(SLEEP, ...)
53
--- yield and schedule waking up the coroutine at beats beat;
54
-- the coroutine will suspend for the time required to reach the given fraction of a beat;
55
-- must be called from within a coroutine started with clock.run.
56
-- @tparam float beats : next fraction of a beat at which the coroutine will be resumed. may be larger than 1.
57
clock.sync = function(...)
58
return coroutine.yield(SYNC, ...)
61
--- yield and do not schedule wake up, clock must be explicitly resumed
62
-- must be called from within a coroutine started with clock.run.
63
clock.suspend = function()
64
return coroutine.yield(SUSPEND)
65
end
66
67
-- todo: use c api instead
68
clock.resume = function(coro_id, ...)
69
local coro = clock.threads[coro_id]
71
if coro == nil then
72
print('clock: ignoring resumption of canceled clock (no coroutine)')
73
return
76
local result, mode, time = coroutine.resume(clock.threads[coro_id], ...)
78
if coroutine.status(coro) == "dead" then
79
if result then
80
clock.cancel(coro_id)
82
error(mode)
83
end
84
else
85
-- not dead
86
if result and mode ~= nil then
87
if mode == SLEEP then
88
_norns.clock_schedule_sleep(coro_id, time)
89
elseif mode == SYNC then
90
_norns.clock_schedule_sync(coro_id, time)
91
elseif mode == SUSPEND then
92
-- nothing needed for SUSPEND
93
end
95
end
96
end
97
99
clock.cleanup = function()
100
for id, coro in pairs(clock.threads) do
101
if coro then
102
clock.cancel(id)
103
end
104
end
106
clock.transport.start = nil
107
clock.transport.stop = nil
108
end
109
110
--- select the sync source
111
-- @tparam string source : "internal", "midi", or "link"
112
clock.set_source = function(source)
113
if type(source) == "number" then
114
_norns.clock_set_source(util.clamp(source-1,0,3)) -- lua list is 1-indexed
115
elseif source == "internal" then
116
_norns.clock_set_source(0)
117
elseif source == "midi" then
118
_norns.clock_set_source(1)
119
elseif source == "link" then
120
_norns.clock_set_source(2)
121
else
122
error("unknown clock source: "..source)
123
end
124
end
125
127
clock.get_beats = function()
128
return _norns.clock_get_time_beats()
129
end
130
Mar 22, 2020
131
clock.get_tempo = function()
132
return _norns.clock_get_tempo()
133
end
134
135
clock.get_beat_sec = function(x)
136
x = x or 1
137
return 60.0 / clock.get_tempo() * x
138
end
139
Mar 22, 2020
140
141
clock.transport = {}
143
clock.transport.start = nil
144
clock.transport.stop = nil
147
clock.internal = {}
148
149
clock.internal.set_tempo = function(bpm)
150
return _norns.clock_internal_set_tempo(bpm)
151
end
152
153
clock.internal.start = function(beat)
154
beat = beat or 0
155
return _norns.clock_internal_start(beat)
156
end
157
158
clock.internal.stop = function()
159
return _norns.clock_internal_stop()
160
end
161
162
clock.crow = {}
163
164
clock.crow.in_div = function(div)
165
_norns.clock_crow_in_div(div)
166
end
167
168
169
clock.midi = {}
170
171
172
clock.link = {}
173
174
clock.link.set_tempo = function(bpm)
175
return _norns.clock_link_set_tempo(bpm)
176
end
177
178
clock.link.set_quantum = function(quantum)
179
return _norns.clock_link_set_quantum(quantum)
180
end
181
182
183
_norns.clock.start = function()
184
if clock.transport.start ~= nil then
185
clock.transport.start()
187
end
188
189
_norns.clock.stop = function()
190
if clock.transport.stop ~= nil then
191
clock.transport.stop()
Apr 9, 2020
196
function clock.add_params()
197
params:add_group("CLOCK",8)
199
params:add_option("clock_source", "source", {"internal", "midi", "link", "crow"},
200
norns.state.clock.source)
201
params:set_action("clock_source",
Apr 9, 2020
202
function(x)
203
clock.set_source(x)
204
if x==4 then
205
crow.input[1].change = function() end
206
crow.input[1].mode("change",2,0.1,"rising")
Apr 9, 2020
207
end
208
norns.state.clock.source = x
209
if x==1 then clock.internal.set_tempo(params:get("clock_tempo"))
210
elseif x==3 then clock.link.set_tempo(params:get("clock_tempo")) end
Apr 9, 2020
211
end)
212
params:set_save("clock_source", false)
213
params:add_number("clock_tempo", "tempo", 1, 300, norns.state.clock.tempo)
Apr 9, 2020
214
params:set_action("clock_tempo",
215
function(bpm)
Apr 9, 2020
216
local source = params:string("clock_source")
217
if source == "internal" then clock.internal.set_tempo(bpm)
218
elseif source == "link" then clock.link.set_tempo(bpm) end
219
norns.state.clock.tempo = bpm
Apr 9, 2020
220
end)
221
params:set_save("clock_tempo", false)
Apr 9, 2020
222
params:add_trigger("clock_reset", "reset")
223
params:set_action("clock_reset",
224
function()
Apr 9, 2020
225
local source = params:string("clock_source")
226
if source == "internal" then clock.internal.start(bpm)
227
elseif source == "link" then print("link reset not supported") end
228
end)
229
params:add_number("link_quantum", "link quantum", 1, 32, norns.state.clock.link_quantum)
Apr 9, 2020
230
params:set_action("link_quantum",
231
function(x)
232
clock.link.set_quantum(x)
233
norns.state.clock.link_quantum = x
234
end)
235
params:set_save("link_quantum", false)
Apr 9, 2020
236
237
params:add_option("clock_midi_out", "midi out",
238
{"off", "port 1", "port 2", "port 3", "port 4"}, norns.state.clock.midi_out)
239
params:set_action("clock_midi_out", function(x) norns.state.clock.midi_out = x end)
240
params:set_save("clock_midi_out", false)
Apr 9, 2020
241
params:add_option("clock_crow_out", "crow out",
242
{"off", "output 1", "output 2", "output 3", "output 4"}, norns.state.clock.crow_out)
Apr 9, 2020
243
params:set_action("clock_crow_out", function(x)
244
if x>1 then crow.output[x-1].action = "pulse(0.05,8)" end
245
norns.state.clock.crow_out = x
Apr 9, 2020
246
end)
247
params:set_save("clock_crow_out", false)
248
params:add_number("clock_crow_out_div", "crow out div", 1, 32,
249
norns.state.clock.crow_out_div)
250
params:set_action("clock_crow_out_div",
251
function(x) norns.state.clock.crow_out_div = x end)
252
params:set_save("clock_crow_out_div", false)
253
params:add_number("clock_crow_in_div", "crow in div", 1, 32,
254
norns.state.clock.crow_in_div)
255
params:set_action("clock_crow_in_div",
256
function(x)
257
clock.crow.in_div(x)
258
norns.state.clock.crow_in_div = x
259
end)
260
params:set_save("clock_crow_in_div", false)
261
--params:add_trigger("crow_clear", "crow clear")
262
--params:set_action("crow_clear",
263
--function() crow.reset() crow.clear() end)
264
265
params:bang("clock_tempo")
Apr 9, 2020
266
267
-- executes crow sync
268
clock.run(function()
269
while true do
Apr 9, 2020
270
clock.sync(1/params:get("clock_crow_out_div"))
Apr 9, 2020
271
local crow_out = params:get("clock_crow_out")-1
272
if crow_out > 0 then crow.output[crow_out]() end
273
end
274
end)
275
276
-- executes midi out (needs a subtick)
277
-- FIXME: lots of if's every tick blah
278
clock.run(function()
279
while true do
280
clock.sync(1/24)
281
local midi_out = params:get("clock_midi_out")-1
282
if midi_out > 0 then
Apr 9, 2020
283
if midi.vports[midi_out].name ~= "none" then
284
midi.vports[midi_out]:clock()
Apr 9, 2020
286
end
287
end
288
end)
289
290
-- update tempo param value
291
clock.run(function()
292
while true do
293
if params:get("clock_source") ~= 1 then
294
local external_tempo = math.floor(clock.get_tempo() + 0.5)
295
params:set("clock_tempo", external_tempo, true)
296
end
297
298
clock.sleep(1)
299
end
300
end)
301
Apr 9, 2020
302
end
303
304
305
return clock