@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.
Files changed (37) hide show
  1. package/README.md +898 -796
  2. package/dist/index.js +3411 -363
  3. package/dist/index.js.map +1 -1
  4. package/package.json +1 -1
  5. package/types/client.d.ts +58 -2
  6. package/types/engine/astar.d.ts +15 -0
  7. package/types/engine/audio.d.ts +29 -0
  8. package/types/engine/bus.d.ts +13 -0
  9. package/types/engine/connectWithRetry.d.ts +9 -0
  10. package/types/engine/connection.d.ts +56 -0
  11. package/types/engine/cursorNavigator.d.ts +15 -0
  12. package/types/engine/engine.d.ts +30 -0
  13. package/types/engine/eventBus.d.ts +5 -0
  14. package/types/engine/host/fileBrowserHelpers.d.ts +59 -0
  15. package/types/engine/host/fileBrowserSearch.d.ts +13 -0
  16. package/types/engine/host/floatEditor.d.ts +2 -0
  17. package/types/engine/host/hostIO.d.ts +30 -0
  18. package/types/engine/host/hostMethods.d.ts +22 -0
  19. package/types/engine/host/hostState.d.ts +7 -0
  20. package/types/engine/host/keySequences.d.ts +2 -0
  21. package/types/engine/host/midiTransport.d.ts +10 -0
  22. package/types/engine/host/valueEditors.d.ts +7 -0
  23. package/types/engine/host/valueParsers.d.ts +24 -0
  24. package/types/engine/index.d.ts +14 -0
  25. package/types/engine/keys.d.ts +28 -0
  26. package/types/engine/macroRunner.d.ts +15 -0
  27. package/types/engine/protocol.d.ts +138 -0
  28. package/types/engine/queuedReaders.d.ts +18 -0
  29. package/types/engine/store.d.ts +65 -0
  30. package/types/engine/viewExtractor.d.ts +27 -0
  31. package/types/engine/viewGraph.d.ts +9 -0
  32. package/types/engine/viewNavigation.d.ts +16 -0
  33. package/types/index.d.ts +9 -3
  34. package/types/scaleInsight.d.ts +1 -1
  35. package/types/theme.d.ts +42 -0
  36. package/types/types.d.ts +124 -0
  37. package/types/viewContext.d.ts +37 -0
