@ac-kit/format-midi 0.0.0-stage → 0.1.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 (60) hide show
  1. package/CHANGELOG.md +38 -0
  2. package/LICENSE +28 -0
  3. package/README.md +103 -2
  4. package/dist/errors.d.ts +45 -0
  5. package/dist/errors.d.ts.map +1 -0
  6. package/dist/errors.js +36 -0
  7. package/dist/errors.js.map +1 -0
  8. package/dist/file/_meta-types.d.ts +33 -0
  9. package/dist/file/_meta-types.d.ts.map +1 -0
  10. package/dist/file/_meta-types.js +52 -0
  11. package/dist/file/_meta-types.js.map +1 -0
  12. package/dist/file/midi-file.d.ts +68 -0
  13. package/dist/file/midi-file.d.ts.map +1 -0
  14. package/dist/file/midi-file.js +2 -0
  15. package/dist/file/midi-file.js.map +1 -0
  16. package/dist/file/midi-meta-event.d.ts +74 -0
  17. package/dist/file/midi-meta-event.d.ts.map +1 -0
  18. package/dist/file/midi-meta-event.js +2 -0
  19. package/dist/file/midi-meta-event.js.map +1 -0
  20. package/dist/file/parse-midi-file.d.ts +29 -0
  21. package/dist/file/parse-midi-file.d.ts.map +1 -0
  22. package/dist/file/parse-midi-file.js +265 -0
  23. package/dist/file/parse-midi-file.js.map +1 -0
  24. package/dist/file/print-midi-file.d.ts +35 -0
  25. package/dist/file/print-midi-file.d.ts.map +1 -0
  26. package/dist/file/print-midi-file.js +184 -0
  27. package/dist/file/print-midi-file.js.map +1 -0
  28. package/dist/index.d.ts +12 -0
  29. package/dist/index.d.ts.map +1 -0
  30. package/dist/index.js +12 -0
  31. package/dist/index.js.map +1 -0
  32. package/dist/message/_midi-status.d.ts +19 -0
  33. package/dist/message/_midi-status.d.ts.map +1 -0
  34. package/dist/message/_midi-status.js +66 -0
  35. package/dist/message/_midi-status.js.map +1 -0
  36. package/dist/message/encode-midi-message.d.ts +21 -0
  37. package/dist/message/encode-midi-message.d.ts.map +1 -0
  38. package/dist/message/encode-midi-message.js +93 -0
  39. package/dist/message/encode-midi-message.js.map +1 -0
  40. package/dist/message/is-midi-note-release.d.ts +15 -0
  41. package/dist/message/is-midi-note-release.d.ts.map +1 -0
  42. package/dist/message/is-midi-note-release.js +15 -0
  43. package/dist/message/is-midi-note-release.js.map +1 -0
  44. package/dist/message/midi-message-decoder.d.ts +67 -0
  45. package/dist/message/midi-message-decoder.d.ts.map +1 -0
  46. package/dist/message/midi-message-decoder.js +254 -0
  47. package/dist/message/midi-message-decoder.js.map +1 -0
  48. package/dist/message/midi-message-encoder.d.ts +43 -0
  49. package/dist/message/midi-message-encoder.d.ts.map +1 -0
  50. package/dist/message/midi-message-encoder.js +49 -0
  51. package/dist/message/midi-message-encoder.js.map +1 -0
  52. package/dist/message/midi-message.d.ts +111 -0
  53. package/dist/message/midi-message.d.ts.map +1 -0
  54. package/dist/message/midi-message.js +2 -0
  55. package/dist/message/midi-message.js.map +1 -0
  56. package/dist/timeline/compose-midi-timeline.d.ts +42 -0
  57. package/dist/timeline/compose-midi-timeline.d.ts.map +1 -0
  58. package/dist/timeline/compose-midi-timeline.js +87 -0
  59. package/dist/timeline/compose-midi-timeline.js.map +1 -0
  60. package/package.json +62 -3
