@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
package/CHANGELOG.md ADDED
@@ -0,0 +1,38 @@
1
+ # @ac-kit/format-midi
2
+
3
+ ## 0.1.0
4
+
5
+ ### Minor Changes
6
+
7
+ - 992cbbb: New package: MIDI 1.0, from the bytes on a live port to the time in seconds of
8
+ every event in a Standard MIDI File.
9
+
10
+ - `MidiMessage` models every MIDI 1.0 message — the seven Channel Voice
11
+ messages (Channel Mode as `controlChange` 120–127), System Common including
12
+ `sysEx`, and System Real-Time — with `MidiChannelMessage`,
13
+ `MidiSystemCommonMessage` and `MidiSystemRealTimeMessage` naming the groups.
14
+ `encodeMidiMessage` writes one with every field range-checked, and
15
+ `isMidiNoteRelease` treats a note-on with velocity 0 as the note-off
16
+ receivers take it for.
17
+ - `MidiMessageDecoder` reads a live byte stream the way a MIDI receiver does:
18
+ running status, real-time bytes returned the moment they arrive even inside
19
+ another message or a SysEx, SysEx ended by F7 or by any other status, and
20
+ every violation — stray data, a message cut short, an undefined status, a
21
+ lone F7, a SysEx over `maxSysExLength` (1 MiB by default) — a recoverable
22
+ `MidiStreamError` after which it resynchronises. `MidiMessageEncoder` writes
23
+ messages back, optionally with running status.
24
+ - `parseMidiFile` reads a Standard MIDI File (RP-001) into its format, time
25
+ division (metrical or SMPTE) and tracks of channel, SysEx (F0 and F7 forms)
26
+ and meta events, every RP-001 meta type by name and text kept as bytes. It
27
+ skips unknown chunks and extra header bytes, accepts a track without End of
28
+ Track as it is, and throws `MidiFileError` with the byte offset on malformed
29
+ input. `printMidiFile` writes the file back byte for byte, with or without
30
+ running status.
31
+ - `composeMidiTimeline` places every event on one axis with its tick and
32
+ seconds: format 0 and 1 tracks merged by tick under every Set Tempo event,
33
+ format 2 tracks timed independently, SMPTE divisions at a fixed tick length.
34
+
35
+ ### Patch Changes
36
+
37
+ - Updated dependencies [6254a3f]
38
+ - @ac-kit/format-core@0.1.3
package/LICENSE ADDED
@@ -0,0 +1,28 @@
1
+ BSD 3-Clause License
2
+
3
+ Copyright (c) 2024-2026 Anthony Champagne and contributors
4
+
5
+ Redistribution and use in source and binary forms, with or without
6
+ modification, are permitted provided that the following conditions are met:
7
+
8
+ 1. Redistributions of source code must retain the above copyright notice, this
9
+ list of conditions and the following disclaimer.
10
+
11
+ 2. Redistributions in binary form must reproduce the above copyright notice,
12
+ this list of conditions and the following disclaimer in the documentation
13
+ and/or other materials provided with the distribution.
14
+
15
+ 3. Neither the name of the copyright holder nor the names of its
16
+ contributors may be used to endorse or promote products derived from
17
+ this software without specific prior written permission.
18
+
19
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
20
+ AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
21
+ IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
22
+ DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
23
+ FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
24
+ DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
25
+ SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
26
+ CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
27
+ OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
28
+ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
package/README.md CHANGED
@@ -1,3 +1,104 @@
1
- # Temporary Holding Version
1
+ # @ac-kit/format-midi
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ MIDI 1.0 in plain data: the messages a device sends and receives, decoded from
4
+ and encoded to a live byte stream; Standard MIDI Files, parsed and written back
5
+ byte for byte; and the time, in seconds, of every event in a file.
6
+
7
+ ```ts
8
+ import { ByteAccumulator } from "@ac-kit/core";
9
+ import { DecodeStream } from "@ac-kit/format-core";
10
+ import {
11
+ MidiMessageDecoder,
12
+ composeMidiTimeline,
13
+ parseMidiFile,
14
+ } from "@ac-kit/format-midi";
15
+
16
+ // A live port: running status, real-time bytes anywhere, SysEx of any length.
17
+ const messages = port.readable.pipeThrough(
18
+ new DecodeStream(new MidiMessageDecoder(), {
19
+ buffer: new ByteAccumulator(65_536),
20
+ onError: (error) => console.warn(error),
21
+ }),
22
+ );
23
+
24
+ // A file: every event with its tick and its time in seconds.
25
+ for (const { tick, seconds, event } of composeMidiTimeline(parseMidiFile(bytes))) {
26
+ // …
27
+ }
28
+ ```
29
+
30
+ It opens no device: read bytes from wherever the platform exposes MIDI — an
31
+ ALSA raw MIDI node, a serial port, a USB-MIDI endpoint, Web MIDI — and write
32
+ bytes back the same way.
33
+
34
+ ## Messages
35
+
36
+ `MidiMessage` is a union discriminated by `type`, following the _MIDI 1.0
37
+ Detailed Specification_: the seven Channel Voice messages, System Common
38
+ (`sysEx`, `timeCodeQuarterFrame`, `songPositionPointer`, `songSelect`,
39
+ `tuneRequest`) and System Real-Time (`timingClock`, `start`, `continue`, `stop`,
40
+ `activeSensing`, `systemReset`). `MidiChannelMessage`,
41
+ `MidiSystemCommonMessage` and `MidiSystemRealTimeMessage` name the three groups.
42
+
43
+ Values are the ones on the wire, with two exceptions: `pitchBend` is -8192–8191
44
+ with 0 at the centre, and a `sysEx` holds its payload without the F0 and F7
45
+ around it. Channel Mode messages are `controlChange` with controller 120–127, as
46
+ on the wire. A note-on with velocity 0 stays a `noteOn`; `isMidiNoteRelease`
47
+ answers whether a message releases a note in either form.
48
+
49
+ `encodeMidiMessage` writes one message, checking every field's range.
50
+
51
+ ## Streams
52
+
53
+ `MidiMessageDecoder` is a `Decoder` from `@ac-kit/format-core`, so it runs under
54
+ `DecodeStream`, `decodeAll` or any other driver. It reads a stream the way a
55
+ MIDI receiver does:
56
+
57
+ - data bytes after a channel message reuse its status (running status), until
58
+ a System Common message or a SysEx cancels it;
59
+ - a real-time byte is returned the moment it arrives, even in the middle of
60
+ another message or of a SysEx, which then carries on;
61
+ - a SysEx ends at F7 or at any other status byte;
62
+ - stray data bytes, a message cut short, an undefined status (F4, F5, F9, FD),
63
+ a lone F7 and a SysEx over `maxSysExLength` (1 MiB by default) are
64
+ recoverable `error` resu rather than the driver's buffer, so the buffer only
65
+ ever holds a few bytes beyond the chunk just received. One instance serves
66
+ exactly one stream; `reset` decoding resumes at the next status byte.
67
+
68
+ The decoder keeps a message interrupted by a real-time byte, and a SysEx in
69
+ progress, in its own state, so one instance serves exactly one stream; `reset`
70
+ clears it.
71
+
72
+ `MidiMessageEncoder` is the matching `Encoder`, with an optional
73
+ `runningStatus`.
74
+
75
+ ## Standard MIDI Files
76
+
77
+ `parseMidiFile` reads a Standard MIDI File (RP-001) into a `MidiFile`: its
78
+ `format` (0, 1 or 2), its `timeDivision` (ticks per quarter note, or SMPTE
79
+ frames and ticks per frame) and its tracks of `{ deltaTime, event }`. An event
80
+ is a channel message, an F0 or F7 SysEx event with its bytes as stored, or a
81
+ `MidiMetaEvent` — every meta type of RP-001 by name, the others as `unknown`.
82
+ Text meta events keep their bytes, because the standard does not say which
83
+ encoding they use.
84
+
85
+ It skips chunks of unknown type and extra header bytes, and accepts a track
86
+ that does not end with End of Track, without adding one. A malformed file throws
87
+ `MidiFileError`, a `SyntaxError` carrying the byte `offset` it gave up at.
88
+
89
+ `printMidiFile` writes a `MidiFile` back, with `runningStatus` as an option. The
90
+ events are written as given and none is added, so a track built by hand needs
91
+ its own End of Track. A field out of range is a `RangeError`.
92
+
93
+ ## Timeline
94
+
95
+ `composeMidiTimeline` lists every event of a file with its `trackIndex`, `tick`
96
+ and `seconds`, in playing order. Tracks of a format 0 or 1 file are merged by
97
+ tick, under every Set Tempo event in any track. Tracks of a format 2 file are
98
+ independent sequences: each is timed on its own and they follow one another.
99
+ An SMPTE division gives each tick a fixed length, with frame rate 29 meaning
100
+ 29.97.
101
+
102
+ See the
103
+ [generated API docs](https://anthochamp.github.io/node-essentials/api/format-midi/)
104
+ for the full reference.
@@ -0,0 +1,45 @@
1
+ /**
2
+ * What a {@link MidiStreamError} reports:
3
+ *
4
+ * - `strayData` — data bytes with no status to give them meaning: nothing was
5
+ * received yet, or a System Common or SysEx message cancelled running
6
+ * status.
7
+ * - `incompleteMessage` — a status byte arrived before the data bytes of the
8
+ * previous message were all received; the partial message is dropped.
9
+ * - `undefinedStatus` — one of the status bytes MIDI 1.0 leaves undefined: F4 and
10
+ * F5 (System Common) or F9 and FD (System Real-Time).
11
+ * - `unexpectedEndOfExclusive` — an F7 (End of Exclusive) with no SysEx open.
12
+ * - `sysExTooLong` — a SysEx payload exceeded the decoder's `maxSysExLength`; the
13
+ * rest of it, up to its end, is discarded.
14
+ */
15
+ export type MidiStreamErrorReason = "strayData" | "incompleteMessage" | "undefinedStatus" | "unexpectedEndOfExclusive" | "sysExTooLong";
16
+ /**
17
+ * A recoverable violation in a live MIDI byte stream, reported by
18
+ * {@link MidiMessageDecoder} as an `error` decode result. The offending bytes
19
+ * are consumed and decoding resumes at the next status byte, the way a MIDI
20
+ * receiver resynchronises.
21
+ */
22
+ export declare class MidiStreamError extends SyntaxError {
23
+ /** What kind of violation was found. */
24
+ readonly reason: MidiStreamErrorReason;
25
+ constructor(message: string,
26
+ /** What kind of violation was found. */
27
+ reason: MidiStreamErrorReason);
28
+ }
29
+ /**
30
+ * A Standard MIDI File is malformed: a chunk or event is truncated, a
31
+ * variable-length quantity is invalid, a data byte has no running status to
32
+ * apply to, or a meta event's length does not match its type.
33
+ *
34
+ * Thrown by {@link parseMidiFile}. A model built by the caller that
35
+ * {@link printMidiFile} cannot write — a field out of range, a message that is
36
+ * not a channel message — is a `RangeError` instead.
37
+ */
38
+ export declare class MidiFileError extends SyntaxError {
39
+ /** Byte offset in the file of the structure the parser gave up at. */
40
+ readonly offset: number;
41
+ constructor(message: string,
42
+ /** Byte offset in the file of the structure the parser gave up at. */
43
+ offset: number, options?: ErrorOptions);
44
+ }
45
+ //# sourceMappingURL=errors.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AACH,MAAM,MAAM,qBAAqB,GAC9B,WAAW,GACX,mBAAmB,GACnB,iBAAiB,GACjB,0BAA0B,GAC1B,cAAc,CAAC;AAElB;;;;;GAKG;AACH,qBAAa,eAAgB,SAAQ,WAAW;IAG9C,wCAAwC;IACxC,QAAQ,CAAC,MAAM,EAAE,qBAAqB;IAHvC,YACC,OAAO,EAAE,MAAM;IACf,wCAAwC;IAC/B,MAAM,EAAE,qBAAqB,EAItC;CACD;AAED;;;;;;;;GAQG;AACH,qBAAa,aAAc,SAAQ,WAAW;IAG5C,sEAAsE;IACtE,QAAQ,CAAC,MAAM,EAAE,MAAM;IAHxB,YACC,OAAO,EAAE,MAAM;IACf,sEAAsE;IAC7D,MAAM,EAAE,MAAM,EACvB,OAAO,CAAC,EAAE,YAAY,EAItB;CACD"}
package/dist/errors.js ADDED
@@ -0,0 +1,36 @@
1
+ /**
2
+ * A recoverable violation in a live MIDI byte stream, reported by
3
+ * {@link MidiMessageDecoder} as an `error` decode result. The offending bytes
4
+ * are consumed and decoding resumes at the next status byte, the way a MIDI
5
+ * receiver resynchronises.
6
+ */
7
+ export class MidiStreamError extends SyntaxError {
8
+ reason;
9
+ constructor(message,
10
+ /** What kind of violation was found. */
11
+ reason) {
12
+ super(message);
13
+ this.reason = reason;
14
+ this.name = "MidiStreamError";
15
+ }
16
+ }
17
+ /**
18
+ * A Standard MIDI File is malformed: a chunk or event is truncated, a
19
+ * variable-length quantity is invalid, a data byte has no running status to
20
+ * apply to, or a meta event's length does not match its type.
21
+ *
22
+ * Thrown by {@link parseMidiFile}. A model built by the caller that
23
+ * {@link printMidiFile} cannot write — a field out of range, a message that is
24
+ * not a channel message — is a `RangeError` instead.
25
+ */
26
+ export class MidiFileError extends SyntaxError {
27
+ offset;
28
+ constructor(message,
29
+ /** Byte offset in the file of the structure the parser gave up at. */
30
+ offset, options) {
31
+ super(`${message} (at byte ${offset})`, options);
32
+ this.offset = offset;
33
+ this.name = "MidiFileError";
34
+ }
35
+ }
36
+ //# sourceMappingURL=errors.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"errors.js","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":"AAqBA;;;;;GAKG;AACH,MAAM,OAAO,eAAgB,SAAQ,WAAW;IAIrC,MAAM;IAHhB,YACC,OAAe;IACf,wCAAwC;IAC/B,MAA6B;QAEtC,KAAK,CAAC,OAAO,CAAC,CAAC;sBAFN,MAAM;QAGf,IAAI,CAAC,IAAI,GAAG,iBAAiB,CAAC;IAC/B,CAAC;CACD;AAED;;;;;;;;GAQG;AACH,MAAM,OAAO,aAAc,SAAQ,WAAW;IAInC,MAAM;IAHhB,YACC,OAAe;IACf,sEAAsE;IAC7D,MAAc,EACvB,OAAsB;QAEtB,KAAK,CAAC,GAAG,OAAO,aAAa,MAAM,GAAG,EAAE,OAAO,CAAC,CAAC;sBAHxC,MAAM;QAIf,IAAI,CAAC,IAAI,GAAG,eAAe,CAAC;IAC7B,CAAC;CACD"}
@@ -0,0 +1,33 @@
1
+ import type { MidiMetaEvent } from "./midi-meta-event.js";
2
+ export type TextMetaType_ = Exclude<Extract<MidiMetaEvent, {
3
+ data: Uint8Array;
4
+ }>["type"], "sequencerSpecific" | "unknown">;
5
+ export declare const META_SEQUENCE_NUMBER_ = 0;
6
+ export declare const META_CHANNEL_PREFIX_ = 32;
7
+ export declare const META_MIDI_PORT_ = 33;
8
+ export declare const META_END_OF_TRACK_ = 47;
9
+ export declare const META_SET_TEMPO_ = 81;
10
+ export declare const META_SMPTE_OFFSET_ = 84;
11
+ export declare const META_TIME_SIGNATURE_ = 88;
12
+ export declare const META_KEY_SIGNATURE_ = 89;
13
+ export declare const META_SEQUENCER_SPECIFIC_ = 127;
14
+ export declare const TEXT_META_TYPES_: Readonly<Record<number, TextMetaType_>>;
15
+ export declare const TEXT_META_CODES_: {
16
+ readonly text: 1;
17
+ readonly copyright: 2;
18
+ readonly trackName: 3;
19
+ readonly instrumentName: 4;
20
+ readonly lyric: 5;
21
+ readonly marker: 6;
22
+ readonly cuePoint: 7;
23
+ readonly programName: 8;
24
+ readonly deviceName: 9;
25
+ };
26
+ /** Every meta type a member other than `unknown` spells. */
27
+ export declare const KNOWN_META_TYPES_: ReadonlySet<number>;
28
+ /**
29
+ * SMPTE frame rates by the 2-bit code in bits 5–6 of an SMPTE Offset's hour
30
+ * byte.
31
+ */
32
+ export declare const SMPTE_FRAME_RATES_: readonly [24, 25, 29, 30];
33
+ //# sourceMappingURL=_meta-types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"_meta-types.d.ts","sourceRoot":"","sources":["../../src/file/_meta-types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAC;AAE1D,MAAM,MAAM,aAAa,GAAG,OAAO,CAClC,OAAO,CAAC,aAAa,EAAE;IAAE,IAAI,EAAE,UAAU,CAAA;CAAE,CAAC,CAAC,MAAM,CAAC,EACpD,mBAAmB,GAAG,SAAS,CAC/B,CAAC;AAIF,eAAO,MAAM,qBAAqB,IAAO,CAAC;AAC1C,eAAO,MAAM,oBAAoB,KAAO,CAAC;AACzC,eAAO,MAAM,eAAe,KAAO,CAAC;AACpC,eAAO,MAAM,kBAAkB,KAAO,CAAC;AACvC,eAAO,MAAM,eAAe,KAAO,CAAC;AACpC,eAAO,MAAM,kBAAkB,KAAO,CAAC;AACvC,eAAO,MAAM,oBAAoB,KAAO,CAAC;AACzC,eAAO,MAAM,mBAAmB,KAAO,CAAC;AACxC,eAAO,MAAM,wBAAwB,MAAO,CAAC;AAE7C,eAAO,MAAM,gBAAgB,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,aAAa,CAAC,CAUpE,CAAC;AAEF,eAAO,MAAM,gBAAgB;mBACtB,CAAI;wBACC,CAAI;wBACJ,CAAI;6BACC,CAAI;oBACb,CAAI;qBACH,CAAI;uBACF,CAAI;0BACD,CAAI;yBACL,CAAI;CACiC,CAAC;AAEnD,4DAA4D;AAC5D,eAAO,MAAM,iBAAiB,EAAE,WAAW,CAAC,MAAM,CAWhD,CAAC;AAEH;;;GAGG;AACH,eAAO,MAAM,kBAAkB,YAAI,EAAE,EAAE,EAAE,EAAE,EAAE,EAAE,EAAE,CAAU,CAAC"}
@@ -0,0 +1,52 @@
1
+ // Meta event types of RP-001, with 0x08 and 0x09 from RP-019 and 0x21 from the
2
+ // MMA's later addition (Program Name, Device Name, MIDI Port).
3
+ export const META_SEQUENCE_NUMBER_ = 0x00;
4
+ export const META_CHANNEL_PREFIX_ = 0x20;
5
+ export const META_MIDI_PORT_ = 0x21;
6
+ export const META_END_OF_TRACK_ = 0x2f;
7
+ export const META_SET_TEMPO_ = 0x51;
8
+ export const META_SMPTE_OFFSET_ = 0x54;
9
+ export const META_TIME_SIGNATURE_ = 0x58;
10
+ export const META_KEY_SIGNATURE_ = 0x59;
11
+ export const META_SEQUENCER_SPECIFIC_ = 0x7f;
12
+ export const TEXT_META_TYPES_ = {
13
+ 0x01: "text",
14
+ 0x02: "copyright",
15
+ 0x03: "trackName",
16
+ 0x04: "instrumentName",
17
+ 0x05: "lyric",
18
+ 0x06: "marker",
19
+ 0x07: "cuePoint",
20
+ 0x08: "programName",
21
+ 0x09: "deviceName",
22
+ };
23
+ export const TEXT_META_CODES_ = {
24
+ text: 0x01,
25
+ copyright: 0x02,
26
+ trackName: 0x03,
27
+ instrumentName: 0x04,
28
+ lyric: 0x05,
29
+ marker: 0x06,
30
+ cuePoint: 0x07,
31
+ programName: 0x08,
32
+ deviceName: 0x09,
33
+ };
34
+ /** Every meta type a member other than `unknown` spells. */
35
+ export const KNOWN_META_TYPES_ = new Set([
36
+ META_SEQUENCE_NUMBER_,
37
+ ...Object.values(TEXT_META_CODES_),
38
+ META_CHANNEL_PREFIX_,
39
+ META_MIDI_PORT_,
40
+ META_END_OF_TRACK_,
41
+ META_SET_TEMPO_,
42
+ META_SMPTE_OFFSET_,
43
+ META_TIME_SIGNATURE_,
44
+ META_KEY_SIGNATURE_,
45
+ META_SEQUENCER_SPECIFIC_,
46
+ ]);
47
+ /**
48
+ * SMPTE frame rates by the 2-bit code in bits 5–6 of an SMPTE Offset's hour
49
+ * byte.
50
+ */
51
+ export const SMPTE_FRAME_RATES_ = [24, 25, 29, 30];
52
+ //# sourceMappingURL=_meta-types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"_meta-types.js","sourceRoot":"","sources":["../../src/file/_meta-types.ts"],"names":[],"mappings":"AAOA,+EAA+E;AAC/E,+DAA+D;AAC/D,MAAM,CAAC,MAAM,qBAAqB,GAAG,IAAI,CAAC;AAC1C,MAAM,CAAC,MAAM,oBAAoB,GAAG,IAAI,CAAC;AACzC,MAAM,CAAC,MAAM,eAAe,GAAG,IAAI,CAAC;AACpC,MAAM,CAAC,MAAM,kBAAkB,GAAG,IAAI,CAAC;AACvC,MAAM,CAAC,MAAM,eAAe,GAAG,IAAI,CAAC;AACpC,MAAM,CAAC,MAAM,kBAAkB,GAAG,IAAI,CAAC;AACvC,MAAM,CAAC,MAAM,oBAAoB,GAAG,IAAI,CAAC;AACzC,MAAM,CAAC,MAAM,mBAAmB,GAAG,IAAI,CAAC;AACxC,MAAM,CAAC,MAAM,wBAAwB,GAAG,IAAI,CAAC;AAE7C,MAAM,CAAC,MAAM,gBAAgB,GAA4C;IACxE,IAAI,EAAE,MAAM;IACZ,IAAI,EAAE,WAAW;IACjB,IAAI,EAAE,WAAW;IACjB,IAAI,EAAE,gBAAgB;IACtB,IAAI,EAAE,OAAO;IACb,IAAI,EAAE,QAAQ;IACd,IAAI,EAAE,UAAU;IAChB,IAAI,EAAE,aAAa;IACnB,IAAI,EAAE,YAAY;CAClB,CAAC;AAEF,MAAM,CAAC,MAAM,gBAAgB,GAAG;IAC/B,IAAI,EAAE,IAAI;IACV,SAAS,EAAE,IAAI;IACf,SAAS,EAAE,IAAI;IACf,cAAc,EAAE,IAAI;IACpB,KAAK,EAAE,IAAI;IACX,MAAM,EAAE,IAAI;IACZ,QAAQ,EAAE,IAAI;IACd,WAAW,EAAE,IAAI;IACjB,UAAU,EAAE,IAAI;CACiC,CAAC;AAEnD,4DAA4D;AAC5D,MAAM,CAAC,MAAM,iBAAiB,GAAwB,IAAI,GAAG,CAAC;IAC7D,qBAAqB;IACrB,GAAG,MAAM,CAAC,MAAM,CAAC,gBAAgB,CAAC;IAClC,oBAAoB;IACpB,eAAe;IACf,kBAAkB;IAClB,eAAe;IACf,kBAAkB;IAClB,oBAAoB;IACpB,mBAAmB;IACnB,wBAAwB;CACxB,CAAC,CAAC;AAEH;;;GAGG;AACH,MAAM,CAAC,MAAM,kBAAkB,GAAG,CAAC,EAAE,EAAE,EAAE,EAAE,EAAE,EAAE,EAAE,CAAU,CAAC"}
@@ -0,0 +1,68 @@
1
+ import type { MidiChannelMessage } from "../message/midi-message.js";
2
+ import type { MidiMetaEvent } from "./midi-meta-event.js";
3
+ /**
4
+ * How a Standard MIDI File counts time in its delta times.
5
+ *
6
+ * - `metrical` — ticks per quarter note, so a tick's length follows the tempo.
7
+ * - `timecode` — ticks per SMPTE frame, independent of tempo. A `framesPerSecond`
8
+ * of 29 means 30 drop-frame, 29.97 frames per second.
9
+ */
10
+ export type MidiTimeDivision = {
11
+ type: "metrical";
12
+ /** 1–32767. */
13
+ ticksPerQuarterNote: number;
14
+ } | {
15
+ type: "timecode";
16
+ framesPerSecond: 24 | 25 | 29 | 30;
17
+ /** 1–255. */
18
+ ticksPerFrame: number;
19
+ };
20
+ /**
21
+ * One event of a track, with the bytes it was stored as.
22
+ *
23
+ * - `channel` — a channel message, stored without running status or with it.
24
+ * - `sysEx` — an F0 event: `data` is the stored bytes after the length, which
25
+ * normally end with F7. A SysEx split over several events has its first part
26
+ * here and the rest in `sysExEscape` events.
27
+ * - `sysExEscape` — an F7 event: `data` is sent as is, which carries a SysEx
28
+ * continuation or any bytes not otherwise expressible, real-time ones
29
+ * included.
30
+ * - `meta` — a meta event (FF), never sent to a device.
31
+ */
32
+ export type MidiFileEvent = {
33
+ kind: "channel";
34
+ message: MidiChannelMessage;
35
+ } | {
36
+ kind: "sysEx";
37
+ data: Uint8Array;
38
+ } | {
39
+ kind: "sysExEscape";
40
+ data: Uint8Array;
41
+ } | {
42
+ kind: "meta";
43
+ meta: MidiMetaEvent;
44
+ };
45
+ /** An event and the ticks elapsed since the previous one in its track. */
46
+ export type MidiTrackEvent = {
47
+ /** 0–268 435 455 (0x0FFFFFFF), the most a 4-byte quantity holds. */
48
+ deltaTime: number;
49
+ event: MidiFileEvent;
50
+ };
51
+ /** One `MTrk` chunk. */
52
+ export type MidiTrack = {
53
+ /** In file order. A well-formed track ends with an `endOfTrack` meta event. */
54
+ events: MidiTrackEvent[];
55
+ };
56
+ /**
57
+ * A Standard MIDI File (RP-001).
58
+ *
59
+ * `format` says how the tracks relate: 0 is a single track holding every
60
+ * channel, 1 is simultaneous tracks played together, 2 is independent
61
+ * single-track sequences.
62
+ */
63
+ export type MidiFile = {
64
+ format: 0 | 1 | 2;
65
+ timeDivision: MidiTimeDivision;
66
+ tracks: MidiTrack[];
67
+ };
68
+ //# sourceMappingURL=midi-file.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"midi-file.d.ts","sourceRoot":"","sources":["../../src/file/midi-file.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,4BAA4B,CAAC;AACrE,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAC;AAE1D;;;;;;GAMG;AACH,MAAM,MAAM,gBAAgB,GACzB;IACA,IAAI,EAAE,UAAU,CAAC;IACjB,eAAe;IACf,mBAAmB,EAAE,MAAM,CAAC;CAC3B,GACD;IACA,IAAI,EAAE,UAAU,CAAC;IACjB,eAAe,EAAE,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,CAAC;IACnC,aAAa;IACb,aAAa,EAAE,MAAM,CAAC;CACrB,CAAC;AAEL;;;;;;;;;;;GAWG;AACH,MAAM,MAAM,aAAa,GACtB;IAAE,IAAI,EAAE,SAAS,CAAC;IAAC,OAAO,EAAE,kBAAkB,CAAA;CAAE,GAChD;IAAE,IAAI,EAAE,OAAO,CAAC;IAAC,IAAI,EAAE,UAAU,CAAA;CAAE,GACnC;IAAE,IAAI,EAAE,aAAa,CAAC;IAAC,IAAI,EAAE,UAAU,CAAA;CAAE,GACzC;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,aAAa,CAAA;CAAE,CAAC;AAEzC,0EAA0E;AAC1E,MAAM,MAAM,cAAc,GAAG;IAC5B,oEAAoE;IACpE,SAAS,EAAE,MAAM,CAAC;IAClB,KAAK,EAAE,aAAa,CAAC;CACrB,CAAC;AAEF,wBAAwB;AACxB,MAAM,MAAM,SAAS,GAAG;IACvB,+EAA+E;IAC/E,MAAM,EAAE,cAAc,EAAE,CAAC;CACzB,CAAC;AAEF;;;;;;GAMG;AACH,MAAM,MAAM,QAAQ,GAAG;IACtB,MAAM,EAAE,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;IAClB,YAAY,EAAE,gBAAgB,CAAC;IAC/B,MAAM,EAAE,SAAS,EAAE,CAAC;CACpB,CAAC"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=midi-file.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"midi-file.js","sourceRoot":"","sources":["../../src/file/midi-file.ts"],"names":[],"mappings":""}
@@ -0,0 +1,74 @@
1
+ /**
2
+ * A meta event of a Standard MIDI File track (RP-001), discriminated by `type`.
3
+ *
4
+ * The text events keep their bytes: the standard does not say which character
5
+ * encoding they use, and files in the wild use ASCII, Latin-1, Shift JIS and
6
+ * UTF-8 alike. Decode them with the encoding the file is known to use.
7
+ */
8
+ export type MidiMetaEvent = {
9
+ type: "sequenceNumber";
10
+ /**
11
+ * 0–65535, or `undefined` for the zero-length form, which numbers the
12
+ * sequence by its position in the file.
13
+ */
14
+ number: number | undefined;
15
+ } | {
16
+ type: "text" | "copyright" | "trackName" | "instrumentName" | "lyric" | "marker" | "cuePoint" | "programName" | "deviceName";
17
+ /** The text, in an unspecified encoding. */
18
+ data: Uint8Array;
19
+ } | {
20
+ type: "channelPrefix";
21
+ /** 0–15: the channel the following SysEx and meta events relate to. */
22
+ channel: number;
23
+ } | {
24
+ type: "midiPort";
25
+ /** 0–255: the output port the track's events go to. */
26
+ port: number;
27
+ } | {
28
+ type: "endOfTrack";
29
+ } | {
30
+ type: "setTempo";
31
+ /** Length of a quarter note, 0–16 777 215 µs; 500 000 is 120 bpm. */
32
+ microsecondsPerQuarterNote: number;
33
+ } | {
34
+ type: "smpteOffset";
35
+ /** Frame rate, encoded in the hour byte; 29 means 30 drop-frame (29.97). */
36
+ frameRate: 24 | 25 | 29 | 30;
37
+ /** 0–23 in practice; 0–31 fits the hour byte. */
38
+ hours: number;
39
+ /** 0–59 in practice; 0–255 fits the byte. */
40
+ minutes: number;
41
+ /** 0–59 in practice; 0–255 fits the byte. */
42
+ seconds: number;
43
+ /** Below the frame rate in practice; 0–255 fits the byte. */
44
+ frames: number;
45
+ /** Hundredths of a frame, 0–99 in practice; 0–255 fits the byte. */
46
+ fractionalFrames: number;
47
+ } | {
48
+ type: "timeSignature";
49
+ /** Beats per bar, 0–255. */
50
+ numerator: number;
51
+ /** The beat's note value as a power of two: 2 is a quarter note. */
52
+ denominatorPower: number;
53
+ /** MIDI clocks (24 per quarter note) per metronome click, 0–255. */
54
+ clocksPerClick: number;
55
+ /** Notated 32nd notes per MIDI quarter note, normally 8. */
56
+ thirtySecondNotesPerQuarter: number;
57
+ } | {
58
+ type: "keySignature";
59
+ /** Sharps when positive, flats when negative, -7–7. */
60
+ sharpsFlats: number;
61
+ /** Whether the key is minor rather than major. */
62
+ minor: boolean;
63
+ } | {
64
+ type: "sequencerSpecific";
65
+ /** The payload, manufacturer ID first. */
66
+ data: Uint8Array;
67
+ } | {
68
+ type: "unknown";
69
+ /** A meta type, 0–127, that none of the other members spells. */
70
+ metaType: number;
71
+ /** The payload, as stored. */
72
+ data: Uint8Array;
73
+ };
74
+ //# sourceMappingURL=midi-meta-event.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"midi-meta-event.d.ts","sourceRoot":"","sources":["../../src/file/midi-meta-event.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AACH,MAAM,MAAM,aAAa,GACtB;IACA,IAAI,EAAE,gBAAgB,CAAC;IACvB;;;OAGG;IACH,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC;CAC1B,GACD;IACA,IAAI,EACD,MAAM,GACN,WAAW,GACX,WAAW,GACX,gBAAgB,GAChB,OAAO,GACP,QAAQ,GACR,UAAU,GACV,aAAa,GACb,YAAY,CAAC;IAChB,4CAA4C;IAC5C,IAAI,EAAE,UAAU,CAAC;CAChB,GACD;IACA,IAAI,EAAE,eAAe,CAAC;IACtB,uEAAuE;IACvE,OAAO,EAAE,MAAM,CAAC;CACf,GACD;IACA,IAAI,EAAE,UAAU,CAAC;IACjB,uDAAuD;IACvD,IAAI,EAAE,MAAM,CAAC;CACZ,GACD;IAAE,IAAI,EAAE,YAAY,CAAA;CAAE,GACtB;IACA,IAAI,EAAE,UAAU,CAAC;IACjB,qEAAqE;IACrE,0BAA0B,EAAE,MAAM,CAAC;CAClC,GACD;IACA,IAAI,EAAE,aAAa,CAAC;IACpB,4EAA4E;IAC5E,SAAS,EAAE,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,CAAC;IAC7B,iDAAiD;IACjD,KAAK,EAAE,MAAM,CAAC;IACd,6CAA6C;IAC7C,OAAO,EAAE,MAAM,CAAC;IAChB,6CAA6C;IAC7C,OAAO,EAAE,MAAM,CAAC;IAChB,6DAA6D;IAC7D,MAAM,EAAE,MAAM,CAAC;IACf,oEAAoE;IACpE,gBAAgB,EAAE,MAAM,CAAC;CACxB,GACD;IACA,IAAI,EAAE,eAAe,CAAC;IACtB,4BAA4B;IAC5B,SAAS,EAAE,MAAM,CAAC;IAClB,oEAAoE;IACpE,gBAAgB,EAAE,MAAM,CAAC;IACzB,oEAAoE;IACpE,cAAc,EAAE,MAAM,CAAC;IACvB,4DAA4D;IAC5D,2BAA2B,EAAE,MAAM,CAAC;CACnC,GACD;IACA,IAAI,EAAE,cAAc,CAAC;IACrB,uDAAuD;IACvD,WAAW,EAAE,MAAM,CAAC;IACpB,kDAAkD;IAClD,KAAK,EAAE,OAAO,CAAC;CACd,GACD;IACA,IAAI,EAAE,mBAAmB,CAAC;IAC1B,0CAA0C;IAC1C,IAAI,EAAE,UAAU,CAAC;CAChB,GACD;IACA,IAAI,EAAE,SAAS,CAAC;IAChB,iEAAiE;IACjE,QAAQ,EAAE,MAAM,CAAC;IACjB,8BAA8B;IAC9B,IAAI,EAAE,UAAU,CAAC;CAChB,CAAC"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=midi-meta-event.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"midi-meta-event.js","sourceRoot":"","sources":["../../src/file/midi-meta-event.ts"],"names":[],"mappings":""}
@@ -0,0 +1,29 @@
1
+ import type { MidiFile } from "./midi-file.js";
2
+ /**
3
+ * Parses a Standard MIDI File (RP-001) into its header and tracks, every event
4
+ * kept with the bytes it was stored as.
5
+ *
6
+ * Tolerates what RP-001 asks readers to tolerate: an `MThd` chunk longer than 6
7
+ * bytes (the extra bytes are skipped) and chunks of an unknown type (skipped
8
+ * whole). Running status is applied within a track and cancelled by SysEx and
9
+ * meta events.
10
+ *
11
+ * Reads the tracks as they are, without repair: the track count in the header
12
+ * is not checked against the `MTrk` chunks present, every `MTrk` chunk becomes
13
+ * a track, and a track that does not end with End of Track — common in files
14
+ * written by hand-rolled tools — is accepted with nothing appended, so
15
+ * {@link printMidiFile} writes it back unchanged.
16
+ *
17
+ * Time complexity: O(n) in `bytes.length`. Memory: O(n); every payload is
18
+ * copied, so the result does not alias `bytes`.
19
+ *
20
+ * @param bytes - The whole file.
21
+ * @returns The file's format, time division and tracks.
22
+ * @throws {MidiFileError} When the file has no `MThd` header, a chunk or event
23
+ * is truncated, a variable-length quantity is overlong or longer than 4
24
+ * bytes, a data byte has no running status, a status byte other than a
25
+ * channel, SysEx or meta one appears in a track, or a known meta event has
26
+ * the wrong length or an out-of-range field.
27
+ */
28
+ export declare function parseMidiFile(bytes: Uint8Array): MidiFile;
29
+ //# sourceMappingURL=parse-midi-file.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"parse-midi-file.d.ts","sourceRoot":"","sources":["../../src/file/parse-midi-file.ts"],"names":[],"mappings":"AAuBA,OAAO,KAAK,EACX,QAAQ,EAKR,MAAM,gBAAgB,CAAC;AA6RxB;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AACH,wBAAgB,aAAa,CAAC,KAAK,EAAE,UAAU,GAAG,QAAQ,CAuCzD"}