@@ -0,0 +1,138 @@
1
+ export type RectCommand = ReturnType<typeof parseRectCommand>;
2
+ declare const parseRectCommand: (data: DataView, length: number) => {
3
+ readonly type: 1;
4
+ readonly pos: {
5
+ readonly x: number;
6
+ readonly y: number;
7
+ };
8
+ readonly size: {
9
+ readonly width: 1;
10
+ readonly height: 1;
11
+ };
12
+ readonly color?: undefined;
13
+ } | {
14
+ readonly type: 2;
15
+ readonly pos: {
16
+ readonly x: number;
17
+ readonly y: number;
18
+ };
19
+ readonly size: {
20
+ readonly width: 1;
21
+ readonly height: 1;
22
+ };
23
+ readonly color: {
24
+ readonly r: number;
25
+ readonly g: number;
26
+ readonly b: number;
27
+ };
28
+ } | {
29
+ readonly type: 3;
30
+ readonly pos: {
31
+ readonly x: number;
32
+ readonly y: number;
33
+ };
34
+ readonly size: {
35
+ readonly width: number;
36
+ readonly height: number;
37
+ };
38
+ readonly color?: undefined;
39
+ } | {
40
+ readonly type: 4;
41
+ readonly pos: {
42
+ readonly x: number;
43
+ readonly y: number;
44
+ };
45
+ readonly size: {
46
+ readonly width: number;
47
+ readonly height: number;
48
+ };
49
+ readonly color: {
50
+ readonly r: number;
51
+ readonly g: number;
52
+ readonly b: number;
53
+ };
54
+ } | {
55
+ readonly type: 0;
56
+ readonly pos: {
57
+ readonly x: number;
58
+ readonly y: number;
59
+ };
60
+ readonly size: {
61
+ readonly width: 0;
62
+ readonly height: 0;
63
+ };
64
+ readonly color?: undefined;
65
+ };
66
+ export type CharacterCommand = ReturnType<typeof parseCharacterCommand>;
67
+ declare const parseCharacterCommand: (data: DataView, length: number) => {
68
+ character: string;
69
+ pos: {
70
+ x: number;
71
+ y: number;
72
+ };
73
+ foreground: {
74
+ r: number;
75
+ g: number;
76
+ b: number;
77
+ };
78
+ background: {
79
+ r: number;
80
+ g: number;
81
+ b: number;
82
+ };
83
+ };
84
+ export type WaveCommand = ReturnType<typeof parseWaveCommand>;
85
+ declare const parseWaveCommand: (data: DataView, length: number) => {
86
+ color: {
87
+ r: number;
88
+ g: number;
89
+ b: number;
90
+ };
91
+ wave: number[];
92
+ };
93
+ export type KeyCommand = ReturnType<typeof parseKeyCommand>;
94
+ declare const parseKeyCommand: (data: DataView, length: number) => {
95
+ keys: number;
96
+ };
97
+ export type SystemCommand = ReturnType<typeof parseSystemCommand>;
98
+ declare const parseSystemCommand: (data: DataView, length: number) => {
99
+ model: "Headless" | "Beta M8" | "M8 Model:01" | "M8 Model:02";
100
+ version: string;
101
+ fontMode: 0 | 1 | 2;
102
+ };
103
+ /** System command plus the layout metrics derived from the model and font mode. */
104
+ export type SystemInfos = {
105
+ model: SystemCommand['model'];
106
+ fontMode: SystemCommand['fontMode'];
107
+ spacingX: number;
108
+ spacingY: number;
109
+ offX: number;
110
+ offY: number;
111
+ screenWidth: number;
112
+ screenHeight: number;
113
+ rectOffset: number;
114
+ };
115
+ export type ProtocolEvents = {
116
+ rect: (data: RectCommand) => void;
117
+ text: (data: CharacterCommand) => void;
118
+ wave: (data: WaveCommand) => void;
119
+ key: (data: KeyCommand) => void;
120
+ system: (data: SystemCommand) => void;
121
+ systemInfo: (data: SystemInfos) => void;
122
+ any: (data: Uint8Array<ArrayBufferLike>) => void;
123
+ };
124
+ export declare const protocol: () => {
125
+ eventBus: {
126
+ emit: <K extends keyof ProtocolEvents>(event: K, ...args: Parameters<ProtocolEvents[K]>) => void;
127
+ on: <K extends keyof ProtocolEvents>(event: K, callback: ProtocolEvents[K]) => void;
128
+ off: <K extends keyof ProtocolEvents>(event: K, callback: ProtocolEvents[K]) => void;
129
+ };
130
+ dispatch: (data: DataView, dataLength: number) => void;
131
+ getSystemInfo: () => {
132
+ model: "Headless" | "Beta M8" | "M8 Model:01" | "M8 Model:02";
133
+ version: string;
134
+ fontMode: 0 | 1 | 2;
135
+ } | undefined;
136
+ getSystemInfos: () => SystemInfos;
137
+ };
138
+ export {};
@@ -0,0 +1,18 @@
1
+ export type SlipState = 'read' | 'escape' | 'error';
2
+ export declare const createSlipParser: () => {
3
+ feed: (byte: number) => DataView | undefined;
4
+ };
5
+ export declare const queuedUsbReader: (device: USBDevice) => () => Promise<{
6
+ terminated: true;
7
+ data?: undefined;
8
+ } | {
9
+ terminated: false;
10
+ data: DataView<ArrayBufferLike>;
11
+ }>;
12
+ export declare const queuedSerialReader: (reader: ReadableStreamDefaultReader<Uint8Array<ArrayBufferLike>>) => () => Promise<{
13
+ terminated: true;
14
+ data?: undefined;
15
+ } | {
16
+ terminated: false;
17
+ data: DataView<ArrayBufferLike>;
18
+ }>;
@@ -0,0 +1,65 @@
1
+ import type { CursorPos, CursorRect, RGB } from '../types';
2
+ /** Model, font mode and the layout metrics derived from them (from the M8's system command). */
3
+ export interface M8SystemInfo {
4
+ model: string;
5
+ fontMode: number;
6
+ spacingX: number;
7
+ spacingY: number;
8
+ offX: number;
9
+ offY: number;
10
+ screenWidth: number;
11
+ screenHeight: number;
12
+ rectOffset: number;
13
+ }
14
+ export interface M8CellMetrics {
15
+ cellW: number;
16
+ cellH: number;
17
+ offX: number;
18
+ offY: number;
19
+ }
20
+ export interface M8MacroStatus {
21
+ running: boolean;
22
+ currentStep?: number;
23
+ sequenceLength?: number;
24
+ }
25
+ export interface M8StoreState {
26
+ /** Current view name (lowercased, cleaned). */
27
+ viewName: string | null;
28
+ /** Raw title row text. */
29
+ viewTitle: string | null;
30
+ minimapKey: string | null;
31
+ /** Cursor position in character grid coordinates (0..39, 0..23). */
32
+ cursorPos: CursorPos | null;
33
+ /** Cursor rectangle in M8 pixel space. */
34
+ cursorRect: CursorRect | null;
35
+ /** Whether the cursor is drawn as a 1px selection rectangle. */
36
+ selectionMode: boolean;
37
+ /** Foreground colour of the text under the cursor. */
38
+ highlightColor: RGB | null;
39
+ /** Colour of the cursor rectangle corners as last drawn. */
40
+ cursorColor: RGB | null;
41
+ textUnderCursor: string | null;
42
+ /** The whole screen row the cursor is on. */
43
+ currentLine: string | null;
44
+ titleColor: RGB | null;
45
+ backgroundColor: RGB | null;
46
+ deviceModel: string | null;
47
+ fontMode: number | null;
48
+ /** Pixel size and offset of one character cell (model and font dependent). */
49
+ cellMetrics: M8CellMetrics;
50
+ systemInfo: M8SystemInfo | null;
51
+ macroStatus: M8MacroStatus;
52
+ }
53
+ export type M8StoreKey = keyof M8StoreState;
54
+ export interface M8Store {
55
+ get<K extends M8StoreKey>(key: K): M8StoreState[K];
56
+ /** Sets a value; listeners run synchronously, and only when the value changed (by identity). */
57
+ set<K extends M8StoreKey>(key: K, value: M8StoreState[K]): void;
58
+ /** Listens to one key. Returns the unsubscribe function. */
59
+ sub(key: M8StoreKey, listener: () => void): () => void;
60
+ /** Listens to every key. Returns the unsubscribe function. */
61
+ subAll(listener: (key: M8StoreKey) => void): () => void;
62
+ snapshot(): M8StoreState;
63
+ }
64
+ export declare const initialStoreState: () => M8StoreState;
65
+ export declare const createM8Store: (initial?: Partial<M8StoreState>) => M8Store;
@@ -0,0 +1,27 @@
1
+ import type { CharacterCommand, RectCommand, SystemInfos } from './protocol';
2
+ import type { M8Store } from './store';
3
+ export declare const SCREEN_ROWS = 24;
4
+ export declare const SCREEN_COLUMNS = 40;
5
+ /** Colour of every character cell, row by row ('#rrggbb', null where nothing was drawn). */
6
+ export type ScreenCellColors = {
7
+ fg: (string | null)[][];
8
+ bg: (string | null)[][];
9
+ };
10
+ /** The protocol events the extractor listens to (a ConnectedBus's protocol.eventBus fits). */
11
+ export interface DrawEventSource {
12
+ on(event: 'text', callback: (data: CharacterCommand) => void): void;
13
+ on(event: 'rect', callback: (data: RectCommand) => void): void;
14
+ on(event: 'systemInfo', callback: (data: SystemInfos) => void): void;
15
+ off(event: 'text', callback: (data: CharacterCommand) => void): void;
16
+ off(event: 'rect', callback: (data: RectCommand) => void): void;
17
+ off(event: 'systemInfo', callback: (data: SystemInfos) => void): void;
18
+ }
19
+ export interface ViewExtractor {
20
+ /** Whole M8 screen as text rows, one string per row (columns preserved, trailing spaces trimmed). */
21
+ getScreenLines(): string[];
22
+ /** Colours of the whole M8 screen, same grid as getScreenLines. */
23
+ getScreenCellColors(): ScreenCellColors;
24
+ dispose(): void;
25
+ }
26
+ /** Listens to the draw stream and keeps `store` up to date. */
27
+ export declare function registerViewExtractor(events: DrawEventSource, store: M8Store): ViewExtractor;
@@ -0,0 +1,9 @@
1
+ import type { Graph } from './astar';
2
+ export interface M8ViewGraph {
3
+ graph: Graph;
4
+ /** Explicit key frame sequences per edge: from -> to -> frames. */
5
+ edgeKeys: Map<string, Map<string, number[]>>;
6
+ }
7
+ export declare const getViewGraph: () => M8ViewGraph;
8
+ /** Names of the M8 views, lowercased (as viewName reports them). */
9
+ export declare const getViewNames: () => Set<string>;
@@ -0,0 +1,16 @@
1
+ import { type Direction } from './astar';
2
+ import type { MacroRunner } from './macroRunner';
3
+ import type { M8Store } from './store';
4
+ /** Shift+direction frames, used for edges without an explicit key sequence. */
5
+ export declare const toKeyMasks: (directions: Direction[], opts?: {
6
+ shift?: boolean;
7
+ }) => number[];
8
+ export interface ViewNavigation {
9
+ /** Resolves when the view is reached, no path is found, or the step limit is hit. */
10
+ navigateToView(viewName: string): Promise<void>;
11
+ dispose(): void;
12
+ }
13
+ export declare const createViewNavigation: ({ store, runner }: {
14
+ store: M8Store;
15
+ runner: MacroRunner;
16
+ }) => ViewNavigation;
package/types/index.d.ts CHANGED
@@ -1,4 +1,10 @@
1
- export type { CursorPos, CursorRect, M8ClientEvents, M8ClientMethods, M8HostEvents, M8HostMethods, M8KeyName, M8Screen, M8SdkConfig, M8State, RGB, SystemInfos, } from './types';
2
- export { createM8Client, createM8ClientSync, type M8Client, type M8Snapshot } from './client';
3
- export { getSemanticContext, describeContext, describeContextParts, formatFieldValue, lookupFxCommand, getInstrumentFxCommands, RELATIVE_FX_RULE, type M8FieldType, type M8ParsedField, type M8ParsedRow, type M8ActiveField, type M8SemanticContext, type M8DescriptionPart, type M8DescriptionPartKind, type M8FileKind, type M8FxCommandInfo, type M8InstrumentFxList, type M8FxLaneTrace, type M8RelativeFxState, type M8ScaleInfo, type M8ScaleNote, } from './viewContext';
1
+ export type { CursorPos, CursorRect, M8ClientEvents, M8ClientMethods, M8ClientMode, M8DeviceBus, M8DeviceConnectOptions, M8DeviceEvents, M8DeviceSupport, M8DeviceTransport, M8HostEvents, M8HostMethods, M8KeyName, M8Screen, M8ScreenCells, M8SdkConfig, M8State, M8TransportEvent, RGB, SystemInfos, } from './types';
2
+ export { createM8Client, createM8ClientSync, getDeviceSupport, type M8Client, type M8Snapshot, YAM8D_FRAME_NAME } from './client';
3
+ export { connection as createM8Connection, type ConnectedBus as M8ConnectedBus, type M8ConnectOptions } from './engine/connection';
4
+ export { createM8Audio, getM8InputStream, type M8Audio, M8_AUDIO_CAPTURE_CONSTRAINTS } from './engine/audio';
5
+ export { createM8Engine, type M8Engine, type M8EngineOptions } from './engine/engine';
6
+ export { M8KeyMask, pressKeys } from './engine/keys';
7
+ export type { M8MacroStatus, M8Store, M8StoreKey, M8StoreState } from './engine/store';
8
+ export { getSemanticContext, parseGridScreen, readPlayPositions, parseViewTitle, SCREEN_MARGIN, describeContext, describeContextParts, formatFieldValue, lookupFxCommand, getInstrumentFxCommands, RELATIVE_FX_RULE, type M8FieldType, type M8ParsedField, type M8ParsedRow, type M8ScreenGridRow, type M8PlayPositions, type M8ActiveField, type M8SemanticContext, type M8DescriptionPart, type M8DescriptionPartKind, type M8FileKind, type M8FxCommandInfo, type M8InstrumentFxList, type M8FxLaneTrace, type M8RelativeFxState, type M8ScaleInfo, type M8ScaleNote, } from './viewContext';
9
+ export { parseThemeScreen, observeThemeColors, M8_THEME_ROLES, M8_THEME_VIEW, type M8Theme, type M8ThemeRole, } from './theme';
4
10
  export { default } from './client';
