@ac-kit/model-music 0.0.0-stage → 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md ADDED
@@ -0,0 +1,24 @@
1
+ # @ac-kit/model-music
2
+
3
+ ## 0.1.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
+
11
+ ## 0.1.0
12
+
13
+ ### Minor Changes
14
+
15
+ - eb2aa3d: First release: `Song`, a piece of music as notes timed in ticks and seconds,
16
+ with its parts, `TempoMap` (`composeTempoMap`, `tempoMapSeconds`,
17
+ `tempoMapTick`), time and key signatures, measures (`composeMeasures`), pedals,
18
+ markers and lyrics.
19
+
20
+ ### Patch Changes
21
+
22
+ - Updated dependencies [aca1cb1]
23
+ - @ac-kit/core@0.4.0
24
+ - @ac-kit/algo@0.2.1
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,43 @@
1
- # Temporary Holding Version
1
+ # @ac-kit/model-music
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
+ Music as plain data: a `Song` lists the notes to play with the time each one is
4
+ played at, in ticks and in seconds, together with the parts, tempo map, meter,
5
+ key, measures, pedals, markers and lyrics around them.
6
+
7
+ ```ts
8
+ import {
9
+ composeMeasures,
10
+ composeTempoMap,
11
+ tempoMapSeconds,
12
+ } from "@ac-kit/model-music";
13
+
14
+ const tempoMap = composeTempoMap(480, [
15
+ { tick: 0, microsecondsPerQuarterNote: 600_000 },
16
+ { tick: 7680, microsecondsPerQuarterNote: 500_000 },
17
+ ]);
18
+
19
+ tempoMapSeconds(tempoMap, 1920); // 2.4
20
+ composeMeasures([{ tick: 0, numerator: 3, denominator: 4 }], tempoMap, 7680);
21
+ ```
22
+
23
+ ## What it is for
24
+
25
+ One description of a piece, several sources and several readers. A Standard
26
+ MIDI File and a MusicXML score both convert to a `Song`; a practice tool, a
27
+ falling-notes view and a player read it without knowing where it came from.
28
+ `@ac-kit/format-midi` provides the MIDI conversion.
29
+
30
+ ## Contents
31
+
32
+ - `Song`, `SongPart`, `SongNote`, `Spelling`, `SongPedal`, `SongMarker`,
33
+ `SongLyric` — the description. A note carries its part, staff, voice, MIDI
34
+ pitch, velocity and its start and end, plus the written spelling, fingering
35
+ and grace flag a score provides and a MIDI file cannot.
36
+ - `TempoMap`, `composeTempoMap`, `tempoMapSeconds`, `tempoMapTick` — ticks to
37
+ seconds and back, under every tempo change.
38
+ - `TimeSignature`, `KeySignature`, `Measure`, `composeMeasures` — the meter,
39
+ and measures built from it when the source has none of its own.
40
+
41
+ See the
42
+ [generated API docs](https://anthochamp.github.io/node-essentials/api/model-music/)
43
+ for the full reference.
@@ -0,0 +1,4 @@
1
+ export * from "./song/song.js";
2
+ export * from "./time/meter.js";
3
+ export * from "./time/tempo-map.js";
4
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,gBAAgB,CAAC;AAC/B,cAAc,iBAAiB,CAAC;AAChC,cAAc,qBAAqB,CAAC"}
package/dist/index.js ADDED
@@ -0,0 +1,4 @@
1
+ export * from "./song/song.js";
2
+ export * from "./time/meter.js";
3
+ export * from "./time/tempo-map.js";
4
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,gBAAgB,CAAC;AAC/B,cAAc,iBAAiB,CAAC;AAChC,cAAc,qBAAqB,CAAC"}
@@ -0,0 +1,96 @@
1
+ import type { KeySignature, Measure, TimeSignature } from "../time/meter.js";
2
+ import type { TempoMap } from "../time/tempo-map.js";
3
+ /**
4
+ * A piece of music as the notes to play and the time each is played at, with
5
+ * what a score or a player needs around them.
6
+ *
7
+ * Every `tick` counts in `tempoMap.ticksPerQuarterNote`, and every `seconds` is
8
+ * the time from the start of the song under that tempo map. Every list is
9
+ * sorted by tick; `notes` by start tick, then by pitch.
10
+ */
11
+ export type Song = {
12
+ title: string | undefined;
13
+ tempoMap: TempoMap;
14
+ /** Where the song ends: its last event, or the end of its last note. */
15
+ endTick: number;
16
+ endSeconds: number;
17
+ parts: SongPart[];
18
+ notes: SongNote[];
19
+ timeSignatures: TimeSignature[];
20
+ keySignatures: KeySignature[];
21
+ measures: Measure[];
22
+ markers: SongMarker[];
23
+ pedals: SongPedal[];
24
+ lyrics: SongLyric[];
25
+ };
26
+ /** One instrument's part, which notes and pedals refer to by index. */
27
+ export type SongPart = {
28
+ name: string | undefined;
29
+ /** General MIDI program, 0–127, when the source names one. */
30
+ program: number | undefined;
31
+ /** Unpitched percussion: `pitch` then names a drum, not a pitch. */
32
+ percussion: boolean;
33
+ /** Staves the part is written on: 2 for a piano's grand staff. */
34
+ staves: number;
35
+ };
36
+ /** A pitch as written: letter, alteration and octave. */
37
+ export type Spelling = {
38
+ step: "A" | "B" | "C" | "D" | "E" | "F" | "G";
39
+ /** Semitones: -1 flat, 1 sharp; fractions for microtones. */
40
+ alter: number;
41
+ /** Scientific pitch notation: middle C is C4. */
42
+ octave: number;
43
+ };
44
+ /** One sounding note. Tied notes are one note. */
45
+ export type SongNote = {
46
+ /** Index into `Song.parts`. */
47
+ part: number;
48
+ /** 1-based staff of the part, when the source says. */
49
+ staff: number | undefined;
50
+ /** Voice within the part, when the source says. */
51
+ voice: number | undefined;
52
+ /** MIDI note number, 0–127; 60 is middle C. */
53
+ pitch: number;
54
+ /** On the MIDI velocity scale, 1–127. */
55
+ velocity: number;
56
+ startTick: number;
57
+ endTick: number;
58
+ startSeconds: number;
59
+ endSeconds: number;
60
+ /** The written pitch, which the MIDI number alone cannot tell (C♯ or D♭). */
61
+ spelling?: Spelling;
62
+ /** Finger number, 1 (thumb) to 5. */
63
+ fingering?: number;
64
+ /** A grace note, played in no time of its own as written. */
65
+ grace?: boolean;
66
+ };
67
+ /** A named point in the song. */
68
+ export type SongMarker = {
69
+ tick: number;
70
+ seconds: number;
71
+ text: string;
72
+ /** `rehearsal` is a score's rehearsal mark; `cue` a cue point for a performer. */
73
+ kind: "marker" | "cue" | "rehearsal";
74
+ };
75
+ /** A pedal position from `tick` on, until the next change of the same pedal. */
76
+ export type SongPedal = {
77
+ /** Index into `Song.parts`. */
78
+ part: number;
79
+ tick: number;
80
+ seconds: number;
81
+ pedal: "sustain" | "sostenuto" | "soft";
82
+ /** 0 is released, 1 fully pressed; values between are half-pedalling. */
83
+ value: number;
84
+ };
85
+ /** One sung syllable. */
86
+ export type SongLyric = {
87
+ /** Index into `Song.parts`, when the lyrics belong to one. */
88
+ part: number | undefined;
89
+ tick: number;
90
+ seconds: number;
91
+ /** The syllable, with the spaces between words. */
92
+ text: string;
93
+ /** A break before the syllable: a new line, or a new paragraph or screen. */
94
+ break: "none" | "line" | "paragraph";
95
+ };
96
+ //# sourceMappingURL=song.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"song.d.ts","sourceRoot":"","sources":["../../src/song/song.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,OAAO,EAAE,aAAa,EAAE,MAAM,kBAAkB,CAAC;AAC7E,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,sBAAsB,CAAC;AAErD;;;;;;;GAOG;AACH,MAAM,MAAM,IAAI,GAAG;IAClB,KAAK,EAAE,MAAM,GAAG,SAAS,CAAC;IAC1B,QAAQ,EAAE,QAAQ,CAAC;IACnB,wEAAwE;IACxE,OAAO,EAAE,MAAM,CAAC;IAChB,UAAU,EAAE,MAAM,CAAC;IACnB,KAAK,EAAE,QAAQ,EAAE,CAAC;IAClB,KAAK,EAAE,QAAQ,EAAE,CAAC;IAClB,cAAc,EAAE,aAAa,EAAE,CAAC;IAChC,aAAa,EAAE,YAAY,EAAE,CAAC;IAC9B,QAAQ,EAAE,OAAO,EAAE,CAAC;IACpB,OAAO,EAAE,UAAU,EAAE,CAAC;IACtB,MAAM,EAAE,SAAS,EAAE,CAAC;IACpB,MAAM,EAAE,SAAS,EAAE,CAAC;CACpB,CAAC;AAEF,uEAAuE;AACvE,MAAM,MAAM,QAAQ,GAAG;IACtB,IAAI,EAAE,MAAM,GAAG,SAAS,CAAC;IACzB,8DAA8D;IAC9D,OAAO,EAAE,MAAM,GAAG,SAAS,CAAC;IAC5B,oEAAoE;IACpE,UAAU,EAAE,OAAO,CAAC;IACpB,kEAAkE;IAClE,MAAM,EAAE,MAAM,CAAC;CACf,CAAC;AAEF,yDAAyD;AACzD,MAAM,MAAM,QAAQ,GAAG;IACtB,IAAI,EAAE,GAAG,GAAG,GAAG,GAAG,GAAG,GAAG,GAAG,GAAG,GAAG,GAAG,GAAG,GAAG,GAAG,CAAC;IAC9C,6DAA6D;IAC7D,KAAK,EAAE,MAAM,CAAC;IACd,iDAAiD;IACjD,MAAM,EAAE,MAAM,CAAC;CACf,CAAC;AAEF,kDAAkD;AAClD,MAAM,MAAM,QAAQ,GAAG;IACtB,+BAA+B;IAC/B,IAAI,EAAE,MAAM,CAAC;IACb,uDAAuD;IACvD,KAAK,EAAE,MAAM,GAAG,SAAS,CAAC;IAC1B,mDAAmD;IACnD,KAAK,EAAE,MAAM,GAAG,SAAS,CAAC;IAC1B,+CAA+C;IAC/C,KAAK,EAAE,MAAM,CAAC;IACd,yCAAyC;IACzC,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,EAAE,MAAM,CAAC;IAClB,OAAO,EAAE,MAAM,CAAC;IAChB,YAAY,EAAE,MAAM,CAAC;IACrB,UAAU,EAAE,MAAM,CAAC;IACnB,6EAA6E;IAC7E,QAAQ,CAAC,EAAE,QAAQ,CAAC;IACpB,qCAAqC;IACrC,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,6DAA6D;IAC7D,KAAK,CAAC,EAAE,OAAO,CAAC;CAChB,CAAC;AAEF,iCAAiC;AACjC,MAAM,MAAM,UAAU,GAAG;IACxB,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,EAAE,MAAM,CAAC;IACb,kFAAkF;IAClF,IAAI,EAAE,QAAQ,GAAG,KAAK,GAAG,WAAW,CAAC;CACrC,CAAC;AAEF,gFAAgF;AAChF,MAAM,MAAM,SAAS,GAAG;IACvB,+BAA+B;IAC/B,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,EAAE,SAAS,GAAG,WAAW,GAAG,MAAM,CAAC;IACxC,yEAAyE;IACzE,KAAK,EAAE,MAAM,CAAC;CACd,CAAC;AAEF,yBAAyB;AACzB,MAAM,MAAM,SAAS,GAAG;IACvB,8DAA8D;IAC9D,IAAI,EAAE,MAAM,GAAG,SAAS,CAAC;IACzB,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;IAChB,mDAAmD;IACnD,IAAI,EAAE,MAAM,CAAC;IACb,6EAA6E;IAC7E,KAAK,EAAE,MAAM,GAAG,MAAM,GAAG,WAAW,CAAC;CACrC,CAAC"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=song.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"song.js","sourceRoot":"","sources":["../../src/song/song.ts"],"names":[],"mappings":""}
@@ -0,0 +1,64 @@
1
+ import { type TempoMap } from "./tempo-map.js";
2
+ /**
3
+ * A time signature in force from `tick` on: `numerator` beats of
4
+ * `1/denominator` notes per measure.
5
+ */
6
+ export type TimeSignature = {
7
+ tick: number;
8
+ /** Beats per measure, 1 or more. */
9
+ numerator: number;
10
+ /** The beat's note value: 4 is a quarter note, 8 an eighth. A power of two. */
11
+ denominator: number;
12
+ };
13
+ /** The mode of a key signature, as MusicXML names them. */
14
+ export type KeyMode = "major" | "minor" | "ionian" | "dorian" | "phrygian" | "lydian" | "mixolydian" | "aeolian" | "locrian" | "none";
15
+ /** A key signature in force from `tick` on. */
16
+ export type KeySignature = {
17
+ tick: number;
18
+ /** Sharps when positive, flats when negative, -7–7. */
19
+ fifths: number;
20
+ mode: KeyMode;
21
+ };
22
+ /** One measure: where it starts and ends, and the meter it is in. */
23
+ export type Measure = {
24
+ /** The measure number as printed: `"1"`, `"12a"`, `"0"` for a pickup. */
25
+ label: string;
26
+ startTick: number;
27
+ endTick: number;
28
+ startSeconds: number;
29
+ endSeconds: number;
30
+ numerator: number;
31
+ denominator: number;
32
+ };
33
+ /** 4/4, the meter of a song before any time signature (MIDI RP-001). */
34
+ export declare const DEFAULT_TIME_SIGNATURE: Omit<TimeSignature, "tick">;
35
+ /**
36
+ * Divides a song into measures from its time signatures, numbering them from 1,
37
+ * when nothing gives the measures themselves — a MIDI file, unlike a score.
38
+ *
39
+ * - Before the first time signature, or with none, the meter is
40
+ * {@link DEFAULT_TIME_SIGNATURE}, 4/4.
41
+ * - A time signature falling inside a measure cuts that measure short, and the
42
+ * next one starts at the change.
43
+ * - Measures cover `[0, endTick)`, with at least one; the last one keeps its full
44
+ * length past `endTick`.
45
+ *
46
+ * Time complexity: O(m + s) in the number of measures and of time signatures,
47
+ * plus O(m log t) in the tempo changes for the times. Memory: O(m).
48
+ *
49
+ * @example
50
+ * composeMeasures([], composeTempoMap(96, []), 400).map(
51
+ * ({ startTick }) => startTick,
52
+ * );
53
+ * // [0, 384]
54
+ *
55
+ * @param timeSignatures - Sorted by tick.
56
+ * @param tempoMap - The song's tempo map, for the times and the tick
57
+ * resolution.
58
+ * @param endTick - Where the song ends.
59
+ * @returns The measures, in order.
60
+ * @throws {RangeError} When a time signature has a numerator below 1 or a
61
+ * denominator that is not a power of two, or when they are not sorted.
62
+ */
63
+ export declare function composeMeasures(timeSignatures: readonly TimeSignature[], tempoMap: TempoMap, endTick: number): Measure[];
64
+ //# sourceMappingURL=meter.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"meter.d.ts","sourceRoot":"","sources":["../../src/time/meter.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,KAAK,QAAQ,EAAmB,MAAM,gBAAgB,CAAC;AAEhE;;;GAGG;AACH,MAAM,MAAM,aAAa,GAAG;IAC3B,IAAI,EAAE,MAAM,CAAC;IACb,oCAAoC;IACpC,SAAS,EAAE,MAAM,CAAC;IAClB,+EAA+E;IAC/E,WAAW,EAAE,MAAM,CAAC;CACpB,CAAC;AAEF,2DAA2D;AAC3D,MAAM,MAAM,OAAO,GAChB,OAAO,GACP,OAAO,GACP,QAAQ,GACR,QAAQ,GACR,UAAU,GACV,QAAQ,GACR,YAAY,GACZ,SAAS,GACT,SAAS,GACT,MAAM,CAAC;AAEV,+CAA+C;AAC/C,MAAM,MAAM,YAAY,GAAG;IAC1B,IAAI,EAAE,MAAM,CAAC;IACb,uDAAuD;IACvD,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,OAAO,CAAC;CACd,CAAC;AAEF,qEAAqE;AACrE,MAAM,MAAM,OAAO,GAAG;IACrB,yEAAyE;IACzE,KAAK,EAAE,MAAM,CAAC;IACd,SAAS,EAAE,MAAM,CAAC;IAClB,OAAO,EAAE,MAAM,CAAC;IAChB,YAAY,EAAE,MAAM,CAAC;IACrB,UAAU,EAAE,MAAM,CAAC;IACnB,SAAS,EAAE,MAAM,CAAC;IAClB,WAAW,EAAE,MAAM,CAAC;CACpB,CAAC;AAEF,wEAAwE;AACxE,eAAO,MAAM,sBAAsB,EAAE,IAAI,CAAC,aAAa,EAAE,MAAM,CAG9D,CAAC;AAEF;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AACH,wBAAgB,eAAe,CAC9B,cAAc,EAAE,SAAS,aAAa,EAAE,EACxC,QAAQ,EAAE,QAAQ,EAClB,OAAO,EAAE,MAAM,GACb,OAAO,EAAE,CAkDX"}
@@ -0,0 +1,79 @@
1
+ import { isPowerOfTwo } from "@ac-kit/core";
2
+ import { tempoMapSeconds } from "./tempo-map.js";
3
+ /** 4/4, the meter of a song before any time signature (MIDI RP-001). */
4
+ export const DEFAULT_TIME_SIGNATURE = {
5
+ numerator: 4,
6
+ denominator: 4,
7
+ };
8
+ /**
9
+ * Divides a song into measures from its time signatures, numbering them from 1,
10
+ * when nothing gives the measures themselves — a MIDI file, unlike a score.
11
+ *
12
+ * - Before the first time signature, or with none, the meter is
13
+ * {@link DEFAULT_TIME_SIGNATURE}, 4/4.
14
+ * - A time signature falling inside a measure cuts that measure short, and the
15
+ * next one starts at the change.
16
+ * - Measures cover `[0, endTick)`, with at least one; the last one keeps its full
17
+ * length past `endTick`.
18
+ *
19
+ * Time complexity: O(m + s) in the number of measures and of time signatures,
20
+ * plus O(m log t) in the tempo changes for the times. Memory: O(m).
21
+ *
22
+ * @example
23
+ * composeMeasures([], composeTempoMap(96, []), 400).map(
24
+ * ({ startTick }) => startTick,
25
+ * );
26
+ * // [0, 384]
27
+ *
28
+ * @param timeSignatures - Sorted by tick.
29
+ * @param tempoMap - The song's tempo map, for the times and the tick
30
+ * resolution.
31
+ * @param endTick - Where the song ends.
32
+ * @returns The measures, in order.
33
+ * @throws {RangeError} When a time signature has a numerator below 1 or a
34
+ * denominator that is not a power of two, or when they are not sorted.
35
+ */
36
+ export function composeMeasures(timeSignatures, tempoMap, endTick) {
37
+ let previousTick = 0;
38
+ for (const { tick, numerator, denominator } of timeSignatures) {
39
+ if (!(numerator >= 1 && Number.isInteger(numerator))) {
40
+ throw new RangeError(`time signature numerator ${numerator} is below 1`);
41
+ }
42
+ if (!(denominator >= 1) || isPowerOfTwo(denominator) !== true) {
43
+ throw new RangeError(`time signature denominator ${denominator} is not a power of two`);
44
+ }
45
+ if (!(tick >= previousTick)) {
46
+ throw new RangeError(`time signature at tick ${tick} is out of order`);
47
+ }
48
+ previousTick = tick;
49
+ }
50
+ const measures = [];
51
+ let meter = DEFAULT_TIME_SIGNATURE;
52
+ let nextIndex = 0;
53
+ let startTick = 0;
54
+ let startSeconds = 0;
55
+ while (startTick < endTick || measures.length === 0) {
56
+ while (nextIndex < timeSignatures.length &&
57
+ timeSignatures[nextIndex].tick <= startTick) {
58
+ meter = timeSignatures[nextIndex];
59
+ nextIndex++;
60
+ }
61
+ const fullLength = (tempoMap.ticksPerQuarterNote * 4 * meter.numerator) / meter.denominator;
62
+ const nextChangeTick = timeSignatures[nextIndex]?.tick ?? Infinity;
63
+ const endOfMeasure = Math.min(startTick + fullLength, nextChangeTick);
64
+ const endSeconds = tempoMapSeconds(tempoMap, endOfMeasure);
65
+ measures.push({
66
+ label: String(measures.length + 1),
67
+ startTick,
68
+ endTick: endOfMeasure,
69
+ startSeconds,
70
+ endSeconds,
71
+ numerator: meter.numerator,
72
+ denominator: meter.denominator,
73
+ });
74
+ startTick = endOfMeasure;
75
+ startSeconds = endSeconds;
76
+ }
77
+ return measures;
78
+ }
79
+ //# sourceMappingURL=meter.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"meter.js","sourceRoot":"","sources":["../../src/time/meter.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,cAAc,CAAC;AAE5C,OAAO,EAAiB,eAAe,EAAE,MAAM,gBAAgB,CAAC;AA+ChE,wEAAwE;AACxE,MAAM,CAAC,MAAM,sBAAsB,GAAgC;IAClE,SAAS,EAAE,CAAC;IACZ,WAAW,EAAE,CAAC;CACd,CAAC;AAEF;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AACH,MAAM,UAAU,eAAe,CAC9B,cAAwC,EACxC,QAAkB,EAClB,OAAe;IAEf,IAAI,YAAY,GAAG,CAAC,CAAC;IACrB,KAAK,MAAM,EAAE,IAAI,EAAE,SAAS,EAAE,WAAW,EAAE,IAAI,cAAc,EAAE,CAAC;QAC/D,IAAI,CAAC,CAAC,SAAS,IAAI,CAAC,IAAI,MAAM,CAAC,SAAS,CAAC,SAAS,CAAC,CAAC,EAAE,CAAC;YACtD,MAAM,IAAI,UAAU,CAAC,4BAA4B,SAAS,aAAa,CAAC,CAAC;QAC1E,CAAC;QACD,IAAI,CAAC,CAAC,WAAW,IAAI,CAAC,CAAC,IAAI,YAAY,CAAC,WAAW,CAAC,KAAK,IAAI,EAAE,CAAC;YAC/D,MAAM,IAAI,UAAU,CACnB,8BAA8B,WAAW,wBAAwB,CACjE,CAAC;QACH,CAAC;QACD,IAAI,CAAC,CAAC,IAAI,IAAI,YAAY,CAAC,EAAE,CAAC;YAC7B,MAAM,IAAI,UAAU,CAAC,0BAA0B,IAAI,kBAAkB,CAAC,CAAC;QACxE,CAAC;QACD,YAAY,GAAG,IAAI,CAAC;IACrB,CAAC;IAED,MAAM,QAAQ,GAAc,EAAE,CAAC;IAC/B,IAAI,KAAK,GAAG,sBAAsB,CAAC;IACnC,IAAI,SAAS,GAAG,CAAC,CAAC;IAClB,IAAI,SAAS,GAAG,CAAC,CAAC;IAClB,IAAI,YAAY,GAAG,CAAC,CAAC;IACrB,OAAO,SAAS,GAAG,OAAO,IAAI,QAAQ,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACrD,OACC,SAAS,GAAG,cAAc,CAAC,MAAM;YACjC,cAAc,CAAC,SAAS,CAAE,CAAC,IAAI,IAAI,SAAS,EAC3C,CAAC;YACF,KAAK,GAAG,cAAc,CAAC,SAAS,CAAE,CAAC;YACnC,SAAS,EAAE,CAAC;QACb,CAAC;QAED,MAAM,UAAU,GACf,CAAC,QAAQ,CAAC,mBAAmB,GAAG,CAAC,GAAG,KAAK,CAAC,SAAS,CAAC,GAAG,KAAK,CAAC,WAAW,CAAC;QAC1E,MAAM,cAAc,GAAG,cAAc,CAAC,SAAS,CAAC,EAAE,IAAI,IAAI,QAAQ,CAAC;QACnE,MAAM,YAAY,GAAG,IAAI,CAAC,GAAG,CAAC,SAAS,GAAG,UAAU,EAAE,cAAc,CAAC,CAAC;QACtE,MAAM,UAAU,GAAG,eAAe,CAAC,QAAQ,EAAE,YAAY,CAAC,CAAC;QAC3D,QAAQ,CAAC,IAAI,CAAC;YACb,KAAK,EAAE,MAAM,CAAC,QAAQ,CAAC,MAAM,GAAG,CAAC,CAAC;YAClC,SAAS;YACT,OAAO,EAAE,YAAY;YACrB,YAAY;YACZ,UAAU;YACV,SAAS,EAAE,KAAK,CAAC,SAAS;YAC1B,WAAW,EAAE,KAAK,CAAC,WAAW;SAC9B,CAAC,CAAC;QACH,SAAS,GAAG,YAAY,CAAC;QACzB,YAAY,GAAG,UAAU,CAAC;IAC3B,CAAC;IAED,OAAO,QAAQ,CAAC;AACjB,CAAC"}
@@ -0,0 +1,81 @@
1
+ /** A tempo in force from `tick` on, until the next change. */
2
+ export type TempoChange = {
3
+ tick: number;
4
+ /** The time of `tick`, from the start of the song. */
5
+ seconds: number;
6
+ /**
7
+ * Length of a quarter note, in microseconds; 500 000 is 120 bpm. A real
8
+ * number, not limited to what a MIDI Set Tempo event can store.
9
+ */
10
+ microsecondsPerQuarterNote: number;
11
+ };
12
+ /**
13
+ * How ticks map to seconds over a whole song: the tick resolution and every
14
+ * tempo change, in tick order, the first one at tick 0.
15
+ *
16
+ * Build one with {@link composeTempoMap}, which keeps that invariant and fills
17
+ * in each change's `seconds`.
18
+ */
19
+ export type TempoMap = {
20
+ /** Ticks per quarter note, the resolution every `tick` of a song uses. */
21
+ ticksPerQuarterNote: number;
22
+ changes: TempoChange[];
23
+ };
24
+ /** A tempo change before its time in seconds is known. */
25
+ export type TempoMark = Omit<TempoChange, "seconds">;
26
+ /** 120 bpm, the tempo of a song before any tempo mark (MIDI RP-001). */
27
+ export declare const DEFAULT_MICROSECONDS_PER_QUARTER_NOTE = 500000;
28
+ /**
29
+ * Builds the tempo map of a song from its tempo marks, computing the time of
30
+ * each.
31
+ *
32
+ * - A song with no mark at tick 0 starts at
33
+ * {@link DEFAULT_MICROSECONDS_PER_QUARTER_NOTE}, 120 bpm.
34
+ * - Of several marks at one tick, the last one given wins.
35
+ * - A mark equal to the tempo already in force is kept out of the map.
36
+ *
37
+ * Time complexity: O(n) in `marks.length`. Memory: O(n).
38
+ *
39
+ * @example
40
+ * composeTempoMap(96, [
41
+ * { tick: 192, microsecondsPerQuarterNote: 1_000_000 },
42
+ * ]);
43
+ * // { ticksPerQuarterNote: 96, changes: [
44
+ * // { tick: 0, seconds: 0, microsecondsPerQuarterNote: 500_000 },
45
+ * // { tick: 192, seconds: 1, microsecondsPerQuarterNote: 1_000_000 } ] }
46
+ *
47
+ * @param ticksPerQuarterNote - The song's tick resolution, above 0.
48
+ * @param marks - The tempo marks, sorted by tick, none below 0.
49
+ * @returns The tempo map.
50
+ * @throws {RangeError} When `ticksPerQuarterNote` or a tempo is not above 0, or
51
+ * when the marks are not sorted from tick 0 on.
52
+ */
53
+ export declare function composeTempoMap(ticksPerQuarterNote: number, marks: readonly TempoMark[]): TempoMap;
54
+ /**
55
+ * The time of a tick, in seconds from the start of the song.
56
+ *
57
+ * Ticks need not be integers. A tick below 0 extrapolates the first tempo.
58
+ *
59
+ * Time complexity: O(log n) in the number of tempo changes. Memory: O(1).
60
+ *
61
+ * @example
62
+ * tempoMapSeconds(composeTempoMap(96, []), 96); // 0.5
63
+ *
64
+ * @see {@link tempoMapTick} for the converse.
65
+ */
66
+ export declare function tempoMapSeconds(tempoMap: TempoMap, tick: number): number;
67
+ /**
68
+ * The tick at a time in seconds from the start of the song, as a real number:
69
+ * round it where a whole tick is needed.
70
+ *
71
+ * A time below 0 extrapolates the first tempo.
72
+ *
73
+ * Time complexity: O(log n) in the number of tempo changes. Memory: O(1).
74
+ *
75
+ * @example
76
+ * tempoMapTick(composeTempoMap(96, []), 0.5); // 96
77
+ *
78
+ * @see {@link tempoMapSeconds} for the converse.
79
+ */
80
+ export declare function tempoMapTick(tempoMap: TempoMap, seconds: number): number;
81
+ //# sourceMappingURL=tempo-map.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tempo-map.d.ts","sourceRoot":"","sources":["../../src/time/tempo-map.ts"],"names":[],"mappings":"AAEA,8DAA8D;AAC9D,MAAM,MAAM,WAAW,GAAG;IACzB,IAAI,EAAE,MAAM,CAAC;IACb,sDAAsD;IACtD,OAAO,EAAE,MAAM,CAAC;IAChB;;;OAGG;IACH,0BAA0B,EAAE,MAAM,CAAC;CACnC,CAAC;AAEF;;;;;;GAMG;AACH,MAAM,MAAM,QAAQ,GAAG;IACtB,0EAA0E;IAC1E,mBAAmB,EAAE,MAAM,CAAC;IAC5B,OAAO,EAAE,WAAW,EAAE,CAAC;CACvB,CAAC;AAEF,0DAA0D;AAC1D,MAAM,MAAM,SAAS,GAAG,IAAI,CAAC,WAAW,EAAE,SAAS,CAAC,CAAC;AAErD,wEAAwE;AACxE,eAAO,MAAM,qCAAqC,SAAU,CAAC;AAE7D;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AACH,wBAAgB,eAAe,CAC9B,mBAAmB,EAAE,MAAM,EAC3B,KAAK,EAAE,SAAS,SAAS,EAAE,GACzB,QAAQ,CA8CV;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,eAAe,CAAC,QAAQ,EAAE,QAAQ,EAAE,IAAI,EAAE,MAAM,GAAG,MAAM,CAQxE;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,YAAY,CAAC,QAAQ,EAAE,QAAQ,EAAE,OAAO,EAAE,MAAM,GAAG,MAAM,CAcxE"}
@@ -0,0 +1,114 @@
1
+ import { bisectRight } from "@ac-kit/algo";
2
+ /** 120 bpm, the tempo of a song before any tempo mark (MIDI RP-001). */
3
+ export const DEFAULT_MICROSECONDS_PER_QUARTER_NOTE = 500000;
4
+ /**
5
+ * Builds the tempo map of a song from its tempo marks, computing the time of
6
+ * each.
7
+ *
8
+ * - A song with no mark at tick 0 starts at
9
+ * {@link DEFAULT_MICROSECONDS_PER_QUARTER_NOTE}, 120 bpm.
10
+ * - Of several marks at one tick, the last one given wins.
11
+ * - A mark equal to the tempo already in force is kept out of the map.
12
+ *
13
+ * Time complexity: O(n) in `marks.length`. Memory: O(n).
14
+ *
15
+ * @example
16
+ * composeTempoMap(96, [
17
+ * { tick: 192, microsecondsPerQuarterNote: 1_000_000 },
18
+ * ]);
19
+ * // { ticksPerQuarterNote: 96, changes: [
20
+ * // { tick: 0, seconds: 0, microsecondsPerQuarterNote: 500_000 },
21
+ * // { tick: 192, seconds: 1, microsecondsPerQuarterNote: 1_000_000 } ] }
22
+ *
23
+ * @param ticksPerQuarterNote - The song's tick resolution, above 0.
24
+ * @param marks - The tempo marks, sorted by tick, none below 0.
25
+ * @returns The tempo map.
26
+ * @throws {RangeError} When `ticksPerQuarterNote` or a tempo is not above 0, or
27
+ * when the marks are not sorted from tick 0 on.
28
+ */
29
+ export function composeTempoMap(ticksPerQuarterNote, marks) {
30
+ if (!(ticksPerQuarterNote > 0)) {
31
+ throw new RangeError(`ticks per quarter note ${ticksPerQuarterNote} is not above 0`);
32
+ }
33
+ const changes = [
34
+ {
35
+ tick: 0,
36
+ seconds: 0,
37
+ microsecondsPerQuarterNote: DEFAULT_MICROSECONDS_PER_QUARTER_NOTE,
38
+ },
39
+ ];
40
+ for (const { tick, microsecondsPerQuarterNote } of marks) {
41
+ if (!(microsecondsPerQuarterNote > 0)) {
42
+ throw new RangeError(`tempo ${microsecondsPerQuarterNote} µs per quarter note is not above 0`);
43
+ }
44
+ const last = changes.at(-1);
45
+ if (!(tick >= last.tick)) {
46
+ throw new RangeError(`tempo mark at tick ${tick} is out of order`);
47
+ }
48
+ if (tick === last.tick) {
49
+ const previous = changes.at(-2);
50
+ if (previous?.microsecondsPerQuarterNote === microsecondsPerQuarterNote) {
51
+ changes.pop();
52
+ }
53
+ else {
54
+ last.microsecondsPerQuarterNote = microsecondsPerQuarterNote;
55
+ }
56
+ continue;
57
+ }
58
+ if (last.microsecondsPerQuarterNote === microsecondsPerQuarterNote) {
59
+ continue;
60
+ }
61
+ changes.push({
62
+ tick,
63
+ seconds: secondsAfter_(last, tick, ticksPerQuarterNote),
64
+ microsecondsPerQuarterNote,
65
+ });
66
+ }
67
+ return { ticksPerQuarterNote, changes };
68
+ }
69
+ /**
70
+ * The time of a tick, in seconds from the start of the song.
71
+ *
72
+ * Ticks need not be integers. A tick below 0 extrapolates the first tempo.
73
+ *
74
+ * Time complexity: O(log n) in the number of tempo changes. Memory: O(1).
75
+ *
76
+ * @example
77
+ * tempoMapSeconds(composeTempoMap(96, []), 96); // 0.5
78
+ *
79
+ * @see {@link tempoMapTick} for the converse.
80
+ */
81
+ export function tempoMapSeconds(tempoMap, tick) {
82
+ const { changes, ticksPerQuarterNote } = tempoMap;
83
+ const index = Math.max(0, bisectRight(changes, tick, (change, value) => change.tick - value) - 1);
84
+ return secondsAfter_(changes[index], tick, ticksPerQuarterNote);
85
+ }
86
+ /**
87
+ * The tick at a time in seconds from the start of the song, as a real number:
88
+ * round it where a whole tick is needed.
89
+ *
90
+ * A time below 0 extrapolates the first tempo.
91
+ *
92
+ * Time complexity: O(log n) in the number of tempo changes. Memory: O(1).
93
+ *
94
+ * @example
95
+ * tempoMapTick(composeTempoMap(96, []), 0.5); // 96
96
+ *
97
+ * @see {@link tempoMapSeconds} for the converse.
98
+ */
99
+ export function tempoMapTick(tempoMap, seconds) {
100
+ const { changes, ticksPerQuarterNote } = tempoMap;
101
+ const index = Math.max(0, bisectRight(changes, seconds, (change, value) => change.seconds - value) -
102
+ 1);
103
+ const change = changes[index];
104
+ return (change.tick +
105
+ ((seconds - change.seconds) * 1000000 * ticksPerQuarterNote) /
106
+ change.microsecondsPerQuarterNote);
107
+ }
108
+ // Measured from the change rather than accumulated, so a long song does not drift.
109
+ function secondsAfter_(change, tick, ticksPerQuarterNote) {
110
+ return (change.seconds +
111
+ ((tick - change.tick) * change.microsecondsPerQuarterNote) /
112
+ (1000000 * ticksPerQuarterNote));
113
+ }
114
+ //# sourceMappingURL=tempo-map.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tempo-map.js","sourceRoot":"","sources":["../../src/time/tempo-map.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,64 @@
1
1
  {
2
2
  "name": "@ac-kit/model-music",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
3
+ "version": "0.1.1",
4
+ "description": "Music as plain data: the notes of a song with their times in ticks and seconds, its parts, tempo map, meter, measures, pedals, markers and lyrics.",
5
+ "keywords": [
6
+ "measures",
7
+ "music",
8
+ "notes",
9
+ "song",
10
+ "tempo-map"
11
+ ],
12
+ "homepage": "https://anthochamp.github.io/node-essentials/api/model-music/",
13
+ "bugs": {
14
+ "url": "https://git.grenadine.io/anthochamp/node-essentials/issues"
15
+ },
16
+ "license": "BSD-3-Clause",
17
+ "author": "Anthony Champagne <dev@anthonychampagne.fr>",
18
+ "repository": {
19
+ "type": "git",
20
+ "url": "git+https://git.grenadine.io/anthochamp/node-essentials.git",
21
+ "directory": "packages/model-music"
22
+ },
23
+ "files": [
24
+ "dist"
25
+ ],
26
+ "type": "module",
27
+ "sideEffects": false,
28
+ "main": "./dist/index.js",
29
+ "exports": {
30
+ ".": "./dist/index.js"
31
+ },
32
+ "publishConfig": {
33
+ "main": "./dist/index.js",
34
+ "exports": {
35
+ ".": "./dist/index.js"
36
+ },
37
+ "access": "public"
38
+ },
39
+ "scripts": {
40
+ "compile": "tsc -p ./tsconfig.build.json -outDir dist",
41
+ "compile:check": "tsc -noEmit -p ./tsconfig.json",
42
+ "clean": "rimraf dist",
43
+ "build": "yarn clean && yarn compile",
44
+ "test": "yarn run -T vitest run",
45
+ "prepack": "yarn build"
46
+ },
47
+ "dependencies": {
48
+ "@ac-kit/algo": "^0.2.2",
49
+ "@ac-kit/core": "^0.5.0",
50
+ "@types/web": "^0.0.359"
51
+ },
52
+ "devDependencies": {
53
+ "@ac-kit/tsconfig": "0.1.0",
54
+ "@ac-kit/typedoc-config": "0.1.0",
55
+ "@ac-kit/vitest-config": "0.1.1",
56
+ "@typescript/native": "typescript@7.0.2",
57
+ "rimraf": "6.1.3",
58
+ "typedoc": "0.28.20",
59
+ "typescript": "@typescript/typescript6@6.0.2"
60
+ },
61
+ "engines": {
62
+ "node": ">=24"
63
+ }
6
64
  }