@yam8d/m8-sdk 0.4.0 → 0.5.1
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 +798 -796
- package/dist/index.js +3411 -363
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
- 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/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@yam8d/m8-sdk",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "Client SDK for
|
|
3
|
+
"version": "0.5.1",
|
|
4
|
+
"description": "Client SDK for apps that read and drive a Dirtywave M8, inside a yam8d External Apps iframe or standalone over Web Serial, WebUSB or Web MIDI.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
7
7
|
"module": "./dist/index.js",
|
package/types/client.d.ts
CHANGED
|
@@ -1,7 +1,12 @@
|
|
|
1
|
-
import type { M8KeyName, M8Screen, M8SdkConfig, M8State } from './types';
|
|
1
|
+
import type { M8ClientMode, M8DeviceBus, M8DeviceConnectOptions, M8DeviceSupport, M8DeviceTransport, M8KeyName, M8Screen, M8ScreenCells, M8SdkConfig, M8State, M8TransportEvent } from './types';
|
|
2
2
|
import type { M8DescriptionPart, M8SemanticContext } from './viewContext';
|
|
3
|
-
export type { CursorPos, CursorRect, M8KeyName, M8Screen, M8State, RGB, SystemInfos } from './types';
|
|
3
|
+
export type { CursorPos, CursorRect, M8KeyName, M8Screen, M8ScreenCells, M8State, M8TransportEvent, RGB, SystemInfos } from './types';
|
|
4
4
|
export type { M8DescriptionPart, M8SemanticContext } from './viewContext';
|
|
5
|
+
/**
|
|
6
|
+
* The iframe name yam8d gives its External Apps frames. A client that sees it waits for the
|
|
7
|
+
* host handshake however long it takes, instead of falling back to standalone mode.
|
|
8
|
+
*/
|
|
9
|
+
export declare const YAM8D_FRAME_NAME = "yam8d-external-app";
|
|
5
10
|
/**
|
|
6
11
|
* Everything the SDK knows about the moment: raw state, semantic context, its
|
|
7
12
|
* human-readable description and the whole screen. Plain JSON, safe to persist.
|
|
@@ -18,7 +23,15 @@ export interface M8Snapshot {
|
|
|
18
23
|
}
|
|
19
24
|
export interface M8Client {
|
|
20
25
|
readonly state: M8State;
|
|
26
|
+
/**
|
|
27
|
+
* True when calls reach an M8: in host mode once the yam8d handshake is done, in
|
|
28
|
+
* standalone mode while a device is connected.
|
|
29
|
+
*/
|
|
21
30
|
readonly isConnected: boolean;
|
|
31
|
+
/** 'host' inside yam8d, 'standalone' otherwise; null until connect() has decided. */
|
|
32
|
+
readonly mode: M8ClientMode | null;
|
|
33
|
+
/** Standalone: the API the device is connected through, null while disconnected (always null in host mode). */
|
|
34
|
+
readonly deviceTransport: M8DeviceTransport | null;
|
|
22
35
|
navigateToView(viewName: string): Promise<boolean>;
|
|
23
36
|
navigateTo(x: number, y: number): Promise<void>;
|
|
24
37
|
setValueToHex(targetHex: number): Promise<boolean>;
|
|
@@ -37,6 +50,11 @@ export interface M8Client {
|
|
|
37
50
|
* No cursor movement involved, so it is instant and side-effect free.
|
|
38
51
|
*/
|
|
39
52
|
getScreen(): Promise<M8Screen>;
|
|
53
|
+
/**
|
|
54
|
+
* The screen plus the foreground/background colour of every character cell ('#rrggbb').
|
|
55
|
+
* Rejects when the host predates this method (yam8d before SDK 0.5).
|
|
56
|
+
*/
|
|
57
|
+
getScreenCells(): Promise<M8ScreenCells>;
|
|
40
58
|
/** Fresh state + semantic context + description + whole screen in one call. */
|
|
41
59
|
captureSnapshot(): Promise<M8Snapshot>;
|
|
42
60
|
onStateChange(callback: (state: M8State) => void): () => void;
|
|
@@ -44,6 +62,37 @@ export interface M8Client {
|
|
|
44
62
|
onCursorMove(callback: (pos: M8State['cursorPos'], rect: M8State['cursorRect'], selectionMode: boolean) => void): () => void;
|
|
45
63
|
onTextUpdate(callback: (textUnderCursor: string | null, currentLine: string | null) => void): () => void;
|
|
46
64
|
onKeyPress(callback: (keys: number) => void): () => void;
|
|
65
|
+
/**
|
|
66
|
+
* Starts forwarding the M8's MIDI clock and transport (needs SYNC OUT set in the M8's
|
|
67
|
+
* MIDI Settings, and Web MIDI permission in the page that owns the device). Hosts before
|
|
68
|
+
* SDK 0.5 reject the call.
|
|
69
|
+
*/
|
|
70
|
+
enableMidiTransport(): Promise<{
|
|
71
|
+
ok: boolean;
|
|
72
|
+
error?: string;
|
|
73
|
+
}>;
|
|
74
|
+
/** MIDI start / stop / continue, the clock once per sixteenth, and song positions. */
|
|
75
|
+
onTransport(callback: (event: M8TransportEvent) => void): () => void;
|
|
76
|
+
/**
|
|
77
|
+
* Standalone: connects to the M8 from this page. Without a previously granted device the
|
|
78
|
+
* browser shows its device picker, which only opens from a user gesture (call this from a
|
|
79
|
+
* click handler). Replaces the current device. In host mode yam8d owns the device: no-op.
|
|
80
|
+
*/
|
|
81
|
+
connectDevice(options?: M8DeviceConnectOptions): Promise<void>;
|
|
82
|
+
/** Standalone: releases the device so another page (yam8d, another app) can open it. */
|
|
83
|
+
disconnectDevice(): Promise<void>;
|
|
84
|
+
/** Fires when isConnected changes (handshake done, device connected, lost or released). */
|
|
85
|
+
onConnectionChange(callback: (connected: boolean) => void): () => void;
|
|
86
|
+
/** Standalone: raw protocol events and device commands; null in host mode or while disconnected. */
|
|
87
|
+
getDevice(): M8DeviceBus | null;
|
|
88
|
+
/** Standalone: whether the M8's USB audio is playing through this page. */
|
|
89
|
+
readonly isAudioConnected: boolean;
|
|
90
|
+
/**
|
|
91
|
+
* Standalone: plays the M8's USB audio input through this page (asks for audio permission;
|
|
92
|
+
* rejects when no input named M8 is found). In host mode yam8d plays it: no-op.
|
|
93
|
+
*/
|
|
94
|
+
connectAudio(): Promise<void>;
|
|
95
|
+
disconnectAudio(): Promise<void>;
|
|
47
96
|
disconnect(): void;
|
|
48
97
|
/**
|
|
49
98
|
* Derives semantic context from the current state: view, row, and the typed
|
|
@@ -69,6 +118,13 @@ export interface M8Client {
|
|
|
69
118
|
*/
|
|
70
119
|
describeContextParts(): M8DescriptionPart[] | null;
|
|
71
120
|
}
|
|
121
|
+
/** Which device APIs the browser offers for standalone mode. */
|
|
122
|
+
export declare const getDeviceSupport: () => M8DeviceSupport;
|
|
123
|
+
/**
|
|
124
|
+
* Creates a client and connects it. Inside a yam8d External Apps iframe it talks to yam8d;
|
|
125
|
+
* anywhere else it runs standalone (see M8SdkConfig.mode) and reopens a device the page was
|
|
126
|
+
* granted before. A first standalone connection needs connectDevice() from a click.
|
|
127
|
+
*/
|
|
72
128
|
export declare function createM8Client(config?: M8SdkConfig): Promise<M8Client>;
|
|
73
129
|
export declare function createM8ClientSync(config?: M8SdkConfig): {
|
|
74
130
|
client: M8Client;
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
export type Direction = 'up' | 'down' | 'left' | 'right';
|
|
2
|
+
export type Edge = {
|
|
3
|
+
to: string;
|
|
4
|
+
dir: Direction;
|
|
5
|
+
weight?: number;
|
|
6
|
+
};
|
|
7
|
+
export type Graph = Map<string, Edge[]>;
|
|
8
|
+
export type Heuristic = (a: string, b: string) => number;
|
|
9
|
+
export type EdgeCostFn = (from: string, to: string, dir: Direction) => number;
|
|
10
|
+
export interface PathResult {
|
|
11
|
+
pages: string[];
|
|
12
|
+
directions: Direction[];
|
|
13
|
+
cost: number;
|
|
14
|
+
}
|
|
15
|
+
export declare function aStar(graph: Graph, start: string, goal: string, heuristic?: Heuristic, edgeCost?: EdgeCostFn): PathResult | null;
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
export declare const M8_AUDIO_CAPTURE_CONSTRAINTS: {
|
|
2
|
+
autoGainControl: false;
|
|
3
|
+
channelCount: {
|
|
4
|
+
ideal: number;
|
|
5
|
+
};
|
|
6
|
+
echoCancellation: false;
|
|
7
|
+
noiseSuppression: false;
|
|
8
|
+
};
|
|
9
|
+
/** The M8's audio input, or null (device labels are only visible once audio permission was given). */
|
|
10
|
+
export declare const getM8AudioInputDevice: () => Promise<MediaDeviceInfo | null>;
|
|
11
|
+
/**
|
|
12
|
+
* A stream from the M8's audio input. Without a labelled M8 input yet, it asks for audio
|
|
13
|
+
* permission first and looks again. Still none: `requireM8` rejects, otherwise the default
|
|
14
|
+
* input is returned (yam8d's recorder relies on that fallback).
|
|
15
|
+
*/
|
|
16
|
+
export declare const getM8InputStream: ({ requireM8 }?: {
|
|
17
|
+
requireM8?: boolean;
|
|
18
|
+
}) => Promise<MediaStream>;
|
|
19
|
+
export interface M8Audio {
|
|
20
|
+
/** Starts playing the M8's audio input through the page's speakers. Idempotent. */
|
|
21
|
+
connect(options?: {
|
|
22
|
+
requireM8?: boolean;
|
|
23
|
+
}): Promise<void>;
|
|
24
|
+
disconnect(): Promise<void>;
|
|
25
|
+
readonly connected: boolean;
|
|
26
|
+
/** A copy of what is being played, for recording; null while not connected. */
|
|
27
|
+
getOutputCaptureStream(): MediaStream | null;
|
|
28
|
+
}
|
|
29
|
+
export declare const createM8Audio: () => M8Audio;
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { createEventBus } from './eventBus';
|
|
2
|
+
import type { ProtocolEvents } from './protocol';
|
|
3
|
+
export type ProtocolEventBus = ReturnType<typeof createEventBus<ProtocolEvents>>;
|
|
4
|
+
export interface EngineBus {
|
|
5
|
+
protocol: {
|
|
6
|
+
eventBus: ProtocolEventBus;
|
|
7
|
+
};
|
|
8
|
+
commands: {
|
|
9
|
+
sendKeys(keys: number): void;
|
|
10
|
+
/** Asks the M8 to redraw the whole screen. */
|
|
11
|
+
resetScreen(): void;
|
|
12
|
+
};
|
|
13
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Runs `connect` until it succeeds. The M8 Headless is slower to enumerate and may not be
|
|
3
|
+
* immediately ready when the user clicks Connect, so failures are retried after a delay.
|
|
4
|
+
* Resolves to undefined once all attempts have failed.
|
|
5
|
+
*/
|
|
6
|
+
export declare const connectWithRetry: <T>(connect: () => Promise<T>, { maxAttempts, retryDelayMs }?: {
|
|
7
|
+
maxAttempts?: number;
|
|
8
|
+
retryDelayMs?: number;
|
|
9
|
+
}) => Promise<T | undefined>;
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
export type M8DeviceTransport = 'serial' | 'usb' | 'midi';
|
|
2
|
+
export interface M8ConnectOptions {
|
|
3
|
+
/** Which browser API to use. 'auto' (default) picks Web Serial, then WebUSB, then Web MIDI, by availability. */
|
|
4
|
+
transport?: M8DeviceTransport | 'auto';
|
|
5
|
+
/**
|
|
6
|
+
* false: only reuse a device the page was already granted, never open the browser's device
|
|
7
|
+
* picker (which needs a user gesture). Rejects when there is none. Default true.
|
|
8
|
+
*/
|
|
9
|
+
prompt?: boolean;
|
|
10
|
+
}
|
|
11
|
+
export declare function decode7BitPacked(data: Uint8Array): Uint8Array;
|
|
12
|
+
export declare const connection: () => {
|
|
13
|
+
readonly browserSupport: false;
|
|
14
|
+
readonly available?: undefined;
|
|
15
|
+
readonly connect?: undefined;
|
|
16
|
+
} | {
|
|
17
|
+
readonly browserSupport: true;
|
|
18
|
+
readonly available: Record<M8DeviceTransport, boolean>;
|
|
19
|
+
readonly connect: ({ transport, prompt }?: M8ConnectOptions) => Promise<{
|
|
20
|
+
transport: M8DeviceTransport;
|
|
21
|
+
commands: {
|
|
22
|
+
stop: () => void;
|
|
23
|
+
resetScreen: () => void;
|
|
24
|
+
sendKeys: (keys: number) => void;
|
|
25
|
+
sendNoteOn: (note: number, velocity: number) => void;
|
|
26
|
+
sendNoteOff: () => void;
|
|
27
|
+
sendProgramChange: (program: number, channel?: number) => void;
|
|
28
|
+
};
|
|
29
|
+
reader: () => Promise<{
|
|
30
|
+
terminated: boolean;
|
|
31
|
+
data?: undefined;
|
|
32
|
+
} | {
|
|
33
|
+
terminated: boolean;
|
|
34
|
+
data: DataView<ArrayBufferLike> | undefined;
|
|
35
|
+
}>;
|
|
36
|
+
protocol: {
|
|
37
|
+
eventBus: {
|
|
38
|
+
emit: <K extends keyof import("./protocol").ProtocolEvents>(event: K, ...args: Parameters<import("./protocol").ProtocolEvents[K]>) => void;
|
|
39
|
+
on: <K extends keyof import("./protocol").ProtocolEvents>(event: K, callback: import("./protocol").ProtocolEvents[K]) => void;
|
|
40
|
+
off: <K extends keyof import("./protocol").ProtocolEvents>(event: K, callback: import("./protocol").ProtocolEvents[K]) => void;
|
|
41
|
+
};
|
|
42
|
+
dispatch: (data: DataView, dataLength: number) => void;
|
|
43
|
+
getSystemInfo: () => {
|
|
44
|
+
model: "Headless" | "Beta M8" | "M8 Model:01" | "M8 Model:02";
|
|
45
|
+
version: string;
|
|
46
|
+
fontMode: 0 | 1 | 2;
|
|
47
|
+
} | undefined;
|
|
48
|
+
getSystemInfos: () => import("./protocol").SystemInfos;
|
|
49
|
+
};
|
|
50
|
+
/** Resolves when the device stops sending (unplugged, or close() was called). */
|
|
51
|
+
closed: Promise<void>;
|
|
52
|
+
/** Releases the device so another page (or yam8d) can open it. */
|
|
53
|
+
close: () => Promise<void>;
|
|
54
|
+
}>;
|
|
55
|
+
};
|
|
56
|
+
export type ConnectedBus = Awaited<ReturnType<Exclude<ReturnType<typeof connection>['connect'], undefined>>>;
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { EngineBus } from './bus';
|
|
2
|
+
import type { M8Store } from './store';
|
|
3
|
+
export type Point = {
|
|
4
|
+
x: number;
|
|
5
|
+
y: number;
|
|
6
|
+
};
|
|
7
|
+
export interface CursorNavigator {
|
|
8
|
+
/** Moves the cursor towards `target` (M8 pixel coordinates). Resolves when it stops. */
|
|
9
|
+
navigateTo(target: Point): Promise<void>;
|
|
10
|
+
dispose(): void;
|
|
11
|
+
}
|
|
12
|
+
export declare const createCursorNavigator: ({ bus, store }: {
|
|
13
|
+
bus: EngineBus;
|
|
14
|
+
store: M8Store;
|
|
15
|
+
}) => CursorNavigator;
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import type { M8HostEvents, M8HostMethods, M8State } from '../types';
|
|
2
|
+
import type { EngineBus } from './bus';
|
|
3
|
+
import { type Point } from './cursorNavigator';
|
|
4
|
+
import { type MacroRunner } from './macroRunner';
|
|
5
|
+
import { type M8Store } from './store';
|
|
6
|
+
import { type ScreenCellColors } from './viewExtractor';
|
|
7
|
+
export interface M8EngineOptions {
|
|
8
|
+
debug?: boolean;
|
|
9
|
+
/** Cancel a running macro (view navigation) on any keydown in this window. Default false. */
|
|
10
|
+
cancelMacroOnKeydown?: boolean;
|
|
11
|
+
/** Use this store instead of a new one (for tests, or to keep state across reconnects). */
|
|
12
|
+
store?: M8Store;
|
|
13
|
+
}
|
|
14
|
+
export interface M8Engine {
|
|
15
|
+
readonly bus: EngineBus;
|
|
16
|
+
readonly store: M8Store;
|
|
17
|
+
/** The SDK methods, as an iframe client or the standalone client calls them. */
|
|
18
|
+
readonly methods: M8HostMethods;
|
|
19
|
+
readonly macro: MacroRunner;
|
|
20
|
+
/** Moves the cursor towards a point in M8 pixel coordinates. */
|
|
21
|
+
navigateTo(point: Point): Promise<void>;
|
|
22
|
+
navigateToView(viewName: string): Promise<void>;
|
|
23
|
+
getState(): M8State;
|
|
24
|
+
getScreenLines(): string[];
|
|
25
|
+
getScreenCellColors(): ScreenCellColors;
|
|
26
|
+
/** Listens to an SDK event. Returns the unsubscribe function. */
|
|
27
|
+
on<K extends keyof M8HostEvents>(event: K, callback: (payload: M8HostEvents[K]) => void): () => void;
|
|
28
|
+
dispose(): void;
|
|
29
|
+
}
|
|
30
|
+
export declare const createM8Engine: (bus: EngineBus, options?: M8EngineOptions) => M8Engine;
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
export declare const createEventBus: <T extends Record<string, (...args: any[]) => void>>() => {
|
|
2
|
+
emit: <K extends keyof T>(event: K, ...args: Parameters<T[K]>) => void;
|
|
3
|
+
on: <K extends keyof T>(event: K, callback: T[K]) => void;
|
|
4
|
+
off: <K extends keyof T>(event: K, callback: T[K]) => void;
|
|
5
|
+
};
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import type { M8State } from '../../types';
|
|
2
|
+
export declare const normalizeForSearch: (text: string | null) => string;
|
|
3
|
+
export declare const FILE_BROWSER_EXTENSION_SUFFIX_REGEX: RegExp;
|
|
4
|
+
export type FileBrowserSnapshot = {
|
|
5
|
+
viewName: string | null;
|
|
6
|
+
viewTitle: string | null;
|
|
7
|
+
cursorPos: M8State['cursorPos'];
|
|
8
|
+
textUnderCursor: string | null;
|
|
9
|
+
currentLine: string | null;
|
|
10
|
+
};
|
|
11
|
+
export declare const getFileBrowserSnapshotFingerprint: (snapshot: FileBrowserSnapshot) => string;
|
|
12
|
+
export declare const getSelectedFileBrowserEntry: (snapshot: FileBrowserSnapshot) => string | null;
|
|
13
|
+
export declare const isFileBrowserView: (snapshot: Pick<FileBrowserSnapshot, "viewName" | "viewTitle">) => boolean;
|
|
14
|
+
export declare const isParentDirectoryEntry: (entry: string | null) => boolean;
|
|
15
|
+
export declare const isDirectoryEntry: (entry: string | null) => boolean;
|
|
16
|
+
export declare const isM8FileEntry: (entry: string | null) => boolean;
|
|
17
|
+
export declare const getFileEntrySearchTerms: (entry: string | null) => string[];
|
|
18
|
+
export type FileBrowserEntryKind = 'parent' | 'dir' | 'file' | 'other';
|
|
19
|
+
export type FileBrowserScreenEntry = {
|
|
20
|
+
/** Screen row, equal to the cursor grid row when the cursor stands on it. */
|
|
21
|
+
row: number;
|
|
22
|
+
text: string;
|
|
23
|
+
/** Normalized text, unique inside a directory. */
|
|
24
|
+
key: string;
|
|
25
|
+
kind: FileBrowserEntryKind;
|
|
26
|
+
};
|
|
27
|
+
export type FileBrowserScreen = {
|
|
28
|
+
entries: FileBrowserScreenEntry[];
|
|
29
|
+
cursorRow: number | null;
|
|
30
|
+
cursorKey: string | null;
|
|
31
|
+
/** The "/.." entry is visible, so the top of the directory is on screen. */
|
|
32
|
+
hasParent: boolean;
|
|
33
|
+
/** False when the cursor row disagrees with the cursor atoms: the screen is mid-redraw. */
|
|
34
|
+
consistent: boolean;
|
|
35
|
+
};
|
|
36
|
+
export declare const PARENT_ENTRY_KEY = "/..";
|
|
37
|
+
export declare const classifyFileBrowserEntry: (text: string) => FileBrowserEntryKind;
|
|
38
|
+
export declare const parseFileBrowserScreen: (lines: string[], snapshot: Pick<FileBrowserSnapshot, "cursorPos" | "viewTitle" | "textUnderCursor">) => FileBrowserScreen;
|
|
39
|
+
/**
|
|
40
|
+
* Merge the entry keys of one screen into the display order accumulated for a directory.
|
|
41
|
+
* Screens overlap, so the first known entry anchors the new ones. When the screen
|
|
42
|
+
* contradicts the accumulated order (listing re-sorted or reloaded) the order restarts from
|
|
43
|
+
* the screen and `consistent` is false.
|
|
44
|
+
*/
|
|
45
|
+
export declare const mergeFileBrowserOrder: (order: string[], screen: string[]) => {
|
|
46
|
+
order: string[];
|
|
47
|
+
consistent: boolean;
|
|
48
|
+
};
|
|
49
|
+
/** Words of the search target (without its extension) and of the view title, to rank directories. */
|
|
50
|
+
export declare const getDirectoryHintTokens: (targetText: string, viewTitle: string | null) => {
|
|
51
|
+
target: string[];
|
|
52
|
+
title: string[];
|
|
53
|
+
};
|
|
54
|
+
/**
|
|
55
|
+
* 2: the folder name shares a word with the target ("/Bass" for "bassline"),
|
|
56
|
+
* 1: it shares a word with the view title ("/Instruments" in "LOAD INSTRUMENT"), else 0.
|
|
57
|
+
* Only used to decide which folder to open first, never to skip one.
|
|
58
|
+
*/
|
|
59
|
+
export declare const scoreDirectoryRelevance: (directoryKey: string, hints: ReturnType<typeof getDirectoryHintTokens>) => number;
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { type HostIO } from './hostIO';
|
|
2
|
+
/**
|
|
3
|
+
* Navigate the M8 file browser (load/save views) to a file by name.
|
|
4
|
+
*
|
|
5
|
+
* The whole screen is readable, so every move is decided from what is visible:
|
|
6
|
+
* - a match anywhere on screen is reached directly;
|
|
7
|
+
* - a directory is read page by page (Opt+Up/Down) before any sub-directory is opened,
|
|
8
|
+
* and a window that is not full is the whole directory (nothing to page);
|
|
9
|
+
* - sub-directories are only opened once the files of the level have been checked, and
|
|
10
|
+
* after coming back the cursor already sits on the folder just visited, so the next
|
|
11
|
+
* folder is a few rows away instead of a trip to the top of the list.
|
|
12
|
+
*/
|
|
13
|
+
export declare const createFileBrowserSearch: (io: HostIO) => (targetText: string, exact?: boolean) => Promise<boolean>;
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import type { M8Store } from '../store';
|
|
2
|
+
import { type FileBrowserSnapshot } from './fileBrowserHelpers';
|
|
3
|
+
export declare const wait: (ms: number) => Promise<unknown>;
|
|
4
|
+
/** Where key frames go: a connected bus's commands, or nothing while disconnected. */
|
|
5
|
+
export type KeySink = {
|
|
6
|
+
sendKeys: (keys: number) => void;
|
|
7
|
+
};
|
|
8
|
+
/**
|
|
9
|
+
* What the value editors and the file browser search need from the engine: key output
|
|
10
|
+
* to the M8 and observation of the screen state.
|
|
11
|
+
*/
|
|
12
|
+
export type HostIO = {
|
|
13
|
+
store: M8Store;
|
|
14
|
+
debugLog: (...args: unknown[]) => void;
|
|
15
|
+
hasBus: () => boolean;
|
|
16
|
+
sendKeys: (keys: number) => void;
|
|
17
|
+
pressAndRelease: (keys: number, delayMs?: number) => Promise<void>;
|
|
18
|
+
waitForTextUnderCursorChange: (previousValue: string | null, timeoutMs?: number) => Promise<string | null>;
|
|
19
|
+
waitForCurrentLineChange: (previousValue: string | null, timeoutMs?: number) => Promise<string | null>;
|
|
20
|
+
getFileBrowserSnapshot: () => FileBrowserSnapshot;
|
|
21
|
+
waitForFileBrowserSnapshotChange: (previousSnapshot: FileBrowserSnapshot, timeoutMs?: number) => Promise<FileBrowserSnapshot>;
|
|
22
|
+
/** Whole M8 screen as text rows (row index === cursor grid row). */
|
|
23
|
+
getScreenLines: () => string[];
|
|
24
|
+
};
|
|
25
|
+
export declare const createHostIO: ({ getKeySink, store, debugLog, getScreenLines, }: {
|
|
26
|
+
getKeySink: () => KeySink | undefined;
|
|
27
|
+
store: M8Store;
|
|
28
|
+
debugLog: (...args: unknown[]) => void;
|
|
29
|
+
getScreenLines: () => string[];
|
|
30
|
+
}) => HostIO;
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import type { M8HostMethods } from '../../types';
|
|
2
|
+
import { type ScreenCellColors } from '../viewExtractor';
|
|
3
|
+
import type { HostIO } from './hostIO';
|
|
4
|
+
import type { createValueEditors } from './valueEditors';
|
|
5
|
+
export type HostMethodsDeps = {
|
|
6
|
+
io: HostIO;
|
|
7
|
+
editors: ReturnType<typeof createValueEditors>;
|
|
8
|
+
setValueFloat: (targetFloat: number) => Promise<boolean>;
|
|
9
|
+
browseFile: (targetText: string, exact: boolean) => Promise<boolean>;
|
|
10
|
+
navigateToView: (viewName: string) => Promise<void>;
|
|
11
|
+
navigateTo: (point: {
|
|
12
|
+
x: number;
|
|
13
|
+
y: number;
|
|
14
|
+
}) => Promise<void>;
|
|
15
|
+
enableMidiTransport: () => Promise<{
|
|
16
|
+
ok: boolean;
|
|
17
|
+
error?: string;
|
|
18
|
+
}>;
|
|
19
|
+
getScreenCellColors: () => ScreenCellColors;
|
|
20
|
+
};
|
|
21
|
+
/** The methods an SDK client can call on the engine (transport-agnostic). */
|
|
22
|
+
export declare const createHostMethods: ({ io, editors, setValueFloat, browseFile, navigateToView, navigateTo, enableMidiTransport, getScreenCellColors, }: HostMethodsDeps) => M8HostMethods;
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { M8State } from '../../types';
|
|
2
|
+
import type { M8Store } from '../store';
|
|
3
|
+
export declare const getCurrentState: (store: M8Store) => M8State;
|
|
4
|
+
export declare const textGridToPixel: (store: M8Store, gridX: number, gridY: number) => {
|
|
5
|
+
x: number;
|
|
6
|
+
y: number;
|
|
7
|
+
};
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { M8TransportEvent } from '../../types';
|
|
2
|
+
export type MidiTransport = {
|
|
3
|
+
/** Opens the M8 MIDI input (asks for Web MIDI permission the first time). */
|
|
4
|
+
enable: () => Promise<{
|
|
5
|
+
ok: boolean;
|
|
6
|
+
error?: string;
|
|
7
|
+
}>;
|
|
8
|
+
disable: () => void;
|
|
9
|
+
};
|
|
10
|
+
export declare const createMidiTransport: (emit: (event: M8TransportEvent) => void) => MidiTransport;
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { type HostIO } from './hostIO';
|
|
2
|
+
export declare const createValueEditors: (io: HostIO) => {
|
|
3
|
+
setValueToHex: (targetHex: number) => Promise<boolean>;
|
|
4
|
+
setValueToInt: (targetInt: number) => Promise<boolean>;
|
|
5
|
+
setNote: (targetNoteString: string) => Promise<boolean>;
|
|
6
|
+
setValueToString: (targetString: string, exact?: boolean, searchInCurrentLine?: boolean) => Promise<boolean>;
|
|
7
|
+
};
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
export declare const parseHexValue: (text: string | null) => number | null;
|
|
2
|
+
export declare const parseIntValue: (text: string | null) => number | null;
|
|
3
|
+
export type ParsedFloatToken = {
|
|
4
|
+
raw: string;
|
|
5
|
+
negative: boolean;
|
|
6
|
+
intDigits: string;
|
|
7
|
+
decDigits: string;
|
|
8
|
+
decScale: number;
|
|
9
|
+
tickHundredths: number;
|
|
10
|
+
ticks: number;
|
|
11
|
+
};
|
|
12
|
+
export declare const parseFloatTokens: (line: string | null) => ParsedFloatToken[];
|
|
13
|
+
export declare const pickFloatTokenIndex: (tokens: ParsedFloatToken[], hintText: string | null) => number;
|
|
14
|
+
export type FloatCursorFocus = 'full' | 'int' | 'dec';
|
|
15
|
+
export declare const detectFloatCursorFocus: (token: ParsedFloatToken | null, hintText: string | null) => FloatCursorFocus | null;
|
|
16
|
+
export declare const isCursorOnForeignFloat: (tokens: ParsedFloatToken[], ownIndex: number, hintText: string | null) => boolean;
|
|
17
|
+
export declare const formatFloatFromTicks: (ticks: number, tickHundredths: number) => string;
|
|
18
|
+
export type ParsedNote = {
|
|
19
|
+
label: string;
|
|
20
|
+
noteName: string;
|
|
21
|
+
octave: number;
|
|
22
|
+
semitoneIndex: number;
|
|
23
|
+
};
|
|
24
|
+
export declare const parseNoteValue: (text: string | null) => ParsedNote | null;
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
export type { EngineBus, ProtocolEventBus } from './bus';
|
|
2
|
+
export { connectWithRetry } from './connectWithRetry';
|
|
3
|
+
export { type ConnectedBus, connection, decode7BitPacked, type M8ConnectOptions, type M8DeviceTransport } from './connection';
|
|
4
|
+
export { type CursorNavigator, createCursorNavigator, type Point } from './cursorNavigator';
|
|
5
|
+
export { createM8Engine, type M8Engine, type M8EngineOptions } from './engine';
|
|
6
|
+
export { createEventBus } from './eventBus';
|
|
7
|
+
export { isDown, isEdit, isLeft, isOpt, isPlay, isRight, isShift, isUp, M8KeyMask, pressKeys } from './keys';
|
|
8
|
+
export { createMacroRunner, type MacroRunner } from './macroRunner';
|
|
9
|
+
export { type CharacterCommand, type KeyCommand, type ProtocolEvents, protocol, type RectCommand, type SystemCommand, type SystemInfos, type WaveCommand, } from './protocol';
|
|
10
|
+
export { createSlipParser, queuedSerialReader, queuedUsbReader } from './queuedReaders';
|
|
11
|
+
export { createM8Store, type M8CellMetrics, type M8MacroStatus, type M8Store, type M8StoreKey, type M8StoreState, type M8SystemInfo, } from './store';
|
|
12
|
+
export { type DrawEventSource, registerViewExtractor, SCREEN_COLUMNS, SCREEN_ROWS, type ScreenCellColors, type ViewExtractor } from './viewExtractor';
|
|
13
|
+
export { getViewGraph, getViewNames, type M8ViewGraph } from './viewGraph';
|
|
14
|
+
export { createViewNavigation, toKeyMasks, type ViewNavigation } from './viewNavigation';
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
export declare const M8KeyMask: {
|
|
2
|
+
readonly Left: 128;
|
|
3
|
+
readonly Up: 64;
|
|
4
|
+
readonly Down: 32;
|
|
5
|
+
readonly Shift: 16;
|
|
6
|
+
readonly Play: 8;
|
|
7
|
+
readonly Right: 4;
|
|
8
|
+
readonly Opt: 2;
|
|
9
|
+
readonly Edit: 1;
|
|
10
|
+
};
|
|
11
|
+
export declare const isLeft: (frame: number) => boolean;
|
|
12
|
+
export declare const isUp: (frame: number) => boolean;
|
|
13
|
+
export declare const isDown: (frame: number) => boolean;
|
|
14
|
+
export declare const isShift: (frame: number) => boolean;
|
|
15
|
+
export declare const isPlay: (frame: number) => boolean;
|
|
16
|
+
export declare const isRight: (frame: number) => boolean;
|
|
17
|
+
export declare const isOpt: (frame: number) => boolean;
|
|
18
|
+
export declare const isEdit: (frame: number) => boolean;
|
|
19
|
+
export declare const pressKeys: ({ left, right, up, down, shift, play, opt, edit, }: {
|
|
20
|
+
left?: boolean;
|
|
21
|
+
right?: boolean;
|
|
22
|
+
up?: boolean;
|
|
23
|
+
down?: boolean;
|
|
24
|
+
shift?: boolean;
|
|
25
|
+
play?: boolean;
|
|
26
|
+
opt?: boolean;
|
|
27
|
+
edit?: boolean;
|
|
28
|
+
}) => number;
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { EngineBus } from './bus';
|
|
2
|
+
import type { M8Store } from './store';
|
|
3
|
+
export interface MacroRunner {
|
|
4
|
+
/** Starts a sequence. While one is running, the new frames replace the remaining ones. */
|
|
5
|
+
start(sequence: number[]): void;
|
|
6
|
+
cancel(reason?: string): void;
|
|
7
|
+
readonly running: boolean;
|
|
8
|
+
dispose(): void;
|
|
9
|
+
}
|
|
10
|
+
export declare const createMacroRunner: ({ bus, store, cancelOnKeydown, }: {
|
|
11
|
+
bus: EngineBus;
|
|
12
|
+
store: M8Store;
|
|
13
|
+
/** Cancel the running macro on any keydown in this window (yam8d does, for its keyboard input). */
|
|
14
|
+
cancelOnKeydown?: boolean;
|
|
15
|
+
}) => MacroRunner;
|