@@ -0,0 +1,21 @@
1
+ import type { MidiMessage } from "./midi-message.js";
2
+ /**
3
+ * Encodes one MIDI 1.0 message to its bytes on the wire, status byte always
4
+ * included.
5
+ *
6
+ * Use {@link MidiMessageEncoder} instead to send a sequence of messages with
7
+ * running status. A `sysEx` is framed with F0 and F7; its `data` must not carry
8
+ * them itself.
9
+ *
10
+ * Time complexity: O(1), or O(`data.length`) for a `sysEx`.
11
+ *
12
+ * @example
13
+ * encodeMidiMessage({ type: "pitchBend", channel: 0, value: 0 }); // [0xe0, 0x00, 0x40]
14
+ *
15
+ * @param message - The message to encode.
16
+ * @returns A fresh buffer holding the message.
17
+ * @throws {RangeError} When a field is out of its range or not an integer, or a
18
+ * `sysEx` payload holds a byte above 127.
19
+ */
20
+ export declare function encodeMidiMessage(message: MidiMessage): Uint8Array<ArrayBuffer>;
21
+ //# sourceMappingURL=encode-midi-message.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"encode-midi-message.d.ts","sourceRoot":"","sources":["../../src/message/encode-midi-message.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAgBrD;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAgB,iBAAiB,CAChC,OAAO,EAAE,WAAW,GAClB,UAAU,CAAC,WAAW,CAAC,CAwGzB"}
@@ -0,0 +1,93 @@
1
+ function uint_(field, value, maximum) {
2
+ if (!Number.isInteger(value) || value < 0 || value > maximum) {
3
+ throw new RangeError(`encodeMidiMessage: ${field} must be an integer 0–${maximum}, got ${value}`);
4
+ }
5
+ return value;
6
+ }
7
+ function channelStatus_(kind, channel) {
8
+ return kind | uint_("channel", channel, 0x0f);
9
+ }
10
+ /**
11
+ * Encodes one MIDI 1.0 message to its bytes on the wire, status byte always
12
+ * included.
13
+ *
14
+ * Use {@link MidiMessageEncoder} instead to send a sequence of messages with
15
+ * running status. A `sysEx` is framed with F0 and F7; its `data` must not carry
16
+ * them itself.
17
+ *
18
+ * Time complexity: O(1), or O(`data.length`) for a `sysEx`.
19
+ *
20
+ * @example
21
+ * encodeMidiMessage({ type: "pitchBend", channel: 0, value: 0 }); // [0xe0, 0x00, 0x40]
22
+ *
23
+ * @param message - The message to encode.
24
+ * @returns A fresh buffer holding the message.
25
+ * @throws {RangeError} When a field is out of its range or not an integer, or a
26
+ * `sysEx` payload holds a byte above 127.
27
+ */
28
+ export function encodeMidiMessage(message) {
29
+ switch (message.type) {
30
+ case "noteOff":
31
+ return Uint8Array.of(channelStatus_(0x80, message.channel), uint_("note", message.note, 0x7f), uint_("velocity", message.velocity, 0x7f));
32
+ case "noteOn":
33
+ return Uint8Array.of(channelStatus_(0x90, message.channel), uint_("note", message.note, 0x7f), uint_("velocity", message.velocity, 0x7f));
34
+ case "polyphonicKeyPressure":
35
+ return Uint8Array.of(channelStatus_(0xa0, message.channel), uint_("note", message.note, 0x7f), uint_("pressure", message.pressure, 0x7f));
36
+ case "controlChange":
37
+ return Uint8Array.of(channelStatus_(0xb0, message.channel), uint_("controller", message.controller, 0x7f), uint_("value", message.value, 0x7f));
38
+ case "programChange":
39
+ return Uint8Array.of(channelStatus_(0xc0, message.channel), uint_("program", message.program, 0x7f));
40
+ case "channelPressure":
41
+ return Uint8Array.of(channelStatus_(0xd0, message.channel), uint_("pressure", message.pressure, 0x7f));
42
+ case "pitchBend": {
43
+ if (!Number.isInteger(message.value) ||
44
+ message.value < -8192 ||
45
+ message.value > 8191) {
46
+ throw new RangeError(`encodeMidiMessage: pitch bend value must be an integer -8192–8191, got ${message.value}`);
47
+ }
48
+ const raw = message.value + 8192;
49
+ return Uint8Array.of(channelStatus_(0xe0, message.channel), raw & 0x7f, raw >> 7);
50
+ }
51
+ case "sysEx": {
52
+ const out = new Uint8Array(message.data.length + 2);
53
+ out[0] = 0xf0;
54
+ for (let index = 0; index < message.data.length; index++) {
55
+ const byte = message.data[index];
56
+ if (byte > 0x7f) {
57
+ throw new RangeError(`encodeMidiMessage: SysEx byte ${index} is 0x${byte.toString(16)}, above 0x7F`);
58
+ }
59
+ out[index + 1] = byte;
60
+ }
61
+ out[out.length - 1] = 0xf7;
62
+ return out;
63
+ }
64
+ case "timeCodeQuarterFrame":
65
+ return Uint8Array.of(0xf1, (uint_("piece", message.piece, 7) << 4) |
66
+ uint_("value", message.value, 0x0f));
67
+ case "songPositionPointer": {
68
+ const position = uint_("position", message.position, 0x3fff);
69
+ return Uint8Array.of(0xf2, position & 0x7f, position >> 7);
70
+ }
71
+ case "songSelect":
72
+ return Uint8Array.of(0xf3, uint_("song", message.song, 0x7f));
73
+ case "tuneRequest":
74
+ return Uint8Array.of(0xf6);
75
+ case "timingClock":
76
+ return Uint8Array.of(0xf8);
77
+ case "start":
78
+ return Uint8Array.of(0xfa);
79
+ case "continue":
80
+ return Uint8Array.of(0xfb);
81
+ case "stop":
82
+ return Uint8Array.of(0xfc);
83
+ case "activeSensing":
84
+ return Uint8Array.of(0xfe);
85
+ case "systemReset":
86
+ return Uint8Array.of(0xff);
87
+ default: {
88
+ const unknown = message;
89
+ throw new RangeError(`encodeMidiMessage: unknown message type ${String(unknown.type)}`);
90
+ }
91
+ }
92
+ }
93
+ //# sourceMappingURL=encode-midi-message.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"encode-midi-message.js","sourceRoot":"","sources":["../../src/message/encode-midi-message.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,15 @@
1
+ import type { MidiMessage } from "./midi-message.js";
2
+ /**
3
+ * Whether `message` releases a note: a `noteOff`, or a `noteOn` with velocity
4
+ * 0, which every MIDI receiver treats as a note-off.
5
+ *
6
+ * Use it instead of testing `type === "noteOff"`, which misses the note-on form
7
+ * most senders use so that running status covers a whole phrase.
8
+ *
9
+ * @example
10
+ * isMidiNoteRelease({ type: "noteOn", channel: 0, note: 60, velocity: 0 }); // true
11
+ */
12
+ export declare function isMidiNoteRelease(message: MidiMessage): message is Extract<MidiMessage, {
13
+ type: "noteOff" | "noteOn";
14
+ }>;
15
+ //# sourceMappingURL=is-midi-note-release.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"is-midi-note-release.d.ts","sourceRoot":"","sources":["../../src/message/is-midi-note-release.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAErD;;;;;;;;;GASG;AACH,wBAAgB,iBAAiB,CAChC,OAAO,EAAE,WAAW,GAClB,OAAO,IAAI,OAAO,CAAC,WAAW,EAAE;IAAE,IAAI,EAAE,SAAS,GAAG,QAAQ,CAAA;CAAE,CAAC,CAKjE"}
@@ -0,0 +1,15 @@
1
+ /**
2
+ * Whether `message` releases a note: a `noteOff`, or a `noteOn` with velocity
3
+ * 0, which every MIDI receiver treats as a note-off.
4
+ *
5
+ * Use it instead of testing `type === "noteOff"`, which misses the note-on form
6
+ * most senders use so that running status covers a whole phrase.
7
+ *
8
+ * @example
9
+ * isMidiNoteRelease({ type: "noteOn", channel: 0, note: 60, velocity: 0 }); // true
10
+ */
11
+ export function isMidiNoteRelease(message) {
12
+ return (message.type === "noteOff" ||
13
+ (message.type === "noteOn" && message.velocity === 0));
14
+ }
15
+ //# sourceMappingURL=is-midi-note-release.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"is-midi-note-release.js","sourceRoot":"","sources":["../../src/message/is-midi-note-release.ts"],"names":[],"mappings":"AAEA;;;;;;;;;GASG;AACH,MAAM,UAAU,iBAAiB,CAChC,OAAoB;IAEpB,OAAO,CACN,OAAO,CAAC,IAAI,KAAK,SAAS;QAC1B,CAAC,OAAO,CAAC,IAAI,KAAK,QAAQ,IAAI,OAAO,CAAC,QAAQ,KAAK,CAAC,CAAC,CACrD,CAAC;AACH,CAAC"}
@@ -0,0 +1,67 @@
1
+ import { type DecodeResult, type Decoder } from "@ac-kit/format-core";
2
+ import type { MidiMessage } from "./midi-message.js";
3
+ /** Options for {@link MidiMessageDecoder}. */
4
+ export type MidiMessageDecoderOptions = {
5
+ /**
6
+ * Largest SysEx payload accepted, in bytes between F0 and F7. A longer one is
7
+ * reported as a recoverable `sysExTooLong` error and its remaining bytes are
8
+ * dropped up to its end. This bounds the memory the decoder holds, since a
9
+ * SysEx in progress is kept in its own state rather than the caller's
10
+ * buffer.
11
+ *
12
+ * Defaults to 1 048 576 (1 MiB), above a synthesizer's full bulk dump. Set it
13
+ * to what the devices on the link actually send.
14
+ */
15
+ maxSysExLength?: number;
16
+ };
17
+ /**
18
+ * Decodes MIDI 1.0 messages from a live byte stream — a USB-MIDI or serial
19
+ * port, an ALSA raw MIDI device, a Web MIDI capture — the way a MIDI receiver
20
+ * does.
21
+ *
22
+ * - **Running status**: data bytes after a channel message reuse its status. A
23
+ * System Common message or a SysEx cancels it.
24
+ * - **Real-time bytes** (F8–FF) may arrive anywhere, even between a status and
25
+ * its data bytes or inside a SysEx. They are returned immediately and the
26
+ * interrupted message carries on.
27
+ * - **SysEx** ends at F7, or at any other non-real-time status byte (an implied
28
+ * End of Exclusive): the SysEx is returned, then that status is read.
29
+ * - **Recoverable errors**: stray data bytes with no status, a message cut short
30
+ * by a status byte, the undefined statuses F4, F5, F9 and FD, an F7 with no
31
+ * SysEx open and an over-long SysEx come back as `error` results carrying a
32
+ * {@link MidiStreamError}. Their bytes are consumed and decoding
33
+ * resynchronises at the next status byte. The decoder never returns `fatal`.
34
+ *
35
+ * It is stateful and an instance must back exactly one stream. To return a
36
+ * real-time message that interrupts another one, it moves the interrupted
37
+ * message's leading bytes into its own state and reports them consumed; it does
38
+ * the same with a SysEx as it arrives, so a long dump does not sit in the
39
+ * caller's buffer. A message whose bytes were moved that way is completed by
40
+ * later input. When input ends, a SysEx in progress is still reported as
41
+ * truncated (its last byte is kept in the caller's buffer for that purpose),
42
+ * but a channel or System Common message whose start was absorbed because a
43
+ * real-time byte interrupted it is not: it stays in the state until
44
+ * {@link reset}.
45
+ *
46
+ * Time complexity: O(n) in the bytes received, amortised over calls. Memory:
47
+ * O(`maxSysExLength`).
48
+ *
49
+ * @see MidiMessageEncoder for the write half.
50
+ */
51
+ export declare class MidiMessageDecoder implements Decoder<MidiMessage> {
52
+ private readonly maxSysExLength;
53
+ private state;
54
+ /** SysEx payload bytes moved out of the caller's buffer, in order. */
55
+ private sysExChunks;
56
+ /**
57
+ * @throws {RangeError} When `maxSysExLength` is not a non-negative safe
58
+ * integer.
59
+ */
60
+ constructor(options?: MidiMessageDecoderOptions);
61
+ decode(view: Uint8Array): DecodeResult<MidiMessage>;
62
+ /** Discards running status, any message in progress and any SysEx accumulated. */
63
+ reset(): void;
64
+ private absorbSysEx_;
65
+ private fail_;
66
+ }
67
+ //# sourceMappingURL=midi-message-decoder.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"midi-message-decoder.d.ts","sourceRoot":"","sources":["../../src/message/midi-message-decoder.ts"],"names":[],"mappings":"AACA,OAAO,EAEN,KAAK,YAAY,EACjB,KAAK,OAAO,EACZ,MAAM,qBAAqB,CAAC;AAe7B,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAIrD,8CAA8C;AAC9C,MAAM,MAAM,yBAAyB,GAAG;IACvC;;;;;;;;;OASG;IACH,cAAc,CAAC,EAAE,MAAM,CAAC;CACxB,CAAC;AA0DF;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAiCG;AACH,qBAAa,kBAAmB,YAAW,OAAO,CAAC,WAAW,CAAC;IAC9D,OAAO,CAAC,QAAQ,CAAC,cAAc,CAAS;IAExC,OAAO,CAAC,KAAK,CAAiC;IAE9C,sEAAsE;IACtE,OAAO,CAAC,WAAW,CAAoB;IAEvC;;;OAGG;IACH,YAAY,OAAO,CAAC,EAAE,yBAAyB,EAQ9C;IAED,MAAM,CAAC,IAAI,EAAE,UAAU,GAAG,YAAY,CAAC,WAAW,CAAC,CAoNlD;IAED,kFAAkF;IAClF,KAAK,IAAI,IAAI,CAGZ;IAED,OAAO,CAAC,YAAY;IAMpB,OAAO,CAAC,KAAK;CAcb"}
@@ -0,0 +1,254 @@
1
+ import { concatBytes } from "@ac-kit/core";
2
+ import { DECODE_INCOMPLETE, } from "@ac-kit/format-core";
3
+ import { MidiStreamError } from "../errors.js";
4
+ import { FIRST_REAL_TIME_, SONG_POSITION_POINTER_, SONG_SELECT_, SYSEX_END_, SYSEX_START_, TIME_CODE_QUARTER_FRAME_, TUNE_REQUEST_, channelDataLength_, createChannelMessage_, realTimeMessage_, } from "./_midi-status.js";
5
+ const DEFAULT_MAX_SYSEX_LENGTH_ = 1048576;
6
+ const INITIAL_STATE_ = {
7
+ runningStatus: 0,
8
+ status: 0,
9
+ data1: 0,
10
+ dataCount: 0,
11
+ sysExLength: 0,
12
+ discarding: false,
13
+ };
14
+ function dataLength_(status) {
15
+ if (status === SONG_POSITION_POINTER_) {
16
+ return 2;
17
+ }
18
+ if (status === TIME_CODE_QUARTER_FRAME_ || status === SONG_SELECT_) {
19
+ return 1;
20
+ }
21
+ return channelDataLength_(status);
22
+ }
23
+ function createMessage_(status, data1, data2) {
24
+ switch (status) {
25
+ case TIME_CODE_QUARTER_FRAME_:
26
+ return {
27
+ type: "timeCodeQuarterFrame",
28
+ piece: data1 >> 4,
29
+ value: data1 & 0x0f,
30
+ };
31
+ case SONG_POSITION_POINTER_:
32
+ return { type: "songPositionPointer", position: data1 | (data2 << 7) };
33
+ case SONG_SELECT_:
34
+ return { type: "songSelect", song: data1 };
35
+ default:
36
+ return createChannelMessage_(status, data1, data2);
37
+ }
38
+ }
39
+ /**
40
+ * Decodes MIDI 1.0 messages from a live byte stream — a USB-MIDI or serial
41
+ * port, an ALSA raw MIDI device, a Web MIDI capture — the way a MIDI receiver
42
+ * does.
43
+ *
44
+ * - **Running status**: data bytes after a channel message reuse its status. A
45
+ * System Common message or a SysEx cancels it.
46
+ * - **Real-time bytes** (F8–FF) may arrive anywhere, even between a status and
47
+ * its data bytes or inside a SysEx. They are returned immediately and the
48
+ * interrupted message carries on.
49
+ * - **SysEx** ends at F7, or at any other non-real-time status byte (an implied
50
+ * End of Exclusive): the SysEx is returned, then that status is read.
51
+ * - **Recoverable errors**: stray data bytes with no status, a message cut short
52
+ * by a status byte, the undefined statuses F4, F5, F9 and FD, an F7 with no
53
+ * SysEx open and an over-long SysEx come back as `error` results carrying a
54
+ * {@link MidiStreamError}. Their bytes are consumed and decoding
55
+ * resynchronises at the next status byte. The decoder never returns `fatal`.
56
+ *
57
+ * It is stateful and an instance must back exactly one stream. To return a
58
+ * real-time message that interrupts another one, it moves the interrupted
59
+ * message's leading bytes into its own state and reports them consumed; it does
60
+ * the same with a SysEx as it arrives, so a long dump does not sit in the
61
+ * caller's buffer. A message whose bytes were moved that way is completed by
62
+ * later input. When input ends, a SysEx in progress is still reported as
63
+ * truncated (its last byte is kept in the caller's buffer for that purpose),
64
+ * but a channel or System Common message whose start was absorbed because a
65
+ * real-time byte interrupted it is not: it stays in the state until
66
+ * {@link reset}.
67
+ *
68
+ * Time complexity: O(n) in the bytes received, amortised over calls. Memory:
69
+ * O(`maxSysExLength`).
70
+ *
71
+ * @see MidiMessageEncoder for the write half.
72
+ */
73
+ export class MidiMessageDecoder {
74
+ maxSysExLength;
75
+ state = { ...INITIAL_STATE_ };
76
+ /** SysEx payload bytes moved out of the caller's buffer, in order. */
77
+ sysExChunks = [];
78
+ /**
79
+ * @throws {RangeError} When `maxSysExLength` is not a non-negative safe
80
+ * integer.
81
+ */
82
+ constructor(options) {
83
+ const maxSysExLength = options?.maxSysExLength ?? DEFAULT_MAX_SYSEX_LENGTH_;
84
+ if (!Number.isSafeInteger(maxSysExLength) || maxSysExLength < 0) {
85
+ throw new RangeError(`MidiMessageDecoder: maxSysExLength must be a non-negative safe integer, got ${maxSysExLength}`);
86
+ }
87
+ this.maxSysExLength = maxSysExLength;
88
+ }
89
+ decode(view) {
90
+ if (view.length === 0) {
91
+ return DECODE_INCOMPLETE;
92
+ }
93
+ // Worked on a copy and committed only with a result that consumes bytes:
94
+ // an `incomplete` result leaves the bytes in the caller's buffer, so the
95
+ // next call must start again from the state this one started from.
96
+ const state = { ...this.state };
97
+ let sysExFrom = 0;
98
+ let strayFrom = -1;
99
+ for (let index = 0; index < view.length; index++) {
100
+ const byte = view[index];
101
+ if (byte >= FIRST_REAL_TIME_) {
102
+ if (strayFrom !== -1) {
103
+ return this.fail_(state, "strayData", "data bytes with no status", index);
104
+ }
105
+ if (state.status === SYSEX_START_ && !state.discarding) {
106
+ this.absorbSysEx_(view, sysExFrom, index);
107
+ }
108
+ const message = realTimeMessage_(byte);
109
+ if (message === undefined) {
110
+ return this.fail_(state, "undefinedStatus", `undefined real-time status 0x${byte.toString(16)}`, index + 1);
111
+ }
112
+ this.state = state;
113
+ return { status: "decoded", value: message, consumed: index + 1 };
114
+ }
115
+ if (strayFrom !== -1) {
116
+ if (byte < 0x80) {
117
+ continue;
118
+ }
119
+ return this.fail_(state, "strayData", "data bytes with no status", index);
120
+ }
121
+ if (state.status === SYSEX_START_) {
122
+ if (byte < 0x80) {
123
+ if (state.discarding) {
124
+ continue;
125
+ }
126
+ state.sysExLength++;
127
+ if (state.sysExLength > this.maxSysExLength) {
128
+ state.sysExLength = 0;
129
+ state.discarding = true;
130
+ this.sysExChunks = [];
131
+ return this.fail_(state, "sysExTooLong", `SysEx longer than ${this.maxSysExLength} bytes`, index + 1);
132
+ }
133
+ continue;
134
+ }
135
+ if (state.discarding) {
136
+ state.status = 0;
137
+ state.discarding = false;
138
+ if (byte === SYSEX_END_) {
139
+ this.state = state;
140
+ return { status: "skip", consumed: index + 1 };
141
+ }
142
+ // Any other status ends the discarded SysEx and is read below.
143
+ }
144
+ else {
145
+ const data = concatBytes([
146
+ ...this.sysExChunks,
147
+ view.subarray(sysExFrom, index),
148
+ ]);
149
+ this.sysExChunks = [];
150
+ state.status = 0;
151
+ state.sysExLength = 0;
152
+ this.state = state;
153
+ return {
154
+ status: "decoded",
155
+ value: { type: "sysEx", data },
156
+ // An implied End of Exclusive leaves the status byte to the next call.
157
+ consumed: byte === SYSEX_END_ ? index + 1 : index,
158
+ };
159
+ }
160
+ }
161
+ if (byte >= 0x80) {
162
+ if (state.status !== 0) {
163
+ state.status = 0;
164
+ state.dataCount = 0;
165
+ return this.fail_(state, "incompleteMessage", `message 0x${byte.toString(16)} interrupted an incomplete message`, index);
166
+ }
167
+ if (byte < SYSEX_START_) {
168
+ state.runningStatus = byte;
169
+ state.status = byte;
170
+ continue;
171
+ }
172
+ state.runningStatus = 0;
173
+ switch (byte) {
174
+ case SYSEX_START_:
175
+ state.status = byte;
176
+ state.sysExLength = 0;
177
+ sysExFrom = index + 1;
178
+ continue;
179
+ case TIME_CODE_QUARTER_FRAME_:
180
+ case SONG_POSITION_POINTER_:
181
+ case SONG_SELECT_:
182
+ state.status = byte;
183
+ continue;
184
+ case TUNE_REQUEST_:
185
+ this.state = state;
186
+ return {
187
+ status: "decoded",
188
+ value: { type: "tuneRequest" },
189
+ consumed: index + 1,
190
+ };
191
+ case SYSEX_END_:
192
+ return this.fail_(state, "unexpectedEndOfExclusive", "End of Exclusive with no SysEx open", index + 1);
193
+ default:
194
+ return this.fail_(state, "undefinedStatus", `undefined System Common status 0x${byte.toString(16)}`, index + 1);
195
+ }
196
+ }
197
+ if (state.status === 0) {
198
+ if (state.runningStatus === 0) {
199
+ strayFrom = index;
200
+ continue;
201
+ }
202
+ state.status = state.runningStatus;
203
+ }
204
+ if (state.dataCount === 0 && dataLength_(state.status) === 2) {
205
+ state.data1 = byte;
206
+ state.dataCount = 1;
207
+ continue;
208
+ }
209
+ const message = createMessage_(state.status, state.dataCount === 0 ? byte : state.data1, byte);
210
+ state.status = 0;
211
+ state.dataCount = 0;
212
+ this.state = state;
213
+ return { status: "decoded", value: message, consumed: index + 1 };
214
+ }
215
+ if (strayFrom !== -1) {
216
+ return this.fail_(state, "strayData", "data bytes with no status", view.length);
217
+ }
218
+ if (state.status === SYSEX_START_) {
219
+ if (state.discarding) {
220
+ this.state = state;
221
+ return { status: "skip", consumed: view.length };
222
+ }
223
+ // Everything but the last payload byte moves into the state, so a
224
+ // driver still holds a byte and reports a truncation if input ends.
225
+ const kept = view.length - 1;
226
+ if (sysExFrom <= kept && kept > 0) {
227
+ this.absorbSysEx_(view, sysExFrom, kept);
228
+ state.sysExLength--;
229
+ this.state = state;
230
+ return { status: "skip", consumed: kept };
231
+ }
232
+ }
233
+ return DECODE_INCOMPLETE;
234
+ }
235
+ /** Discards running status, any message in progress and any SysEx accumulated. */
236
+ reset() {
237
+ this.state = { ...INITIAL_STATE_ };
238
+ this.sysExChunks = [];
239
+ }
240
+ absorbSysEx_(view, from, to) {
241
+ if (to > from) {
242
+ this.sysExChunks.push(view.slice(from, to));
243
+ }
244
+ }
245
+ fail_(state, reason, message, consumed) {
246
+ this.state = state;
247
+ return {
248
+ status: "error",
249
+ error: new MidiStreamError(`MidiMessageDecoder: ${message}`, reason),
250
+ consumed,
251
+ };
252
+ }
253
+ }
254
+ //# sourceMappingURL=midi-message-decoder.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"midi-message-decoder.js","sourceRoot":"","sources":["../../src/message/midi-message-decoder.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,43 @@
1
+ import type { Encoder } from "@ac-kit/format-core";
2
+ import type { MidiMessage } from "./midi-message.js";
3
+ /** Options for {@link MidiMessageEncoder}. */
4
+ export type MidiMessageEncoderOptions = {
5
+ /**
6
+ * Omit a channel message's status byte when it repeats the previous one (MIDI
7
+ * 1.0 running status), saving a third of the bandwidth of a run of notes.
8
+ * Defaults to `false`.
9
+ *
10
+ * Enable it only for a link with a single sender and a receiver that sees
11
+ * every byte from the start: a receiver joining mid-stream cannot read data
12
+ * bytes until the next status byte.
13
+ */
14
+ runningStatus?: boolean;
15
+ };
16
+ /**
17
+ * Encodes MIDI 1.0 messages for a byte stream — the write half matching
18
+ * {@link MidiMessageDecoder}.
19
+ *
20
+ * With `runningStatus`, the encoder remembers the last channel status it wrote
21
+ * and an instance must back exactly one stream. A System Common message or a
22
+ * SysEx clears that memory, as it does in a receiver; a System Real-Time
23
+ * message leaves it untouched. Without it, the result of every call is
24
+ * {@link encodeMidiMessage}'s.
25
+ */
26
+ export declare class MidiMessageEncoder implements Encoder<MidiMessage> {
27
+ private readonly runningStatus;
28
+ /** Last channel status written, 0 when running status is not in effect. */
29
+ private lastStatus;
30
+ constructor(options?: MidiMessageEncoderOptions);
31
+ /**
32
+ * Encodes one message, omitting its status byte under running status.
33
+ *
34
+ * @throws {RangeError} As {@link encodeMidiMessage}.
35
+ */
36
+ encode(message: MidiMessage): Uint8Array<ArrayBuffer>;
37
+ /**
38
+ * Forgets the running status, so the next channel message carries its status
39
+ * byte.
40
+ */
41
+ reset(): void;
42
+ }
43
+ //# sourceMappingURL=midi-message-encoder.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"midi-message-encoder.d.ts","sourceRoot":"","sources":["../../src/message/midi-message-encoder.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,qBAAqB,CAAC;AAGnD,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAErD,8CAA8C;AAC9C,MAAM,MAAM,yBAAyB,GAAG;IACvC;;;;;;;;OAQG;IACH,aAAa,CAAC,EAAE,OAAO,CAAC;CACxB,CAAC;AAEF;;;;;;;;;GASG;AACH,qBAAa,kBAAmB,YAAW,OAAO,CAAC,WAAW,CAAC;IAC9D,OAAO,CAAC,QAAQ,CAAC,aAAa,CAAU;IAExC,2EAA2E;IAC3E,OAAO,CAAC,UAAU,CAAK;IAEvB,YAAY,OAAO,CAAC,EAAE,yBAAyB,EAE9C;IAED;;;;OAIG;IACH,MAAM,CAAC,OAAO,EAAE,WAAW,GAAG,UAAU,CAAC,WAAW,CAAC,CAiBpD;IAED;;;OAGG;IACH,KAAK,IAAI,IAAI,CAEZ;CACD"}
@@ -0,0 +1,49 @@
1
+ import { encodeMidiMessage } from "./encode-midi-message.js";
2
+ /**
3
+ * Encodes MIDI 1.0 messages for a byte stream — the write half matching
4
+ * {@link MidiMessageDecoder}.
5
+ *
6
+ * With `runningStatus`, the encoder remembers the last channel status it wrote
7
+ * and an instance must back exactly one stream. A System Common message or a
8
+ * SysEx clears that memory, as it does in a receiver; a System Real-Time
9
+ * message leaves it untouched. Without it, the result of every call is
10
+ * {@link encodeMidiMessage}'s.
11
+ */
12
+ export class MidiMessageEncoder {
13
+ runningStatus;
14
+ /** Last channel status written, 0 when running status is not in effect. */
15
+ lastStatus = 0;
16
+ constructor(options) {
17
+ this.runningStatus = options?.runningStatus ?? false;
18
+ }
19
+ /**
20
+ * Encodes one message, omitting its status byte under running status.
21
+ *
22
+ * @throws {RangeError} As {@link encodeMidiMessage}.
23
+ */
24
+ encode(message) {
25
+ const bytes = encodeMidiMessage(message);
26
+ if (!this.runningStatus) {
27
+ return bytes;
28
+ }
29
+ const status = bytes[0];
30
+ if (status < 0xf0) {
31
+ if (status === this.lastStatus) {
32
+ return bytes.subarray(1);
33
+ }
34
+ this.lastStatus = status;
35
+ }
36
+ else if (status < 0xf8) {
37
+ this.lastStatus = 0;
38
+ }
39
+ return bytes;
40
+ }
41
+ /**
42
+ * Forgets the running status, so the next channel message carries its status
43
+ * byte.
44
+ */
45
+ reset() {
46
+ this.lastStatus = 0;
47
+ }
48
+ }
49
+ //# sourceMappingURL=midi-message-encoder.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"midi-message-encoder.js","sourceRoot":"","sources":["../../src/message/midi-message-encoder.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,iBAAiB,EAAE,MAAM,0BAA0B,CAAC;AAiB7D;;;;;;;;;GASG;AACH,MAAM,OAAO,kBAAkB;IACb,aAAa,CAAU;IAExC,2EAA2E;IACnE,UAAU,GAAG,CAAC,CAAC;IAEvB,YAAY,OAAmC;QAC9C,IAAI,CAAC,aAAa,GAAG,OAAO,EAAE,aAAa,IAAI,KAAK,CAAC;IACtD,CAAC;IAED;;;;OAIG;IACH,MAAM,CAAC,OAAoB;QAC1B,MAAM,KAAK,GAAG,iBAAiB,CAAC,OAAO,CAAC,CAAC;QACzC,IAAI,CAAC,IAAI,CAAC,aAAa,EAAE,CAAC;YACzB,OAAO,KAAK,CAAC;QACd,CAAC;QAED,MAAM,MAAM,GAAG,KAAK,CAAC,CAAC,CAAE,CAAC;QACzB,IAAI,MAAM,GAAG,IAAI,EAAE,CAAC;YACnB,IAAI,MAAM,KAAK,IAAI,CAAC,UAAU,EAAE,CAAC;gBAChC,OAAO,KAAK,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC;YAC1B,CAAC;YACD,IAAI,CAAC,UAAU,GAAG,MAAM,CAAC;QAC1B,CAAC;aAAM,IAAI,MAAM,GAAG,IAAI,EAAE,CAAC;YAC1B,IAAI,CAAC,UAAU,GAAG,CAAC,CAAC;QACrB,CAAC;QAED,OAAO,KAAK,CAAC;IACd,CAAC;IAED;;;OAGG;IACH,KAAK;QACJ,IAAI,CAAC,UAAU,GAAG,CAAC,CAAC;IACrB,CAAC;CACD"}