@ac-kit/format-midi 0.1.1 → 0.2.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.
Files changed (34) hide show
  1. package/CHANGELOG.md +39 -0
  2. package/README.md +36 -0
  3. package/dist/file/_time-division.d.ts +9 -0
  4. package/dist/file/_time-division.d.ts.map +1 -0
  5. package/dist/file/_time-division.js +9 -0
  6. package/dist/file/_time-division.js.map +1 -0
  7. package/dist/file/detect-midi-text-encoding.d.ts +42 -0
  8. package/dist/file/detect-midi-text-encoding.d.ts.map +1 -0
  9. package/dist/file/detect-midi-text-encoding.js +55 -0
  10. package/dist/file/detect-midi-text-encoding.js.map +1 -0
  11. package/dist/index.d.ts +3 -0
  12. package/dist/index.d.ts.map +1 -1
  13. package/dist/index.js +3 -0
  14. package/dist/index.js.map +1 -1
  15. package/dist/song/compose-midi-song.d.ts +55 -0
  16. package/dist/song/compose-midi-song.d.ts.map +1 -0
  17. package/dist/song/compose-midi-song.js +250 -0
  18. package/dist/song/compose-midi-song.js.map +1 -0
  19. package/dist/song/index.d.ts +2 -0
  20. package/dist/song/index.d.ts.map +1 -0
  21. package/dist/song/index.js +2 -0
  22. package/dist/song/index.js.map +1 -0
  23. package/dist/timeline/compose-midi-karaoke.d.ts +61 -0
  24. package/dist/timeline/compose-midi-karaoke.d.ts.map +1 -0
  25. package/dist/timeline/compose-midi-karaoke.js +98 -0
  26. package/dist/timeline/compose-midi-karaoke.js.map +1 -0
  27. package/dist/timeline/compose-midi-notes.d.ts +54 -0
  28. package/dist/timeline/compose-midi-notes.d.ts.map +1 -0
  29. package/dist/timeline/compose-midi-notes.js +97 -0
  30. package/dist/timeline/compose-midi-notes.js.map +1 -0
  31. package/dist/timeline/compose-midi-timeline.d.ts.map +1 -1
  32. package/dist/timeline/compose-midi-timeline.js +2 -4
  33. package/dist/timeline/compose-midi-timeline.js.map +1 -1
  34. package/package.json +11 -7
package/CHANGELOG.md CHANGED
@@ -1,5 +1,44 @@
1
1
  # @ac-kit/format-midi
2
2
 
3
+ ## 0.2.1
4
+
5
+ ### Patch Changes
6
+
7
+ - Updated dependencies [235ec3c]
8
+ - @ac-kit/core@0.5.0
9
+ - @ac-kit/algo@0.2.2
10
+ - @ac-kit/format-core@0.1.5
11
+ - @ac-kit/format-varint@0.1.4
12
+ - @ac-kit/model-music@0.1.1
13
+
14
+ ## 0.2.0
15
+
16
+ ### Minor Changes
17
+
18
+ - eb2aa3d: Reads a Standard MIDI File as music, not only as events:
19
+
20
+ - `composeMidiNotes` pairs every note-on with its release into a `MidiNote`
21
+ with its start and end in ticks and seconds.
22
+ - `detectMidiTextEncoding` guesses whether a file's text meta events are
23
+ UTF-8, Shift JIS or windows-1252, to decode them with `@ac-kit/core`'s
24
+ `decodeText`.
25
+ - `composeMidiKaraoke` reads the tags and timed syllables of a Soft Karaoke
26
+ (`.kar`) file.
27
+ - `composeMidiSong`, on the new `@ac-kit/format-midi/song` subpath, converts a
28
+ file into an `@ac-kit/model-music` `Song`. The package now depends on
29
+ `@ac-kit/algo` and `@ac-kit/model-music`; the main entry does not load
30
+ `@ac-kit/model-music`.
31
+
32
+ ### Patch Changes
33
+
34
+ - Updated dependencies [aca1cb1]
35
+ - Updated dependencies [eb2aa3d]
36
+ - @ac-kit/core@0.4.0
37
+ - @ac-kit/model-music@0.1.0
38
+ - @ac-kit/algo@0.2.1
39
+ - @ac-kit/format-core@0.1.4
40
+ - @ac-kit/format-varint@0.1.3
41
+
3
42
  ## 0.1.1
4
43
 
5
44
  ### Patch Changes
package/README.md CHANGED
@@ -99,6 +99,42 @@ independent sequences: each is timed on its own and they follow one another.
99
99
  An SMPTE division gives each tick a fixed length, with frame rate 29 meaning
100
100
  29.97.
101
101
 
