@yam8d/m8-sdk 0.2.0 → 0.3.2
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 +655 -644
- package/dist/index.js +4 -0
- package/dist/index.js.map +1 -1
- package/package.json +4 -3
- package/types/client.d.ts +48 -0
- package/types/index.d.ts +4 -115
- package/types/types.d.ts +83 -0
- package/types/viewContext.d.ts +112 -0
package/README.md
CHANGED
|
@@ -1,644 +1,655 @@
|
|
|
1
|
-
# @yam8d/m8-sdk
|
|
2
|
-
|
|
3
|
-
Client SDK for iframe applications that communicate with an M8 tracker host via [yam8d](https://github.com/mio-moto/yam8d).
|
|
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
|
-
- [M8Client](#m8client)
|
|
15
|
-
- [Properties](#properties)
|
|
16
|
-
- [Navigation](#navigation)
|
|
17
|
-
- [Value setters](#value-setters)
|
|
18
|
-
- [Key input](#key-input)
|
|
19
|
-
- [State](#state)
|
|
20
|
-
- [Event subscriptions](#event-subscriptions)
|
|
21
|
-
- [Semantic context](#semantic-context-on-the-client)
|
|
22
|
-
- [Lifecycle](#lifecycle)
|
|
23
|
-
- [Standalone semantic context functions](#standalone-semantic-context-functions)
|
|
24
|
-
- [Types reference](#types-reference)
|
|
25
|
-
- [M8State](#m8state)
|
|
26
|
-
- [CursorPos / CursorRect / RGB / SystemInfos](#cursorpos--cursorrect--rgb--systeminfos)
|
|
27
|
-
- [M8SemanticContext](#m8semanticcontext)
|
|
28
|
-
- [M8ParsedRow](#m8parsedrow)
|
|
29
|
-
- [M8ActiveField / M8ParsedField](#m8activefield--m8parsedfield)
|
|
30
|
-
- [M8FieldType](#m8fieldtype)
|
|
31
|
-
- [M8KeyName](#m8keyname)
|
|
32
|
-
- [M8SdkConfig](#m8sdkconfig)
|
|
33
|
-
- [Host events](#host-events)
|
|
34
|
-
- [Supported views](#supported-views)
|
|
35
|
-
- [Notes](#notes)
|
|
36
|
-
|
|
37
|
-
---
|
|
38
|
-
|
|
39
|
-
## Install
|
|
40
|
-
|
|
41
|
-
```bash
|
|
42
|
-
npm install @yam8d/m8-sdk
|
|
43
|
-
```
|
|
44
|
-
|
|
45
|
-
For local development before publishing:
|
|
46
|
-
|
|
47
|
-
```bash
|
|
48
|
-
npm install ../yam8d/packages/m8-sdk
|
|
49
|
-
```
|
|
50
|
-
|
|
51
|
-
> The app **must run inside the yam8d host iframe**. Opening it directly in a standalone browser tab will fail the SDK handshake.
|
|
52
|
-
|
|
53
|
-
---
|
|
54
|
-
|
|
55
|
-
## Quick start
|
|
56
|
-
|
|
57
|
-
```ts
|
|
58
|
-
import { createM8Client } from '@yam8d/m8-sdk'
|
|
59
|
-
|
|
60
|
-
const m8 = await createM8Client()
|
|
61
|
-
|
|
62
|
-
console.log(m8.state.viewName) // e.g. 'song'
|
|
63
|
-
|
|
64
|
-
await m8.sendKeyPress(['right'])
|
|
65
|
-
|
|
66
|
-
const off = m8.onStateChange((state) => {
|
|
67
|
-
console.log(state.cursorPos)
|
|
68
|
-
})
|
|
69
|
-
|
|
70
|
-
off() // unsubscribe
|
|
71
|
-
m8.disconnect()
|
|
72
|
-
```
|
|
73
|
-
|
|
74
|
-
---
|
|
75
|
-
|
|
76
|
-
## Factory functions
|
|
77
|
-
|
|
78
|
-
### `createM8Client(config?): Promise<M8Client>`
|
|
79
|
-
|
|
80
|
-
Creates and connects an `M8Client` in one step using top-level `await`.
|
|
81
|
-
|
|
82
|
-
```ts
|
|
83
|
-
const m8 = await createM8Client({ debug: true })
|
|
84
|
-
```
|
|
85
|
-
|
|
86
|
-
### `createM8ClientSync(config?): { client: M8Client; connect: () => Promise<void> }`
|
|
87
|
-
|
|
88
|
-
Returns a client instance and a deferred `connect()` call. Useful in React or other frameworks where top-level `await` is inconvenient.
|
|
89
|
-
|
|
90
|
-
```ts
|
|
91
|
-
import { createM8ClientSync } from '@yam8d/m8-sdk'
|
|
92
|
-
|
|
93
|
-
const { client: m8, connect } = createM8ClientSync()
|
|
94
|
-
|
|
95
|
-
useEffect(() => {
|
|
96
|
-
connect().then(() => console.log('connected'))
|
|
97
|
-
return () => m8.disconnect()
|
|
98
|
-
}, [])
|
|
99
|
-
```
|
|
100
|
-
|
|
101
|
-
---
|
|
102
|
-
|
|
103
|
-
## M8Client
|
|
104
|
-
|
|
105
|
-
### Properties
|
|
106
|
-
|
|
107
|
-
| Property | Type | Description |
|
|
108
|
-
|---|---|---|
|
|
109
|
-
| `state` | `M8State` | Last known full device state (updated on every host event). |
|
|
110
|
-
| `isConnected` | `boolean` | Whether the SDK handshake has completed. |
|
|
111
|
-
|
|
112
|
-
---
|
|
113
|
-
|
|
114
|
-
### Navigation
|
|
115
|
-
|
|
116
|
-
#### `navigateToView(viewName: string): Promise<boolean>`
|
|
117
|
-
|
|
118
|
-
Asks the host to navigate to a named view (e.g. `'song'`, `'phrase'`). Returns `true` on success.
|
|
119
|
-
|
|
120
|
-
```ts
|
|
121
|
-
await m8.navigateToView('phrase')
|
|
122
|
-
```
|
|
123
|
-
|
|
124
|
-
#### `navigateTo(x: number, y: number): Promise<void>`
|
|
125
|
-
|
|
126
|
-
Moves the cursor to the given character-grid position.
|
|
127
|
-
|
|
128
|
-
```ts
|
|
129
|
-
await m8.navigateTo(3, 5)
|
|
130
|
-
```
|
|
131
|
-
|
|
132
|
-
---
|
|
133
|
-
|
|
134
|
-
### Value setters
|
|
135
|
-
|
|
136
|
-
All setters act on the field currently under the cursor.
|
|
137
|
-
|
|
138
|
-
#### `setValueToHex(targetHex: number): Promise<boolean>`
|
|
139
|
-
|
|
140
|
-
Sets the current field to the given hex integer (e.g. `0x0A`). Returns `true` if the value was successfully applied.
|
|
141
|
-
|
|
142
|
-
#### `setValueToInt(targetInt: number): Promise<boolean>`
|
|
143
|
-
|
|
144
|
-
Sets the current field to the given decimal integer. Returns `true` on success.
|
|
145
|
-
|
|
146
|
-
#### `
|
|
147
|
-
|
|
148
|
-
Sets a
|
|
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
|
-
Fires
|
|
225
|
-
|
|
226
|
-
```ts
|
|
227
|
-
m8.
|
|
228
|
-
console.log(
|
|
229
|
-
})
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
})
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
console.log(
|
|
250
|
-
})
|
|
251
|
-
```
|
|
252
|
-
|
|
253
|
-
#### `
|
|
254
|
-
|
|
255
|
-
Fires when
|
|
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
|
-
| `viewName` |
|
|
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
|
-
const ctx = getSemanticContext(
|
|
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
|
-
|
|
1
|
+
# @yam8d/m8-sdk
|
|
2
|
+
|
|
3
|
+
Client SDK for iframe applications that communicate with an M8 tracker host via [yam8d](https://github.com/mio-moto/yam8d).
|
|
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
|
+
- [M8Client](#m8client)
|
|
15
|
+
- [Properties](#properties)
|
|
16
|
+
- [Navigation](#navigation)
|
|
17
|
+
- [Value setters](#value-setters)
|
|
18
|
+
- [Key input](#key-input)
|
|
19
|
+
- [State](#state)
|
|
20
|
+
- [Event subscriptions](#event-subscriptions)
|
|
21
|
+
- [Semantic context](#semantic-context-on-the-client)
|
|
22
|
+
- [Lifecycle](#lifecycle)
|
|
23
|
+
- [Standalone semantic context functions](#standalone-semantic-context-functions)
|
|
24
|
+
- [Types reference](#types-reference)
|
|
25
|
+
- [M8State](#m8state)
|
|
26
|
+
- [CursorPos / CursorRect / RGB / SystemInfos](#cursorpos--cursorrect--rgb--systeminfos)
|
|
27
|
+
- [M8SemanticContext](#m8semanticcontext)
|
|
28
|
+
- [M8ParsedRow](#m8parsedrow)
|
|
29
|
+
- [M8ActiveField / M8ParsedField](#m8activefield--m8parsedfield)
|
|
30
|
+
- [M8FieldType](#m8fieldtype)
|
|
31
|
+
- [M8KeyName](#m8keyname)
|
|
32
|
+
- [M8SdkConfig](#m8sdkconfig)
|
|
33
|
+
- [Host events](#host-events)
|
|
34
|
+
- [Supported views](#supported-views)
|
|
35
|
+
- [Notes](#notes)
|
|
36
|
+
|
|
37
|
+
---
|
|
38
|
+
|
|
39
|
+
## Install
|
|
40
|
+
|
|
41
|
+
```bash
|
|
42
|
+
npm install @yam8d/m8-sdk
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
For local development before publishing:
|
|
46
|
+
|
|
47
|
+
```bash
|
|
48
|
+
npm install ../yam8d/packages/m8-sdk
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
> The app **must run inside the yam8d host iframe**. Opening it directly in a standalone browser tab will fail the SDK handshake.
|
|
52
|
+
|
|
53
|
+
---
|
|
54
|
+
|
|
55
|
+
## Quick start
|
|
56
|
+
|
|
57
|
+
```ts
|
|
58
|
+
import { createM8Client } from '@yam8d/m8-sdk'
|
|
59
|
+
|
|
60
|
+
const m8 = await createM8Client()
|
|
61
|
+
|
|
62
|
+
console.log(m8.state.viewName) // e.g. 'song'
|
|
63
|
+
|
|
64
|
+
await m8.sendKeyPress(['right'])
|
|
65
|
+
|
|
66
|
+
const off = m8.onStateChange((state) => {
|
|
67
|
+
console.log(state.cursorPos)
|
|
68
|
+
})
|
|
69
|
+
|
|
70
|
+
off() // unsubscribe
|
|
71
|
+
m8.disconnect()
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
---
|
|
75
|
+
|
|
76
|
+
## Factory functions
|
|
77
|
+
|
|
78
|
+
### `createM8Client(config?): Promise<M8Client>`
|
|
79
|
+
|
|
80
|
+
Creates and connects an `M8Client` in one step using top-level `await`.
|
|
81
|
+
|
|
82
|
+
```ts
|
|
83
|
+
const m8 = await createM8Client({ debug: true })
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
### `createM8ClientSync(config?): { client: M8Client; connect: () => Promise<void> }`
|
|
87
|
+
|
|
88
|
+
Returns a client instance and a deferred `connect()` call. Useful in React or other frameworks where top-level `await` is inconvenient.
|
|
89
|
+
|
|
90
|
+
```ts
|
|
91
|
+
import { createM8ClientSync } from '@yam8d/m8-sdk'
|
|
92
|
+
|
|
93
|
+
const { client: m8, connect } = createM8ClientSync()
|
|
94
|
+
|
|
95
|
+
useEffect(() => {
|
|
96
|
+
connect().then(() => console.log('connected'))
|
|
97
|
+
return () => m8.disconnect()
|
|
98
|
+
}, [])
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
---
|
|
102
|
+
|
|
103
|
+
## M8Client
|
|
104
|
+
|
|
105
|
+
### Properties
|
|
106
|
+
|
|
107
|
+
| Property | Type | Description |
|
|
108
|
+
|---|---|---|
|
|
109
|
+
| `state` | `M8State` | Last known full device state (updated on every host event). |
|
|
110
|
+
| `isConnected` | `boolean` | Whether the SDK handshake has completed. |
|
|
111
|
+
|
|
112
|
+
---
|
|
113
|
+
|
|
114
|
+
### Navigation
|
|
115
|
+
|
|
116
|
+
#### `navigateToView(viewName: string): Promise<boolean>`
|
|
117
|
+
|
|
118
|
+
Asks the host to navigate to a named view (e.g. `'song'`, `'phrase'`). Returns `true` on success.
|
|
119
|
+
|
|
120
|
+
```ts
|
|
121
|
+
await m8.navigateToView('phrase')
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
#### `navigateTo(x: number, y: number): Promise<void>`
|
|
125
|
+
|
|
126
|
+
Moves the cursor to the given character-grid position.
|
|
127
|
+
|
|
128
|
+
```ts
|
|
129
|
+
await m8.navigateTo(3, 5)
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
---
|
|
133
|
+
|
|
134
|
+
### Value setters
|
|
135
|
+
|
|
136
|
+
All setters act on the field currently under the cursor.
|
|
137
|
+
|
|
138
|
+
#### `setValueToHex(targetHex: number): Promise<boolean>`
|
|
139
|
+
|
|
140
|
+
Sets the current field to the given hex integer (e.g. `0x0A`). Returns `true` if the value was successfully applied.
|
|
141
|
+
|
|
142
|
+
#### `setValueToInt(targetInt: number): Promise<boolean>`
|
|
143
|
+
|
|
144
|
+
Sets the current field to the given decimal integer. Returns `true` on success.
|
|
145
|
+
|
|
146
|
+
#### `setValueFloat(targetFloat: number): Promise<boolean>`
|
|
147
|
+
|
|
148
|
+
Sets a float parameter parsed from the whole line, e.g. `TUNE 440.00 G` or `GAIN 05.25 -19.75 -15.00`
|
|
149
|
+
(multiple floats per line are supported; the cursor's highlight selects which one is edited).
|
|
150
|
+
Works with the cursor on a single digit group (`440` / `00`) or spanning the whole float (`19.75`,
|
|
151
|
+
with the sign usually excluded from the highlight). With whole-float focus, edit+up/down moves the
|
|
152
|
+
integer part and edit+left/right the decimal part; with group focus the SDK relocates the cursor
|
|
153
|
+
between the groups for the fastest path. Negative values are supported and step sizes are measured
|
|
154
|
+
per field from the device.
|
|
155
|
+
Returns `true` when the displayed value matches.
|
|
156
|
+
|
|
157
|
+
#### `setNote(noteString: string): Promise<boolean>`
|
|
158
|
+
|
|
159
|
+
Sets a note field using M8 note format, e.g. `'C-4'`, `'A#3'`, `'OFF'`. Returns `true` on success.
|
|
160
|
+
|
|
161
|
+
#### `setValueToString(targetString: string, exact?: boolean, searchInCurrentLine?: boolean): Promise<boolean>`
|
|
162
|
+
|
|
163
|
+
Sets the current field by navigating to match `targetString`.
|
|
164
|
+
|
|
165
|
+
- `exact` (default `true`): require an exact string match.
|
|
166
|
+
- `searchInCurrentLine` (default `false`): restrict the search to the current row.
|
|
167
|
+
|
|
168
|
+
Returns `true` if the target was reached.
|
|
169
|
+
|
|
170
|
+
#### `browseFile(targetText: string, exact?: boolean): Promise<boolean>`
|
|
171
|
+
|
|
172
|
+
Navigates a file-browse dialog to an entry matching `targetText`. Returns `true` on success.
|
|
173
|
+
|
|
174
|
+
---
|
|
175
|
+
|
|
176
|
+
### Key input
|
|
177
|
+
|
|
178
|
+
#### `sendKeyPress(keys: M8KeyName[]): Promise<void>`
|
|
179
|
+
|
|
180
|
+
Sends a simultaneous key press + release for the given keys.
|
|
181
|
+
|
|
182
|
+
```ts
|
|
183
|
+
await m8.sendKeyPress(['shift', 'play'])
|
|
184
|
+
```
|
|
185
|
+
|
|
186
|
+
#### `sendKeyDown(keys: M8KeyName[]): Promise<void>`
|
|
187
|
+
|
|
188
|
+
Holds the given keys down without releasing. Pair with `sendKeyUp()`.
|
|
189
|
+
|
|
190
|
+
```ts
|
|
191
|
+
await m8.sendKeyDown(['shift'])
|
|
192
|
+
await m8.sendKeyPress(['right'])
|
|
193
|
+
await m8.sendKeyUp()
|
|
194
|
+
```
|
|
195
|
+
|
|
196
|
+
#### `sendKeyUp(): Promise<void>`
|
|
197
|
+
|
|
198
|
+
Releases all currently held keys.
|
|
199
|
+
|
|
200
|
+
---
|
|
201
|
+
|
|
202
|
+
### State
|
|
203
|
+
|
|
204
|
+
#### `getState(): M8State`
|
|
205
|
+
|
|
206
|
+
Returns the in-memory cached state synchronously. Same as reading `m8.state`.
|
|
207
|
+
|
|
208
|
+
#### `fetchState(): Promise<M8State>`
|
|
209
|
+
|
|
210
|
+
Requests a fresh state snapshot from the host and updates the internal cache.
|
|
211
|
+
|
|
212
|
+
```ts
|
|
213
|
+
const state = await m8.fetchState()
|
|
214
|
+
```
|
|
215
|
+
|
|
216
|
+
---
|
|
217
|
+
|
|
218
|
+
### Event subscriptions
|
|
219
|
+
|
|
220
|
+
Every `on*` method returns an **unsubscribe function** — call it to stop listening.
|
|
221
|
+
|
|
222
|
+
#### `onStateChange(callback: (state: M8State) => void): () => void`
|
|
223
|
+
|
|
224
|
+
Fires whenever any part of the device state changes.
|
|
225
|
+
|
|
226
|
+
```ts
|
|
227
|
+
const off = m8.onStateChange((state) => {
|
|
228
|
+
console.log(state.viewName, state.cursorPos)
|
|
229
|
+
})
|
|
230
|
+
off() // unsubscribe
|
|
231
|
+
```
|
|
232
|
+
|
|
233
|
+
#### `onViewChange(callback: (viewName: string | null, viewTitle: string | null) => void): () => void`
|
|
234
|
+
|
|
235
|
+
Fires when the active view changes.
|
|
236
|
+
|
|
237
|
+
```ts
|
|
238
|
+
m8.onViewChange((name, title) => {
|
|
239
|
+
console.log(`Switched to ${name}: ${title}`)
|
|
240
|
+
})
|
|
241
|
+
```
|
|
242
|
+
|
|
243
|
+
#### `onCursorMove(callback: (pos: CursorPos | null, rect: CursorRect | null, selectionMode: boolean) => void): () => void`
|
|
244
|
+
|
|
245
|
+
Fires when the cursor position or selection mode changes.
|
|
246
|
+
|
|
247
|
+
```ts
|
|
248
|
+
m8.onCursorMove((pos, rect, sel) => {
|
|
249
|
+
console.log(`Cursor at (${pos?.x}, ${pos?.y}), selecting: ${sel}`)
|
|
250
|
+
})
|
|
251
|
+
```
|
|
252
|
+
|
|
253
|
+
#### `onTextUpdate(callback: (textUnderCursor: string | null, currentLine: string | null) => void): () => void`
|
|
254
|
+
|
|
255
|
+
Fires when the text under the cursor or the full current line changes.
|
|
256
|
+
|
|
257
|
+
```ts
|
|
258
|
+
m8.onTextUpdate((text, line) => {
|
|
259
|
+
console.log('Under cursor:', text)
|
|
260
|
+
console.log('Full line:', line)
|
|
261
|
+
})
|
|
262
|
+
```
|
|
263
|
+
|
|
264
|
+
#### `onKeyPress(callback: (keys: number) => void): () => void`
|
|
265
|
+
|
|
266
|
+
Fires when a physical key event occurs on the device. `keys` is a bitmask of active keys.
|
|
267
|
+
|
|
268
|
+
---
|
|
269
|
+
|
|
270
|
+
### Semantic context on the client
|
|
271
|
+
|
|
272
|
+
#### `getSemanticContext(): M8SemanticContext | null`
|
|
273
|
+
|
|
274
|
+
Parses `m8.state` into structured field data. Returns `null` when no view is active.
|
|
275
|
+
|
|
276
|
+
```ts
|
|
277
|
+
const ctx = m8.getSemanticContext()
|
|
278
|
+
|
|
279
|
+
if (ctx?.activeField?.type === 'chainRef') {
|
|
280
|
+
const track = ctx.activeField.meta?.trackIndex // 1–8
|
|
281
|
+
const chain = ctx.activeField.hexValue // 0–255 or null if empty
|
|
282
|
+
const row = ctx.row?.rowIndex
|
|
283
|
+
}
|
|
284
|
+
```
|
|
285
|
+
|
|
286
|
+
#### `describeContext(): string | null`
|
|
287
|
+
|
|
288
|
+
Returns a human-readable one-liner, e.g.:
|
|
289
|
+
|
|
290
|
+
- `"Track 3 — Chain 0A — Song View row 02"`
|
|
291
|
+
- `"Note A#4 — Step 7 (Phrase View)"`
|
|
292
|
+
- `"FX1 Command: KIL (Kill Note) — Step 0 — Phrase View row 03"`
|
|
293
|
+
|
|
294
|
+
Returns `null` when not connected or no view is active.
|
|
295
|
+
|
|
296
|
+
---
|
|
297
|
+
|
|
298
|
+
### Lifecycle
|
|
299
|
+
|
|
300
|
+
#### `disconnect(): void`
|
|
301
|
+
|
|
302
|
+
Closes the host connection and clears all event listeners and internal state.
|
|
303
|
+
|
|
304
|
+
---
|
|
305
|
+
|
|
306
|
+
## Standalone semantic context functions
|
|
307
|
+
|
|
308
|
+
These are re-exported from `@yam8d/m8-sdk` for use without an `M8Client` instance.
|
|
309
|
+
|
|
310
|
+
### `getSemanticContext(state: M8State): M8SemanticContext | null`
|
|
311
|
+
|
|
312
|
+
Parses an `M8State` snapshot into a semantic context. Returns `null` if `state.viewName` is not set.
|
|
313
|
+
|
|
314
|
+
```ts
|
|
315
|
+
import { getSemanticContext } from '@yam8d/m8-sdk'
|
|
316
|
+
|
|
317
|
+
const ctx = getSemanticContext(state)
|
|
318
|
+
if (ctx?.isGridView && ctx.row) {
|
|
319
|
+
const note = ctx.row.fields['note']
|
|
320
|
+
console.log(note?.rawValue) // e.g. 'C-4'
|
|
321
|
+
}
|
|
322
|
+
```
|
|
323
|
+
|
|
324
|
+
### `describeContext(ctx: M8SemanticContext): string`
|
|
325
|
+
|
|
326
|
+
Returns a human-readable summary of the context (see examples above).
|
|
327
|
+
|
|
328
|
+
### `formatFieldValue(field: M8ParsedField): string`
|
|
329
|
+
|
|
330
|
+
Formats a single parsed field as a display string:
|
|
331
|
+
|
|
332
|
+
| Type | Example output |
|
|
333
|
+
|---|---|
|
|
334
|
+
| `note` | `"Note C-4"`, `"Note Off"` |
|
|
335
|
+
| `transpose` | `"+3 st"`, `"-12 st"`, `"±0 st"` |
|
|
336
|
+
| `velocity` | `"57h (87 / 127 max)"` |
|
|
337
|
+
| `fxValue` | `"1Ah (dec 26)"` |
|
|
338
|
+
| `ticks` | `"06h (6 ticks)"`, `"00 (skip step)"` |
|
|
339
|
+
| `chainRef` / `phraseRef` / `fxCommand` / `volume` | raw value, e.g. `"0A"`, `"KIL"` |
|
|
340
|
+
| empty cell | `"(empty)"` |
|
|
341
|
+
|
|
342
|
+
### `lookupFxCommand(cmd: string): { description: string } | undefined`
|
|
343
|
+
|
|
344
|
+
Looks up a 3-character FX command name (case-insensitive) across all categories (sequencer, instrument, mixer).
|
|
345
|
+
|
|
346
|
+
```ts
|
|
347
|
+
import { lookupFxCommand } from '@yam8d/m8-sdk'
|
|
348
|
+
|
|
349
|
+
const info = lookupFxCommand('KIL')
|
|
350
|
+
// → { description: 'Kill: stops the playing instrument after XX ticks.' }
|
|
351
|
+
```
|
|
352
|
+
|
|
353
|
+
---
|
|
354
|
+
|
|
355
|
+
## Types reference
|
|
356
|
+
|
|
357
|
+
### `M8State`
|
|
358
|
+
|
|
359
|
+
The complete snapshot of the device at a point in time.
|
|
360
|
+
|
|
361
|
+
```ts
|
|
362
|
+
interface M8State {
|
|
363
|
+
viewName: string | null // active view key, e.g. 'song', 'phrase'
|
|
364
|
+
viewTitle: string | null // raw title string from screen, e.g. ' SONG '
|
|
365
|
+
minimapKey: string | null // current minimap position key
|
|
366
|
+
cursorPos: CursorPos | null // character-grid position of the cursor
|
|
367
|
+
cursorRect: CursorRect | null // pixel rectangle of the cursor overlay
|
|
368
|
+
selectionMode: boolean // true when a selection is active
|
|
369
|
+
highlightColor: RGB | null // cursor highlight colour
|
|
370
|
+
titleColor: RGB | null // view title text colour
|
|
371
|
+
backgroundColor: RGB | null // screen background colour
|
|
372
|
+
textUnderCursor: string | null // characters covered by the cursor rect
|
|
373
|
+
currentLine: string | null // full text of the row at cursorPos.y
|
|
374
|
+
deviceModel: string | null // e.g. 'Headless', 'Model:02'
|
|
375
|
+
fontMode: number | null // 0=standard, 1=bold, 2=large
|
|
376
|
+
systemInfo: SystemInfos | null // device-reported system properties
|
|
377
|
+
macroRunning: boolean // true while a macro is executing
|
|
378
|
+
macroCurrentStep?: number // current macro step index
|
|
379
|
+
macroSequenceLength?: number // total steps in the running macro
|
|
380
|
+
}
|
|
381
|
+
```
|
|
382
|
+
|
|
383
|
+
### `CursorPos / CursorRect / RGB / SystemInfos`
|
|
384
|
+
|
|
385
|
+
```ts
|
|
386
|
+
interface CursorPos { x: number; y: number }
|
|
387
|
+
interface CursorRect { x: number; y: number; w: number; h: number }
|
|
388
|
+
interface RGB { r: number; g: number; b: number }
|
|
389
|
+
interface SystemInfos { [key: string]: string | number | boolean | undefined }
|
|
390
|
+
```
|
|
391
|
+
|
|
392
|
+
### `M8SemanticContext`
|
|
393
|
+
|
|
394
|
+
```ts
|
|
395
|
+
interface M8SemanticContext {
|
|
396
|
+
viewName: string // e.g. 'song', 'phrase', 'instrumentpool'
|
|
397
|
+
viewTitle: string | null // e.g. 'Song View'
|
|
398
|
+
viewDescription: string | null // long description from schema
|
|
399
|
+
viewId: string | null // item ID parsed from screen title, e.g. 'F2', '03'; null for song/instrumentpool
|
|
400
|
+
isUnsaved: boolean // true when the title carries a '*' suffix (unsaved changes)
|
|
401
|
+
isGridView: boolean // true for song/chain/phrase/table/groove/instrumentpool
|
|
402
|
+
row: M8ParsedRow | null // all columns on the line at cursorPos.y
|
|
403
|
+
activeField: M8ActiveField | null // the column currently under the cursor
|
|
404
|
+
}
|
|
405
|
+
```
|
|
406
|
+
|
|
407
|
+
### `M8ParsedRow`
|
|
408
|
+
|
|
409
|
+
```ts
|
|
410
|
+
interface M8ParsedRow {
|
|
411
|
+
rowIndex: number | null // hex row index from leftmost chars; null if unparseable
|
|
412
|
+
rowKey: string // schema key, e.g. 'songRow', 'step', 'chainPos'
|
|
413
|
+
fields: Record<string, M8ParsedField> // column key → parsed field
|
|
414
|
+
}
|
|
415
|
+
```
|
|
416
|
+
|
|
417
|
+
### `M8ActiveField / M8ParsedField`
|
|
418
|
+
|
|
419
|
+
`M8ActiveField` extends `M8ParsedField` with two extra convenience fields.
|
|
420
|
+
|
|
421
|
+
```ts
|
|
422
|
+
interface M8ParsedField {
|
|
423
|
+
key: string // schema key, e.g. 'fx1cmd', 'track3', 'note'
|
|
424
|
+
label: string // human label, e.g. 'FX1 Command', 'Track 3'
|
|
425
|
+
type: M8FieldType
|
|
426
|
+
rawValue: string // trimmed text sliced from currentLine
|
|
427
|
+
hexValue: number | null // parsed integer; null for empty cells and text types
|
|
428
|
+
isEmpty: boolean // true when '--', '---', '---00', etc.
|
|
429
|
+
meta?: Record<string, unknown> // view-specific extras (e.g. { trackIndex: 3 })
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
interface M8ActiveField extends M8ParsedField {
|
|
433
|
+
viewName: string // repeated for convenience
|
|
434
|
+
rowIndex: number | null // row index of the line containing this field
|
|
435
|
+
}
|
|
436
|
+
```
|
|
437
|
+
|
|
438
|
+
### `M8FieldType`
|
|
439
|
+
|
|
440
|
+
```ts
|
|
441
|
+
type M8FieldType =
|
|
442
|
+
| 'chainRef' // chain number hex 00–FF; '--' = empty
|
|
443
|
+
| 'phraseRef' // phrase number hex 00–FF; '--' = empty
|
|
444
|
+
| 'note' // note name e.g. 'C-4', 'A#3'; '---' = empty; 'OFF' = note-off
|
|
445
|
+
| 'velocity' // velocity 00–7F hex; '--' = inherit
|
|
446
|
+
| 'instrumentRef' // instrument slot 00–7F hex; '--' = keep current
|
|
447
|
+
| 'fxCommand' // 3-char command name e.g. 'KIL'; '---' = none
|
|
448
|
+
| 'fxValue' // FX parameter 00–FF hex
|
|
449
|
+
| 'transpose' // relative semitones: 00=none, 01–7F=+1–+127, FF–80=−1–−128
|
|
450
|
+
| 'ticks' // groove ticks per step hex; '00'=skip; '--'=end of groove
|
|
451
|
+
| 'volume' // level 00–FF hex; '--' = no override
|
|
452
|
+
| 'ppq' // pulses-per-quarter-note decimal; groove row 0 only
|
|
453
|
+
| 'instrumentName' // instrument name text ≤12 chars; no hexValue
|
|
454
|
+
| 'eqSlot' // EQ slot assignment hex; '--' = none
|
|
455
|
+
| 'rowIndex' // row/step index hex
|
|
456
|
+
```
|
|
457
|
+
|
|
458
|
+
### `M8KeyName`
|
|
459
|
+
|
|
460
|
+
```ts
|
|
461
|
+
type M8KeyName = 'left' | 'right' | 'up' | 'down' | 'shift' | 'play' | 'opt' | 'edit'
|
|
462
|
+
```
|
|
463
|
+
|
|
464
|
+
### `M8SdkConfig`
|
|
465
|
+
|
|
466
|
+
```ts
|
|
467
|
+
interface M8SdkConfig {
|
|
468
|
+
debug?: boolean // enables verbose postMessage logging via DebugMessenger
|
|
469
|
+
}
|
|
470
|
+
```
|
|
471
|
+
|
|
472
|
+
---
|
|
473
|
+
|
|
474
|
+
## Host events
|
|
475
|
+
|
|
476
|
+
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.
|
|
477
|
+
|
|
478
|
+
| Event | Payload | Fired when |
|
|
479
|
+
|---|---|---|
|
|
480
|
+
| `stateChanged` | `M8State` | Any state field changes |
|
|
481
|
+
| `viewChanged` | `{ viewName, viewTitle }` | The active view changes |
|
|
482
|
+
| `cursorMoved` | `{ pos, rect, selectionMode }` | Cursor position or selection mode changes |
|
|
483
|
+
| `textUpdated` | `{ textUnderCursor, currentLine }` | Text under cursor or current line changes |
|
|
484
|
+
| `keyPressed` | `{ keys: number }` | A physical key event fires on the device |
|
|
485
|
+
|
|
486
|
+
---
|
|
487
|
+
|
|
488
|
+
## Supported views
|
|
489
|
+
|
|
490
|
+
The semantic context parser supports these `viewName` values:
|
|
491
|
+
|
|
492
|
+
| `viewName` | Columns |
|
|
493
|
+
|---|---|
|
|
494
|
+
| `song` | `track1`–`track8` (chainRef, meta: trackIndex 1–8) |
|
|
495
|
+
| `chain` | `phrase` (phraseRef), `transpose` |
|
|
496
|
+
| `phrase` | `note`, `vel`, `inst`, `fx1cmd`, `fx1val`, `fx2cmd`, `fx2val`, `fx3cmd`, `fx3val` |
|
|
497
|
+
| `table` | `transpose`, `volume`, `fx1cmd`, `fx1val`, `fx2cmd`, `fx2val`, `fx3cmd`, `fx3val` |
|
|
498
|
+
| `groove` | `ticks`, `ppq` (row 0 only) |
|
|
499
|
+
| `instrumentpool` | `name`, `dry`, `mx`, `de`, `rv`, `eq` |
|
|
500
|
+
|
|
501
|
+
All other views (`isGridView: false`) return a context with `row: null` and `activeField: null`.
|
|
502
|
+
|
|
503
|
+
---
|
|
504
|
+
|
|
505
|
+
## Notes
|
|
506
|
+
|
|
507
|
+
- **Playback indicator** — When a row is actively playing the M8 prepends `<` or `>` to `currentLine`. The SDK strips this automatically before parsing so column offsets remain consistent.
|
|
508
|
+
- **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.
|
|
509
|
+
- **Schema file** — View and column definitions live in `src/m8-view-context.json` and are bundled into `dist/index.js` at build time.
|
|
510
|
+
- **Requirement** — The app must run inside the yam8d host iframe. The `ChildHandshake` from `post-me` will never resolve in a standalone tab.
|
|
511
|
+
|
|
512
|
+
|
|
513
|
+
Client SDK for iframe applications that communicate with an M8 host.
|
|
514
|
+
|
|
515
|
+
## Install
|
|
516
|
+
|
|
517
|
+
```bash
|
|
518
|
+
npm install @yam8d/m8-sdk
|
|
519
|
+
```
|
|
520
|
+
|
|
521
|
+
For local development before the package is published:
|
|
522
|
+
|
|
523
|
+
```bash
|
|
524
|
+
npm install ../yam8d/packages/m8-sdk
|
|
525
|
+
```
|
|
526
|
+
|
|
527
|
+
## Usage
|
|
528
|
+
|
|
529
|
+
```ts
|
|
530
|
+
import { createM8Client } from '@yam8d/m8-sdk'
|
|
531
|
+
|
|
532
|
+
const m8 = await createM8Client()
|
|
533
|
+
|
|
534
|
+
console.log(m8.state.viewName)
|
|
535
|
+
await m8.sendKeyPress(['play'])
|
|
536
|
+
|
|
537
|
+
const unsubscribe = m8.onStateChange((state) => {
|
|
538
|
+
console.log(state.cursorPos)
|
|
539
|
+
})
|
|
540
|
+
|
|
541
|
+
unsubscribe()
|
|
542
|
+
m8.disconnect()
|
|
543
|
+
```
|
|
544
|
+
|
|
545
|
+
For React or other environments where top-level `await` is not convenient:
|
|
546
|
+
|
|
547
|
+
```ts
|
|
548
|
+
import { createM8ClientSync } from '@yam8d/m8-sdk'
|
|
549
|
+
|
|
550
|
+
const { client, connect } = createM8ClientSync()
|
|
551
|
+
await connect()
|
|
552
|
+
```
|
|
553
|
+
|
|
554
|
+
The app must run inside the host iframe. Opening it directly in a standalone browser tab cannot establish the SDK handshake.
|
|
555
|
+
|
|
556
|
+
---
|
|
557
|
+
|
|
558
|
+
## Semantic Context
|
|
559
|
+
|
|
560
|
+
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.
|
|
561
|
+
|
|
562
|
+
### Via the client
|
|
563
|
+
|
|
564
|
+
```ts
|
|
565
|
+
const ctx = m8.getSemanticContext()
|
|
566
|
+
// or as a human-readable string:
|
|
567
|
+
const description = m8.describeContext()
|
|
568
|
+
// e.g. "Track 3 — Chain 0A — Song View row 02"
|
|
569
|
+
```
|
|
570
|
+
|
|
571
|
+
### Standalone functions
|
|
572
|
+
|
|
573
|
+
```ts
|
|
574
|
+
import { getSemanticContext, describeContext, formatFieldValue, lookupFxCommand } from '@yam8d/m8-sdk'
|
|
575
|
+
|
|
576
|
+
const ctx = getSemanticContext(state)
|
|
577
|
+
|
|
578
|
+
if (ctx?.activeField) {
|
|
579
|
+
const field = ctx.activeField
|
|
580
|
+
|
|
581
|
+
console.log(field.key) // e.g. 'track3', 'note', 'fx1cmd'
|
|
582
|
+
console.log(field.label) // e.g. 'Track 3', 'Note (N)', 'FX1 Command'
|
|
583
|
+
console.log(field.type) // e.g. 'chainRef', 'note', 'fxCommand'
|
|
584
|
+
console.log(field.rawValue) // raw text from the line, e.g. '0A', 'C-4', 'KIL'
|
|
585
|
+
console.log(field.hexValue) // parsed integer or null (null for empty cells and text types)
|
|
586
|
+
console.log(field.isEmpty) // true when '--' / '---' / '---00'
|
|
587
|
+
|
|
588
|
+
// Song view: which track is the cursor on?
|
|
589
|
+
if (field.type === 'chainRef') {
|
|
590
|
+
console.log(field.meta?.trackIndex) // 1–8
|
|
591
|
+
}
|
|
592
|
+
|
|
593
|
+
// FX command: look up its description
|
|
594
|
+
if (field.type === 'fxCommand' && !field.isEmpty) {
|
|
595
|
+
const info = lookupFxCommand(field.rawValue)
|
|
596
|
+
console.log(info?.description)
|
|
597
|
+
}
|
|
598
|
+
}
|
|
599
|
+
|
|
600
|
+
// Row-level fields (all columns on the current line)
|
|
601
|
+
if (ctx?.row) {
|
|
602
|
+
console.log(ctx.row.rowIndex) // hex-parsed row index, e.g. 2
|
|
603
|
+
console.log(ctx.row.fields['note']) // M8ParsedField for the note column
|
|
604
|
+
}
|
|
605
|
+
```
|
|
606
|
+
|
|
607
|
+
### Returned types
|
|
608
|
+
|
|
609
|
+
```ts
|
|
610
|
+
interface M8SemanticContext {
|
|
611
|
+
viewName: string // e.g. 'song', 'phrase'
|
|
612
|
+
viewTitle: string | null // e.g. 'Song View'
|
|
613
|
+
viewDescription: string | null
|
|
614
|
+
isGridView: boolean
|
|
615
|
+
row: M8ParsedRow | null // all columns on the cursor's line
|
|
616
|
+
activeField: M8ActiveField | null // the column under the cursor
|
|
617
|
+
}
|
|
618
|
+
|
|
619
|
+
interface M8ParsedField {
|
|
620
|
+
key: string // schema key, e.g. 'fx1cmd', 'track3'
|
|
621
|
+
label: string // human label, e.g. 'FX1 Command', 'Track 3'
|
|
622
|
+
type: M8FieldType
|
|
623
|
+
rawValue: string // trimmed text from currentLine
|
|
624
|
+
hexValue: number | null
|
|
625
|
+
isEmpty: boolean
|
|
626
|
+
meta?: Record<string, unknown>
|
|
627
|
+
}
|
|
628
|
+
```
|
|
629
|
+
|
|
630
|
+
### Supported field types (`M8FieldType`)
|
|
631
|
+
|
|
632
|
+
| Type | Description |
|
|
633
|
+
|---|---|
|
|
634
|
+
| `chainRef` | Chain number (hex). `--` = empty. |
|
|
635
|
+
| `phraseRef` | Phrase number (hex). `--` = empty. |
|
|
636
|
+
| `note` | Note name, e.g. `C-4`, `A#3`. `---` = empty. |
|
|
637
|
+
| `velocity` | Note velocity 00–7F (hex). |
|
|
638
|
+
| `instrumentRef` | Instrument slot 00–7F (hex). `--` = keep current. |
|
|
639
|
+
| `fxCommand` | 3-char FX command name, e.g. `KIL`, `ARP`. `---` = none. |
|
|
640
|
+
| `fxValue` | FX value 00–FF (hex). |
|
|
641
|
+
| `transpose` | Relative transpose (hex): `00`=none, `01`–`7F`=+1–+127, `FF`–`80`=−1–−128. |
|
|
642
|
+
| `ticks` | Groove ticks per step (hex). |
|
|
643
|
+
| `volume` | Volume level 00–FF (hex). `--` = no override. |
|
|
644
|
+
| `ppq` | Pulses Per Quarter note (decimal). Groove row 0 only. |
|
|
645
|
+
| `instrumentName` | Instrument name text (up to 12 chars). No `hexValue`. |
|
|
646
|
+
| `eqSlot` | EQ slot assignment (hex). `--` = none. |
|
|
647
|
+
| `rowIndex` | Row/step index (hex). |
|
|
648
|
+
|
|
649
|
+
### Playback indicator
|
|
650
|
+
|
|
651
|
+
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.
|
|
652
|
+
|
|
653
|
+
### Notes file
|
|
654
|
+
|
|
655
|
+
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.
|