@yam8d/m8-sdk 0.4.0 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +898 -796
- package/dist/index.js +3411 -363
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/types/client.d.ts +58 -2
- package/types/engine/astar.d.ts +15 -0
- package/types/engine/audio.d.ts +29 -0
- package/types/engine/bus.d.ts +13 -0
- package/types/engine/connectWithRetry.d.ts +9 -0
- package/types/engine/connection.d.ts +56 -0
- package/types/engine/cursorNavigator.d.ts +15 -0
- package/types/engine/engine.d.ts +30 -0
- package/types/engine/eventBus.d.ts +5 -0
- package/types/engine/host/fileBrowserHelpers.d.ts +59 -0
- package/types/engine/host/fileBrowserSearch.d.ts +13 -0
- package/types/engine/host/floatEditor.d.ts +2 -0
- package/types/engine/host/hostIO.d.ts +30 -0
- package/types/engine/host/hostMethods.d.ts +22 -0
- package/types/engine/host/hostState.d.ts +7 -0
- package/types/engine/host/keySequences.d.ts +2 -0
- package/types/engine/host/midiTransport.d.ts +10 -0
- package/types/engine/host/valueEditors.d.ts +7 -0
- package/types/engine/host/valueParsers.d.ts +24 -0
- package/types/engine/index.d.ts +14 -0
- package/types/engine/keys.d.ts +28 -0
- package/types/engine/macroRunner.d.ts +15 -0
- package/types/engine/protocol.d.ts +138 -0
- package/types/engine/queuedReaders.d.ts +18 -0
- package/types/engine/store.d.ts +65 -0
- package/types/engine/viewExtractor.d.ts +27 -0
- package/types/engine/viewGraph.d.ts +9 -0
- package/types/engine/viewNavigation.d.ts +16 -0
- package/types/index.d.ts +9 -3
- package/types/scaleInsight.d.ts +1 -1
- package/types/theme.d.ts +42 -0
- package/types/types.d.ts +124 -0
- package/types/viewContext.d.ts +37 -0
package/README.md
CHANGED
|
@@ -1,796 +1,898 @@
|
|
|
1
|
-
# @yam8d/m8-sdk
|
|
2
|
-
|
|
3
|
-
Client SDK for
|
|
4
|
-
|
|
5
|
-
> **License:** MIT — © kronsilds
|
|
6
|
-
|
|
7
|
-
---
|
|
8
|
-
|
|
9
|
-
## Table of contents
|
|
10
|
-
|
|
11
|
-
- [Install](#install)
|
|
12
|
-
- [Quick start](#quick-start)
|
|
13
|
-
- [Factory functions](#factory-functions)
|
|
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
|
-
```ts
|
|
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
|
-
```ts
|
|
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
|
-
|
|
1
|
+
# @yam8d/m8-sdk
|
|
2
|
+
|
|
3
|
+
Client SDK for apps that read and drive a Dirtywave M8 tracker. Inside a [yam8d](https://github.com/mio-moto/yam8d) External Apps iframe it talks to yam8d; anywhere else it connects to the M8 itself over Web Serial, WebUSB or Web MIDI ([standalone mode](#standalone-mode)). The API is the same in both modes.
|
|
4
|
+
|
|
5
|
+
> **License:** MIT — © kronsilds
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## Table of contents
|
|
10
|
+
|
|
11
|
+
- [Install](#install)
|
|
12
|
+
- [Quick start](#quick-start)
|
|
13
|
+
- [Factory functions](#factory-functions)
|
|
14
|
+
- [Standalone mode](#standalone-mode)
|
|
15
|
+
- [M8Client](#m8client)
|
|
16
|
+
- [Properties](#properties)
|
|
17
|
+
- [Navigation](#navigation)
|
|
18
|
+
- [Value setters](#value-setters)
|
|
19
|
+
- [Key input](#key-input)
|
|
20
|
+
- [State](#state)
|
|
21
|
+
- [Event subscriptions](#event-subscriptions)
|
|
22
|
+
- [Semantic context](#semantic-context-on-the-client)
|
|
23
|
+
- [Device connection (standalone)](#device-connection-standalone)
|
|
24
|
+
- [Lifecycle](#lifecycle)
|
|
25
|
+
- [Standalone semantic context functions](#standalone-semantic-context-functions)
|
|
26
|
+
- [Types reference](#types-reference)
|
|
27
|
+
- [M8State](#m8state)
|
|
28
|
+
- [CursorPos / CursorRect / RGB / SystemInfos](#cursorpos--cursorrect--rgb--systeminfos)
|
|
29
|
+
- [M8SemanticContext](#m8semanticcontext)
|
|
30
|
+
- [M8DescriptionPart](#m8descriptionpart)
|
|
31
|
+
- [M8ParsedRow](#m8parsedrow)
|
|
32
|
+
- [M8ActiveField / M8ParsedField](#m8activefield--m8parsedfield)
|
|
33
|
+
- [M8FieldType](#m8fieldtype)
|
|
34
|
+
- [M8KeyName](#m8keyname)
|
|
35
|
+
- [M8SdkConfig](#m8sdkconfig)
|
|
36
|
+
- [Host events](#host-events)
|
|
37
|
+
- [Supported views](#supported-views)
|
|
38
|
+
- [Notes](#notes)
|
|
39
|
+
|
|
40
|
+
---
|
|
41
|
+
|
|
42
|
+
## Install
|
|
43
|
+
|
|
44
|
+
```bash
|
|
45
|
+
npm install @yam8d/m8-sdk
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
For local development before publishing:
|
|
49
|
+
|
|
50
|
+
```bash
|
|
51
|
+
npm install ../yam8d/packages/m8-sdk
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
> Inside yam8d the app uses yam8d's connection to the M8. Opened in its own tab, it runs in [standalone mode](#standalone-mode) and connects to the M8 itself.
|
|
55
|
+
|
|
56
|
+
---
|
|
57
|
+
|
|
58
|
+
## Quick start
|
|
59
|
+
|
|
60
|
+
```ts
|
|
61
|
+
import { createM8Client } from '@yam8d/m8-sdk'
|
|
62
|
+
|
|
63
|
+
const m8 = await createM8Client()
|
|
64
|
+
|
|
65
|
+
console.log(m8.state.viewName) // e.g. 'song'
|
|
66
|
+
|
|
67
|
+
await m8.sendKeyPress(['right'])
|
|
68
|
+
|
|
69
|
+
const off = m8.onStateChange((state) => {
|
|
70
|
+
console.log(state.cursorPos)
|
|
71
|
+
})
|
|
72
|
+
|
|
73
|
+
off() // unsubscribe
|
|
74
|
+
m8.disconnect()
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
---
|
|
78
|
+
|
|
79
|
+
## Factory functions
|
|
80
|
+
|
|
81
|
+
### `createM8Client(config?): Promise<M8Client>`
|
|
82
|
+
|
|
83
|
+
Creates and connects an `M8Client` in one step using top-level `await`.
|
|
84
|
+
|
|
85
|
+
```ts
|
|
86
|
+
const m8 = await createM8Client({ debug: true })
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
### `createM8ClientSync(config?): { client: M8Client; connect: () => Promise<void> }`
|
|
90
|
+
|
|
91
|
+
Returns a client instance and a deferred `connect()` call. Useful in React or other frameworks where top-level `await` is inconvenient.
|
|
92
|
+
|
|
93
|
+
```ts
|
|
94
|
+
import { createM8ClientSync } from '@yam8d/m8-sdk'
|
|
95
|
+
|
|
96
|
+
const { client: m8, connect } = createM8ClientSync()
|
|
97
|
+
|
|
98
|
+
useEffect(() => {
|
|
99
|
+
connect().then(() => console.log('connected'))
|
|
100
|
+
return () => m8.disconnect()
|
|
101
|
+
}, [])
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
---
|
|
105
|
+
|
|
106
|
+
## Standalone mode
|
|
107
|
+
|
|
108
|
+
`createM8Client()` decides the mode when it connects (`client.mode`):
|
|
109
|
+
|
|
110
|
+
- **`'host'`**: the page runs in an iframe that yam8d named `yam8d-external-app` (exported as `YAM8D_FRAME_NAME`). The client waits for yam8d's handshake, however long it takes.
|
|
111
|
+
- **`'standalone'`**: the page is a top-level tab. The client runs the same engine yam8d uses, in the page itself: the Web Serial / WebUSB / Web MIDI SysEx connection, the display protocol (SLIP, draw rect/character commands), screen text extraction, cursor tracking, view navigation over the view graph, the value editors, file browsing and the MIDI clock.
|
|
112
|
+
- In an iframe that yam8d did not name (an older yam8d, or another page embedding the app), the client tries the handshake for `handshakeTimeoutMs` (3 s) and then goes standalone.
|
|
113
|
+
- Force a mode with `createM8Client({ mode: 'host' | 'standalone' })`.
|
|
114
|
+
|
|
115
|
+
In standalone mode the M8 has to be connected before any call works:
|
|
116
|
+
|
|
117
|
+
```ts
|
|
118
|
+
const m8 = await createM8Client()
|
|
119
|
+
if (m8.mode === 'standalone' && !m8.isConnected) {
|
|
120
|
+
// The browser's device picker only opens from a user gesture
|
|
121
|
+
connectButton.onclick = () => m8.connectDevice() // or connectDevice({ transport: 'midi' })
|
|
122
|
+
}
|
|
123
|
+
m8.onConnectionChange((connected) => updateUi(connected))
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
- **Audio**: `connectDevice({ audio: true })` (or `connectAudio()` later) plays the M8's USB audio input through the page, as yam8d does. The browser asks for audio permission; only an input whose label contains "M8" is played, never another microphone. Without it the device stays connected and the warning goes to the console; `isAudioConnected` tells. `disconnectDevice()` stops it too.
|
|
127
|
+
- **MIDI clock**: `enableMidiTransport()` / `onTransport()` work the same as in yam8d: the page opens the M8's MIDI input itself (Web MIDI permission). Enable it again after each new device connection.
|
|
128
|
+
- At start, a standalone client reopens a device the page was granted before, without a prompt (`autoConnect: false` turns that off).
|
|
129
|
+
- Only one page can hold the M8's serial port. Disconnect it in yam8d (or close that tab) before connecting from another page, and call `disconnectDevice()` to hand it back.
|
|
130
|
+
- `getDevice()` gives standalone apps the raw protocol events (`text`, `rect`, `wave`, `key`, `system`) and device commands (`sendKeys`, `resetScreen`, `sendNoteOn`, `sendNoteOff`, `sendProgramChange`), e.g. to draw the screen themselves.
|
|
131
|
+
- `getDeviceSupport()` tells which of the three browser APIs are available (Chromium browsers have all three; Firefox and Safari have none).
|
|
132
|
+
- The engine is exported too (`createM8Connection`, `createM8Engine`, `M8KeyMask`) for apps that want to drive the device below the client.
|
|
133
|
+
|
|
134
|
+
---
|
|
135
|
+
|
|
136
|
+
## M8Client
|
|
137
|
+
|
|
138
|
+
### Properties
|
|
139
|
+
|
|
140
|
+
| Property | Type | Description |
|
|
141
|
+
|---|---|---|
|
|
142
|
+
| `state` | `M8State` | Last known full device state (updated on every host event). |
|
|
143
|
+
| `isConnected` | `boolean` | Whether calls reach an M8: host mode, after the yam8d handshake; standalone, while a device is connected. |
|
|
144
|
+
| `mode` | `'host' \| 'standalone' \| null` | Chosen at connect; `null` before. |
|
|
145
|
+
| `deviceTransport` | `'serial' \| 'usb' \| 'midi' \| null` | Standalone: the API the device is connected through. |
|
|
146
|
+
|
|
147
|
+
---
|
|
148
|
+
|
|
149
|
+
### Navigation
|
|
150
|
+
|
|
151
|
+
#### `navigateToView(viewName: string): Promise<boolean>`
|
|
152
|
+
|
|
153
|
+
Asks the host to navigate to a named view (e.g. `'song'`, `'phrase'`). Returns `true` on success.
|
|
154
|
+
|
|
155
|
+
```ts
|
|
156
|
+
await m8.navigateToView('phrase')
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
#### `navigateTo(x: number, y: number): Promise<void>`
|
|
160
|
+
|
|
161
|
+
Moves the cursor to the given character-grid position.
|
|
162
|
+
|
|
163
|
+
```ts
|
|
164
|
+
await m8.navigateTo(3, 5)
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
---
|
|
168
|
+
|
|
169
|
+
### Value setters
|
|
170
|
+
|
|
171
|
+
All setters act on the field currently under the cursor.
|
|
172
|
+
|
|
173
|
+
#### `setValueToHex(targetHex: number): Promise<boolean>`
|
|
174
|
+
|
|
175
|
+
Sets the current field to the given hex integer (e.g. `0x0A`). Returns `true` if the value was successfully applied.
|
|
176
|
+
|
|
177
|
+
#### `setValueToInt(targetInt: number): Promise<boolean>`
|
|
178
|
+
|
|
179
|
+
Sets the current field to the given decimal integer. Returns `true` on success.
|
|
180
|
+
|
|
181
|
+
#### `setValueFloat(targetFloat: number): Promise<boolean>`
|
|
182
|
+
|
|
183
|
+
Sets a float parameter parsed from the whole line, e.g. `TUNE 440.00 G` or `GAIN 05.25 -19.75 -15.00`
|
|
184
|
+
(multiple floats per line are supported; the cursor's highlight selects which one is edited).
|
|
185
|
+
Works with the cursor on a single digit group (`440` / `00`) or spanning the whole float (`19.75`,
|
|
186
|
+
with the sign usually excluded from the highlight). With whole-float focus, edit+up/down moves the
|
|
187
|
+
integer part and edit+left/right the decimal part; with group focus the SDK relocates the cursor
|
|
188
|
+
between the groups for the fastest path. Negative values are supported and step sizes are measured
|
|
189
|
+
per field from the device.
|
|
190
|
+
Returns `true` when the displayed value matches.
|
|
191
|
+
|
|
192
|
+
#### `setNote(noteString: string): Promise<boolean>`
|
|
193
|
+
|
|
194
|
+
Sets a note field using M8 note format, e.g. `'C-4'`, `'A#3'`, `'OFF'`. Returns `true` on success.
|
|
195
|
+
|
|
196
|
+
#### `setValueToString(targetString: string, exact?: boolean, searchInCurrentLine?: boolean): Promise<boolean>`
|
|
197
|
+
|
|
198
|
+
Sets the current field by navigating to match `targetString`.
|
|
199
|
+
|
|
200
|
+
- `exact` (default `true`): require an exact string match.
|
|
201
|
+
- `searchInCurrentLine` (default `false`): restrict the search to the current row.
|
|
202
|
+
|
|
203
|
+
Returns `true` if the target was reached.
|
|
204
|
+
|
|
205
|
+
#### `browseFile(targetText: string, exact?: boolean): Promise<boolean>`
|
|
206
|
+
|
|
207
|
+
Navigates a file-browse dialog to an entry matching `targetText`. Returns `true` on success.
|
|
208
|
+
|
|
209
|
+
---
|
|
210
|
+
|
|
211
|
+
### Key input
|
|
212
|
+
|
|
213
|
+
#### `sendKeyPress(keys: M8KeyName[]): Promise<void>`
|
|
214
|
+
|
|
215
|
+
Sends a simultaneous key press + release for the given keys.
|
|
216
|
+
|
|
217
|
+
```ts
|
|
218
|
+
await m8.sendKeyPress(['shift', 'play'])
|
|
219
|
+
```
|
|
220
|
+
|
|
221
|
+
#### `sendKeyDown(keys: M8KeyName[]): Promise<void>`
|
|
222
|
+
|
|
223
|
+
Holds the given keys down without releasing. Pair with `sendKeyUp()`.
|
|
224
|
+
|
|
225
|
+
```ts
|
|
226
|
+
await m8.sendKeyDown(['shift'])
|
|
227
|
+
await m8.sendKeyPress(['right'])
|
|
228
|
+
await m8.sendKeyUp()
|
|
229
|
+
```
|
|
230
|
+
|
|
231
|
+
#### `sendKeyUp(): Promise<void>`
|
|
232
|
+
|
|
233
|
+
Releases all currently held keys.
|
|
234
|
+
|
|
235
|
+
---
|
|
236
|
+
|
|
237
|
+
### State
|
|
238
|
+
|
|
239
|
+
#### `getState(): M8State`
|
|
240
|
+
|
|
241
|
+
Returns the in-memory cached state synchronously. Same as reading `m8.state`.
|
|
242
|
+
|
|
243
|
+
#### `fetchState(): Promise<M8State>`
|
|
244
|
+
|
|
245
|
+
Requests a fresh state snapshot from the host and updates the internal cache.
|
|
246
|
+
|
|
247
|
+
```ts
|
|
248
|
+
const state = await m8.fetchState()
|
|
249
|
+
```
|
|
250
|
+
|
|
251
|
+
#### `getScreen(): Promise<M8Screen>`
|
|
252
|
+
|
|
253
|
+
The whole screen as text rows (40 columns, column 0 is the margin where play markers are drawn), read from the character stream without moving the cursor.
|
|
254
|
+
|
|
255
|
+
#### `getScreenCells(): Promise<M8ScreenCells>`
|
|
256
|
+
|
|
257
|
+
The same screen plus the colour of every character cell, so an app can look like the device or learn which theme colour the M8 uses for what. `fg[row][col]` and `bg[row][col]` are `'#rrggbb'`, `null` where nothing was drawn. Needs a yam8d host with SDK 0.5 or later; older hosts reject the call.
|
|
258
|
+
|
|
259
|
+
```ts
|
|
260
|
+
const cells = await m8.getScreenCells()
|
|
261
|
+
cells.fg[5][3] // colour of the first note of a phrase, e.g. '#ffffff'
|
|
262
|
+
```
|
|
263
|
+
|
|
264
|
+
---
|
|
265
|
+
|
|
266
|
+
### Event subscriptions
|
|
267
|
+
|
|
268
|
+
Every `on*` method returns an **unsubscribe function** — call it to stop listening.
|
|
269
|
+
|
|
270
|
+
#### `onStateChange(callback: (state: M8State) => void): () => void`
|
|
271
|
+
|
|
272
|
+
Fires whenever any part of the device state changes.
|
|
273
|
+
|
|
274
|
+
```ts
|
|
275
|
+
const off = m8.onStateChange((state) => {
|
|
276
|
+
console.log(state.viewName, state.cursorPos)
|
|
277
|
+
})
|
|
278
|
+
off() // unsubscribe
|
|
279
|
+
```
|
|
280
|
+
|
|
281
|
+
#### `onViewChange(callback: (viewName: string | null, viewTitle: string | null) => void): () => void`
|
|
282
|
+
|
|
283
|
+
Fires when the active view changes.
|
|
284
|
+
|
|
285
|
+
```ts
|
|
286
|
+
m8.onViewChange((name, title) => {
|
|
287
|
+
console.log(`Switched to ${name}: ${title}`)
|
|
288
|
+
})
|
|
289
|
+
```
|
|
290
|
+
|
|
291
|
+
#### `onCursorMove(callback: (pos: CursorPos | null, rect: CursorRect | null, selectionMode: boolean) => void): () => void`
|
|
292
|
+
|
|
293
|
+
Fires when the cursor position or selection mode changes.
|
|
294
|
+
|
|
295
|
+
```ts
|
|
296
|
+
m8.onCursorMove((pos, rect, sel) => {
|
|
297
|
+
console.log(`Cursor at (${pos?.x}, ${pos?.y}), selecting: ${sel}`)
|
|
298
|
+
})
|
|
299
|
+
```
|
|
300
|
+
|
|
301
|
+
#### `onTextUpdate(callback: (textUnderCursor: string | null, currentLine: string | null) => void): () => void`
|
|
302
|
+
|
|
303
|
+
Fires when the text under the cursor or the full current line changes.
|
|
304
|
+
|
|
305
|
+
```ts
|
|
306
|
+
m8.onTextUpdate((text, line) => {
|
|
307
|
+
console.log('Under cursor:', text)
|
|
308
|
+
console.log('Full line:', line)
|
|
309
|
+
})
|
|
310
|
+
```
|
|
311
|
+
|
|
312
|
+
#### `onKeyPress(callback: (keys: number) => void): () => void`
|
|
313
|
+
|
|
314
|
+
Fires when a physical key event occurs on the device. `keys` is a bitmask of active keys.
|
|
315
|
+
|
|
316
|
+
---
|
|
317
|
+
|
|
318
|
+
### Semantic context on the client
|
|
319
|
+
|
|
320
|
+
#### `getSemanticContext(): M8SemanticContext | null`
|
|
321
|
+
|
|
322
|
+
Parses `m8.state` into structured field data. Returns `null` when no view is active.
|
|
323
|
+
|
|
324
|
+
```ts
|
|
325
|
+
const ctx = m8.getSemanticContext()
|
|
326
|
+
|
|
327
|
+
if (ctx?.activeField?.type === 'chainRef') {
|
|
328
|
+
const track = ctx.activeField.meta?.trackIndex // 1–8
|
|
329
|
+
const chain = ctx.activeField.hexValue // 0–255 or null if empty
|
|
330
|
+
const row = ctx.row?.rowIndex
|
|
331
|
+
}
|
|
332
|
+
```
|
|
333
|
+
|
|
334
|
+
#### `describeContext(): string | null`
|
|
335
|
+
|
|
336
|
+
Returns a human-readable one-liner, e.g.:
|
|
337
|
+
|
|
338
|
+
- `"Track 3: 0A — Track 3 — Song View row 01"`
|
|
339
|
+
- `"Note (N): Note C-4 — Phrase View row 00"`
|
|
340
|
+
- `"FX2 Command: KIL — (Kill: stops the playing instrument after XX ticks) — value 03: stops the note after 3 ticks — Phrase View row 01"`
|
|
341
|
+
|
|
342
|
+
Returns `null` when not connected or no view is active. It is the text of `describeContextParts()` joined with `' — '`.
|
|
343
|
+
|
|
344
|
+
It reads the cursor against its surroundings when that changes the meaning:
|
|
345
|
+
|
|
346
|
+
- **FX values are explained** — `ARP37` on `C-4` → `"+3 and +7 semitones: C-4 D#4 G-4"`, `KIL04` → `"stops the note after 4 ticks"`, relative instrument commands show their sign.
|
|
347
|
+
- **REP is followed down its FX lane.** With `VOL10` above, `REP05` reads `"repeats VOL from 10 (step 1), +05 per step → 15 on this step"`, and an empty cell below it reads `"REP still active: VOL reaches 1A on this step (+05 per step since step 3)"`. A new command, `REP00`, or an `RTO` limit in the same lane ends or caps it. `^^` (a REP carried over from the previous phrase) is recognised when it can be attributed to one lane.
|
|
348
|
+
- **Relative commands are tracked.** Per the manual, a relative instrument command (`VOL`, `CUT`, `PIT`, `EA1`… — the ones the device help marks `(RELATIVE)`) keeps its change until a note is triggered with a `RET` or with an instrument number in the `I` column. `VOL02` twice reads `"VOL has moved by +04 in 2 steps since the instrument number on step 0; stays until a note with an instrument number (I) or a RET resets it"`, REP repeats are counted, and the `I` cell or `RET` that resets it lists what it wipes (`"resets the relative changes still in force (CUT +10, VOL +0B)"`). Only the current phrase is visible, so totals start at the phrase's own last reset.
|
|
349
|
+
- **Instrument-type commands say who offers them.** `FM3` → `"…; offered by FM Synth instruments"`.
|
|
350
|
+
- **Scale View** names the scale the enabled notes spell (`"Scale: C D E F G A B — Major (Ionian)"`), says what the row under the cursor is (`"E — a major 3rd (+4 semitones above C) — is in the scale"`), reports detuning, and explains the KEY / TUNE / NAME rows.
|
|
351
|
+
|
|
352
|
+
The surroundings come from a copy of the screen that the client refreshes in the background after every state event. It is only used when its row under the cursor still matches `currentLine`; otherwise the description quietly falls back to the cursor row alone. Call `await m8.getScreen()` or `await m8.captureSnapshot()` first when you need it guaranteed current.
|
|
353
|
+
|
|
354
|
+
#### `describeContextParts(): M8DescriptionPart[] | null`
|
|
355
|
+
|
|
356
|
+
The same description as ordered, tagged parts, so you can keep, drop, reorder or style some of them instead of parsing the string:
|
|
357
|
+
|
|
358
|
+
```ts
|
|
359
|
+
m8.describeContextParts()
|
|
360
|
+
// [{ kind: 'field', text: 'FX2 Command: KIL' },
|
|
361
|
+
// { kind: 'fxInfo', text: '(Kill: stops the playing instrument after XX ticks)' },
|
|
362
|
+
// { kind: 'meaning', text: 'value 03: stops the note after 3 ticks' },
|
|
363
|
+
// { kind: 'location', text: 'Phrase View row 01' }]
|
|
364
|
+
|
|
365
|
+
// Your own view label instead of the SDK's location:
|
|
366
|
+
const text = m8.describeContextParts()?.filter((p) => p.kind !== 'location').map((p) => p.text).join(' · ')
|
|
367
|
+
```
|
|
368
|
+
|
|
369
|
+
See [`M8DescriptionPart`](#m8descriptionpart) for the kinds. Everything a part says is also in the context as data (`activeField`, `fxLane`, `relativeFx`, `resetsRelative`, `scaleNote`, `scale`, …), for when you want to write the text yourself.
|
|
370
|
+
|
|
371
|
+
---
|
|
372
|
+
|
|
373
|
+
### Device connection (standalone)
|
|
374
|
+
|
|
375
|
+
#### `connectDevice(options?: { transport?: 'auto' | 'serial' | 'usb' | 'midi'; audio?: boolean }): Promise<void>`
|
|
376
|
+
|
|
377
|
+
Connects to the M8 from this page. `'auto'` (default) uses Web Serial, then WebUSB, then Web MIDI SysEx, by browser support. Without a previously granted device the browser shows its picker, which needs a user gesture: call it from a click handler. Replaces the current device. No-op in host mode.
|
|
378
|
+
|
|
379
|
+
#### `disconnectDevice(): Promise<void>`
|
|
380
|
+
|
|
381
|
+
Releases the device (and stops its audio) so yam8d or another page can open it.
|
|
382
|
+
|
|
383
|
+
#### `connectAudio(): Promise<void>` / `disconnectAudio(): Promise<void>`
|
|
384
|
+
|
|
385
|
+
Standalone: plays the M8's USB audio through the page, or stops it. `connectAudio()` rejects when no audio input named M8 exists. No-ops in host mode (yam8d plays it). `isAudioConnected` reports the state.
|
|
386
|
+
|
|
387
|
+
#### `onConnectionChange(callback: (connected: boolean) => void): () => void`
|
|
388
|
+
|
|
389
|
+
Fires when `isConnected` changes: handshake done, device connected, unplugged or released.
|
|
390
|
+
|
|
391
|
+
#### `getDevice(): M8DeviceBus | null`
|
|
392
|
+
|
|
393
|
+
Standalone: raw protocol events and device commands. `null` in host mode or while disconnected.
|
|
394
|
+
|
|
395
|
+
---
|
|
396
|
+
|
|
397
|
+
### Lifecycle
|
|
398
|
+
|
|
399
|
+
#### `disconnect(): void`
|
|
400
|
+
|
|
401
|
+
Closes the host connection (and the device, in standalone mode) and clears all event listeners and internal state.
|
|
402
|
+
|
|
403
|
+
---
|
|
404
|
+
|
|
405
|
+
## Standalone semantic context functions
|
|
406
|
+
|
|
407
|
+
These are re-exported from `@yam8d/m8-sdk` for use without an `M8Client` instance.
|
|
408
|
+
|
|
409
|
+
### `getSemanticContext(state: M8State, screen?: M8Screen | null): M8SemanticContext | null`
|
|
410
|
+
|
|
411
|
+
Parses an `M8State` snapshot into a semantic context. Returns `null` if `state.viewName` is not set.
|
|
412
|
+
|
|
413
|
+
Pass the whole `screen` (from `getScreen()`) to fill the context's look-around fields: `steps` (all 16 phrase steps), `fxLane` (what the FX cell under the cursor means in its lane), `relativeFx`, `resetsRelative`, `carriedRepLane`, and `scale` (Scale View). A screen whose cursor row no longer matches `state.currentLine` is ignored. `lineText` is always set for grid views.
|
|
414
|
+
|
|
415
|
+
```ts
|
|
416
|
+
import { getSemanticContext } from '@yam8d/m8-sdk'
|
|
417
|
+
|
|
418
|
+
const ctx = getSemanticContext(state)
|
|
419
|
+
if (ctx?.isGridView && ctx.row) {
|
|
420
|
+
const note = ctx.row.fields['note']
|
|
421
|
+
console.log(note?.rawValue) // e.g. 'C-4'
|
|
422
|
+
}
|
|
423
|
+
```
|
|
424
|
+
|
|
425
|
+
### `describeContext(ctx: M8SemanticContext): string`
|
|
426
|
+
|
|
427
|
+
Returns a human-readable summary of the context (see examples above).
|
|
428
|
+
|
|
429
|
+
### `describeContextParts(ctx: M8SemanticContext): M8DescriptionPart[]`
|
|
430
|
+
|
|
431
|
+
The summary as ordered, tagged parts; `describeContext()` joins their text with `' — '`.
|
|
432
|
+
|
|
433
|
+
### `formatFieldValue(field: M8ParsedField): string`
|
|
434
|
+
|
|
435
|
+
Formats a single parsed field as a display string:
|
|
436
|
+
|
|
437
|
+
| Type | Example output |
|
|
438
|
+
|---|---|
|
|
439
|
+
| `note` | `"Note C-4"`, `"Note Off"` |
|
|
440
|
+
| `transpose` | `"+3 st"`, `"-12 st"`, `"±0 st"` |
|
|
441
|
+
| `velocity` | `"57h (87 / 127 max)"` |
|
|
442
|
+
| `fxValue` | `"1Ah (dec 26)"` |
|
|
443
|
+
| `ticks` | `"06h (6 ticks)"`, `"00 (skip step)"` |
|
|
444
|
+
| `chainRef` / `phraseRef` / `fxCommand` / `volume` | raw value, e.g. `"0A"`, `"KIL"` |
|
|
445
|
+
| empty cell | `"(empty)"` |
|
|
446
|
+
|
|
447
|
+
### `lookupFxCommand(cmd: string): M8FxCommandInfo | undefined`
|
|
448
|
+
|
|
449
|
+
Looks up a 3-character FX command name (case-insensitive) across all categories (sequencer, instrument, modulation, mixer). Modulator commands carry their slot as the third character (`EA1`, `LF3`).
|
|
450
|
+
|
|
451
|
+
```ts
|
|
452
|
+
import { lookupFxCommand } from '@yam8d/m8-sdk'
|
|
453
|
+
|
|
454
|
+
lookupFxCommand('KIL')
|
|
455
|
+
// → { description: 'Kill: stops the playing instrument after XX ticks.',
|
|
456
|
+
// category: 'sequencer', relative: false, instrumentTypes: null, slot: null }
|
|
457
|
+
lookupFxCommand('FM3')
|
|
458
|
+
// → { …, category: 'instrument', relative: true, instrumentTypes: ['fmsynth'], slot: null }
|
|
459
|
+
lookupFxCommand('LF3')
|
|
460
|
+
// → { …, category: 'modulation', relative: false, instrumentTypes: null, slot: 3 }
|
|
461
|
+
```
|
|
462
|
+
|
|
463
|
+
`instrumentTypes` lists the instrument types that offer the command when not every type does (`null` = all, or not an instrument command).
|
|
464
|
+
|
|
465
|
+
### `getInstrumentFxCommands(type: string): M8InstrumentFxList | null`
|
|
466
|
+
|
|
467
|
+
The instrument commands offered by an instrument type (the "Current Instrument" section of the Effect Command Help view, opened with `[EDIT]+[UP/DOWN]` on an FX command). `type` is the key from `M8SemanticContext.instrumentType.key`.
|
|
468
|
+
|
|
469
|
+
```ts
|
|
470
|
+
getInstrumentFxCommands('hypersynth')
|
|
471
|
+
// → { type: 'hypersynth', verified: true,
|
|
472
|
+
// commands: ['VOL','PIT','FIN','CRD','CVO','SWM','WID','SUB','FIL','CUT','RES','AMP','LIM','PAN','SNC','ERR','DRY','SMX','SDL','SRV'] }
|
|
473
|
+
```
|
|
474
|
+
|
|
475
|
+
`verified: true` means the list was read from a real M8 (Model:02): **sampler, macrosynth, fmsynth, hypersynth**. Wavsynth and External were not available on that device, so their lists are unknown (`null`); `midiout` only holds the documented `CHD`/`ADD`. The Sequencer FX and Mixer/Effects sections are the same for every type, and the Instrument Mods section depends on the kind of each modulator slot (envelope: `EA AT HO DE ET`, LFO: `LA LO LS LF LT`).
|
|
476
|
+
|
|
477
|
+
### `RELATIVE_FX_RULE: string`
|
|
478
|
+
|
|
479
|
+
The manual's rule for relative commands, for tooltips.
|
|
480
|
+
|
|
481
|
+
### `parseGridScreen(viewName: string, screen: M8Screen): M8ScreenGridRow[]`
|
|
482
|
+
|
|
483
|
+
Every content row of a grid view (`song`, `chain`, `phrase`, `table`, `groove`, `instrumentpool`) read off the whole screen, top to bottom, each with the `screenRow` it is drawn on. Use it to read a full page at once instead of only the cursor row. Rows whose index does not parse are skipped, so check `rowIndex` continuity when you need the whole page.
|
|
484
|
+
|
|
485
|
+
```ts
|
|
486
|
+
const steps = parseGridScreen('phrase', await m8.getScreen())
|
|
487
|
+
steps[0].fields.note.rawValue // 'c-4' (the text stream is lower case)
|
|
488
|
+
```
|
|
489
|
+
|
|
490
|
+
### `parseViewTitle(rawTitle: string | null): { viewId: string | null; isUnsaved: boolean }`
|
|
491
|
+
|
|
492
|
+
The item number of a title (`'PHRASE 1F*'` → `{ viewId: '1F', isUnsaved: true }`).
|
|
493
|
+
|
|
494
|
+
### Theme: `parseThemeScreen`, `observeThemeColors`, `M8_THEME_ROLES`, `M8_THEME_VIEW`
|
|
495
|
+
|
|
496
|
+
The device theme has 13 colours, edited in System Settings › EDIT THEME (view name `themesettings`, exported as `M8_THEME_VIEW`).
|
|
497
|
+
|
|
498
|
+
- `parseThemeScreen(screen)` reads them off the Theme View screen: `{ colors: { background, textEmpty, textInfo, textDefault, textValue, textTitles, playMarkers, cursor, selection, scopeSlider, meterLow, meterMid, meterPeak }, mode: 'rgb' | 'hsv', name }`, colours as `'#rrggbb'`. HSV rows are converted (each channel read as 00-FF). It returns `null` unless all 13 rows are on screen.
|
|
499
|
+
- `observeThemeColors(state)` gives the colours any screen shows without opening the Theme View: `background` and `textTitles`. (`state.cursorColor` is the cursor rectangle as drawn; the M8 animates it, so it is not the theme's CURSOR colour.)
|
|
500
|
+
- `M8_THEME_ROLES` lists the roles with the labels the device shows.
|
|
501
|
+
|
|
502
|
+
```ts
|
|
503
|
+
if (m8.state.viewName === M8_THEME_VIEW) {
|
|
504
|
+
const theme = parseThemeScreen(await m8.getScreen())
|
|
505
|
+
}
|
|
506
|
+
```
|
|
507
|
+
|
|
508
|
+
---
|
|
509
|
+
|
|
510
|
+
## Types reference
|
|
511
|
+
|
|
512
|
+
### `M8State`
|
|
513
|
+
|
|
514
|
+
The complete snapshot of the device at a point in time.
|
|
515
|
+
|
|
516
|
+
```ts
|
|
517
|
+
interface M8State {
|
|
518
|
+
viewName: string | null // active view key, e.g. 'song', 'phrase'
|
|
519
|
+
viewTitle: string | null // raw title string from screen, e.g. ' SONG '
|
|
520
|
+
minimapKey: string | null // current minimap position key
|
|
521
|
+
cursorPos: CursorPos | null // character-grid position of the cursor
|
|
522
|
+
cursorRect: CursorRect | null // pixel rectangle of the cursor overlay
|
|
523
|
+
selectionMode: boolean // true when a selection is active
|
|
524
|
+
highlightColor: RGB | null // cursor highlight colour
|
|
525
|
+
titleColor: RGB | null // view title text colour
|
|
526
|
+
backgroundColor: RGB | null // screen background colour
|
|
527
|
+
cursorColor?: RGB | null // cursor rectangle as drawn (animated, not the theme CURSOR); SDK 0.5+
|
|
528
|
+
textUnderCursor: string | null // characters covered by the cursor rect
|
|
529
|
+
currentLine: string | null // full text of the row at cursorPos.y
|
|
530
|
+
deviceModel: string | null // e.g. 'Headless', 'Model:02'
|
|
531
|
+
fontMode: number | null // 0=standard, 1=bold, 2=large
|
|
532
|
+
systemInfo: SystemInfos | null // device-reported system properties
|
|
533
|
+
macroRunning: boolean // true while a macro is executing
|
|
534
|
+
macroCurrentStep?: number // current macro step index
|
|
535
|
+
macroSequenceLength?: number // total steps in the running macro
|
|
536
|
+
}
|
|
537
|
+
```
|
|
538
|
+
|
|
539
|
+
### `CursorPos / CursorRect / RGB / SystemInfos`
|
|
540
|
+
|
|
541
|
+
```ts
|
|
542
|
+
interface CursorPos { x: number; y: number }
|
|
543
|
+
interface CursorRect { x: number; y: number; w: number; h: number }
|
|
544
|
+
interface RGB { r: number; g: number; b: number }
|
|
545
|
+
interface SystemInfos { [key: string]: string | number | boolean | undefined }
|
|
546
|
+
```
|
|
547
|
+
|
|
548
|
+
### `M8SemanticContext`
|
|
549
|
+
|
|
550
|
+
```ts
|
|
551
|
+
interface M8SemanticContext {
|
|
552
|
+
viewName: string // e.g. 'song', 'phrase', 'instrumentpool'
|
|
553
|
+
viewTitle: string | null // e.g. 'Song View'
|
|
554
|
+
viewDescription: string | null // long description from schema
|
|
555
|
+
viewId: string | null // item ID parsed from screen title, e.g. 'F2', '03'; null for song/instrumentpool
|
|
556
|
+
isUnsaved: boolean // true when the title carries a '*' suffix (unsaved changes)
|
|
557
|
+
isGridView: boolean // true for song/chain/phrase/table/groove/scale/instrumentpool
|
|
558
|
+
isParameterView: boolean // true for the label/value views (inst, mixer, project, …)
|
|
559
|
+
section: string | null // effectsettings: 'MODFX' | 'DELAY' | 'REVERB'
|
|
560
|
+
instrumentType: { text: string; key: string | null } | null // inst view + screen, e.g. { text: 'WAVSYNTH', key: 'wavsynth' }
|
|
561
|
+
row: M8ParsedRow | null // all columns on the line at cursorPos.y
|
|
562
|
+
activeField: M8ActiveField | null // the column currently under the cursor
|
|
563
|
+
lineText: string | null // cursor row text, playback marker removed
|
|
564
|
+
steps: M8ParsedRow[] | null // phrase view + screen: all 16 steps (index = step)
|
|
565
|
+
carriedRepLane: number | null // FX lane (1–3) showing '^^' (REP from previous phrase)
|
|
566
|
+
fxLane: M8FxLaneTrace | null // phrase view + screen: what the FX cell means in its lane
|
|
567
|
+
relativeFx: Record<string, M8RelativeFxState> | null // phrase view + screen: relative commands in force, with net change since the last trigger
|
|
568
|
+
resetsRelative: Record<string, M8RelativeFxState> | null // phrase view + screen: when the cursor step triggers the instrument (I number or RET), the relative changes it puts back
|
|
569
|
+
scaleNote: M8ScaleNote | null // scale view: the interval row under the cursor { interval, semitone, enabled, offset }
|
|
570
|
+
scale: M8ScaleInfo | null // scale view + screen: the 12 intervals interpreted together
|
|
571
|
+
}
|
|
572
|
+
```
|
|
573
|
+
|
|
574
|
+
### `M8DescriptionPart`
|
|
575
|
+
|
|
576
|
+
```ts
|
|
577
|
+
interface M8DescriptionPart {
|
|
578
|
+
kind: M8DescriptionPartKind
|
|
579
|
+
text: string
|
|
580
|
+
}
|
|
581
|
+
```
|
|
582
|
+
|
|
583
|
+
Parts come in reading order; a kind can appear more than once.
|
|
584
|
+
|
|
585
|
+
| Kind | Example |
|
|
586
|
+
|---|---|
|
|
587
|
+
| `field` | `"FX1 Command: KIL"`, `"Tempo (TEMPO): 120.00 BPM"` |
|
|
588
|
+
| `fxInfo` | `"(Kill: stops the playing instrument after XX ticks)"` |
|
|
589
|
+
| `fxTarget` | on an FX value: `"for KIL (…)"` |
|
|
590
|
+
| `meaning` | `"value 03: stops the note after 3 ticks"`, `"E is detuned by −0.50 st"` |
|
|
591
|
+
| `fxLane` | `"REP still active: VOL reaches 09 on this step (+02 per step since step 1)"` |
|
|
592
|
+
| `relative` | `"relative: VOL has moved by +0C over 2 steps since …"`, `"triggers the instrument again: resets …"` |
|
|
593
|
+
| `optionInfo` | what the selected option does |
|
|
594
|
+
| `fieldInfo` | what the field is |
|
|
595
|
+
| `fileInfo` | `"directory: [EDIT] opens it"` |
|
|
596
|
+
| `instrument` | `"Wavsynth instrument"` |
|
|
597
|
+
| `section` | `"Delay"`, `"MOD2"` |
|
|
598
|
+
| `scale` | `"Scale: C D E F G A B"`, `"Major (Ionian); detuned: E −0.50 st"` |
|
|
599
|
+
| `track` | `"Track 3"` |
|
|
600
|
+
| `location` | `"Phrase View row 07"`, `"Mixer View"` |
|
|
601
|
+
| `line` | the cursor row's text, when no field on it is recognised |
|
|
602
|
+
|
|
603
|
+
### `M8ParsedRow`
|
|
604
|
+
|
|
605
|
+
```ts
|
|
606
|
+
interface M8ParsedRow {
|
|
607
|
+
rowIndex: number | null // hex row index from leftmost chars; null if unparseable
|
|
608
|
+
rowKey: string // schema key, e.g. 'songRow', 'step', 'chainPos'
|
|
609
|
+
rowId: string | null // parameter views: matched schema row ('tempo', 'gain', …)
|
|
610
|
+
fields: Record<string, M8ParsedField> // column key → parsed field
|
|
611
|
+
}
|
|
612
|
+
```
|
|
613
|
+
|
|
614
|
+
### `M8ActiveField / M8ParsedField`
|
|
615
|
+
|
|
616
|
+
`M8ActiveField` extends `M8ParsedField` with two extra convenience fields.
|
|
617
|
+
|
|
618
|
+
```ts
|
|
619
|
+
interface M8ParsedField {
|
|
620
|
+
key: string // schema key, e.g. 'fx1cmd', 'track3', 'note'
|
|
621
|
+
label: string // human label, e.g. 'FX1 Command', 'Track 3'
|
|
622
|
+
type: M8FieldType
|
|
623
|
+
rawValue: string // trimmed text sliced from currentLine
|
|
624
|
+
hexValue: number | null // parsed integer; null for empty cells and text types
|
|
625
|
+
isEmpty: boolean // true when '--', '---', '---00', etc.
|
|
626
|
+
meta?: Record<string, unknown> // view-specific extras (e.g. { trackIndex: 3 })
|
|
627
|
+
// parameter views (all optional):
|
|
628
|
+
description?: string // what the field is
|
|
629
|
+
text?: string | null // non-numeric part: option name, caption, name, instrument type
|
|
630
|
+
numericValue?: number | null // value in its natural base (hex, decimal, fractional)
|
|
631
|
+
values?: number[] | null // both numbers of an 'XX:YY' pair
|
|
632
|
+
parts?: string[] // names of those numbers, e.g. ['left time', 'right time']
|
|
633
|
+
unit?: string // e.g. ' BPM', ' dB'
|
|
634
|
+
valueDescription?: string // what the selected option does
|
|
635
|
+
fileKind?: M8FileKind // file browser rows: 'parent' | 'directory' | 'instrumentPreset' | 'sample' | 'm8file' | 'file'
|
|
636
|
+
}
|
|
637
|
+
|
|
638
|
+
interface M8ActiveField extends M8ParsedField {
|
|
639
|
+
viewName: string // repeated for convenience
|
|
640
|
+
rowIndex: number | null // row index of the line containing this field
|
|
641
|
+
}
|
|
642
|
+
```
|
|
643
|
+
|
|
644
|
+
### `M8FieldType`
|
|
645
|
+
|
|
646
|
+
```ts
|
|
647
|
+
type M8FieldType =
|
|
648
|
+
| 'chainRef' // chain number hex 00–FF; '--' = empty
|
|
649
|
+
| 'phraseRef' // phrase number hex 00–FF; '--' = empty
|
|
650
|
+
| 'note' // note name e.g. 'C-4', 'A#3'; '---' = empty; 'OFF' = note-off
|
|
651
|
+
| 'velocity' // velocity 00–7F hex; '--' = inherit
|
|
652
|
+
| 'instrumentRef' // instrument slot 00–7F hex; '--' = keep current
|
|
653
|
+
| 'fxCommand' // 3-char command name e.g. 'KIL'; '---' = none
|
|
654
|
+
| 'fxValue' // FX parameter 00–FF hex
|
|
655
|
+
| 'transpose' // relative semitones: 00=none, 01–7F=+1–+127, FF–80=−1–−128
|
|
656
|
+
| 'ticks' // groove ticks per step hex; '00'=skip; '--'=end of groove
|
|
657
|
+
| 'volume' // level 00–FF hex; '--' = no override
|
|
658
|
+
| 'ppq' // pulses-per-quarter-note decimal; groove row 0 only
|
|
659
|
+
| 'instrumentName' // instrument name text ≤12 chars; no hexValue
|
|
660
|
+
| 'eqSlot' // EQ slot assignment hex; '--' = none
|
|
661
|
+
| 'rowIndex' // row/step index hex
|
|
662
|
+
| 'noteInterval' // scale view row label: C, C#, D, …
|
|
663
|
+
| 'onOff' // 'ON' / 'OFF' toggle
|
|
664
|
+
| 'semitoneOffset' // scale view detune, e.g. '-00.50'
|
|
665
|
+
| 'swing' // groove swing percentage
|
|
666
|
+
| 'instrumentType' // instrument engine: WAVSYNTH, SAMPLER, FMSYNTH, …
|
|
667
|
+
| 'name' // free-text name field; dashes = unnamed
|
|
668
|
+
| 'parameter' // named editable value: CUTOFF, TEMPO, GAIN, …
|
|
669
|
+
| 'option' // choice from a list: '00CHORUS', 'BELL', 'STEREO'
|
|
670
|
+
| 'action' // button: LOAD, SAVE, RENDER, SETTINGS, EQ, …
|
|
671
|
+
| 'fileEntry' // file browser row: '/folder', '/..', 'Bass.m8i'
|
|
672
|
+
```
|
|
673
|
+
|
|
674
|
+
### `M8KeyName`
|
|
675
|
+
|
|
676
|
+
```ts
|
|
677
|
+
type M8KeyName = 'left' | 'right' | 'up' | 'down' | 'shift' | 'play' | 'opt' | 'edit'
|
|
678
|
+
```
|
|
679
|
+
|
|
680
|
+
### `M8SdkConfig`
|
|
681
|
+
|
|
682
|
+
```ts
|
|
683
|
+
interface M8SdkConfig {
|
|
684
|
+
debug?: boolean // verbose logging (postMessage traffic, engine events)
|
|
685
|
+
mode?: 'auto' | 'host' | 'standalone' // default 'auto', see Standalone mode
|
|
686
|
+
handshakeTimeoutMs?: number // auto mode in an unmarked iframe; default 3000
|
|
687
|
+
autoConnect?: boolean // standalone: reopen a granted device at start; default true
|
|
688
|
+
}
|
|
689
|
+
```
|
|
690
|
+
|
|
691
|
+
---
|
|
692
|
+
|
|
693
|
+
## Host events
|
|
694
|
+
|
|
695
|
+
These events are emitted by the host and consumed internally by `M8Client`. They are listed here for reference if you use the `post-me` connection directly.
|
|
696
|
+
|
|
697
|
+
| Event | Payload | Fired when |
|
|
698
|
+
|---|---|---|
|
|
699
|
+
| `stateChanged` | `M8State` | Any state field changes |
|
|
700
|
+
| `viewChanged` | `{ viewName, viewTitle }` | The active view changes |
|
|
701
|
+
| `cursorMoved` | `{ pos, rect, selectionMode }` | Cursor position or selection mode changes |
|
|
702
|
+
| `textUpdated` | `{ textUnderCursor, currentLine }` | Text under cursor or current line changes |
|
|
703
|
+
| `keyPressed` | `{ keys: number }` | A physical key event fires on the device |
|
|
704
|
+
|
|
705
|
+
---
|
|
706
|
+
|
|
707
|
+
## Supported views
|
|
708
|
+
|
|
709
|
+
The semantic context parser supports these `viewName` values:
|
|
710
|
+
|
|
711
|
+
| `viewName` | Columns |
|
|
712
|
+
|---|---|
|
|
713
|
+
| `song` | `track1`–`track8` (chainRef, meta: trackIndex 1–8) |
|
|
714
|
+
| `chain` | `phrase` (phraseRef), `transpose` |
|
|
715
|
+
| `phrase` | `note`, `vel`, `inst`, `fx1cmd`, `fx1val`, `fx2cmd`, `fx2val`, `fx3cmd`, `fx3val` |
|
|
716
|
+
| `table` | `transpose`, `volume`, `fx1cmd`, `fx1val`, `fx2cmd`, `fx2val`, `fx3cmd`, `fx3val` |
|
|
717
|
+
| `groove` | `ticks`, `ppq` (row 0 only), `swing` (row 0 only) |
|
|
718
|
+
| `scale` | `interval`, `en` (onOff), `offset` (semitoneOffset) |
|
|
719
|
+
| `instrumentpool` | `name`, `dry`, `mx`, `de`, `rv`, `eq` |
|
|
720
|
+
| `inst` | header: `instrumentType`, `load`/`save` (action), `name`, `transpose` (onOff), `tableTic`, `eq`; then the type's parameters as label/value pairs (`parameter` / `option`), described per instrument type |
|
|
721
|
+
| `instmods` | per modulation slot: type (`option`), `dest`, and the type's parameters (`amt`, `atk`, `hold`, `dec`, `sus`, `rel`, `peak`, `body`, `osc`, `trig`, `freq`, `src`, `lval`, `hval`) |
|
|
722
|
+
| `mixer` | `speakerVol`, `track1`–`track8`, `modfxVol`/`delayVol`/`reverbVol`, `inputVol`/`inputVol2`/`usbVol` and their sends, `mixEq` (action), `mainVol`, `limiter`, `djFilter`, `ott` |
|
|
723
|
+
| `project` | `tempo`, `transpose`, `groove`/`scale`/`liveQuantize` (option), `name`, and the `load`/`save`/`new`/`render`/`bundle`/… actions |
|
|
724
|
+
| `effectsettings` | per `section` (MODFX / DELAY / REVERB): type, input EQ (action), depth/frequency pairs, width, sends, delay time, feedback, room size, decay/shimmer |
|
|
725
|
+
| `mixeq`, `modfxeq`, `delayeq`, `reverbeq` | LOW/MID/HIGH band `gain`, `freq`, `q`, `type` (option), `mode` (option) |
|
|
726
|
+
| `mixscope`, `limiterscope` | `zoom`, `peak`, `softClip`, `mainVol`, `limiter`, `djFilter`, `ott`, `mixEq`; `atk`/`rel`/`type`/`res`/`time`/`color` as label/value pairs |
|
|
727
|
+
| `systemsettings` | `backlight`, `fontOptions`, `editTheme` (action), `notePreview`, `recCountIn`, `metronomeVol`, `usbAudioMode`, `usbMainOut`, `lineInGate`, `keyDelayRep`, … |
|
|
728
|
+
| `loadinstrument`, `selectsavedirectory` | `entry` (fileEntry) |
|
|
729
|
+
| `createdirectory` | `name` |
|
|
730
|
+
|
|
731
|
+
Grid views are read by column position (`src/m8-view-context.json`). Parameter views are label/value screens: a row is recognised from its label text and its cells come from `src/m8-parameter-views.json`, whose column positions were measured on the manual's screenshots. Instrument parameters vary with the instrument type, so they are read as label/value pairs and looked up in a per-type vocabulary. Layouts that only appear for some instrument types (Sampler `SAMPLE` row, FM operators, Hypersynth chord, MIDI CC rows) are best-effort.
|
|
732
|
+
|
|
733
|
+
---
|
|
734
|
+
|
|
735
|
+
## Notes
|
|
736
|
+
|
|
737
|
+
- **Screen geometry** — The screen is 40 × 24 characters. Every row has a one-column left margin, so schema `x` positions count from column 1 and `cursorPos.x` is the schema `x` + 1 (the SDK accounts for this). Titles are on row 3 and content starts on row 5. The text stream is lower case (`tempo`, `b-4`, `ff`); the SDK matches case-insensitively and shows values upper case, except user-typed names and file names.
|
|
738
|
+
- **Playback indicator** — When a row is actively playing the M8 draws `<` or `>` in the left margin, so it appears as the first character of `currentLine`. The SDK strips this automatically before parsing so column offsets remain consistent.
|
|
739
|
+
- **Font mode** — All column offsets assume font mode 0 (Headless: 8×10 px cells; Model:02: 12×14 px cells). Font modes 1 (bold) and 2 (large) may shift positions.
|
|
740
|
+
- **Schema file** — View and column definitions live in `src/m8-view-context.json` and are bundled into `dist/index.js` at build time.
|
|
741
|
+
- **Requirement** — The app must run inside the yam8d host iframe. The `ChildHandshake` from `post-me` will never resolve in a standalone tab.
|
|
742
|
+
|
|
743
|
+
|
|
744
|
+
Client SDK for iframe applications that communicate with an M8 host.
|
|
745
|
+
|
|
746
|
+
## Install
|
|
747
|
+
|
|
748
|
+
```bash
|
|
749
|
+
npm install @yam8d/m8-sdk
|
|
750
|
+
```
|
|
751
|
+
|
|
752
|
+
For local development before the package is published:
|
|
753
|
+
|
|
754
|
+
```bash
|
|
755
|
+
npm install ../yam8d/packages/m8-sdk
|
|
756
|
+
```
|
|
757
|
+
|
|
758
|
+
## Usage
|
|
759
|
+
|
|
760
|
+
```ts
|
|
761
|
+
import { createM8Client } from '@yam8d/m8-sdk'
|
|
762
|
+
|
|
763
|
+
const m8 = await createM8Client()
|
|
764
|
+
|
|
765
|
+
console.log(m8.state.viewName)
|
|
766
|
+
await m8.sendKeyPress(['play'])
|
|
767
|
+
|
|
768
|
+
const unsubscribe = m8.onStateChange((state) => {
|
|
769
|
+
console.log(state.cursorPos)
|
|
770
|
+
})
|
|
771
|
+
|
|
772
|
+
unsubscribe()
|
|
773
|
+
m8.disconnect()
|
|
774
|
+
```
|
|
775
|
+
|
|
776
|
+
For React or other environments where top-level `await` is not convenient:
|
|
777
|
+
|
|
778
|
+
```ts
|
|
779
|
+
import { createM8ClientSync } from '@yam8d/m8-sdk'
|
|
780
|
+
|
|
781
|
+
const { client, connect } = createM8ClientSync()
|
|
782
|
+
await connect()
|
|
783
|
+
```
|
|
784
|
+
|
|
785
|
+
The app must run inside the host iframe. Opening it directly in a standalone browser tab cannot establish the SDK handshake.
|
|
786
|
+
|
|
787
|
+
---
|
|
788
|
+
|
|
789
|
+
## Semantic Context
|
|
790
|
+
|
|
791
|
+
The SDK can interpret the current cursor position and row text as typed, labelled field values. This works for the five grid views (`song`, `chain`, `phrase`, `table`, `groove`) and the `instrumentpool` view.
|
|
792
|
+
|
|
793
|
+
### Via the client
|
|
794
|
+
|
|
795
|
+
```ts
|
|
796
|
+
const ctx = m8.getSemanticContext()
|
|
797
|
+
// or as a human-readable string:
|
|
798
|
+
const description = m8.describeContext()
|
|
799
|
+
// e.g. "Track 3: 0A — Track 3 — Song View row 01"
|
|
800
|
+
// or as tagged parts to build your own:
|
|
801
|
+
const parts = m8.describeContextParts()
|
|
802
|
+
```
|
|
803
|
+
|
|
804
|
+
### Standalone functions
|
|
805
|
+
|
|
806
|
+
```ts
|
|
807
|
+
import { getSemanticContext, describeContext, formatFieldValue, lookupFxCommand } from '@yam8d/m8-sdk'
|
|
808
|
+
|
|
809
|
+
const ctx = getSemanticContext(state)
|
|
810
|
+
|
|
811
|
+
if (ctx?.activeField) {
|
|
812
|
+
const field = ctx.activeField
|
|
813
|
+
|
|
814
|
+
console.log(field.key) // e.g. 'track3', 'note', 'fx1cmd'
|
|
815
|
+
console.log(field.label) // e.g. 'Track 3', 'Note (N)', 'FX1 Command'
|
|
816
|
+
console.log(field.type) // e.g. 'chainRef', 'note', 'fxCommand'
|
|
817
|
+
console.log(field.rawValue) // raw text from the line, e.g. '0A', 'C-4', 'KIL'
|
|
818
|
+
console.log(field.hexValue) // parsed integer or null (null for empty cells and text types)
|
|
819
|
+
console.log(field.isEmpty) // true when '--' / '---' / '---00'
|
|
820
|
+
|
|
821
|
+
// Song view: which track is the cursor on?
|
|
822
|
+
if (field.type === 'chainRef') {
|
|
823
|
+
console.log(field.meta?.trackIndex) // 1–8
|
|
824
|
+
}
|
|
825
|
+
|
|
826
|
+
// FX command: look up its description
|
|
827
|
+
if (field.type === 'fxCommand' && !field.isEmpty) {
|
|
828
|
+
const info = lookupFxCommand(field.rawValue)
|
|
829
|
+
console.log(info?.description)
|
|
830
|
+
}
|
|
831
|
+
}
|
|
832
|
+
|
|
833
|
+
// Row-level fields (all columns on the current line)
|
|
834
|
+
if (ctx?.row) {
|
|
835
|
+
console.log(ctx.row.rowIndex) // hex-parsed row index, e.g. 2
|
|
836
|
+
console.log(ctx.row.fields['note']) // M8ParsedField for the note column
|
|
837
|
+
}
|
|
838
|
+
```
|
|
839
|
+
|
|
840
|
+
### Returned types
|
|
841
|
+
|
|
842
|
+
```ts
|
|
843
|
+
interface M8SemanticContext {
|
|
844
|
+
viewName: string // e.g. 'song', 'phrase'
|
|
845
|
+
viewTitle: string | null // e.g. 'Song View'
|
|
846
|
+
viewDescription: string | null
|
|
847
|
+
isGridView: boolean
|
|
848
|
+
row: M8ParsedRow | null // all columns on the cursor's line
|
|
849
|
+
activeField: M8ActiveField | null // the column under the cursor
|
|
850
|
+
}
|
|
851
|
+
|
|
852
|
+
interface M8ParsedField {
|
|
853
|
+
key: string // schema key, e.g. 'fx1cmd', 'track3'
|
|
854
|
+
label: string // human label, e.g. 'FX1 Command', 'Track 3'
|
|
855
|
+
type: M8FieldType
|
|
856
|
+
rawValue: string // trimmed text from currentLine
|
|
857
|
+
hexValue: number | null
|
|
858
|
+
isEmpty: boolean
|
|
859
|
+
meta?: Record<string, unknown>
|
|
860
|
+
}
|
|
861
|
+
```
|
|
862
|
+
|
|
863
|
+
### Supported field types (`M8FieldType`)
|
|
864
|
+
|
|
865
|
+
| Type | Description |
|
|
866
|
+
|---|---|
|
|
867
|
+
| `chainRef` | Chain number (hex). `--` = empty. |
|
|
868
|
+
| `phraseRef` | Phrase number (hex). `--` = empty. |
|
|
869
|
+
| `note` | Note name, e.g. `C-4`, `A#3`. `---` = empty. |
|
|
870
|
+
| `velocity` | Note velocity 00–7F (hex). |
|
|
871
|
+
| `instrumentRef` | Instrument slot 00–7F (hex). `--` = keep current. |
|
|
872
|
+
| `fxCommand` | 3-char FX command name, e.g. `KIL`, `ARP`. `---` = none. |
|
|
873
|
+
| `fxValue` | FX value 00–FF (hex). |
|
|
874
|
+
| `transpose` | Relative transpose (hex): `00`=none, `01`–`7F`=+1–+127, `FF`–`80`=−1–−128. |
|
|
875
|
+
| `ticks` | Groove ticks per step (hex). |
|
|
876
|
+
| `volume` | Volume level 00–FF (hex). `--` = no override. |
|
|
877
|
+
| `ppq` | Pulses Per Quarter note (decimal). Groove row 0 only. |
|
|
878
|
+
| `instrumentName` | Instrument name text (up to 12 chars). No `hexValue`. |
|
|
879
|
+
| `eqSlot` | EQ slot assignment (hex). `--` = none. |
|
|
880
|
+
| `rowIndex` | Row/step index (hex). |
|
|
881
|
+
| `noteInterval` | Scale View row label (`C`, `C#`, …). |
|
|
882
|
+
| `onOff` | `ON` / `OFF` toggle. |
|
|
883
|
+
| `semitoneOffset` | Scale View detune, e.g. `-00.50`. |
|
|
884
|
+
| `swing` | Groove swing percentage. |
|
|
885
|
+
| `instrumentType` | Instrument engine (`WAVSYNTH`, `SAMPLER`, …); `text` holds the name. |
|
|
886
|
+
| `name` | Free-text name field; a row of dashes = unnamed. |
|
|
887
|
+
| `parameter` | Named editable value (`CUTOFF`, `TEMPO`, `GAIN`); read `hexValue` / `numericValue` / `values`. |
|
|
888
|
+
| `option` | Choice from a list (`00CHORUS`, `BELL`); `hexValue` is the index when shown, `text` the name. |
|
|
889
|
+
| `action` | Button caption (`LOAD`, `SAVE`, `RENDER`, `SETTINGS`); `text` holds it. |
|
|
890
|
+
| `fileEntry` | File browser row: `/folder`, `/..`, or a file name. |
|
|
891
|
+
|
|
892
|
+
### Playback indicator
|
|
893
|
+
|
|
894
|
+
When a row is actively playing, the M8 prepends a `<` or `>` character to `currentLine`. The SDK strips this automatically before parsing, so column offsets are always consistent.
|
|
895
|
+
|
|
896
|
+
### Notes file
|
|
897
|
+
|
|
898
|
+
View/column definitions live in `src/m8-view-context.json` and are bundled into `dist/index.js` at build time. All x/width values assume **font mode 0** (8×10 px cells on Headless, 12×14 px on Model:02). Font modes 1 and 2 may shift column positions.
|