-
Notifications
You must be signed in to change notification settings - Fork 2
/
Copy pathmangl.lua
894 lines (751 loc) · 23.3 KB
/
mangl.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
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
-- mangl
--
-- arc suggested.
-- grid capable.
--
-- ----------
--
-- based on the script angl
-- by @tehn and the
-- engine/script glut
-- by @artfwo
--
-- ----------
--
-- load samples via param menu
--
-- ----------
--
-- mangl is a 7 track granular
-- sample player.
--
-- arc ring 1 = speed
-- arc ring 2 = pitch
-- arc ring 3 = grain size
-- arc ring 4 = density
--
-- norns key1 = alt
-- norns key2 = enable/disable
-- voice
-- norns key3 = set speed to 0
--
-- norns enc1 = volume
-- norns enc3 = nav
--
-- ----------
--
-- holding alt and turning a ring,
-- or pressing a button,
-- performs a secondary
-- function.
--
-- alt + ring1 = scrub
-- alt + ring2 = fine tune
-- alt + ring3 = spread
-- alt + ring4 = jitter
--
-- alt + key2 = loop in/out
-- alt + key3 = loop clear
--
-- alt + enc1 = filter cutoff
-- alt + enc2 = delay send
--
-- nb: loop in/out is set in
-- one button press. loop in
-- on press, loop out on release.
--
-- ----------
--
-- @justmat v2.2
--
-- llllllll.co/t/21066
engine.name = 'MGlut'
local a = arc.connect(1)
local g = grid.connect(1)
local lfo = include 'lib/hnds_mangl'
local gridbuf = require 'lib/gridbuf'
local grid_ctl = gridbuf.new(16, 8)
local grid_voc = gridbuf.new(16, 8)
local tau = math.pi * 2
local VOICES = 7
local positions = {}
local gates = {}
local voice_levels = {}
local track_speed = {}
local loop_in = {}
local loop_out = {}
local loops = {}
local latched = {}
for i = 1, VOICES do
positions[i] = -1
track_speed[i] = 0
loop_in[i] = nil
loop_out[i] = nil
gates[i] = 0
voice_levels[i] = 0
loops[i] = {
state = 0,
dir = 1
}
latched[i] = true
end
local tracks = {"one", "two", "three", "four", "five", "six", "seven"}
local track = 1
local alt = false
local last_enc = 0
local time_last_enc = 0
local time_last_scrub = 0
local was_playing = false
local metro_grid_refresh
local metro_blink
-- arc sensitivity settings
local scrub_sens
local speed_sens
local size_sens
local density_sens
local spread_sens
local jitter_sens
-- for lib/hnds
local lfo_targets = {"none"}
for i = 1, VOICES do
table.insert(lfo_targets, i .. "position")
table.insert(lfo_targets, i .. "volume")
table.insert(lfo_targets, i .. "size")
table.insert(lfo_targets, i .. "density")
table.insert(lfo_targets, i .. "spread")
table.insert(lfo_targets, i .. "jitter")
table.insert(lfo_targets, i .. "cutoff")
table.insert(lfo_targets, i .. "send")
end
-- pattern recorder. should likely be swapped out for pattern_time lib
local pattern_banks = {}
local pattern_timers = {}
local pattern_leds = {} -- for displaying button presses
local pattern_positions = {} -- playback positions
local record_bank = -1
local record_prevtime = -1
local record_length = -1
local alt = false
local blink = 0
local metro_blink
local function record_event(x, y, z)
if record_bank > 0 then
-- record first event tick
local current_time = util.time()
if record_prevtime < 0 then
record_prevtime = current_time
end
local time_delta = current_time - record_prevtime
table.insert(pattern_banks[record_bank], {time_delta, x, y, z})
record_prevtime = current_time
end
end
local function start_playback(n)
pattern_timers[n]:start(0.001, 1) -- TODO: timer doesn't start immediately with zero
end
local function stop_playback(n)
pattern_timers[n]:stop()
pattern_positions[n] = 1
end
local function arm_recording(n)
record_bank = n
end
local function stop_recording()
local recorded_events = #pattern_banks[record_bank]
if recorded_events > 0 then
-- save last delta to first event
local current_time = util.time()
local final_delta = current_time - record_prevtime
pattern_banks[record_bank][1][1] = final_delta
start_playback(record_bank)
end
record_bank = -1
record_prevtime = -1
end
local function pattern_next(n)
local bank = pattern_banks[n]
local pos = pattern_positions[n]
local event = bank[pos]
local delta, x, y, z = table.unpack(event)
pattern_leds[n] = z
grid_key(x, y, z, true)
local next_pos = pos + 1
if next_pos > #bank then
next_pos = 1
end
local next_event = bank[next_pos]
local next_delta = next_event[1]
pattern_positions[n] = next_pos
-- schedule next event
pattern_timers[n]:start(next_delta, 1)
end
local function record_handler(n)
if alt then
-- clear pattern
if n == record_bank then stop_recording() end
if pattern_timers[n].is_running then stop_playback(n) end
pattern_banks[n] = {}
do return end
end
if n == record_bank then
-- stop if pressed current recording
stop_recording()
else
local pattern = pattern_banks[n]
if #pattern > 0 then
-- toggle playback if there's data
if pattern_timers[n].is_running then stop_playback(n) else start_playback(n) end
else
-- stop recording if it's happening
if record_bank > 0 then
stop_recording()
end
-- arm new pattern for recording
arm_recording(n)
end
end
end
-- internals
local function get_sample_name()
-- strips the path and extension from filenames
-- if filename is over 15 chars, returns a folded filename
local long_name = string.match(params:get(track .. "sample"), "[^/]*$")
local short_name = string.match(long_name, "(.+)%..+$")
if string.len(short_name) >= 15 then
return string.sub(short_name, 1, 4) .. '...' .. string.sub(short_name, -4)
else
return short_name
end
end
local function display_voice(phase, width)
local pos = phase * width
local levels = {}
for i = 1, width do levels[i] = 0 end
local left = math.floor(pos)
local index_left = left + 1
local dist_left = math.abs(pos - left)
local right = math.floor(pos + 1)
local index_right = right + 1
local dist_right = math.abs(pos - right)
if index_left < 1 then index_left = width end
if index_left > width then index_left = 1 end
if index_right < 1 then index_right = width end
if index_right > width then index_right = 1 end
levels[index_left] = math.floor(math.abs(1 - dist_left) * 15)
levels[index_right] = math.floor(math.abs(1 - dist_right) * 15)
return levels
end
local function start_voice(voice, pos)
engine.seek(voice, pos)
engine.gate(voice, 1)
gates[voice] = 1
end
local function stop_voice(voice)
gates[voice] = 0
engine.gate(voice, 0)
end
local function set_speed(n)
params:set(n .. "speed", track_speed[n])
end
local function hold_track_speed(n)
-- remember track speed and direction while scrubbing audio file
local speed = params:get(n .. "speed")
if speed ~= 0 then
track_speed[n] = speed
if speed < 0 then
loops[n].dir = -1
else
loops[n].dir = 1
end
end
end
local function scrub(n, d)
-- scrub playback position
hold_track_speed(n)
params:set(n .. "speed", 0)
was_playing = true
engine.seek(n, positions[n] + d / scrub_sens)
end
local function clear_loop(track)
loop_in[track] = nil
loop_out[track] = nil
loops[track].state = 0
end
function loop_pos()
-- keeps playback inside the loop
for i = 1, VOICES do
if loops[i].state == 1 then
if loops[i].dir == -1 then
if positions[i] <= loop_in[i] then
positions[i] = loop_out[i]
engine.seek(i, loop_out[i])
end
else
if positions[i] >= loop_out[i] then
positions[i] = loop_in[i]
engine.seek(i, loop_in[i])
end
end
end
end
end
-- for hnds
function lfo.process()
-- for lib hnds
for i = 1, 4 do
local target = params:get(i .. "lfo_target")
local target_name = string.sub(lfo_targets[target], 2)
local voice = string.sub(lfo_targets[target], 1, 1)
if params:get(i .. "lfo") == 2 then
-- volume
if target_name == "volume" then
params:set(lfo_targets[target], lfo.scale(lfo[i].slope, -1.0, 2.0, -60, 20))
-- size
elseif target_name == "size" then
params:set(lfo_targets[target], lfo.scale(lfo[i].slope, -1.0, 2.0, 1, 500))
-- density
elseif target_name == "density" then
params:set(lfo_targets[target], lfo.scale(lfo[i].slope, -1.0, 2.0, 0, 512))
-- spread
elseif target_name == "spread" then
params:set(lfo_targets[target], lfo.scale(lfo[i].slope, -1.0, 2.0, 0, 100))
-- jitter
elseif target_name == "jitter" then
params:set(lfo_targets[target], lfo.scale(lfo[i].slope, -1.0, 2.0, 0, 500))
-- position
elseif target_name == "position" then
engine.seek(voice, lfo.scale(lfo[i].slope, -1.0, 2.0, 0, 1))
-- filter cutoff
elseif target_name == "cutoff" then
params:set(lfo_targets[target], lfo.scale(lfo[i].slope, -1.0, 2.0, 0, 20000))
-- delay send
elseif target_name == "send" then
params:set(lfo_targets[target], lfo.scale(lfo[i].slope, -1.0, 2.0, 0.00, 1.00))
end
end
end
end
-- init ----------
function init()
g.key = function(x, y, z)
grid_key(x, y, z)
end
-- polls
for v = 1, VOICES do
local phase_poll = poll.set('phase_' .. v, function(pos) positions[v] = pos end)
phase_poll.time = 0.025
phase_poll:start()
local level_poll = poll.set('level_' .. v, function(lvl) voice_levels[v] = lvl end)
level_poll.time = 0.05
level_poll:start()
end
-- recorders
for v = 1, VOICES do
table.insert(pattern_timers, metro.init(function(tick) pattern_next(v) end))
table.insert(pattern_banks, {})
table.insert(pattern_leds, 0)
table.insert(pattern_positions, 1)
end
params:add_separator("load samples")
local sep = ": "
--params:add_group("samples", VOICES)
for i = 1, VOICES do
params:add_file(i .. "sample", i .. sep .. "sample")
params:set_action(i .. "sample", function(file) engine.read(i, file) end)
end
params:add_separator("mangl params")
for v = 1, VOICES do
params:add_group("voice " .. v, 15)
params:add_option(v .. "play", "play", {"off","on"}, 1)
params:set_action(v .. "play", function(x) engine.gate(v, x-1) end)
params:add_taper(v .. "gain", "gain", -60, 20, -12, 0, "dB")
params:set_action(v .. "gain", function(value) engine.gain(v, math.pow(10, value / 20)) end)
params:add_control(v .. "pos", "pos", controlspec.new(0, 1, "lin", 0.001, 0))
params:set_action(v .. "pos", function(value) engine.seek(v, value) end)
params:add_taper(v .. "speed", "speed", -300, 300, 0, 0, "%")
params:set_action(v .. "speed", function(value) engine.speed(v, value / 100) end)
params:add_taper(v .. "jitter", "jitter", 0, 500, 0, 5, "ms")
params:set_action(v .. "jitter", function(value) engine.jitter(v, value / 1000) end)
params:add_taper(v .. "size", "size", 1, 500, 100, 5, "ms")
params:set_action(v .. "size", function(value) engine.size(v, value / 1000) end)
params:add_taper(v .. "density", "density", 0, 512, 20, 6, "hz")
params:set_action(v .. "density", function(value) engine.density(v, value) end)
params:add_control(v .. "density_mod_amt", "density mod amt", controlspec.new(0, 1, "lin", 0, 0))
params:set_action(v .. "density_mod_amt", function(value) engine.density_mod_amt(v, value) end)
params:add_taper(v .. "pitch", "pitch", -24, 24, 0, 0, "st")
params:set_action(v .. "pitch", function(value) engine.pitch(v, math.pow(0.5, -value / 12)) end)
params:add_taper(v.."pan", v..sep.."pan", -100, 100, 0, 0, "%")
params:set_action(v.."pan", function(value) engine.pan(v, value / 100) end)
params:add_taper(v .. "spread", "spread", 0, 100, 0, 0, "%")
params:set_action(v .. "spread", function(value) engine.spread(v, value / 100) end)
params:add_control(v .. "cutoff", "filter cutoff", controlspec.new(20, 20000, "exp", 0, 20000, "hz"))
params:set_action(v .. "cutoff", function(value) engine.cutoff(v, value) end)
params:add_control(v .. "q", "filter q", controlspec.new(0.00, 1.00, "lin", 0.01, 1))
params:set_action(v .. "q", function(value) engine.q(v, value) end)
params:add_control(v .. "send", "delay send", controlspec.new(0.0, 1.0, "lin", 0.01, .2))
params:set_action(v .. "send", function(value) engine.send(v, value) end)
params:add_taper(v .. "fade", "att / dec", 1, 9000, 1000, 3, "ms")
params:set_action(v .. "fade", function(value) engine.envscale(v, value / 1000) end)
end
params:add_group("delay", 8)
-- effect controls
-- delay time
params:add_control("delay_time", "*" .. "delay time", controlspec.new(0.0, 60.0, "lin", .01, 2.00, ""))
params:set_action("delay_time", function(value) engine.delay_time(value) end)
-- delay size
params:add_control("delay_size", "*" .. "delay size", controlspec.new(0.5, 5.0, "lin", 0.01, 2.00, ""))
params:set_action("delay_size", function(value) engine.delay_size(value) end)
-- dampening
params:add_control("delay_damp", "*" .. "delay damp", controlspec.new(0.0, 1.0, "lin", 0.01, 0.10, ""))
params:set_action("delay_damp", function(value) engine.delay_damp(value) end)
-- diffusion
params:add_control("delay_diff", "*" .. "delay diff", controlspec.new(0.0, 1.0, "lin", 0.01, 0.707, ""))
params:set_action("delay_diff", function(value) engine.delay_diff(value) end)
-- feedback
params:add_control("delay_fdbk", "*" .. "delay fdbk", controlspec.new(0.00, 1.0, "lin", 0.01, 0.20, ""))
params:set_action("delay_fdbk", function(value) engine.delay_fdbk(value) end)
-- mod depth
params:add_control("delay_mod_depth", "*" .. "delay mod depth", controlspec.new(0.0, 1.0, "lin", 0.01, 0.00, ""))
params:set_action("delay_mod_depth", function(value) engine.delay_mod_depth(value) end)
-- mod rate
params:add_control("delay_mod_freq", "*" .. "delay mod freq", controlspec.new(0.0, 10.0, "lin", 0.01, 0.10, "hz"))
params:set_action("delay_mod_freq", function(value) engine.delay_mod_freq(value) end)
-- delay output volume
params:add_control("delay_volume", "*" .. "delay output volume", controlspec.new(0.0, 1.0, "lin", 0, 1.0, ""))
params:set_action("delay_volume", function(value) engine.delay_volume(value) end)
-- for hnds
for i = 1, 4 do
lfo[i].lfo_targets = lfo_targets
end
params:add_group("lfo's", 28)
lfo.init()
-- arc sensitivty settings
params:add_separator("hardware config")
params:add_option("alt_behavior", "alt behavior", {"momentary", "toggle"}, 1)
params:add_number("speed_sens", "speed sens" .. sep, 1, 100, 10)
params:set_action("speed_sens", function(x) speed_sens = x end)
params:add_number("size_sens", "size sens" .. sep, 1, 100, 10)
params:set_action("size_sens", function(x) size_sens = x end)
params:add_number("density_sens", "density sens" .. sep, 1, 100, 10)
params:set_action("density_sens", function(x) density_sens = x end)
params:add_number("scrub_sens", "scrub sens" .. sep, 100, 1000, 500)
params:set_action("scrub_sens", function(x) scrub_sens = x end)
params:add_number("spread_sens", "spread sens" .. sep, 1, 100, 10)
params:set_action("spread_sens", function(x) spread_sens = x end)
params:add_number("jitter_sens", "jitter sens" .. sep, 1, 100, 10)
params:set_action("jitter_sens", function(x) jitter_sens = x end)
params:read()
params:bang()
-- grid refresh timer, 40 fps
metro_grid_refresh = metro.init(function(stage) grid_refresh() end, 1 / 40)
metro_grid_refresh:start()
metro_blink = metro.init(function(stage) blink = blink ~ 1 end, 1 / 4)
metro_blink:start()
-- arc redraw metro
local arc_redraw_timer = metro.init()
arc_redraw_timer.time = 0.025
arc_redraw_timer.event = function() arc_redraw() end
arc_redraw_timer:start()
-- norns redraw metro
local norns_redraw_timer = metro.init()
norns_redraw_timer.time = 0.025
norns_redraw_timer.event = function() redraw() end
norns_redraw_timer:start()
-- loop metro
local loop_timer = metro.init()
loop_timer.time = 0.005
loop_timer.event = function() loop_pos() end
loop_timer:start()
norns.enc.sens(3, 8)
norns.enc.accel(3, false)
end
-- norns
function key(n, z)
if n == 1 then
hold_track_speed(track)
if params:get("alt_behavior") == 1 then
alt = z == 1 and true or false
elseif z == 1 and params:get("alt_behavior") == 2 then
alt = not alt
end
end
if alt then
-- key 2 sets the loop_in and loop_out points
-- loop_in on press, loop_out on release
if n == 2 then
if z == 1 then
if loop_in[track] == nil then
if loops[track].dir == -1 then
loop_out[track] = positions[track]
else
loop_in[track] = positions[track]
end
end
else
if loops[track].dir == -1 then
loop_in[track] = positions[track]
positions[track] = loop_out[track]
engine.seek(track, loop_out[track])
loops[track].state = 1
else
loop_out[track] = positions[track]
positions[track] = loop_in[track]
engine.seek(track, loop_in[track])
loops[track].state = 1
end
end
-- key 3 clears the currently selected track
elseif n == 3 then
clear_loop(track)
end
else
if was_playing then
set_speed(track)
was_playing = false
end
-- key 2 activates and deactivates the currently selected voice
if n == 2 and z == 1 then
params:set(track .. "play", params:get(track .. "play") == 2 and 1 or 2)
-- key 3 sets speed to 0
elseif n == 3 and z == 1 then
params:set(track .. "speed", 0)
end
end
end
function enc(n, d)
if alt then
if n == 1 then
params:delta(track .. "cutoff", d)
elseif n == 2 then
params:delta(track .. "send", d)
end
else
if n == 1 then
params:delta(track .. "gain", d)
elseif n == 3 then
track = util.clamp(track + d, 1, 7)
end
end
last_enc = n
time_last_enc = util.time()
end
function redraw()
screen.aa(1)
screen.clear()
screen.move(123, 10)
screen.font_face(25)
screen.font_size(6)
screen.level(2)
if alt then
screen.text_right(params:get(track .. "send"))
else
if params:get(track .. "sample") == "-" then
screen.level(1)
screen.text_right("-")
else
screen.text_right(get_sample_name())
end
end
screen.move(64, 36)
screen.level(params:get(track .. "play") == 2 and 15 or 3)
screen.font_face(10)
screen.font_size(30)
screen.text_center(tracks[track])
screen.level(2)
screen.move(20, 10)
screen.font_face(25)
screen.font_size(6)
if alt then
screen.text_center(string.format("%.2f", params:get(track .. "cutoff")))
else
screen.text_center(string.format("%.2f", params:get(track .. "gain")))
end
screen.move(20, 50)
screen.font_size(6)
screen.font_face(25)
screen.level(2)
screen.text_center(alt and "scrub" or "speed")
screen.move(50, 50)
screen.text_center(alt and "fine" or "pitch")
screen.move(80, 50)
screen.text_center(alt and "spread" or "size")
screen.move(110, 50)
screen.text_center(alt and "jitter" or "density")
screen.level(params:get(track .. "play") == 2 and 15 or 3)
screen.move(20, 60)
screen.font_size(8)
screen.font_face(1)
screen.text_center(alt and "-" or string.format("%.2f", params:get(track .. "speed")))
screen.move(50, 60)
screen.text_center(string.format("%.2f", params:get(track .. "pitch")))
screen.move(80, 60)
screen.text_center(string.format("%.2f", alt and params:get(track .. "spread") or params:get(track .. "size")))
screen.move(110, 60)
screen.text_center(string.format("%.2f", alt and params:get(track .. "jitter") or params:get(track .. "density")))
if track == 3 then
screen.move(100, 36)
elseif track == 6 then
screen.move(84, 36)
elseif track == 7 then
screen.move(103, 36)
else
screen.move(90, 36)
end
screen.level(loops[track].state == 1 and 12 or 0)
screen.font_size(12)
screen.font_face(12)
screen.text("L")
if not latched[track] then
screen.level(12)
screen.font_face(12)
screen.font_size(8)
screen.move_rel(-6, -11)
screen.text("m")
end
screen.update()
end
-- arc ----------
function a.delta(n, d)
if alt then
if n == 1 then
scrub(track, d)
elseif n == 2 then
params:delta(track .. "pitch", d / 20)
elseif n == 3 then
params:delta(track .. "spread", d / spread_sens)
elseif n == 4 then
params:delta(track .. "jitter", d / jitter_sens)
end
else
if n == 1 then
params:delta(track .. "speed", d / speed_sens)
elseif n == 2 then
params:delta(track .. "pitch", d / 2)
elseif n == 3 then
params:delta(track .. "size", d / size_sens)
elseif n == 4 then
params:delta(track .. "density", d / density_sens)
end
end
end
function a.key(n, z)
-- for old push button arcs and 4e support
alt = z == 1 and true or false
if not alt and was_playing then
set_speed(track)
end
end
function arc_redraw()
a:all(0)
a:segment(1, positions[track] * tau, tau * positions[track] + 0.2, 15)
local pitch = params:get(track .. "pitch") / 10
if pitch > 0 then
a:segment(2, 0.5, 0.5 + pitch, 15)
else
a:segment(2, pitch - 0.5, -0.5, 15)
end
if alt == true then
local spread = params:get(track .. "spread") / 40
local jitter = params:get(track .. "jitter") / 80
a:segment(3, 0.5, 0.5 + spread, 15)
a:segment(4, 0.5, 0.5 + jitter, 15)
else
local size = params:get(track .. "size") / 80
local density = params:get(track .. "density") / 82
a:segment(3, 0.5, 0.5 + size, 15)
a:segment(4, 0.5, 0.5 + density, 15)
end
a:refresh()
end
-- grid ----------
function grid_key(x, y, z, skip_record)
if y > 1 or (y == 1 and x < 9) then
if not skip_record then
record_event(x, y, z)
end
end
-- track selection via grid press
if y >= 2 and not skip_record then
track = y - 1
if alt and z == 1 then
params:set(track .. "play", 2)
latched[track] = not latched[track]
else
if latched[track] == false then
params:set(track .. "play", z == 1 and 2 or 1)
end
end
end
if z > 0 then
-- set voice pos
if y > 1 then
local voice = y - 1
start_voice(voice, (x - 1) / 16)
params:set(voice .. "play", 2)
else
if x == 16 then
-- alt
alt = true
elseif x > 8 then
record_handler(x - 8)
elseif x == 8 then
-- reserved
elseif x < 8 then
local voice = x
if alt then
-- stop
stop_voice(voice)
params:set(voice .. "play", 1)
else
track = voice
end
end
end
else
-- alt
if x == 16 and y == 1 then alt = false end
end
end
function grid_refresh()
if g == nil then
return
end
grid_ctl:led_level_all(0)
grid_voc:led_level_all(0)
-- alt
grid_ctl:led_level_set(16, 1, alt and 15 or 2)
-- pattern banks
for i=1, VOICES do
local level = 4
if #pattern_banks[i] > 0 then level = 8 end
if pattern_timers[i].is_running then
level = 12
if pattern_leds[i] > 0 then
level = 12
end
end
grid_ctl:led_level_set(8 + i, 1, level)
end
-- blink armed pattern
if record_bank > 0 then
grid_ctl:led_level_set(8 + record_bank, 1, 15 * blink)
end
-- voices
for i=1, VOICES do
if i == track then
grid_ctl:led_level_set(i, 1, 4)
else
grid_ctl:led_level_set(i, 1, 2)
end
if voice_levels[i] > 0 then
grid_ctl:led_level_set(i, 1, math.min(math.ceil(voice_levels[i] * 15), 15))
grid_voc:led_level_row(1, i + 1, display_voice(positions[i], 16))
end
end
local buf = grid_ctl | grid_voc
buf:render(g)
g:refresh()
end
function cleanup()
poll.clear_all()
end