@@ -11,7 +11,7 @@ export interface M8ScaleNote {
11
11
  interval: string;
12
12
  /** Semitones above the first row ('C'), 0-11. */
13
13
  semitone: number;
14
- /** true = ON, false = OFF, null = unset ('--'). */
14
+ /** true = ON, false = out of the scale ('--' on the device, 'OFF' if shown), null = not read. */
15
15
  enabled: boolean | null;
16
16
  /** Detune in semitones (-24..+24), null when unreadable. */
17
17
  offset: number | null;
@@ -0,0 +1,42 @@
1
+ /**
2
+ * theme.ts
3
+ *
4
+ * The device theme: the 13 colours of the Theme View (System Settings > EDIT THEME),
5
+ * read off its screen, and the few colours any screen shows on its own.
6
+ */
7
+ import type { M8Screen, M8State } from './types';
8
+ export type M8ThemeRole = 'background' | 'textEmpty' | 'textInfo' | 'textDefault' | 'textValue' | 'textTitles' | 'playMarkers' | 'cursor' | 'selection' | 'scopeSlider' | 'meterLow' | 'meterMid' | 'meterPeak';
9
+ /** The Theme View rows, top to bottom, with the label the device shows. */
10
+ export declare const M8_THEME_ROLES: readonly {
11
+ role: M8ThemeRole;
12
+ label: string;
13
+ }[];
14
+ /** View name the SDK reports on the Theme View (title "THEME SETTINGS"). */
15
+ export declare const M8_THEME_VIEW = "themesettings";
16
+ export interface M8Theme {
17
+ /** Every theme colour as '#rrggbb'. */
18
+ colors: Record<M8ThemeRole, string>;
19
+ /** Colour model the Theme View was showing. HSV values are converted to RGB (H, S and V read as 00-FF). */
20
+ mode: 'rgb' | 'hsv';
21
+ /** THEME NAME, or null when unnamed. */
22
+ name: string | null;
23
+ }
24
+ /**
25
+ * Reads the theme off the Theme View screen (from getScreen()). Returns null unless
26
+ * all 13 colour rows are found, so a half-drawn screen is never taken for a theme.
27
+ *
28
+ * @example
29
+ * ```ts
30
+ * if (m8.state.viewName === M8_THEME_VIEW) {
31
+ * const theme = parseThemeScreen(await m8.getScreen())
32
+ * theme?.colors.textValue // '#ffffff'
33
+ * }
34
+ * ```
35
+ */
36
+ export declare function parseThemeScreen(screen: M8Screen): M8Theme | null;
37
+ /**
38
+ * The theme colours any screen gives away without opening the Theme View: the
39
+ * background and the title colour. (The cursor rectangle is animated on the device, so
40
+ * `state.cursorColor` is not the theme's CURSOR colour.)
41
+ */
42
+ export declare function observeThemeColors(state: M8State): Partial<Record<M8ThemeRole, string>>;
package/types/types.d.ts CHANGED
@@ -26,6 +26,11 @@ export interface M8State {
26
26
  highlightColor: RGB | null;
27
27
  titleColor: RGB | null;
28
28
  backgroundColor: RGB | null;
29
+ /**
30
+ * Colour of the cursor rectangle as last drawn. The M8 animates the cursor, so it is not
31
+ * the theme's CURSOR colour (read that with parseThemeScreen). Absent before SDK 0.5.
32
+ */
33
+ cursorColor?: RGB | null;
29
34
  textUnderCursor: string | null;
30
35
  currentLine: string | null;
31
36
  deviceModel: string | null;
@@ -41,7 +46,32 @@ export interface M8Screen {
41
46
  /** One string per screen row; columns preserved, trailing spaces trimmed. */
42
47
  lines: string[];
43
48
  }
49
+ /**
50
+ * The screen with the colour of every character cell, for apps that want to look like
51
+ * the device (or learn which theme colour the M8 uses for what).
52
+ */
53
+ export interface M8ScreenCells extends M8Screen {
54
+ /** fg[row][col]: foreground colour as '#rrggbb'; null where no character was drawn. Each row has `width` entries. */
55
+ fg: (string | null)[][];
56
+ /** bg[row][col]: background colour behind the character, same layout as `fg`. */
57
+ bg: (string | null)[][];
58
+ }
44
59
  export type M8KeyName = 'left' | 'right' | 'up' | 'down' | 'shift' | 'play' | 'opt' | 'edit';
60
+ /**
61
+ * MIDI clock / transport from the M8 (SYNC OUT in its MIDI Settings, stored per song).
62
+ * `at` is epoch milliseconds (performance.timeOrigin + timestamp), comparable across frames.
63
+ * `clock` comes once per sixteenth (6 ticks at 24 PPQ); `ticks` counts clock ticks since the
64
+ * host started listening (monotonic: the M8 can send clock while stopped and without Start);
65
+ * `msPerTick` is the measured tick length, null until known.
66
+ */
67
+ export interface M8TransportEvent {
68
+ kind: 'start' | 'stop' | 'continue' | 'clock' | 'position';
69
+ ticks: number;
70
+ at: number;
71
+ msPerTick: number | null;
72
+ /** Song Position Pointer (sixteenths), on 'position' events. */
73
+ spp?: number;
74
+ }
45
75
  export interface M8HostMethods {
46
76
  navigateToView(viewName: string): Promise<boolean>;
47
77
  navigateTo(x: number, y: number): Promise<void>;
@@ -56,6 +86,11 @@ export interface M8HostMethods {
56
86
  sendKeyUp(): Promise<void>;
57
87
  getState(): Promise<M8State>;
58
88
  getScreen(): Promise<M8Screen>;
89
+ getScreenCells(): Promise<M8ScreenCells>;
90
+ enableMidiTransport(): Promise<{
91
+ ok: boolean;
92
+ error?: string;
93
+ }>;
59
94
  }
60
95
  export interface M8ClientMethods {
61
96
  ping(): Promise<string>;
@@ -78,6 +113,7 @@ export interface M8HostEvents {
78
113
  keyPressed: {
79
114
  keys: number;
80
115
  };
116
+ transport: M8TransportEvent;
81
117
  }
82
118
  export interface M8ClientEvents {
83
119
  ready: undefined;
@@ -85,6 +121,94 @@ export interface M8ClientEvents {
85
121
  message: string;
86
122
  };
87
123
  }
124
+ /** 'host': yam8d owns the M8 and the app talks to it from an iframe. 'standalone': the app connects to the M8 itself. */
125
+ export type M8ClientMode = 'host' | 'standalone';
126
+ /** The browser API a standalone client uses to reach the M8. */
127
+ export type M8DeviceTransport = 'serial' | 'usb' | 'midi';
88
128
  export interface M8SdkConfig {
89
129
  debug?: boolean;
130
+ /**
131
+ * 'auto' (default): use the yam8d host when the page runs in a yam8d External Apps iframe,
132
+ * otherwise connect to the M8 from this page. 'host' and 'standalone' force one mode.
133
+ */
134
+ mode?: 'auto' | M8ClientMode;
135
+ /**
136
+ * Auto mode in an iframe that yam8d did not mark: how long to wait for a host handshake
137
+ * before going standalone. Default 3000 ms.
138
+ */
139
+ handshakeTimeoutMs?: number;
140
+ /**
141
+ * Standalone: at connect, reopen a device this page was granted before, without a prompt.
142
+ * Default true. A first connection always needs connectDevice() from a user gesture.
143
+ */
144
+ autoConnect?: boolean;
145
+ }
146
+ export interface M8DeviceConnectOptions {
147
+ /** 'auto' (default) picks Web Serial, then WebUSB, then Web MIDI SysEx, by browser support. */
148
+ transport?: 'auto' | M8DeviceTransport;
149
+ /** Also play the M8's USB audio through the page (see M8Client.connectAudio). Default false. */
150
+ audio?: boolean;
151
+ }
152
+ /** Which device APIs this browser offers for standalone mode. */
153
+ export interface M8DeviceSupport {
154
+ serial: boolean;
155
+ usb: boolean;
156
+ midi: boolean;
157
+ }
158
+ /**
159
+ * Low-level access to the M8 a standalone client is connected to: the raw draw / key /
160
+ * system events and the device commands. For apps that render the screen themselves or
161
+ * play notes; everything else is simpler through the client methods.
162
+ */
163
+ export interface M8DeviceBus {
164
+ readonly transport: M8DeviceTransport;
165
+ readonly events: {
166
+ on<K extends keyof M8DeviceEvents>(event: K, callback: (data: M8DeviceEvents[K]) => void): void;
167
+ off<K extends keyof M8DeviceEvents>(event: K, callback: (data: M8DeviceEvents[K]) => void): void;
168
+ };
169
+ readonly commands: {
170
+ /** Raw key mask (see M8KeyMask); 0 releases every key. */
171
+ sendKeys(keys: number): void;
172
+ /** Asks the M8 to redraw the whole screen. */
173
+ resetScreen(): void;
174
+ sendNoteOn(note: number, velocity: number): void;
175
+ sendNoteOff(): void;
176
+ sendProgramChange(program: number, channel?: number): void;
177
+ };
178
+ }
179
+ /** Payloads of the M8 display protocol, in M8 pixel coordinates. */
180
+ export interface M8DeviceEvents {
181
+ text: {
182
+ character: string;
183
+ pos: {
184
+ x: number;
185
+ y: number;
186
+ };
187
+ foreground: RGB;
188
+ background: RGB;
189
+ };
190
+ rect: {
191
+ type: number;
192
+ pos: {
193
+ x: number;
194
+ y: number;
195
+ };
196
+ size: {
197
+ width: number;
198
+ height: number;
199
+ };
200
+ color?: RGB;
201
+ };
202
+ wave: {
203
+ color: RGB;
204
+ wave: number[];
205
+ };
206
+ key: {
207
+ keys: number;
208
+ };
209
+ system: {
210
+ model: string;
211
+ version: string;
212
+ fontMode: 0 | 1 | 2;
213
+ };
90
214
  }
@@ -219,6 +219,12 @@ export interface M8DescriptionPart {
219
219
  kind: M8DescriptionPartKind;
220
220
  text: string;
221
221
  }
222
+ /** The item number of a screen title and the marker after it ('PHRASE 1F*', 'PHRASE 27^'). */
223
+ export declare function parseViewTitle(rawTitle: string | null): {
224
+ viewId: string | null;
225
+ isUnsaved: boolean;
226
+ marker: '*' | '^' | null;
227
+ };
222
228
  /**
223
229
  * Every screen row has a one-column left margin (the playback marker '<' / '>' is drawn
224
230
  * in it), so the schema's x positions are counted from column 1, and `cursorPos.x` is
@@ -226,6 +232,37 @@ export interface M8DescriptionPart {
226
232
  * already starts at the schema's column 0.
227
233
  */
228
234
  export declare const SCREEN_MARGIN = 1;
235
+ /** A grid row read off the whole screen, with the screen row it was found on. */
236
+ export interface M8ScreenGridRow extends M8ParsedRow {
237
+ screenRow: number;
238
+ }
239
+ /**
240
+ * Every content row of a grid view (song, chain, phrase, table, groove, instrument pool)
241
+ * read off the whole screen, top to bottom. Rows whose index column does not parse are
242
+ * skipped, so check `rowIndex` continuity when you need a full page.
243
+ *
244
+ * @example
245
+ * ```ts
246
+ * const rows = parseGridScreen('phrase', await m8.getScreen())
247
+ * rows[0].fields.note.rawValue // 'C-4'
248
+ * ```
249
+ */
250
+ export declare function parseGridScreen(viewName: string, screen: M8Screen): M8ScreenGridRow[];
251
+ /** Where the song is playing, as far as the screen shows it. */
252
+ export interface M8PlayPositions {
253
+ /** Song view: the song row each track's play marker is on (null when its marker is not on screen). */
254
+ tracks: (number | null)[] | null;
255
+ /** Chain, phrase, table, groove views: the row with the play marker after its index. */
256
+ row: number | null;
257
+ }
258
+ /**
259
+ * Reads the play markers off a grid view. On the Song view the M8 draws a marker in the
260
+ * blank just left of each playing chain (`00>00>01>02`); on the other grid views in the
261
+ * blank after the row index of the playing row (`F>--- -- --`), both seen on a Model:02,
262
+ * firmware 6.5. The '<' drawn in the margin marks the cursor row, not playback. Returns
263
+ * null for views without a grid; all nulls when nothing is playing.
264
+ */
265
+ export declare function readPlayPositions(viewName: string, screen: M8Screen): M8PlayPositions | null;
229
266
  /**
230
267
  * Derives a full semantic context from the current M8State.
231
268
  *