forked from rakyll/launchpad
-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathsequencer.go
542 lines (494 loc) · 12.8 KB
/
sequencer.go
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
package launchpad
import (
"context"
"fmt"
"io"
"os"
"time"
"github.com/pkg/errors"
"github.com/scgolang/psync"
)
const (
gridX = 8
gridY = 8
gridSize = gridX * gridY
)
// Colors used to light the pads.
// We use a different color for showing sequencer position versus
// showing which steps are turned on in a pattern.
var (
posColor = Color{Green: Medium}
stepColor = Color{Red: Full}
)
// Mode is a
type Mode int
const (
ModePattern Mode = iota
ModeMutes
)
// Trig is a sequencer trigger.
// It provides the track that is being triggered as well
// as the value of the sequencer for that track.
type Trig struct {
Track uint8
Value uint8
}
// Trigger is a thing that can be triggered by a Sequencer.
// Use the Track method to get notified of track selection.
type Trigger interface {
Track(track uint8) error
Trig(step uint8, trigs []Trig) error
}
// Sequencer is a simple sequencer controlled by a Novation Launchpad.
type Sequencer struct {
Sync psync.Synchronizer
modeChan chan Mode
mutes [gridSize]bool
pad *Launchpad
prevStep uint8
resetChan chan struct{}
step uint8
stepSkip int
syncHost string
tick chan psync.Pulse
track uint8
tracks [gridSize][gridSize]uint8 // Track => Step => Value
triggers []Trigger
}
// NewSequencer creates a new sequencer.
func (l *Launchpad) NewSequencer(synchronizer psync.Synchronizer) *Sequencer {
return &Sequencer{
Sync: synchronizer,
modeChan: make(chan Mode, 1),
pad: l,
resetChan: make(chan struct{}, 1),
tick: make(chan psync.Pulse),
}
}
// AddTrigger adds the Trigger to the sequencer.
func (seq *Sequencer) AddTrigger(t Trigger) {
seq.triggers = append(seq.triggers, t)
}
// advance advances the internal counter of the sequencer.
// It returns the true if the sequencer's internal counter has actually advanced
// and false otherwise.
func (seq *Sequencer) advance(step int32) bool {
if seq.stepSkip <= 1 {
// step increments the sequencer's counter directly.
seq.prevStep = seq.step
seq.step = uint8(step % gridSize)
return true
}
// This step has no effect.
if step%int32(seq.stepSkip) != 0 {
return false
}
// step does not increment the sequencer's counter directly
// we increment it based on the previous value
seq.prevStep = seq.step
seq.step = uint8((seq.step + 1) % gridSize)
return true
}
// advanceLights advances the lights on the launchpad according to the
// internal counter of the sequencer.
func (seq *Sequencer) advanceLights() error {
var (
prevValue = seq.tracks[seq.track][seq.prevStep]
prevHit = stepToHit(seq.prevStep)
hit = stepToHit(seq.step)
)
if seq.prevStep == seq.step {
// We just started the sequencer and the first pulse
// is the beginning of the sequence.
return seq.pad.Light(0, 0, posColor)
}
if err := seq.pad.Light(hit.X, hit.Y, posColor); err != nil {
return err
}
if prevValue == 0 {
if err := seq.pad.Light(prevHit.X, prevHit.Y, Color{}); err != nil {
return err
}
} else {
if err := seq.pad.Light(prevHit.X, prevHit.Y, stepColor); err != nil {
return err
}
}
return nil
}
// flashStepTriggers flashes the tracks that are triggered for the current step.
func (seq *Sequencer) flashStepTriggers() error {
hits := []Hit{}
for track, steps := range seq.tracks {
if seq.mutes[track] {
continue
}
if val := steps[seq.step]; val > 0 {
hit := stepToHit(uint8(track))
hits = append(hits, hit)
if err := seq.pad.Light(hit.X, hit.Y, posColor); err != nil {
return err
}
}
}
time.Sleep(40 * time.Millisecond)
for _, hit := range hits {
if err := seq.pad.Light(hit.X, hit.Y, Color{}); err != nil {
return err
}
}
return nil
}
// handleMutesPulse handles a pulse while in mutes mode.
func (seq *Sequencer) handleMutesPulse(count int32) error {
if advanced := seq.advance(count); !advanced {
return nil
}
// Flash all the triggered tracks.
if err := seq.flashStepTriggers(); err != nil {
return err
}
return seq.invokeTriggers()
}
// handleMutesTrackHit handles a hit on the track buttons while in mutes mode.
func (seq *Sequencer) handleMutesHit(hit Hit) error {
if hit.Err != nil {
return hit.Err
}
if hit.X == gridX || hit.Y == gridY {
if err := seq.setCurrentTrackFrom(hit); err != nil {
return err
}
if err := seq.pad.Reset(); err != nil {
return err
}
if err := seq.lightCurrentTrack(); err != nil {
return err
}
if err := seq.lightMutes(); err != nil {
return err
}
return seq.invokeTriggersTrack()
}
return seq.toggleMuteFrom(hit)
}
// handlePatternHit handles a hit while in pattern mode.
func (seq *Sequencer) handlePatternHit(hit Hit) error {
if hit.Err != nil {
return hit.Err
}
if hit.X == gridX || hit.Y == gridY {
if err := seq.setCurrentTrackFrom(hit); err != nil {
return err
}
if err := seq.selectPatternTrackFrom(hit); err != nil {
return err
}
return seq.invokeTriggersTrack()
}
return seq.toggle(hit)
}
// handlePatternPulse handles a pulse while in pattern mode.
func (seq *Sequencer) handlePatternPulse(count int32) error {
if advanced := seq.advance(count); !advanced {
return nil
}
if err := seq.advanceLights(); err != nil {
return err
}
return seq.invokeTriggers()
}
// invokeTriggers invokes the sequencer's triggers for the provided step.
func (seq *Sequencer) invokeTriggers() error {
trigs := []Trig{}
for track, steps := range seq.tracks {
if seq.mutes[track] {
continue
}
if val := steps[seq.step]; val > 0 {
trigs = append(trigs, Trig{
Track: uint8(track),
Value: val,
})
}
}
for _, trigger := range seq.triggers {
if err := trigger.Trig(uint8(seq.step), trigs); err != nil {
return err
}
}
return nil
}
// invokeTriggersTrack invokes the Track method of all the Trigger's
// that have been added to the sequencer.
func (seq *Sequencer) invokeTriggersTrack() error {
// Invoke all the trigs.
for _, trig := range seq.triggers {
if err := trig.Track(seq.track); err != nil {
return err
}
}
return nil
}
// lightCurrentTrack lights the track buttons based on the currently selected track.
func (seq *Sequencer) lightCurrentTrack() error {
var (
curX = seq.track % gridX
curY = seq.track / gridY
)
if err := seq.pad.Light(curX, gridY, stepColor); err != nil {
return err
}
return seq.pad.Light(gridX, curY, stepColor)
}
// lightMutes lights the mutes.
func (seq *Sequencer) lightMutes() error {
for track, isMuted := range seq.mutes {
if isMuted {
hit := stepToHit(uint8(track))
if err := seq.pad.Light(hit.X, hit.Y, stepColor); err != nil {
return err
}
}
}
return nil
}
// lightTrackSteps lights all the steps of the current track.
func (seq *Sequencer) lightTrackSteps() error {
for step, val := range seq.tracks[seq.track] {
hit := stepToHit(uint8(step))
if val > 0 {
if err := seq.pad.Light(hit.X, hit.Y, stepColor); err != nil {
return err
}
continue
}
if err := seq.pad.Light(hit.X, hit.Y, Color{}); err != nil {
return err
}
}
return nil
}
// loopMutes is an infinite loop that the sequencer uses when it is in "Mutes" mode.
// If the mode is changed then it will be returned with a nil error.
// The only other time this func returns is when there is an error.
func (seq *Sequencer) loopMutes(ctx context.Context, hits <-chan Hit) (Mode, error) {
if err := seq.lightMutes(); err != nil {
return 0, err
}
for {
select {
case <-ctx.Done():
return 0, ctx.Err()
case hit := <-hits:
if err := seq.handleMutesHit(hit); err != nil {
return 0, err
}
case <-seq.resetChan:
if err := seq.reset(); err != nil {
return 0, err
}
case mode := <-seq.modeChan:
return mode, nil
case pulse := <-seq.tick:
if err := seq.handleMutesPulse(pulse.Count); err != nil {
return 0, err
}
}
}
}
// loopPattern is an infinite loop that the sequencer uses when it is in "Pattern" mode.
// If the mode is changed then it will be returned with a nil error.
// The only other time this func returns is when there is an error.
func (seq *Sequencer) loopPattern(ctx context.Context, hits <-chan Hit) (Mode, error) {
if err := seq.lightTrackSteps(); err != nil {
return 0, err
}
for {
select {
case <-ctx.Done():
return 0, ctx.Err()
case hit := <-hits:
if err := seq.handlePatternHit(hit); err != nil {
return 0, err
}
case mode := <-seq.modeChan:
return mode, nil
case <-seq.resetChan:
if err := seq.reset(); err != nil {
return 0, err
}
case pulse := <-seq.tick:
if err := seq.handlePatternPulse(pulse.Count); err != nil {
return 0, err
}
}
}
}
// Main is the main loop of the sequencer.
// It loops forever on input from the launchpad.
// If ctx is cancelled it returns the ctx.Err().
func (seq *Sequencer) Main(ctx context.Context) error {
hits, err := seq.pad.Hits()
if err != nil {
return err
}
// This func could block forever.
go func() {
ctx, cancel := context.WithCancel(ctx)
if err := seq.Sync.Synchronize(ctx, seq); err != nil {
cancel()
fmt.Fprintf(os.Stderr, "connecting to sync source: %s", err.Error())
}
}()
if err := seq.lightCurrentTrack(); err != nil {
return err
}
loop := seq.loopPattern
for {
if err := seq.pad.Reset(); err != nil {
return err
}
if err := seq.lightCurrentTrack(); err != nil {
return err
}
mode, err := loop(ctx, hits)
if err != nil {
return err
}
switch mode {
case ModePattern:
loop = seq.loopPattern
case ModeMutes:
loop = seq.loopMutes
default:
return errors.Errorf("unrecognized mode: %d", mode)
}
}
}
// Pulse receives pulses from oscsync.
func (seq *Sequencer) Pulse(pulse psync.Pulse) error {
seq.tick <- pulse
return nil
}
// ReadFrom reads the sequencer's state from an io.Reader.
// TODO
func (seq *Sequencer) ReadFrom(r io.Reader) (int64, error) {
return 0, nil
}
// Reset resets the state of the sequencer.
func (seq *Sequencer) Reset() error {
seq.resetChan <- struct{}{}
return nil
}
// reset **actually** resets the state of the sequencer.
func (seq *Sequencer) reset() error {
// Reset pattern data.
for track, steps := range seq.tracks {
for step := range steps {
seq.tracks[track][step] = 0
}
}
// Reset mutes data.
for track := range seq.mutes {
seq.mutes[track] = false
}
if err := seq.pad.Reset(); err != nil {
return err
}
return seq.lightCurrentTrack()
}
// selectPatternTrackFrom selects a track from the provided hit.
// This func is used in pattern mode.
func (seq *Sequencer) selectPatternTrackFrom(hit Hit) error {
// Reset the launchpad.
if err := seq.pad.Reset(); err != nil {
return errors.Wrap(err, "resetting launchpad")
}
// Light the current track.
if err := seq.lightCurrentTrack(); err != nil {
return err
}
// Light all the steps of the current track.
return seq.lightTrackSteps()
}
func (seq *Sequencer) setCurrentTrackFrom(hit Hit) error {
if hit.Y == gridY {
// We hit the top row.
curX := seq.track % gridX
if curX == hit.X {
return nil // Nothing to do.
}
// Set the current track.
seq.track = hit.X + seq.track - curX
} else if hit.X == gridX {
// Hit the column on the right side of the device.
curY := seq.track / gridY
if curY == hit.Y {
return nil // Nothing to do.
}
// Set the current track.
seq.track = (hit.Y * gridY) + (seq.track % gridX)
} else {
return errors.New("hit is not for track selection")
}
return nil
}
// SetMode sets the display mode of the sequencer.
func (seq *Sequencer) SetMode(mode Mode) error {
seq.modeChan <- mode
return nil
}
// SetResolution sets the clock resolution for the sequencer.
// This is set as a human-readable note resolution, e.g. 16th or 32nd.
func (seq *Sequencer) SetResolution(resolution string) error {
res, ok := resolutionMap[resolution]
if !ok {
return errors.Errorf("unrecognized resolution: %s", resolution)
}
seq.stepSkip = res
return nil
}
// toggle toggles the button that has been hit.
func (seq *Sequencer) toggle(hit Hit) error {
var (
step = hitToStep(hit)
val = seq.tracks[seq.track][step]
)
if val == 0 {
seq.tracks[seq.track][step] = 1
return seq.pad.Light(hit.X, hit.Y, stepColor)
}
seq.tracks[seq.track][step] = 0
return seq.pad.Light(hit.X, hit.Y, Color{})
}
// toggleMuteFrom toggles the state of a mute from a hit on the launchpad.
func (seq *Sequencer) toggleMuteFrom(hit Hit) error {
track := hitToStep(hit)
if seq.mutes[track] {
seq.mutes[track] = false
return seq.pad.Light(hit.X, hit.Y, Color{})
}
seq.mutes[track] = true
return seq.pad.Light(hit.X, hit.Y, stepColor)
}
// WriteTo writes the current sequencer data to w.
// TODO
func (seq *Sequencer) WriteTo(w io.Writer) (int64, error) {
return 0, nil
}
func hitToStep(hit Hit) uint8 {
return (8 * hit.Y) + hit.X
}
func stepToHit(step uint8) Hit {
return Hit{
X: step % gridX,
Y: step / 8,
}
}
var resolutionMap = map[string]int{
"16th": 6,
"32nd": 3,
"96th": 1,
}
type loopFunc func(ctx context.Context, hits <-chan Hit) error