-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathmultisequencer.h
461 lines (427 loc) · 15.7 KB
/
multisequencer.h
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
/**
* multisequencer.h -- Multitrack Sequencer (for Feather M4 Express)
* Part of https://github.com/PatchworkBoy/Neotrellis-Gate-Sequencer
* 04 Nov 2023 - @apatchworkboy / Marci
* Based on https://github.com/todbot/picostepseq/
* 28 Apr 2023 - @todbot / Tod Kurt
* 15 Aug 2022 - @todbot / Tod Kurt
*/
#ifndef MULTI_SEQUENCER_
#define MULTI_SEQUENCER_
// typedefs
typedef enum {
TRIGATE = 0,
CC = 1,
NOTE = 2,
ARP = 3,
CHORD = 4,
} track_mode;
typedef enum {
NONE,
START,
STOP,
CLOCK,
} clock_type_t;
typedef enum {
QUARTER_NOTE = 24,
EIGHTH_NOTE = 12,
SIXTEENTH_NOTE = 6,
} valid_ticks_per_step;
const int ticks_per_quarternote = 24;
const int midi_clock_divider = 1;
const int steps_per_beat_default = 6;
const int valid_step_sizes[] = { QUARTER_NOTE, EIGHTH_NOTE, SIXTEENTH_NOTE };
const int valid_step_sizes_cnt = 3;
const uint8_t numarps = numtracks / 2;
typedef void (*TriggerFunc)(uint8_t note, uint8_t vel, uint8_t gate, bool on, uint8_t chan);
typedef void (*CCFunc)(uint8_t cc, uint8_t val, bool on, uint8_t chan);
typedef void (*ClockFunc)(clock_type_t type); // , int pos);
typedef void (*PosFunc)(int beat);
typedef void (*DispFunc)();
typedef void (*ResetFunc)();
typedef void (*GateFunc)(uint8_t pin, uint8_t direction);
typedef void (*CVFunc)(uint8_t pin, uint16_t val);
// stubs for when Sequencer object is only partially initialized
void fake_updatedisplay_callback() {}
void fake_resetdisplay_callback() {}
void fake_clock_callback(clock_type_t type) {}
void fake_note_callback(uint8_t note, uint8_t vel, uint8_t gate, bool on, uint8_t chan) {}
void fake_cc_callback(uint8_t cc, uint8_t val, bool on, uint8_t chan) {}
void fake_pos_callback(int pos) {}
void fake_gate_callback(uint8_t pin, uint8_t direction) {}
void fake_cv_callback(uint8_t pin, uint16_t val) {}
#include "arp.h"
byte arp_patterns[numarps];
byte arp_octaves[numarps];
Arp<10> arps[numarps];
template<uint8_t tracks = 1, uint8_t _presets = 1, uint8_t _steps = 1, uint8_t _dacs = 1, uint8_t _arps = 1>
class MultiStepSequencer {
public:
uint32_t tick_micros; // "micros_per_tick", microsecs per clock (6 clocks / step; 4 steps / quarternote)
uint32_t last_tick_micros; // only change in update()
uint32_t extclk_micros; // 0 = internal clock, non-zero = external clock
uint32_t held_gate_millis[tracks];
uint32_t held_gate_notes[tracks];
uint32_t held_gate_chans[tracks];
short int multistepi[tracks];
int outcomes[tracks];
int lengths[tracks];
int offsets[tracks];
int ticks_per_step; // expected values: 6 = 1/16th, 12 = 1/8, 24 = 1/4,
short int ticki; // which midi clock we're on
short int stepi; // which sequencer step we're on
int seqno;
int length;
int transpose;
short int divcounts[tracks];
uint8_t divs[tracks];
uint8_t gates[_presets][tracks][_steps];
uint8_t notes[_presets][tracks][_steps];
uint8_t presets[_presets];
uint8_t probs[_presets][tracks][_steps];
uint8_t vels[_presets][tracks][_steps];
short int laststeps[tracks];
uint8_t track_notes[tracks]; // C2 thru G2
uint8_t ctrl_notes[3];
uint8_t track_chan[tracks];
uint16_t lastdac[_dacs];
uint8_t tick_pc;
uint8_t ctrl_chan = 16;
uint8_t laststep;
uint8_t swing;
short int pos;
bool analog_io;
bool mutes[tracks];
bool seqs[_presets][tracks][_steps];
bool pulse;
bool playing;
bool send_clock;
bool resetflag;
track_mode modes[tracks] = { TRIGATE };
uint8_t Tracks() { return tracks; }
uint8_t Presets() { return _presets; }
uint8_t Steps() { return _steps; }
uint8_t Dacs() { return _dacs; }
uint8_t Arps() { return _arps; }
TriggerFunc on_func;
TriggerFunc off_func;
CCFunc cc_func;
ClockFunc clk_func;
PosFunc pos_func;
DispFunc disp_func;
ResetFunc reset_func;
GateFunc gate_func;
CVFunc cv_func;
MultiStepSequencer(float atempo = 120, uint8_t aseqno = 0) {
transpose = 0;
last_tick_micros = 0;
resetflag = 0;
stepi = 0;
ticki = 0;
ticks_per_step = 12; // 12 = 1/16th, 24 = 1/8, 48 = 1/4,
seqno = aseqno;
length = _steps;
playing = false;
extclk_micros = 0;
send_clock = false;
analog_io = false;
set_tempo(atempo);
on_func = fake_note_callback;
off_func = fake_note_callback;
cc_func = fake_cc_callback;
clk_func = fake_clock_callback;
pos_func = fake_pos_callback;
disp_func = fake_updatedisplay_callback;
reset_func = fake_resetdisplay_callback;
gate_func = fake_gate_callback;
cv_func = fake_cv_callback;
}
// get tempo as floating point, computed dynamically from ticks_micros
float tempo() {
return 60 * 1000 * 1000 / tick_micros / ticks_per_quarternote; //steps_per_beat * ticks_per_step);
}
// set tempo as floating point, computes ticks_micros
void set_tempo(float bpm) {
tick_micros = 60 * 1000 * 1000 / bpm / ticks_per_quarternote;
tick_pc = tick_micros / 100;
}
void update() {
uint8_t sw = length > 1 ? swing : 0;
uint32_t now_micros = micros();
if ((stepi % 2 ? (now_micros - last_tick_micros) < (tick_micros - (tick_pc * sw)) : (now_micros - last_tick_micros) < (tick_micros + (tick_pc * sw)))) {
return;
} // not yet, with Swing!
last_tick_micros = now_micros;
// if we have a held note and it's time to turn it off, turn it off
for (uint8_t i = 0; i < tracks; ++i) {
if (held_gate_millis[i] != 0 && millis() >= held_gate_millis[i]) {
held_gate_millis[i] = 0;
switch (modes[i]) {
case ARP:
if (i >= _arps) {
off_func(held_gate_notes[i], 0, 1, true, held_gate_chans[i]);
if (analog_io) gate_func(gatepins[i], 0);
}
break;
case TRIGATE:
off_func(track_notes[i] + transpose, 0, 1, true, track_chan[i]);
if (analog_io) gate_func(gatepins[i], 0);
break;
case CC:
if (analog_io) gate_func(gatepins[i], 0);
break;
case NOTE:
off_func(held_gate_notes[i], 0, 1, true, held_gate_chans[i]);
if (analog_io) gate_func(gatepins[i], 0);
held_gate_notes[i] = 0;
held_gate_chans[i] = 0;
break;
default: break;
}
}
}
if (send_clock && playing && !extclk_micros) {
clk_func(CLOCK);
}
if (ticki == 0) {
// do a sequence step (i.e. every "ticks_per_step" ticks)
if (extclk_micros) {
// do nothing, let midi clock trigger notes, but fall back to
// internal clock if not externally clocked for a while
if ((now_micros - extclk_micros) > tick_micros * ticks_per_quarternote) {
extclk_micros = 0;
Serial.println("Turning EXT CLOCK off");
}
} else { // else internally clocked
trigger(now_micros); // delta_t);
}
} else {
if (ticki % ticks_per_step/2) trellis.read();
}
// increment our ticks-per-step counter: 0,1,2,3,4,5, 0,1,2,3,4,5, ...
ticki = (ticki + 1) % ticks_per_step;
}
// Trigger step when externally clocked (turns on external clock flag)
void trigger_ext(uint32_t now_micros) {
extclk_micros = now_micros;
trigger(now_micros);
}
// Trigger step in sequence, when internally clocked
void trigger(uint32_t now_micros) {
if (!playing) {
disp_func();
return;
}
if (resetflag == 1) {
resetflag = 0;
reset();
}
// Base sequencer
pulse = pulse == 0 ? 1 : 0;
stepi = (stepi + 1) % length; // go to next step
if (stepi % steps_per_beat_default) {
pos = pos + 1;
pos_func(pos);
}
laststep = stepi - 1 < 0 ? length - 1 : stepi - 1;
if (analog_io) {
cv_func(cvpins[0], lastdac[0]);
cv_func(cvpins[1], lastdac[1]);
}
uint8_t trk_arr = sel_track - 1;
uint32_t micros_per_step = ticks_per_step * tick_micros;
uint32_t gate_micros;
for (uint8_t i = 0; i < tracks; ++i) {
divcounts[i] = divcounts[i] == divs[i] ? 0 : divcounts[i] + 1;
if (divcounts[i] == 0) {
//decouple per-track step counters from main sequencer
uint8_t nstep = (multistepi[i] + 1) > lengths[i] - 1 ? 0 + (offsets[i] - 1 < 0 ? 0 : offsets[i] - 1) : (multistepi[i] + 1); // go to next step
uint8_t lstep = nstep > offsets[i] ? (nstep - 1 < 0 + (offsets[i] - 1 < 0 ? 0 : offsets[i] - 1) ? lengths[i] - 1 : nstep - 1) : (nstep - 1 < 0 ? lengths[i] - 1 : nstep - 1);
laststeps[i] = lstep;
multistepi[i] = nstep;
if (probs[presets[i]][i][multistepi[i]] < 10) {
outcomes[i] = random(10) <= (probs[presets[i]][i][multistepi[i]]);
} else {
outcomes[i] = 1;
}
gate_micros = (gates[presets[i]][i][multistepi[i]] * micros_per_step / 16) * (divs[i] + 1);
switch (modes[i]) {
case ARP:
if (seqs[presets[i]][i][multistepi[i]] == 1 && mutes[i] == 0 ? outcomes[i] : false) {
uint8_t n;
if (i >= _arps) {
uint8_t arp_id = i - _arps;
n = arps[arp_id].process(arp_patterns[arp_id], arp_octaves[arp_id]); // pattern (1-7), octaves(1-4)
if (n != 0) {
if (marci_debug) {
Serial.print("ArpNote: ");
Serial.println(n);
}
held_gate_millis[i] = (now_micros + gate_micros) / 1000;
held_gate_notes[i] = n;
held_gate_chans[i] = track_chan[i];
if (analog_io && i >= (tracks - _dacs) && mutes[i] == 0) {
// CV Output for track 7 & 8 on A0 & A1
if (hzv[i - (tracks - _dacs)] == 1) {
// MS20 / K2 hz/v output
float val = constrain(map(125.0 * exp(0.0578 * ((n % 36) - 5)), 0, 5000, 0, dacrange), 0, dacrange);
if (val > 0) {
lastdac[i - (tracks - _dacs)] = val;
cv_func(cvpins[i - 6], val);
}
} else {
// Plain old v/oct
float val = constrain(map(n % 36, 0, 36, 0, 3708), 0, dacrange);
if (val > 0) {
lastdac[i - (tracks - _dacs)] = val;
cv_func(cvpins[i - 6], val);
}
}
}
if (analog_io) gate_func(gatepins[i], 1);
on_func(n, vels[presets[i]][i][multistepi[i]], gates[presets[i]][i][multistepi[i]], true, track_chan[i]);
}
}
}
break;
case TRIGATE:
if (seqs[presets[i]][i][multistepi[i]] == 1 && mutes[i] == 0 ? outcomes[i] : false) {
held_gate_millis[i] = (now_micros + gate_micros) / 1000;
if (analog_io) gate_func(gatepins[i], 1);
on_func(track_notes[i] + transpose, vels[presets[i]][i][multistepi[i]], gates[presets[i]][i][multistepi[i]], true, track_chan[i]);
}
break;
case CC:
if (seqs[presets[i]][i][multistepi[i]] == 1 && mutes[i] == 0 ? outcomes[i] : false) {
held_gate_millis[i] = (now_micros + gate_micros) / 1000;
if (analog_io && i >= (tracks - _dacs) && mutes[i] == 0) {
// CV Output for track 7 & 8 on A0 & A1
if (hzv[i - (tracks - _dacs)] == 1) {
// MS20 / K2 hz/v output
float val = constrain(map(125.0 * exp(0.0578 * ((vels[presets[i]][i][multistepi[i]] % 36) - 5)), 0, 5000, 0, dacrange), 0, dacrange);
if (val > 0) {
lastdac[i - (tracks - _dacs)] = val;
cv_func(cvpins[i - 6], val);
}
} else {
// Plain old v/oct
float val = constrain(map(vels[presets[i]][i][multistepi[i]] % 36, 0, 36, 0, 3708), 0, dacrange);
if (val > 0) {
lastdac[i - (tracks - _dacs)] = val;
cv_func(cvpins[i - 6], val);
}
}
}
if (analog_io) gate_func(gatepins[i], 1);
cc_func(track_notes[i], vels[presets[i]][i][multistepi[trk_arr]], true, track_chan[i]);
}
break;
case NOTE:
if (seqs[presets[i]][i][multistepi[i]] == 1 && mutes[i] == 0 ? outcomes[i] : false) {
held_gate_millis[i] = (now_micros + gate_micros) / 1000;
held_gate_notes[i] = notes[presets[i]][i][multistepi[i]] + transpose;
held_gate_chans[i] = track_chan[i];
if (analog_io && i >= (tracks - _dacs) && mutes[i] == 0) {
// CV Output for track 7 & 8 on A0 & A1
if (hzv[i - (tracks - _dacs)] == 1) {
// MS20 / K2 hz/v output
float val = constrain(map(125.0 * exp(0.0578 * ((notes[presets[i]][i][multistepi[i]] % 36) - 5)), 0, 5000, 0, dacrange), 0, dacrange);
if (val > 0) {
lastdac[i - (tracks - _dacs)] = val;
cv_func(cvpins[i - 6], val);
}
} else {
// Plain old v/oct
float val = constrain(map(notes[presets[i]][i][multistepi[i]] % 36, 0, 36, 0, 3708), 0, dacrange);
if (val > 0) {
lastdac[i - (tracks - _dacs)] = val;
cv_func(cvpins[i - 6], val);
}
}
}
if (analog_io) gate_func(gatepins[i], 1);
on_func(notes[presets[i]][i][multistepi[i]] + transpose, vels[presets[i]][i][multistepi[i]], gates[presets[i]][i][multistepi[i]], true, track_chan[i]);
}
break;
default: break;
}
}
}
disp_func();
}
void ctrl_stop() {
off_func(ctrl_notes[0] + transpose, 127, 5, true, ctrl_chan);
off_func(ctrl_notes[1] + transpose, 127, 5, true, ctrl_chan);
off_func(ctrl_notes[2] + transpose, 127, 5, true, ctrl_chan);
}
void toggle_play_stop() {
if (playing) {
stop();
} else {
play();
}
}
// signal to sequencer/MIDI core we want to start playing
void play() {
playing = true;
if (send_clock && !extclk_micros) {
clk_func(START);
}
on_func(ctrl_notes[0], 127, 5, true, ctrl_chan);
ctrl_stop();
}
// signal to sequencer/MIDI core we want to stop playing
void stop() {
if (playing) {
playing = false;
if (send_clock && !extclk_micros) {
clk_func(STOP);
}
for (uint8_t i = 0; i < tracks; ++i) {
switch (modes[i]) {
case TRIGATE:
off_func(track_notes[i] + transpose, 0, 1, true, track_chan[i]);
break;
case CC:
break;
case NOTE:
for (uint8_t k = 0; k < _steps; ++k) {
off_func(notes[presets[i]][i][k] + transpose, 0, 0, true, track_chan[i]);
}
break;
default: break;
}
if (analog_io) gate_func(gatepins[i], 0);
}
on_func(ctrl_notes[1], 127, 5, true, ctrl_chan);
ctrl_stop();
reset_func();
} else {
ticki = 0;
reset();
reset_func();
}
}
void reset() {
stepi = -1;
pos = -1;
if (!playing) {
resetflag = 1;
//disp_func();
} else {
ticki = 0;
}
for (uint8_t s = 0; s < numtracks; ++s) {
multistepi[s] = -1;
divcounts[s] = -1;
laststeps[s] = -1;
if (s <= _arps) {
// FIXME: setting this to -1 causes hard crash when saving. I have NO idea why... even calling a seperate function to specifically set it to 0 before save doesn't work. *shrug*
arps[s]._step = 0;
}
}
on_func(ctrl_notes[2], 127, 5, true, ctrl_chan);
ctrl_stop();
}
};
#endif