102
+ ## Notes
103
+
104
+ `composeMidiNotes` pairs every note-on of a timeline with the release that ends
105
+ it — a `noteOff`, or a `noteOn` of velocity 0, on the same track, channel and
106
+ note number — and gives each note its start and end in ticks and in seconds.
107
+ Overlapping strikes of one note close first in, first out, and a note never
108
+ released ends where its track ends. The sustain pedal is not applied: its
109
+ controller events stay in the timeline.
110
+
111
+ ## Text
112
+
113
+ Text meta events keep their bytes. `detectMidiTextEncoding` guesses one
114
+ `MidiTextEncoding` for a whole file from all its text events — `utf-8`,
115
+ `shift_jis` or `windows-1252` — and `decodeText` from `@ac-kit/core` decodes
116
+ each event with it, or with strict ISO-8859-1 (`latin1`) when that is known.
117
+
118
+ `composeMidiKaraoke` reads a Soft Karaoke (`.kar`) file: its `@V`, `@L`, `@T`
119
+ and `@I` tags, and the syllables of its lyrics track with their times and their
120
+ `/` line and `\` paragraph breaks. A file without the `@K` tag is not karaoke.
121
+
122
+ ## Songs
123
+
124
+ `composeMidiSong`, from the `@ac-kit/format-midi/song` subpath, converts a file
125
+ into a `Song` of `@ac-kit/model-music`: notes with their part, pitch, velocity
126
+ and times, one part per track and channel with its name and program, the tempo
127
+ map, time and key signatures, measures, the sustain, sostenuto and soft pedals,
128
+ markers, and lyrics from karaoke or `lyric` events. A format 2 file is read one
129
+ sequence at a time.
130
+
131
+ ```ts
132
+ import { parseMidiFile } from "@ac-kit/format-midi";
133
+ import { composeMidiSong } from "@ac-kit/format-midi/song";
134
+
135
+ const song = composeMidiSong(parseMidiFile(bytes));
136
+ ```
137
+
102
138
  See the
103
139
  [generated API docs](https://anthochamp.github.io/node-essentials/api/format-midi/)
104
140
  for the full reference.
@@ -0,0 +1,9 @@
1
+ import type { MidiTimeDivision } from "./midi-file.js";
2
+ /**
3
+ * Ticks per second of an SMPTE time division, with frame rate 29 read as 30
4
+ * drop-frame, 30000/1001 frames per second.
5
+ */
6
+ export declare function timecodeTicksPerSecond_(division: Extract<MidiTimeDivision, {
7
+ type: "timecode";
8
+ }>): number;
9
+ //# sourceMappingURL=_time-division.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"_time-division.d.ts","sourceRoot":"","sources":["../../src/file/_time-division.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,gBAAgB,CAAC;AAEvD;;;GAGG;AACH,wBAAgB,uBAAuB,CACtC,QAAQ,EAAE,OAAO,CAAC,gBAAgB,EAAE;IAAE,IAAI,EAAE,UAAU,CAAA;CAAE,CAAC,GACvD,MAAM,CAKR"}
@@ -0,0 +1,9 @@
1
+ /**
2
+ * Ticks per second of an SMPTE time division, with frame rate 29 read as 30
3
+ * drop-frame, 30000/1001 frames per second.
4
+ */
5
+ export function timecodeTicksPerSecond_(division) {
6
+ const framesPerSecond = division.framesPerSecond === 29 ? 30000 / 1001 : division.framesPerSecond;
7
+ return framesPerSecond * division.ticksPerFrame;
8
+ }
9
+ //# sourceMappingURL=_time-division.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"_time-division.js","sourceRoot":"","sources":["../../src/file/_time-division.ts"],"names":[],"mappings":"AAEA;;;GAGG;AACH,MAAM,UAAU,uBAAuB,CACtC,QAAyD;IAEzD,MAAM,eAAe,GACpB,QAAQ,CAAC,eAAe,KAAK,EAAE,CAAC,CAAC,CAAC,KAAM,GAAG,IAAI,CAAC,CAAC,CAAC,QAAQ,CAAC,eAAe,CAAC;IAE5E,OAAO,eAAe,GAAG,QAAQ,CAAC,aAAa,CAAC;AACjD,CAAC"}
@@ -0,0 +1,42 @@
1
+ import { type TextDecodingName } from "@ac-kit/core";
2
+ import type { MidiFile } from "./midi-file.js";
3
+ /**
4
+ * A character encoding found in the text meta events of Standard MIDI Files, to
5
+ * decode them with `@ac-kit/core`'s `decodeText`.
6
+ *
7
+ * - `utf-8` — recent tools; ASCII text is valid UTF-8 too.
8
+ * - `shift_jis` — Japanese sequencers.
9
+ * - `windows-1252` — Western European text from most other tools, and what Web
10
+ * APIs mean by "Latin-1".
11
+ * - `latin1` — strict ISO-8859-1, mapping 0x80–0x9F to the C1 control codes where
12
+ * `windows-1252` has printable characters.
13
+ */
14
+ export type MidiTextEncoding = Extract<TextDecodingName, "utf-8" | "shift_jis" | "windows-1252" | "latin1">;
15
+ /**
16
+ * Guesses the encoding of a file's text meta events, all of them together,
17
+ * since one tool wrote them.
18
+ *
19
+ * Use it when nothing else says which encoding a file uses, then decode each
20
+ * text event with `decodeText(data, encoding)` from `@ac-kit/core`, or pass the
21
+ * result to {@link composeMidiKaraoke}. It is a heuristic: Japanese text
22
+ * written in kanji only reads as `windows-1252`.
23
+ *
24
+ * - `utf-8` when every text is valid UTF-8, which includes plain ASCII and a file
25
+ * with no text at all;
26
+ * - Otherwise `shift_jis` when every text is valid Shift JIS and some of it is
27
+ * kana;
28
+ * - Otherwise `windows-1252`, which decodes any byte.
29
+ *
30
+ * Time complexity: O(n) in the total size of the text meta events. Memory:
31
+ * O(n).
32
+ *
33
+ * @example
34
+ * detectMidiTextEncoding(parseMidiFile(bytes)); // "utf-8"
35
+ *
36
+ * @param file - A parsed file.
37
+ * @returns The most plausible encoding.
38
+ * @throws {RangeError} When the text is not UTF-8 and the runtime's
39
+ * `TextDecoder` lacks Shift JIS.
40
+ */
41
+ export declare function detectMidiTextEncoding(file: MidiFile): MidiTextEncoding;
42
+ //# sourceMappingURL=detect-midi-text-encoding.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"detect-midi-text-encoding.d.ts","sourceRoot":"","sources":["../../src/file/detect-midi-text-encoding.ts"],"names":[],"mappings":"AAAA,OAAO,EAA2B,KAAK,gBAAgB,EAAE,MAAM,cAAc,CAAC;AAE9E,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,gBAAgB,CAAC;AAE/C;;;;;;;;;;GAUG;AACH,MAAM,MAAM,gBAAgB,GAAG,OAAO,CACrC,gBAAgB,EAChB,OAAO,GAAG,WAAW,GAAG,cAAc,GAAG,QAAQ,CACjD,CAAC;AAMF;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AACH,wBAAgB,sBAAsB,CAAC,IAAI,EAAE,QAAQ,GAAG,gBAAgB,CA8BvE"}
@@ -0,0 +1,55 @@
1
+ import { decodeText, isValidText } from "@ac-kit/core";
2
+ // Full-width hiragana and katakana: Japanese text nearly always has some, and
3
+ // the Shift JIS pairs that Latin-1 letters happen to form are kanji instead.
4
+ const KANA_PATTERN_ = /[\u3041-\u30ff]/u;
5
+ /**
6
+ * Guesses the encoding of a file's text meta events, all of them together,
7
+ * since one tool wrote them.
8
+ *
9
+ * Use it when nothing else says which encoding a file uses, then decode each
10
+ * text event with `decodeText(data, encoding)` from `@ac-kit/core`, or pass the
11
+ * result to {@link composeMidiKaraoke}. It is a heuristic: Japanese text
12
+ * written in kanji only reads as `windows-1252`.
13
+ *
14
+ * - `utf-8` when every text is valid UTF-8, which includes plain ASCII and a file
15
+ * with no text at all;
16
+ * - Otherwise `shift_jis` when every text is valid Shift JIS and some of it is
17
+ * kana;
18
+ * - Otherwise `windows-1252`, which decodes any byte.
19
+ *
20
+ * Time complexity: O(n) in the total size of the text meta events. Memory:
21
+ * O(n).
22
+ *
23
+ * @example
24
+ * detectMidiTextEncoding(parseMidiFile(bytes)); // "utf-8"
25
+ *
26
+ * @param file - A parsed file.
27
+ * @returns The most plausible encoding.
28
+ * @throws {RangeError} When the text is not UTF-8 and the runtime's
29
+ * `TextDecoder` lacks Shift JIS.
30
+ */
31
+ export function detectMidiTextEncoding(file) {
32
+ const texts = [];
33
+ for (const { events } of file.tracks) {
34
+ for (const { event } of events) {
35
+ if (event.kind === "meta" &&
36
+ "data" in event.meta &&
37
+ event.meta.type !== "sequencerSpecific" &&
38
+ event.meta.type !== "unknown") {
39
+ texts.push(event.meta.data);
40
+ }
41
+ }
42
+ }
43
+ if (texts.every((text) => isValidText(text, "utf-8"))) {
44
+ return "utf-8";
45
+ }
46
+ if (!texts.every((text) => isValidText(text, "shift_jis"))) {
47
+ return "windows-1252";
48
+ }
49
+ const hasKana = texts.some((text) => KANA_PATTERN_.test(decodeText(text, "shift_jis")));
50
+ if (hasKana) {
51
+ return "shift_jis";
52
+ }
53
+ return "windows-1252";
54
+ }
55
+ //# sourceMappingURL=detect-midi-text-encoding.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"detect-midi-text-encoding.js","sourceRoot":"","sources":["../../src/file/detect-midi-text-encoding.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,WAAW,EAAyB,MAAM,cAAc,CAAC;AAoB9E,8EAA8E;AAC9E,6EAA6E;AAC7E,MAAM,aAAa,GAAG,kBAAkB,CAAC;AAEzC;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AACH,MAAM,UAAU,sBAAsB,CAAC,IAAc;IACpD,MAAM,KAAK,GAAiB,EAAE,CAAC;IAC/B,KAAK,MAAM,EAAE,MAAM,EAAE,IAAI,IAAI,CAAC,MAAM,EAAE,CAAC;QACtC,KAAK,MAAM,EAAE,KAAK,EAAE,IAAI,MAAM,EAAE,CAAC;YAChC,IACC,KAAK,CAAC,IAAI,KAAK,MAAM;gBACrB,MAAM,IAAI,KAAK,CAAC,IAAI;gBACpB,KAAK,CAAC,IAAI,CAAC,IAAI,KAAK,mBAAmB;gBACvC,KAAK,CAAC,IAAI,CAAC,IAAI,KAAK,SAAS,EAC5B,CAAC;gBACF,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;YAC7B,CAAC;QACF,CAAC;IACF,CAAC;IAED,IAAI,KAAK,CAAC,KAAK,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,WAAW,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC,EAAE,CAAC;QACvD,OAAO,OAAO,CAAC;IAChB,CAAC;IACD,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,WAAW,CAAC,IAAI,EAAE,WAAW,CAAC,CAAC,EAAE,CAAC;QAC5D,OAAO,cAAc,CAAC;IACvB,CAAC;IAED,MAAM,OAAO,GAAG,KAAK,CAAC,IAAI,CAAC,CAAC,IAAI,EAAE,EAAE,CACnC,aAAa,CAAC,IAAI,CAAC,UAAU,CAAC,IAAI,EAAE,WAAW,CAAC,CAAC,CACjD,CAAC;IACF,IAAI,OAAO,EAAE,CAAC;QACb,OAAO,WAAW,CAAC;IACpB,CAAC;IAED,OAAO,cAAc,CAAC;AACvB,CAAC"}
package/dist/index.d.ts CHANGED
@@ -1,4 +1,5 @@
1
1
  export * from "./errors.js";
2
+ export * from "./file/detect-midi-text-encoding.js";
2
3
  export * from "./file/midi-file.js";
3
4
  export * from "./file/midi-meta-event.js";
4
5
  export * from "./file/parse-midi-file.js";
@@ -8,5 +9,7 @@ export * from "./message/is-midi-note-release.js";
8
9
  export * from "./message/midi-message-decoder.js";
9
10
  export * from "./message/midi-message-encoder.js";
10
11
  export * from "./message/midi-message.js";
12
+ export * from "./timeline/compose-midi-karaoke.js";
13
+ export * from "./timeline/compose-midi-notes.js";
11
14
  export * from "./timeline/compose-midi-timeline.js";
12
15
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,aAAa,CAAC;AAC5B,cAAc,qBAAqB,CAAC;AACpC,cAAc,2BAA2B,CAAC;AAC1C,cAAc,2BAA2B,CAAC;AAC1C,cAAc,2BAA2B,CAAC;AAC1C,cAAc,kCAAkC,CAAC;AACjD,cAAc,mCAAmC,CAAC;AAClD,cAAc,mCAAmC,CAAC;AAClD,cAAc,mCAAmC,CAAC;AAClD,cAAc,2BAA2B,CAAC;AAC1C,cAAc,qCAAqC,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,aAAa,CAAC;AAC5B,cAAc,qCAAqC,CAAC;AACpD,cAAc,qBAAqB,CAAC;AACpC,cAAc,2BAA2B,CAAC;AAC1C,cAAc,2BAA2B,CAAC;AAC1C,cAAc,2BAA2B,CAAC;AAC1C,cAAc,kCAAkC,CAAC;AACjD,cAAc,mCAAmC,CAAC;AAClD,cAAc,mCAAmC,CAAC;AAClD,cAAc,mCAAmC,CAAC;AAClD,cAAc,2BAA2B,CAAC;AAC1C,cAAc,oCAAoC,CAAC;AACnD,cAAc,kCAAkC,CAAC;AACjD,cAAc,qCAAqC,CAAC"}
package/dist/index.js CHANGED
@@ -1,4 +1,5 @@
1
1
  export * from "./errors.js";
2
+ export * from "./file/detect-midi-text-encoding.js";
2
3
  export * from "./file/midi-file.js";
3
4
  export * from "./file/midi-meta-event.js";
4
5
  export * from "./file/parse-midi-file.js";
@@ -8,5 +9,7 @@ export * from "./message/is-midi-note-release.js";
8
9
  export * from "./message/midi-message-decoder.js";
9
10
  export * from "./message/midi-message-encoder.js";
10
11
  export * from "./message/midi-message.js";
12
+ export * from "./timeline/compose-midi-karaoke.js";
13
+ export * from "./timeline/compose-midi-notes.js";
11
14
  export * from "./timeline/compose-midi-timeline.js";
12
15
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,aAAa,CAAC;AAC5B,cAAc,qBAAqB,CAAC;AACpC,cAAc,2BAA2B,CAAC;AAC1C,cAAc,2BAA2B,CAAC;AAC1C,cAAc,2BAA2B,CAAC;AAC1C,cAAc,kCAAkC,CAAC;AACjD,cAAc,mCAAmC,CAAC;AAClD,cAAc,mCAAmC,CAAC;AAClD,cAAc,mCAAmC,CAAC;AAClD,cAAc,2BAA2B,CAAC;AAC1C,cAAc,qCAAqC,CAAC"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,aAAa,CAAC;AAC5B,cAAc,qCAAqC,CAAC;AACpD,cAAc,qBAAqB,CAAC;AACpC,cAAc,2BAA2B,CAAC;AAC1C,cAAc,2BAA2B,CAAC;AAC1C,cAAc,2BAA2B,CAAC;AAC1C,cAAc,kCAAkC,CAAC;AACjD,cAAc,mCAAmC,CAAC;AAClD,cAAc,mCAAmC,CAAC;AAClD,cAAc,mCAAmC,CAAC;AAClD,cAAc,2BAA2B,CAAC;AAC1C,cAAc,oCAAoC,CAAC;AACnD,cAAc,kCAAkC,CAAC;AACjD,cAAc,qCAAqC,CAAC"}
@@ -0,0 +1,55 @@
1
+ import { type Song } from "@ac-kit/model-music";
2
+ import { type MidiTextEncoding } from "../file/detect-midi-text-encoding.js";
3
+ import type { MidiFile } from "../file/midi-file.js";
4
+ /** Options of {@link composeMidiSong}. */
5
+ export type ComposeMidiSongOptions = {
6
+ /**
7
+ * The encoding of the file's text; detected with `detectMidiTextEncoding`
8
+ * when omitted.
9
+ */
10
+ encoding?: MidiTextEncoding;
11
+ /**
12
+ * Which track of a format 2 file to read, each being a song of its own: 0 by
13
+ * default. Ignored for formats 0 and 1.
14
+ */
15
+ sequence?: number;
16
+ };
17
+ /**
18
+ * Converts a Standard MIDI File into a `Song` of `@ac-kit/model-music`: every
19
+ * note with its start and end in ticks and seconds, under one tempo map, with
20
+ * parts, meter, key, measures, pedals, markers and lyrics.
21
+ *
22
+ * Use it to read a file as music; use {@link composeMidiTimeline} and
23
+ * {@link composeMidiNotes} for its events as stored.
24
+ *
25
+ * - **Parts**: one per track and channel carrying notes, in the order they first
26
+ * sound. Each is named by its track's name and given the channel's last
27
+ * program change before its first note; channel 10 is percussion.
28
+ * - **Title**: the karaoke `@T` title, else the name of track 0 (of the sequence,
29
+ * for format 2).
30
+ * - **Time**: a metrical file keeps its ticks and its Set Tempo events. An
31
+ * SMPTE-timed file is given 480 ticks per quarter note and the one tempo that
32
+ * keeps every event at its time.
33
+ * - **Measures** come from the time signatures, 4/4 before the first; a time
34
+ * signature of numerator 0 is skipped.
35
+ * - **Pedals**: sustain (CC 64), sostenuto (CC 66) and soft (CC 67) on a channel
36
+ * with notes, as 0–1.
37
+ * - **Lyrics**: the syllables of a Soft Karaoke file, else the `lyric` meta
38
+ * events. **Markers**: `marker` and `cuePoint` meta events.
39
+ *
40
+ * Time complexity: O(n log n) in the number of events, for the timeline's sort.
41
+ * Memory: O(n).
42
+ *
43
+ * @example
44
+ * composeMidiSong(parseMidiFile(bytes)).notes[0];
45
+ * // { part: 0, staff: undefined, voice: undefined, pitch: 48, velocity: 96,
46
+ * // startTick: 0, endTick: 384, startSeconds: 0, endSeconds: 2 }
47
+ *
48
+ * @param file - A parsed file.
49
+ * @param options - The text encoding and, for format 2, the sequence.
50
+ * @returns The song.
51
+ * @throws {RangeError} When `options.sequence` names no track of a format 2
52
+ * file.
53
+ */
54
+ export declare function composeMidiSong(file: MidiFile, options?: ComposeMidiSongOptions): Song;
55
+ //# sourceMappingURL=compose-midi-song.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"compose-midi-song.d.ts","sourceRoot":"","sources":["../../src/song/compose-midi-song.ts"],"names":[],"mappings":"AACA,OAAO,EAKN,KAAK,IAAI,EAST,MAAM,qBAAqB,CAAC;AAG7B,OAAO,EAEN,KAAK,gBAAgB,EACrB,MAAM,sCAAsC,CAAC;AAC9C,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,sBAAsB,CAAC;AAQrD,0CAA0C;AAC1C,MAAM,MAAM,sBAAsB,GAAG;IACpC;;;OAGG;IACH,QAAQ,CAAC,EAAE,gBAAgB,CAAC;IAC5B;;;OAGG;IACH,QAAQ,CAAC,EAAE,MAAM,CAAC;CAClB,CAAC;AAaF;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAoCG;AACH,wBAAgB,eAAe,CAC9B,IAAI,EAAE,QAAQ,EACd,OAAO,CAAC,EAAE,sBAAsB,GAC9B,IAAI,CAoKN"}
@@ -0,0 +1,250 @@
1
+ import { decodeText } from "@ac-kit/core";
2
+ import { composeMeasures, composeTempoMap, tempoMapSeconds, } from "@ac-kit/model-music";
3
+ import { timecodeTicksPerSecond_ } from "../file/_time-division.js";
4
+ import { detectMidiTextEncoding, } from "../file/detect-midi-text-encoding.js";
5
+ import { composeMidiKaraoke } from "../timeline/compose-midi-karaoke.js";
6
+ import { composeMidiNotes } from "../timeline/compose-midi-notes.js";
7
+ import { composeMidiTimeline, } from "../timeline/compose-midi-timeline.js";
8
+ // The resolution given to an SMPTE-timed file, whose ticks have no musical length.
9
+ const TIMECODE_TICKS_PER_QUARTER_NOTE_ = 480;
10
+ const PEDAL_CONTROLLERS_ = new Map([
11
+ [64, "sustain"],
12
+ [66, "sostenuto"],
13
+ [67, "soft"],
14
+ ]);
15
+ const PERCUSSION_CHANNEL_ = 9;
16
+ /**
17
+ * Converts a Standard MIDI File into a `Song` of `@ac-kit/model-music`: every
18
+ * note with its start and end in ticks and seconds, under one tempo map, with
19
+ * parts, meter, key, measures, pedals, markers and lyrics.
20
+ *
21
+ * Use it to read a file as music; use {@link composeMidiTimeline} and
22
+ * {@link composeMidiNotes} for its events as stored.
23
+ *
24
+ * - **Parts**: one per track and channel carrying notes, in the order they first
25
+ * sound. Each is named by its track's name and given the channel's last
26
+ * program change before its first note; channel 10 is percussion.
27
+ * - **Title**: the karaoke `@T` title, else the name of track 0 (of the sequence,
28
+ * for format 2).
29
+ * - **Time**: a metrical file keeps its ticks and its Set Tempo events. An
30
+ * SMPTE-timed file is given 480 ticks per quarter note and the one tempo that
31
+ * keeps every event at its time.
32
+ * - **Measures** come from the time signatures, 4/4 before the first; a time
33
+ * signature of numerator 0 is skipped.
34
+ * - **Pedals**: sustain (CC 64), sostenuto (CC 66) and soft (CC 67) on a channel
35
+ * with notes, as 0–1.
36
+ * - **Lyrics**: the syllables of a Soft Karaoke file, else the `lyric` meta
37
+ * events. **Markers**: `marker` and `cuePoint` meta events.
38
+ *
39
+ * Time complexity: O(n log n) in the number of events, for the timeline's sort.
40
+ * Memory: O(n).
41
+ *
42
+ * @example
43
+ * composeMidiSong(parseMidiFile(bytes)).notes[0];
44
+ * // { part: 0, staff: undefined, voice: undefined, pitch: 48, velocity: 96,
45
+ * // startTick: 0, endTick: 384, startSeconds: 0, endSeconds: 2 }
46
+ *
47
+ * @param file - A parsed file.
48
+ * @param options - The text encoding and, for format 2, the sequence.
49
+ * @returns The song.
50
+ * @throws {RangeError} When `options.sequence` names no track of a format 2
51
+ * file.
52
+ */
53
+ export function composeMidiSong(file, options) {
54
+ const encoding = options?.encoding ?? detectMidiTextEncoding(file);
55
+ const sequence = options?.sequence ?? 0;
56
+ const timeline = selectSequence_(composeMidiTimeline(file), file, sequence);
57
+ const tempoMap = composeTempoMap_(file, timeline);
58
+ const secondsAt = (tick) => tempoMapSeconds(tempoMap, tick);
59
+ const midiNotes = composeMidiNotes(timeline);
60
+ const trackNames = new Map();
61
+ const programs = new Map();
62
+ const firstPrograms = new Map();
63
+ const partIndices = new Map();
64
+ const parts = [];
65
+ const timeSignatures = [];
66
+ const keySignatures = [];
67
+ const markers = [];
68
+ const lyricEvents = [];
69
+ let endTick = 0;
70
+ for (const { trackIndex, tick, event } of timeline) {
71
+ endTick = Math.max(endTick, tick);
72
+ if (event.kind === "channel") {
73
+ if (event.message.type === "programChange") {
74
+ programs.set(partKey_(trackIndex, event.message.channel), event.message.program);
75
+ }
76
+ else if (event.message.type === "noteOn" &&
77
+ event.message.velocity > 0) {
78
+ const key = partKey_(trackIndex, event.message.channel);
79
+ if (!partIndices.has(key)) {
80
+ partIndices.set(key, partIndices.size);
81
+ const program = programs.get(key);
82
+ if (program !== undefined) {
83
+ firstPrograms.set(key, program);
84
+ }
85
+ }
86
+ }
87
+ continue;
88
+ }
89
+ if (event.kind !== "meta") {
90
+ continue;
91
+ }
92
+ const { meta } = event;
93
+ switch (meta.type) {
94
+ case "trackName":
95
+ if (!trackNames.has(trackIndex)) {
96
+ trackNames.set(trackIndex, decodeText(meta.data, encoding));
97
+ }
98
+ break;
99
+ case "timeSignature":
100
+ if (meta.numerator >= 1) {
101
+ pushAtTick_(timeSignatures, {
102
+ tick,
103
+ numerator: meta.numerator,
104
+ denominator: 2 ** meta.denominatorPower,
105
+ });
106
+ }
107
+ break;
108
+ case "keySignature":
109
+ pushAtTick_(keySignatures, {
110
+ tick,
111
+ fifths: meta.sharpsFlats,
112
+ mode: meta.minor ? "minor" : "major",
113
+ });
114
+ break;
115
+ case "marker":
116
+ case "cuePoint":
117
+ markers.push({
118
+ tick,
119
+ seconds: secondsAt(tick),
120
+ text: decodeText(meta.data, encoding),
121
+ kind: meta.type === "marker" ? "marker" : "cue",
122
+ });
123
+ break;
124
+ case "lyric":
125
+ lyricEvents.push({
126
+ part: undefined,
127
+ tick,
128
+ seconds: secondsAt(tick),
129
+ text: decodeText(meta.data, encoding),
130
+ break: "none",
131
+ });
132
+ break;
133
+ default:
134
+ // Tempo is read by composeTempoMap_; the other meta events carry nothing a Song holds.
135
+ break;
136
+ }
137
+ }
138
+ for (const [key, part] of partIndices) {
139
+ const trackIndex = Math.floor(key / 16);
140
+ const channel = key % 16;
141
+ parts[part] = {
142
+ name: trackNames.get(trackIndex),
143
+ program: firstPrograms.get(key),
144
+ percussion: channel === PERCUSSION_CHANNEL_,
145
+ staves: 1,
146
+ };
147
+ }
148
+ const notes = midiNotes.map((note) => {
149
+ endTick = Math.max(endTick, note.endTick);
150
+ return {
151
+ part: partIndices.get(partKey_(note.trackIndex, note.channel)),
152
+ staff: undefined,
153
+ voice: undefined,
154
+ pitch: note.note,
155
+ velocity: note.velocity,
156
+ startTick: note.startTick,
157
+ endTick: note.endTick,
158
+ startSeconds: secondsAt(note.startTick),
159
+ endSeconds: secondsAt(note.endTick),
160
+ };
161
+ });
162
+ notes.sort((a, b) => a.startTick - b.startTick || a.pitch - b.pitch);
163
+ const pedals = [];
164
+ for (const { trackIndex, tick, event } of timeline) {
165
+ if (event.kind !== "channel" || event.message.type !== "controlChange") {
166
+ continue;
167
+ }
168
+ const pedal = PEDAL_CONTROLLERS_.get(event.message.controller);
169
+ const part = partIndices.get(partKey_(trackIndex, event.message.channel));
170
+ if (pedal !== undefined && part !== undefined) {
171
+ pedals.push({
172
+ part,
173
+ tick,
174
+ seconds: secondsAt(tick),
175
+ pedal,
176
+ value: event.message.value / 127,
177
+ });
178
+ }
179
+ }
180
+ const karaoke = composeMidiKaraoke(timeline, encoding);
181
+ const lyrics = karaoke?.syllables.map(({ tick, text, break: lineBreak }) => ({
182
+ part: undefined,
183
+ tick,
184
+ seconds: secondsAt(tick),
185
+ text,
186
+ break: lineBreak,
187
+ })) ?? lyricEvents;
188
+ return {
189
+ title: karaoke?.titles[0] ?? trackNames.get(file.format === 2 ? sequence : 0),
190
+ tempoMap,
191
+ endTick,
192
+ endSeconds: secondsAt(endTick),
193
+ parts,
194
+ notes,
195
+ timeSignatures,
196
+ keySignatures,
197
+ measures: composeMeasures(timeSignatures, tempoMap, endTick),
198
+ markers,
199
+ pedals,
200
+ lyrics,
201
+ };
202
+ }
203
+ function partKey_(trackIndex, channel) {
204
+ return trackIndex * 16 + channel;
205
+ }
206
+ // Several signatures at one tick: the last one stands, as for a tempo.
207
+ function pushAtTick_(items, item) {
208
+ if (items.at(-1)?.tick === item.tick) {
209
+ items[items.length - 1] = item;
210
+ }
211
+ else {
212
+ items.push(item);
213
+ }
214
+ }
215
+ function selectSequence_(timeline, file, sequence) {
216
+ if (file.format !== 2) {
217
+ return timeline;
218
+ }
219
+ if (!(Number.isInteger(sequence) &&
220
+ sequence >= 0 &&
221
+ sequence < file.tracks.length)) {
222
+ throw new RangeError(`sequence ${sequence} is not a track of this ${file.tracks.length}-track file`);
223
+ }
224
+ return timeline.filter(({ trackIndex }) => trackIndex === sequence);
225
+ }
226
+ function composeTempoMap_(file, timeline) {
227
+ const division = file.timeDivision;
228
+ if (division.type === "timecode") {
229
+ return composeTempoMap(TIMECODE_TICKS_PER_QUARTER_NOTE_, [
230
+ {
231
+ tick: 0,
232
+ microsecondsPerQuarterNote: (1000000 * TIMECODE_TICKS_PER_QUARTER_NOTE_) /
233
+ timecodeTicksPerSecond_(division),
234
+ },
235
+ ]);
236
+ }
237
+ const marks = [];
238
+ for (const { tick, event } of timeline) {
239
+ if (event.kind === "meta" &&
240
+ event.meta.type === "setTempo" &&
241
+ event.meta.microsecondsPerQuarterNote > 0) {
242
+ marks.push({
243
+ tick,
244
+ microsecondsPerQuarterNote: event.meta.microsecondsPerQuarterNote,
245
+ });
246
+ }
247
+ }
248
+ return composeTempoMap(division.ticksPerQuarterNote, marks);
249
+ }
250
+ //# sourceMappingURL=compose-midi-song.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"compose-midi-song.js","sourceRoot":"","sources":["../../src/song/compose-midi-song.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ export * from "./compose-midi-song.js";
2
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/song/index.ts"],"names":[],"mappings":"AAAA,cAAc,wBAAwB,CAAC"}
@@ -0,0 +1,2 @@
1
+ export * from "./compose-midi-song.js";
2
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/song/index.ts"],"names":[],"mappings":"AAAA,cAAc,wBAAwB,CAAC"}
@@ -0,0 +1,61 @@
1
+ import type { MidiTextEncoding } from "../file/detect-midi-text-encoding.js";
2
+ import type { MidiTimelineEvent } from "./compose-midi-timeline.js";
3
+ /** One lyric syllable of a karaoke file, at the time it is sung. */
4
+ export type MidiKaraokeSyllable = {
5
+ trackIndex: number;
6
+ tick: number;
7
+ seconds: number;
8
+ /** The syllable without its break mark; spaces between words included. */
9
+ text: string;
10
+ /**
11
+ * What comes before the syllable: `paragraph` for a `\` mark (a new screen),
12
+ * `line` for a `/` mark, `none` otherwise.
13
+ */
14
+ break: "none" | "line" | "paragraph";
15
+ };
16
+ /** The lyrics and tags of a Soft Karaoke (`.kar`) file. */
17
+ export type MidiKaraoke = {
18
+ /** The `@V` tag: the format version, `0100` for 1.0. */
19
+ version: string | undefined;
20
+ /** The `@L` tag: the lyrics' language, such as `ENGL`. */
21
+ language: string | undefined;
22
+ /**
23
+ * The `@T` tags in order: by convention the title, the performer, then who
24
+ * sequenced it.
25
+ */
26
+ titles: string[];
27
+ /** The `@I` tags in order: free information lines. */
28
+ infos: string[];
29
+ syllables: MidiKaraokeSyllable[];
30
+ };
31
+ /**
32
+ * Reads the lyrics of a Soft Karaoke file, the `.kar` convention that carries
33
+ * them as text events with `@`-prefixed tags.
34
+ *
35
+ * Use it on files that carry `@K` (`@KMIDI KARAOKE FILE`); for lyrics stored as
36
+ * `lyric` meta events, read those from the timeline directly.
37
+ *
38
+ * - The tags are text events starting with `@K`, `@V`, `@L`, `@T` or `@I`, in any
39
+ * track.
40
+ * - The syllables are the other text events of the lyrics track: the track
41
+ * holding the first `@L` or `@T` tag, or else the track with the most
42
+ * untagged text events.
43
+ * - A syllable starting with `\` opens a paragraph and one starting with `/`
44
+ * opens a line; the mark is removed from `text`.
45
+ *
46
+ * Time complexity: O(n) in `timeline.length`. Memory: O(k) in the number of
47
+ * text events.
48
+ *
49
+ * @example
50
+ * composeMidiKaraoke(composeMidiTimeline(file), "windows-1252")?.titles;
51
+ * // ["Twinkle, Twinkle", "Traditional"]
52
+ *
53
+ * @param timeline - A file's events in playing order, from
54
+ * {@link composeMidiTimeline}.
55
+ * @param encoding - The file's text encoding, typically from
56
+ * {@link detectMidiTextEncoding}.
57
+ * @returns The karaoke content, or `undefined` when no `@K` tag marks the file
58
+ * as Soft Karaoke.
59
+ */
60
+ export declare function composeMidiKaraoke(timeline: readonly MidiTimelineEvent[], encoding: MidiTextEncoding): MidiKaraoke | undefined;
61
+ //# sourceMappingURL=compose-midi-karaoke.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"compose-midi-karaoke.d.ts","sourceRoot":"","sources":["../../src/timeline/compose-midi-karaoke.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,sCAAsC,CAAC;AAC7E,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,4BAA4B,CAAC;AAEpE,oEAAoE;AACpE,MAAM,MAAM,mBAAmB,GAAG;IACjC,UAAU,EAAE,MAAM,CAAC;IACnB,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;IAChB,0EAA0E;IAC1E,IAAI,EAAE,MAAM,CAAC;IACb;;;OAGG;IACH,KAAK,EAAE,MAAM,GAAG,MAAM,GAAG,WAAW,CAAC;CACrC,CAAC;AAEF,2DAA2D;AAC3D,MAAM,MAAM,WAAW,GAAG;IACzB,wDAAwD;IACxD,OAAO,EAAE,MAAM,GAAG,SAAS,CAAC;IAC5B,0DAA0D;IAC1D,QAAQ,EAAE,MAAM,GAAG,SAAS,CAAC;IAC7B;;;OAGG;IACH,MAAM,EAAE,MAAM,EAAE,CAAC;IACjB,sDAAsD;IACtD,KAAK,EAAE,MAAM,EAAE,CAAC;IAChB,SAAS,EAAE,mBAAmB,EAAE,CAAC;CACjC,CAAC;AAIF;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AACH,wBAAgB,kBAAkB,CACjC,QAAQ,EAAE,SAAS,iBAAiB,EAAE,EACtC,QAAQ,EAAE,gBAAgB,GACxB,WAAW,GAAG,SAAS,CAyDzB"}
@@ -0,0 +1,98 @@
1
+ import { maxBy } from "@ac-kit/algo";
2
+ import { decodeText } from "@ac-kit/core";
3
+ const TAG_PATTERN_ = /^@([KVLTI])(.*)$/su;
4
+ /**
5
+ * Reads the lyrics of a Soft Karaoke file, the `.kar` convention that carries
6
+ * them as text events with `@`-prefixed tags.
7
+ *
8
+ * Use it on files that carry `@K` (`@KMIDI KARAOKE FILE`); for lyrics stored as
9
+ * `lyric` meta events, read those from the timeline directly.
10
+ *
11
+ * - The tags are text events starting with `@K`, `@V`, `@L`, `@T` or `@I`, in any
12
+ * track.
13
+ * - The syllables are the other text events of the lyrics track: the track
14
+ * holding the first `@L` or `@T` tag, or else the track with the most
15
+ * untagged text events.
16
+ * - A syllable starting with `\` opens a paragraph and one starting with `/`
17
+ * opens a line; the mark is removed from `text`.
18
+ *
19
+ * Time complexity: O(n) in `timeline.length`. Memory: O(k) in the number of
20
+ * text events.
21
+ *
22
+ * @example
23
+ * composeMidiKaraoke(composeMidiTimeline(file), "windows-1252")?.titles;
24
+ * // ["Twinkle, Twinkle", "Traditional"]
25
+ *
26
+ * @param timeline - A file's events in playing order, from
27
+ * {@link composeMidiTimeline}.
28
+ * @param encoding - The file's text encoding, typically from
29
+ * {@link detectMidiTextEncoding}.
30
+ * @returns The karaoke content, or `undefined` when no `@K` tag marks the file
31
+ * as Soft Karaoke.
32
+ */
33
+ export function composeMidiKaraoke(timeline, encoding) {
34
+ let isKaraoke = false;
35
+ const karaoke = {
36
+ version: undefined,
37
+ language: undefined,
38
+ titles: [],
39
+ infos: [],
40
+ syllables: [],
41
+ };
42
+ let lyricsTrack;
43
+ const untagged = [];
44
+ const untaggedCounts = new Map();
45
+ for (const { trackIndex, tick, seconds, event } of timeline) {
46
+ if (event.kind !== "meta" || event.meta.type !== "text") {
47
+ continue;
48
+ }
49
+ const text = decodeText(event.meta.data, encoding);
50
+ const tag = TAG_PATTERN_.exec(text);
51
+ if (tag === null) {
52
+ untagged.push(composeSyllable_(trackIndex, tick, seconds, text));
53
+ untaggedCounts.set(trackIndex, (untaggedCounts.get(trackIndex) ?? 0) + 1);
54
+ continue;
55
+ }
56
+ const value = tag[2];
57
+ switch (tag[1]) {
58
+ case "K":
59
+ isKaraoke = true;
60
+ break;
61
+ case "V":
62
+ karaoke.version ??= value;
63
+ break;
64
+ case "L":
65
+ karaoke.language ??= value;
66
+ lyricsTrack ??= trackIndex;
67
+ break;
68
+ case "T":
69
+ karaoke.titles.push(value);
70
+ lyricsTrack ??= trackIndex;
71
+ break;
72
+ default:
73
+ karaoke.infos.push(value);
74
+ }
75
+ }
76
+ if (!isKaraoke) {
77
+ return undefined;
78
+ }
79
+ lyricsTrack ??= maxBy(untaggedCounts, ([, count]) => count)?.[0];
80
+ karaoke.syllables = untagged.filter(({ trackIndex }) => trackIndex === lyricsTrack);
81
+ return karaoke;
82
+ }
83
+ function composeSyllable_(trackIndex, tick, seconds, text) {
84
+ if (text.startsWith("\\")) {
85
+ return {
86
+ trackIndex,
87
+ tick,
88
+ seconds,
89
+ text: text.slice(1),
90
+ break: "paragraph",
91
+ };
92
+ }
93
+ if (text.startsWith("/")) {
94
+ return { trackIndex, tick, seconds, text: text.slice(1), break: "line" };
95
+ }
96
+ return { trackIndex, tick, seconds, text, break: "none" };
97
+ }
98
+ //# sourceMappingURL=compose-midi-karaoke.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"compose-midi-karaoke.js","sourceRoot":"","sources":["../../src/timeline/compose-midi-karaoke.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,54 @@
1
+ import type { MidiTimelineEvent } from "./compose-midi-timeline.js";
2
+ /** A sounding note of a Standard MIDI File: a note-on paired with its release. */
3
+ export type MidiNote = {
4
+ /** Index of the track the note-on came from. */
5
+ trackIndex: number;
6
+ /** 0–15. */
7
+ channel: number;
8
+ /** 0–127; 60 is middle C. */
9
+ note: number;
10
+ /** The note-on velocity, 1–127. */
11
+ velocity: number;
12
+ /**
13
+ * The note-off velocity, 0–127, or `undefined` when the note was released by
14
+ * a note-on of velocity 0 or by the end of its track.
15
+ */
16
+ releaseVelocity: number | undefined;
17
+ startTick: number;
18
+ endTick: number;
19
+ startSeconds: number;
20
+ endSeconds: number;
21
+ };
22
+ /**
23
+ * Pairs every note-on of a timeline with the release that ends it, giving each
24
+ * note its start and end in ticks and in seconds — what a piano roll, a
25
+ * practice tool or a converter to notation reads, rather than raw events.
26
+ *
27
+ * Use {@link composeMidiTimeline} directly when the other events matter as they
28
+ * are, controllers and the sustain pedal included: this keeps note events only,
29
+ * and does not lengthen a note held by the pedal.
30
+ *
31
+ * - A release is a `noteOff` or a `noteOn` of velocity 0 on the same track,
32
+ * channel and note number.
33
+ * - A note struck again before its release stays open beside the first, and
34
+ * releases close them first in, first out.
35
+ * - A note never released ends where its track ends: its End of Track event, or
36
+ * its last event when it has none.
37
+ * - A release with no open note is ignored.
38
+ *
39
+ * Time complexity: O(n) in `timeline.length`. Memory: O(m) in the number of
40
+ * notes.
41
+ *
42
+ * @example
43
+ * // RP-001's format 0 example, 96 ticks per quarter note at 120 bpm:
44
+ * composeMidiNotes(composeMidiTimeline(file))[0];
45
+ * // { trackIndex: 0, channel: 2, note: 48, velocity: 96, releaseVelocity: 64,
46
+ * // startTick: 0, endTick: 384, startSeconds: 0, endSeconds: 2 }
47
+ *
48
+ * @param timeline - A file's events in playing order, from
49
+ * {@link composeMidiTimeline}.
50
+ * @returns The notes in the order of their note-ons in `timeline`: by start for
51
+ * formats 0 and 1, track after track for format 2.
52
+ */
53
+ export declare function composeMidiNotes(timeline: readonly MidiTimelineEvent[]): MidiNote[];
54
+ //# sourceMappingURL=compose-midi-notes.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"compose-midi-notes.d.ts","sourceRoot":"","sources":["../../src/timeline/compose-midi-notes.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,4BAA4B,CAAC;AAEpE,kFAAkF;AAClF,MAAM,MAAM,QAAQ,GAAG;IACtB,gDAAgD;IAChD,UAAU,EAAE,MAAM,CAAC;IACnB,YAAY;IACZ,OAAO,EAAE,MAAM,CAAC;IAChB,6BAA6B;IAC7B,IAAI,EAAE,MAAM,CAAC;IACb,mCAAmC;IACnC,QAAQ,EAAE,MAAM,CAAC;IACjB;;;OAGG;IACH,eAAe,EAAE,MAAM,GAAG,SAAS,CAAC;IACpC,SAAS,EAAE,MAAM,CAAC;IAClB,OAAO,EAAE,MAAM,CAAC;IAChB,YAAY,EAAE,MAAM,CAAC;IACrB,UAAU,EAAE,MAAM,CAAC;CACnB,CAAC;AAQF;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8BG;AACH,wBAAgB,gBAAgB,CAC/B,QAAQ,EAAE,SAAS,iBAAiB,EAAE,GACpC,QAAQ,EAAE,CAgEZ"}
@@ -0,0 +1,97 @@
1
+ import { isMidiNoteRelease } from "../message/is-midi-note-release.js";
2
+ function noteKey_(trackIndex, channel, note) {
3
+ return (trackIndex * 16 + channel) * 128 + note;
4
+ }
5
+ /**
6
+ * Pairs every note-on of a timeline with the release that ends it, giving each
7
+ * note its start and end in ticks and in seconds — what a piano roll, a
8
+ * practice tool or a converter to notation reads, rather than raw events.
9
+ *
10
+ * Use {@link composeMidiTimeline} directly when the other events matter as they
11
+ * are, controllers and the sustain pedal included: this keeps note events only,
12
+ * and does not lengthen a note held by the pedal.
13
+ *
14
+ * - A release is a `noteOff` or a `noteOn` of velocity 0 on the same track,
15
+ * channel and note number.
16
+ * - A note struck again before its release stays open beside the first, and
17
+ * releases close them first in, first out.
18
+ * - A note never released ends where its track ends: its End of Track event, or
19
+ * its last event when it has none.
20
+ * - A release with no open note is ignored.
21
+ *
22
+ * Time complexity: O(n) in `timeline.length`. Memory: O(m) in the number of
23
+ * notes.
24
+ *
25
+ * @example
26
+ * // RP-001's format 0 example, 96 ticks per quarter note at 120 bpm:
27
+ * composeMidiNotes(composeMidiTimeline(file))[0];
28
+ * // { trackIndex: 0, channel: 2, note: 48, velocity: 96, releaseVelocity: 64,
29
+ * // startTick: 0, endTick: 384, startSeconds: 0, endSeconds: 2 }
30
+ *
31
+ * @param timeline - A file's events in playing order, from
32
+ * {@link composeMidiTimeline}.
33
+ * @returns The notes in the order of their note-ons in `timeline`: by start for
34
+ * formats 0 and 1, track after track for format 2.
35
+ */
36
+ export function composeMidiNotes(timeline) {
37
+ const notes = [];
38
+ const openNotes = new Map();
39
+ const trackEnds = new Map();
40
+ for (const { trackIndex, tick, seconds, event } of timeline) {
41
+ // A track's events reach the timeline in tick order, so its last one ends it.
42
+ const trackEnd = trackEnds.get(trackIndex);
43
+ if (trackEnd === undefined) {
44
+ trackEnds.set(trackIndex, { tick, seconds });
45
+ }
46
+ else {
47
+ trackEnd.tick = tick;
48
+ trackEnd.seconds = seconds;
49
+ }
50
+ if (event.kind !== "channel") {
51
+ continue;
52
+ }
53
+ const { message } = event;
54
+ if (message.type !== "noteOn" && message.type !== "noteOff") {
55
+ continue;
56
+ }
57
+ const key = noteKey_(trackIndex, message.channel, message.note);
58
+ if (isMidiNoteRelease(message)) {
59
+ const note = openNotes.get(key)?.shift();
60
+ if (note !== undefined) {
61
+ note.endTick = tick;
62
+ note.endSeconds = seconds;
63
+ note.releaseVelocity =
64
+ message.type === "noteOff" ? message.velocity : undefined;
65
+ }
66
+ continue;
67
+ }
68
+ const note = {
69
+ trackIndex,
70
+ channel: message.channel,
71
+ note: message.note,
72
+ velocity: message.velocity,
73
+ releaseVelocity: undefined,
74
+ startTick: tick,
75
+ endTick: tick,
76
+ startSeconds: seconds,
77
+ endSeconds: seconds,
78
+ };
79
+ notes.push(note);
80
+ const queue = openNotes.get(key);
81
+ if (queue === undefined) {
82
+ openNotes.set(key, [note]);
83
+ }
84
+ else {
85
+ queue.push(note);
86
+ }
87
+ }
88
+ for (const queue of openNotes.values()) {
89
+ for (const note of queue) {
90
+ const trackEnd = trackEnds.get(note.trackIndex);
91
+ note.endTick = trackEnd.tick;
92
+ note.endSeconds = trackEnd.seconds;
93
+ }
94
+ }
95
+ return notes;
96
+ }
97
+ //# sourceMappingURL=compose-midi-notes.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"compose-midi-notes.js","sourceRoot":"","sources":["../../src/timeline/compose-midi-notes.ts"],"names":[],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"compose-midi-timeline.d.ts","sourceRoot":"","sources":["../../src/timeline/compose-midi-timeline.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACX,QAAQ,EACR,aAAa,EAEb,MAAM,sBAAsB,CAAC;AAE9B,uEAAuE;AACvE,MAAM,MAAM,iBAAiB,GAAG;IAC/B,8CAA8C;IAC9C,UAAU,EAAE,MAAM,CAAC;IACnB;;;OAGG;IACH,IAAI,EAAE,MAAM,CAAC;IACb,2CAA2C;IAC3C,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,EAAE,aAAa,CAAC;CACrB,CAAC;AA2DF;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AACH,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,QAAQ,GAAG,iBAAiB,EAAE,CAqBvE"}
1
+ {"version":3,"file":"compose-midi-timeline.d.ts","sourceRoot":"","sources":["../../src/timeline/compose-midi-timeline.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EACX,QAAQ,EACR,aAAa,EAEb,MAAM,sBAAsB,CAAC;AAE9B,uEAAuE;AACvE,MAAM,MAAM,iBAAiB,GAAG;IAC/B,8CAA8C;IAC9C,UAAU,EAAE,MAAM,CAAC;IACnB;;;OAGG;IACH,IAAI,EAAE,MAAM,CAAC;IACb,2CAA2C;IAC3C,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,EAAE,aAAa,CAAC;CACrB,CAAC;AAuDF;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AACH,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,QAAQ,GAAG,iBAAiB,EAAE,CAqBvE"}
@@ -1,3 +1,4 @@
1
+ import { timecodeTicksPerSecond_ } from "../file/_time-division.js";
1
2
  /** RP-001: a file with no Set Tempo event before a tick plays at 120 bpm. */
2
3
  const DEFAULT_TEMPO_ = 500000;
3
4
  function appendTrackEvents_(target, file, trackIndex) {
@@ -14,10 +15,7 @@ function appendTrackEvents_(target, file, trackIndex) {
14
15
  */
15
16
  function assignSeconds_(events, from, division) {
16
17
  if (division.type === "timecode") {
17
- const framesPerSecond = division.framesPerSecond === 29
18
- ? 30000 / 1001
19
- : division.framesPerSecond;
20
- const ticksPerSecond = framesPerSecond * division.ticksPerFrame;
18
+ const ticksPerSecond = timecodeTicksPerSecond_(division);
21
19
  for (let index = from; index < events.length; index++) {
22
20
  events[index].seconds = events[index].tick / ticksPerSecond;
23
21
  }
@@ -1 +1 @@
1
- {"version":3,"file":"compose-midi-timeline.js","sourceRoot":"","sources":["../../src/timeline/compose-midi-timeline.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"compose-midi-timeline.js","sourceRoot":"","sources":["../../src/timeline/compose-midi-timeline.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@ac-kit/format-midi",
3
- "version": "0.1.1",
4
- "description": "MIDI 1.0: the message model with a live byte-stream Decoder/Encoder, Standard MIDI File parse/print, and tick-to-seconds timelines.",
3
+ "version": "0.2.1",
4
+ "description": "MIDI 1.0: the message model with a live byte-stream Decoder/Encoder, Standard MIDI File parse/print, tick-to-seconds timelines, notes, text decoding, and conversion to a model-music Song.",
5
5
  "keywords": [
6
6
  "midi",
7
7
  "midi-file",
@@ -27,12 +27,14 @@
27
27
  "sideEffects": false,
28
28
  "main": "./dist/index.js",
29
29
  "exports": {
30
- ".": "./dist/index.js"
30
+ ".": "./dist/index.js",
31
+ "./song": "./dist/song/index.js"
31
32
  },
32
33
  "publishConfig": {
33
34
  "main": "./dist/index.js",
34
35
  "exports": {
35
- ".": "./dist/index.js"
36
+ ".": "./dist/index.js",
37
+ "./song": "./dist/song/index.js"
36
38
  },
37
39
  "access": "public"
38
40
  },
@@ -45,9 +47,11 @@
45
47
  "prepack": "yarn build"
46
48
  },
47
49
  "dependencies": {
48
- "@ac-kit/core": "^0.3.0",
49
- "@ac-kit/format-core": "^0.1.3",
50
- "@ac-kit/format-varint": "^0.1.2",
50
+ "@ac-kit/algo": "^0.2.2",
51
+ "@ac-kit/core": "^0.5.0",
52
+ "@ac-kit/format-core": "^0.1.5",
53
+ "@ac-kit/format-varint": "^0.1.4",
54
+ "@ac-kit/model-music": "^0.1.1",
51
55
  "@types/web": "^0.0.359"
52
56
  },
53
57
  "devDependencies": {