@sudobility/music_types 0.11.35 → 0.13.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/CLAUDE.md +20 -0
- package/dist/bytes/base64.d.ts +40 -0
- package/dist/bytes/base64.d.ts.map +1 -0
- package/dist/bytes/base64.js +101 -0
- package/dist/bytes/base64.js.map +1 -0
- package/dist/bytes/index.d.ts +2 -0
- package/dist/bytes/index.d.ts.map +1 -0
- package/dist/bytes/index.js +2 -0
- package/dist/bytes/index.js.map +1 -0
- package/dist/formats/index.d.ts +12 -0
- package/dist/formats/index.d.ts.map +1 -0
- package/dist/formats/index.js +12 -0
- package/dist/formats/index.js.map +1 -0
- package/dist/{platform → formats}/midi.d.ts +0 -4
- package/dist/formats/midi.d.ts.map +1 -0
- package/dist/{platform → formats}/midi.js.map +1 -1
- package/dist/{platform → formats}/mod.d.ts +0 -20
- package/dist/formats/mod.d.ts.map +1 -0
- package/dist/formats/mod.js.map +1 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +10 -1
- package/dist/index.js.map +1 -1
- package/dist/platform/index.d.ts +0 -2
- package/dist/platform/index.d.ts.map +1 -1
- package/dist/platform/index.js +0 -2
- package/dist/platform/index.js.map +1 -1
- package/dist/platform/midi-input.d.ts +4 -2
- package/dist/platform/midi-input.d.ts.map +1 -1
- package/dist/platform/midi-input.js +4 -2
- package/dist/platform/midi-input.js.map +1 -1
- package/dist/platform/playback.d.ts +9 -90
- package/dist/platform/playback.d.ts.map +1 -1
- package/dist/position/index.d.ts +3 -0
- package/dist/position/index.d.ts.map +1 -0
- package/dist/position/index.js +3 -0
- package/dist/position/index.js.map +1 -0
- package/dist/position/music-position.d.ts +63 -0
- package/dist/position/music-position.d.ts.map +1 -0
- package/dist/position/music-position.js +69 -0
- package/dist/position/music-position.js.map +1 -0
- package/dist/position/singleton.d.ts +34 -0
- package/dist/position/singleton.d.ts.map +1 -0
- package/dist/position/singleton.js +33 -0
- package/dist/position/singleton.js.map +1 -0
- package/package.json +1 -1
- package/dist/platform/midi.d.ts.map +0 -1
- package/dist/platform/mod.d.ts.map +0 -1
- package/dist/platform/mod.js.map +0 -1
- /package/dist/{platform → formats}/midi.js +0 -0
- /package/dist/{platform → formats}/mod.js +0 -0
package/CLAUDE.md
CHANGED
|
@@ -39,6 +39,26 @@ Everything exports from a single sectioned `src/index.ts`:
|
|
|
39
39
|
|
|
40
40
|
## Gotchas
|
|
41
41
|
|
|
42
|
+
- **The playhead is the one stateful service in this package, and that is a
|
|
43
|
+
deliberate precedent.** `MusicPosition` and its singleton live in
|
|
44
|
+
`src/position/`. Everything else here is model and primitives; the playhead
|
|
45
|
+
earns the exception because it has exactly one writer
|
|
46
|
+
(`@sudobility/music_player`) and readers in every other package — the caret,
|
|
47
|
+
the note highlighting, the piano keyboard — so any other home creates a
|
|
48
|
+
dependency edge that exists only to reach it. It still obeys the four rules:
|
|
49
|
+
works on both sides, no dependency, no hooks, no async.
|
|
50
|
+
- **`base64.ts` is here because its two consumers ended up in different
|
|
51
|
+
packages.** `music_io`'s React Native file exporter encodes; `music_player`'s
|
|
52
|
+
sample-pack loader decodes. The module exists precisely so there is one
|
|
53
|
+
alphabet and one round-trip test rather than a copy each — splitting it would
|
|
54
|
+
break the property it was written to guarantee.
|
|
55
|
+
- **The playback *plan* is here; the engine *contract* is not.** `PlaybackPlan`
|
|
56
|
+
composes `PerformanceTimeline`, which `performanceTimeline()` in
|
|
57
|
+
`domain/score/` produces, so the whole plan cluster is anchored here — moving
|
|
58
|
+
it would make this package import from `music_player`. `PlaybackEngine`,
|
|
59
|
+
`PlaybackObserver` and `AuditionVoice` did move, because that package is the
|
|
60
|
+
only thing that implements or calls them.
|
|
61
|
+
|
|
42
62
|
- No domain logic here: tick math, factories, commands, validation logic all live in `@sudobility/music_lib`. This package must never depend on music_lib (music_api depends on this package and must not pull in UI/audio code).
|
|
43
63
|
- `noteEventSchema`/`restEventSchema` are `.strict()` on purpose (a stray `pitch` key must not pass as a rest); other schemas strip unknown keys for forward compatibility.
|
|
44
64
|
- Ticks are integers at 480 PPQ by convention; `startTick` is absolute.
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Base64, both directions.
|
|
3
|
+
*
|
|
4
|
+
* Here rather than in a platform package because its two consumers now live in
|
|
5
|
+
* different ones — `music_io`'s React Native file exporter encodes, and
|
|
6
|
+
* `music_player`'s sample-pack loader decodes — and this module exists
|
|
7
|
+
* precisely so there is one alphabet and one round-trip test rather than a
|
|
8
|
+
* copy each. Pure string and byte work: no dependency, no async, nothing
|
|
9
|
+
* platform-bound, so it sits inside this package's four rules.
|
|
10
|
+
*
|
|
11
|
+
* Written out rather than reached for because Hermes guarantees neither `btoa`
|
|
12
|
+
* nor Node's `Buffer`, and the two places that need it need opposite
|
|
13
|
+
* directions: `file/file.rn.ts` encodes bytes to hand `react-native-fs` a
|
|
14
|
+
* base64 string, and `playback/pack-library.ts` decodes the base64 mp3 data
|
|
15
|
+
* URIs inside a FluidR3 sample pack.
|
|
16
|
+
*
|
|
17
|
+
* One module rather than a copy each, so the alphabet is defined once and the
|
|
18
|
+
* pair can be round-tripped against each other — which is a stronger check than
|
|
19
|
+
* either direction gets alone, since a matching error in both would still
|
|
20
|
+
* survive testing them separately.
|
|
21
|
+
*
|
|
22
|
+
* **Both are hot enough to be worth the shape they are in.** A sample pack is
|
|
23
|
+
* ~2.7MB of base64 decoded on the way into playback, per instrument, and an
|
|
24
|
+
* audio export is tens of megabytes encoded on the way out — on a phone, with
|
|
25
|
+
* no JIT. The obvious implementations of each were measured at 77ms and 580ms
|
|
26
|
+
* for those sizes on a laptop; these are 9ms and 303ms.
|
|
27
|
+
*/
|
|
28
|
+
/** Bytes to base64, padded. */
|
|
29
|
+
export declare function bytesToBase64(bytes: Uint8Array): string;
|
|
30
|
+
/**
|
|
31
|
+
* Base64 to bytes. Anything outside the alphabet — padding, newlines a data URI
|
|
32
|
+
* may carry — is skipped rather than rejected.
|
|
33
|
+
*
|
|
34
|
+
* Skipped *inline* rather than stripped first: the obvious
|
|
35
|
+
* `replace(/[^A-Za-z0-9+/]/g, '')` copies the whole multi-megabyte string
|
|
36
|
+
* before any decoding starts, which on a sample pack costs as much as the
|
|
37
|
+
* decode itself.
|
|
38
|
+
*/
|
|
39
|
+
export declare function base64ToBytes(base64: string): ArrayBuffer;
|
|
40
|
+
//# sourceMappingURL=base64.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"base64.d.ts","sourceRoot":"","sources":["../../src/bytes/base64.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AAyBH,+BAA+B;AAC/B,wBAAgB,aAAa,CAAC,KAAK,EAAE,UAAU,GAAG,MAAM,CAmBvD;AAED;;;;;;;;GAQG;AACH,wBAAgB,aAAa,CAAC,MAAM,EAAE,MAAM,GAAG,WAAW,CAoBzD"}
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Base64, both directions.
|
|
3
|
+
*
|
|
4
|
+
* Here rather than in a platform package because its two consumers now live in
|
|
5
|
+
* different ones — `music_io`'s React Native file exporter encodes, and
|
|
6
|
+
* `music_player`'s sample-pack loader decodes — and this module exists
|
|
7
|
+
* precisely so there is one alphabet and one round-trip test rather than a
|
|
8
|
+
* copy each. Pure string and byte work: no dependency, no async, nothing
|
|
9
|
+
* platform-bound, so it sits inside this package's four rules.
|
|
10
|
+
*
|
|
11
|
+
* Written out rather than reached for because Hermes guarantees neither `btoa`
|
|
12
|
+
* nor Node's `Buffer`, and the two places that need it need opposite
|
|
13
|
+
* directions: `file/file.rn.ts` encodes bytes to hand `react-native-fs` a
|
|
14
|
+
* base64 string, and `playback/pack-library.ts` decodes the base64 mp3 data
|
|
15
|
+
* URIs inside a FluidR3 sample pack.
|
|
16
|
+
*
|
|
17
|
+
* One module rather than a copy each, so the alphabet is defined once and the
|
|
18
|
+
* pair can be round-tripped against each other — which is a stronger check than
|
|
19
|
+
* either direction gets alone, since a matching error in both would still
|
|
20
|
+
* survive testing them separately.
|
|
21
|
+
*
|
|
22
|
+
* **Both are hot enough to be worth the shape they are in.** A sample pack is
|
|
23
|
+
* ~2.7MB of base64 decoded on the way into playback, per instrument, and an
|
|
24
|
+
* audio export is tens of megabytes encoded on the way out — on a phone, with
|
|
25
|
+
* no JIT. The obvious implementations of each were measured at 77ms and 580ms
|
|
26
|
+
* for those sizes on a laptop; these are 9ms and 303ms.
|
|
27
|
+
*/
|
|
28
|
+
const ALPHABET = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/';
|
|
29
|
+
/**
|
|
30
|
+
* Character code to sextet, -1 for anything else.
|
|
31
|
+
*
|
|
32
|
+
* A table rather than `ALPHABET.indexOf(char)`, which is a linear search over
|
|
33
|
+
* 64 characters *per character decoded* — 8.5x the total cost on a pack-sized
|
|
34
|
+
* input, and worse under an interpreter than under V8.
|
|
35
|
+
*/
|
|
36
|
+
const SEXTET = new Int8Array(256).fill(-1);
|
|
37
|
+
for (let i = 0; i < ALPHABET.length; i += 1)
|
|
38
|
+
SEXTET[ALPHABET.charCodeAt(i)] = i;
|
|
39
|
+
/**
|
|
40
|
+
* How much string to accumulate before pushing it into the chunk list.
|
|
41
|
+
*
|
|
42
|
+
* Growing one string four characters at a time asks the engine to keep
|
|
43
|
+
* re-materialising it; joining a list of moderate pieces is roughly twice as
|
|
44
|
+
* fast on an export-sized input. Large enough that the list stays short, small
|
|
45
|
+
* enough that no single piece is itself expensive to grow.
|
|
46
|
+
*/
|
|
47
|
+
const ENCODE_CHUNK_CHARS = 8192;
|
|
48
|
+
/** Bytes to base64, padded. */
|
|
49
|
+
export function bytesToBase64(bytes) {
|
|
50
|
+
const chunks = [];
|
|
51
|
+
let piece = '';
|
|
52
|
+
for (let i = 0; i < bytes.length; i += 3) {
|
|
53
|
+
const a = bytes[i];
|
|
54
|
+
const b = bytes[i + 1];
|
|
55
|
+
const c = bytes[i + 2];
|
|
56
|
+
piece +=
|
|
57
|
+
ALPHABET[a >> 2] +
|
|
58
|
+
ALPHABET[((a & 0x03) << 4) | ((b ?? 0) >> 4)] +
|
|
59
|
+
(b === undefined ? '=' : ALPHABET[((b & 0x0f) << 2) | ((c ?? 0) >> 6)]) +
|
|
60
|
+
(c === undefined ? '=' : ALPHABET[c & 0x3f]);
|
|
61
|
+
if (piece.length >= ENCODE_CHUNK_CHARS) {
|
|
62
|
+
chunks.push(piece);
|
|
63
|
+
piece = '';
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
if (piece)
|
|
67
|
+
chunks.push(piece);
|
|
68
|
+
return chunks.join('');
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* Base64 to bytes. Anything outside the alphabet — padding, newlines a data URI
|
|
72
|
+
* may carry — is skipped rather than rejected.
|
|
73
|
+
*
|
|
74
|
+
* Skipped *inline* rather than stripped first: the obvious
|
|
75
|
+
* `replace(/[^A-Za-z0-9+/]/g, '')` copies the whole multi-megabyte string
|
|
76
|
+
* before any decoding starts, which on a sample pack costs as much as the
|
|
77
|
+
* decode itself.
|
|
78
|
+
*/
|
|
79
|
+
export function base64ToBytes(base64) {
|
|
80
|
+
// An upper bound — exact when there is no padding or whitespace, over by at
|
|
81
|
+
// most a couple of bytes when there is. Trimmed at the end rather than
|
|
82
|
+
// counted in a first pass, which would be another walk over megabytes.
|
|
83
|
+
const out = new Uint8Array(((base64.length * 3) >> 2) + 3);
|
|
84
|
+
let outIndex = 0;
|
|
85
|
+
let accumulator = 0;
|
|
86
|
+
let bits = 0;
|
|
87
|
+
for (let i = 0; i < base64.length; i += 1) {
|
|
88
|
+
const sextet = SEXTET[base64.charCodeAt(i) & 0xff];
|
|
89
|
+
if (sextet < 0)
|
|
90
|
+
continue;
|
|
91
|
+
accumulator = (accumulator << 6) | sextet;
|
|
92
|
+
bits += 6;
|
|
93
|
+
if (bits >= 8) {
|
|
94
|
+
bits -= 8;
|
|
95
|
+
out[outIndex] = (accumulator >> bits) & 0xff;
|
|
96
|
+
outIndex += 1;
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
return out.buffer.slice(0, outIndex);
|
|
100
|
+
}
|
|
101
|
+
//# sourceMappingURL=base64.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"base64.js","sourceRoot":"","sources":["../../src/bytes/base64.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/bytes/index.ts"],"names":[],"mappings":"AAAA,cAAc,aAAa,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/bytes/index.ts"],"names":[],"mappings":"AAAA,cAAc,aAAa,CAAC"}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Score file-format models: the neutral shapes a MIDI file and a tracker
|
|
3
|
+
* module decode into.
|
|
4
|
+
*
|
|
5
|
+
* Not under `platform/`, deliberately. These carry notes rather than samples,
|
|
6
|
+
* so nothing about them is platform-bound — the codecs that produce them live
|
|
7
|
+
* in `@sudobility/music_codecs` and run identically on web, React Native and
|
|
8
|
+
* the server.
|
|
9
|
+
*/
|
|
10
|
+
export * from "./midi.js";
|
|
11
|
+
export * from "./mod.js";
|
|
12
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/formats/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AACH,cAAc,WAAW,CAAC;AAC1B,cAAc,UAAU,CAAC"}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Score file-format models: the neutral shapes a MIDI file and a tracker
|
|
3
|
+
* module decode into.
|
|
4
|
+
*
|
|
5
|
+
* Not under `platform/`, deliberately. These carry notes rather than samples,
|
|
6
|
+
* so nothing about them is platform-bound — the codecs that produce them live
|
|
7
|
+
* in `@sudobility/music_codecs` and run identically on web, React Native and
|
|
8
|
+
* the server.
|
|
9
|
+
*/
|
|
10
|
+
export * from "./midi.js";
|
|
11
|
+
export * from "./mod.js";
|
|
12
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/formats/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AACH,cAAc,WAAW,CAAC;AAC1B,cAAc,UAAU,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"midi.d.ts","sourceRoot":"","sources":["../../src/formats/midi.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,MAAM,MAAM,QAAQ,GAAG;IACrB,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;IACd,aAAa,EAAE,MAAM,CAAC;IACtB,QAAQ,EAAE,MAAM,CAAC;CAClB,CAAC;AACF,MAAM,MAAM,iBAAiB,GAAG;IAC9B,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,MAAM,CAAC;CACf,CAAC;AACF,MAAM,MAAM,cAAc,GAAG;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,GAAG,EAAE,MAAM,CAAA;CAAE,CAAC;AAC5D,MAAM,MAAM,sBAAsB,GAAG;IACnC,KAAK,EAAE,MAAM,CAAC;IACd,aAAa,EAAE,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CACjC,CAAC;AAEF,MAAM,MAAM,aAAa,GAAG;IAC1B,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;IAChB,UAAU,EAAE;QAAE,MAAM,EAAE,MAAM,CAAC;QAAC,IAAI,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;IAC9C,KAAK,EAAE,QAAQ,EAAE,CAAC;IAClB;;;;;OAKG;IACH,cAAc,EAAE,MAAM,CAAC,MAAM,EAAE,iBAAiB,EAAE,CAAC,CAAC;IACpD,aAAa,EAAE,MAAM,CAAC;IACtB,8EAA8E;IAC9E,eAAe,EAAE,MAAM,CAAC;CACzB,CAAC;AAEF,MAAM,MAAM,QAAQ,GAAG;IACrB,MAAM,EAAE;QACN,GAAG,EAAE,MAAM,CAAC;QACZ,IAAI,CAAC,EAAE,MAAM,CAAC;QACd,MAAM,EAAE,cAAc,EAAE,CAAC;QACzB,cAAc,EAAE,sBAAsB,EAAE,CAAC;KAC1C,CAAC;IACF,MAAM,EAAE,aAAa,EAAE,CAAC;IACxB,yCAAyC;IACzC,QAAQ,EAAE,MAAM,CAAC;CAClB,CAAC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"midi.js","sourceRoot":"","sources":["../../src/
|
|
1
|
+
{"version":3,"file":"midi.js","sourceRoot":"","sources":["../../src/formats/midi.ts"],"names":[],"mappings":""}
|
|
@@ -54,24 +54,4 @@ export type TrackerModule = {
|
|
|
54
54
|
/** `patterns[p][row][channel]`. Row count varies per pattern — MOD is always 64, IT allows 200. */
|
|
55
55
|
patterns: TrackerCell[][][];
|
|
56
56
|
};
|
|
57
|
-
/**
|
|
58
|
-
* Reading a tracker module.
|
|
59
|
-
*
|
|
60
|
-
* A capability for surface consistency — the app reaches every file format
|
|
61
|
-
* through `getAppServices().io` — even though nothing about parsing is
|
|
62
|
-
* platform-bound. All three implementations delegate to one shared module.
|
|
63
|
-
*/
|
|
64
|
-
export interface TrackerCodec {
|
|
65
|
-
/** Sniffs the format from the bytes and decodes it. Throws on anything that is not one, rather than returning a garbage module that looks imported. */
|
|
66
|
-
decode(bytes: ArrayBuffer): TrackerModule;
|
|
67
|
-
/**
|
|
68
|
-
* Writes a module out, in the format `module.format` names.
|
|
69
|
-
*
|
|
70
|
-
* No sample data is written: a module is a notes format, so the slots are
|
|
71
|
-
* named and empty and the file is silent until somebody fills them. Throws
|
|
72
|
-
* for a format export does not write (`dsm`, `mptm`) rather than guessing a
|
|
73
|
-
* near neighbour.
|
|
74
|
-
*/
|
|
75
|
-
encode(module: TrackerModule): ArrayBuffer;
|
|
76
|
-
}
|
|
77
57
|
//# sourceMappingURL=mod.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"mod.d.ts","sourceRoot":"","sources":["../../src/formats/mod.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAEH,MAAM,MAAM,aAAa,GAAG,KAAK,GAAG,KAAK,GAAG,IAAI,GAAG,IAAI,GAAG,KAAK,GAAG,MAAM,CAAC;AAEzE,2GAA2G;AAC3G,MAAM,MAAM,iBAAiB,GAAG;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,CAAC;AAEhE;;;;;;;;;;;;GAYG;AACH,MAAM,MAAM,WAAW,GAAG;IACxB,gEAAgE;IAChE,UAAU,EAAE,MAAM,CAAC;IACnB,0FAA0F;IAC1F,IAAI,EAAE,MAAM,GAAG,KAAK,GAAG,IAAI,CAAC;IAC5B,iDAAiD;IACjD,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,oDAAoD;IACpD,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,+CAA+C;IAC/C,YAAY,CAAC,EAAE,OAAO,CAAC;CACxB,CAAC;AAEF,MAAM,MAAM,aAAa,GAAG;IAC1B,MAAM,EAAE,aAAa,CAAC;IACtB,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,MAAM,CAAC;IACjB,WAAW,EAAE,iBAAiB,EAAE,CAAC;IACjC,2FAA2F;IAC3F,KAAK,EAAE,MAAM,EAAE,CAAC;IAChB,mGAAmG;IACnG,QAAQ,EAAE,WAAW,EAAE,EAAE,EAAE,CAAC;CAC7B,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"mod.js","sourceRoot":"","sources":["../../src/formats/mod.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG"}
|
package/dist/index.d.ts
CHANGED
|
@@ -28,6 +28,9 @@ export * from "./model/generation.js";
|
|
|
28
28
|
export * from "./model/api.js";
|
|
29
29
|
export * from "./model/position.js";
|
|
30
30
|
export * from "./model/selection-source.js";
|
|
31
|
+
export * from "./position/index.js";
|
|
32
|
+
export * from "./bytes/index.js";
|
|
33
|
+
export * from "./formats/index.js";
|
|
31
34
|
export * from "./platform/index.js";
|
|
32
35
|
export * from "./domain/pitch/pitch.js";
|
|
33
36
|
export * from "./domain/pitch/transpose.js";
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AASH,cAAc,kBAAkB,CAAC;AACjC,cAAc,oBAAoB,CAAC;AACnC,cAAc,uBAAuB,CAAC;AACtC,cAAc,gBAAgB,CAAC;AAC/B,cAAc,qBAAqB,CAAC;AACpC,cAAc,6BAA6B,CAAC;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AASH,cAAc,kBAAkB,CAAC;AACjC,cAAc,oBAAoB,CAAC;AACnC,cAAc,uBAAuB,CAAC;AACtC,cAAc,gBAAgB,CAAC;AAC/B,cAAc,qBAAqB,CAAC;AACpC,cAAc,6BAA6B,CAAC;AAQ5C,cAAc,qBAAqB,CAAC;AACpC,cAAc,kBAAkB,CAAC;AAEjC,cAAc,oBAAoB,CAAC;AAKnC,cAAc,qBAAqB,CAAC;AAMpC,cAAc,yBAAyB,CAAC;AACxC,cAAc,6BAA6B,CAAC;AAC5C,cAAc,kCAAkC,CAAC;AACjD,cAAc,mCAAmC,CAAC;AAClD,cAAc,2BAA2B,CAAC;AAC1C,cAAc,4BAA4B,CAAC;AAC3C,cAAc,uBAAuB,CAAC;AACtC,cAAc,2BAA2B,CAAC;AAC1C,cAAc,wBAAwB,CAAC;AACvC,cAAc,6BAA6B,CAAC;AAC5C,cAAc,4BAA4B,CAAC;AAC3C,cAAc,2BAA2B,CAAC;AAC1C,cAAc,4BAA4B,CAAC;AAC3C,cAAc,wBAAwB,CAAC;AACvC,cAAc,8BAA8B,CAAC;AAC7C,cAAc,+BAA+B,CAAC;AAC9C,cAAc,kCAAkC,CAAC;AAMjD,cAAc,4BAA4B,CAAC;AAC3C,cAAc,6BAA6B,CAAC;AAC5C,cAAc,+BAA+B,CAAC;AAC9C,cAAc,yCAAyC,CAAC;AACxD,cAAc,qCAAqC,CAAC;AACpD,cAAc,sCAAsC,CAAC;AACrD,cAAc,oCAAoC,CAAC;AACnD,cAAc,iCAAiC,CAAC;AAChD,cAAc,oCAAoC,CAAC;AACnD,cAAc,wCAAwC,CAAC;AACvD,cAAc,sCAAsC,CAAC;AACrD,cAAc,4BAA4B,CAAC;AAC3C,cAAc,gCAAgC,CAAC;AAC/C,cAAc,kCAAkC,CAAC;AACjD,cAAc,sCAAsC,CAAC;AACrD,cAAc,0CAA0C,CAAC;AACzD,cAAc,uCAAuC,CAAC;AACtD,cAAc,iCAAiC,CAAC;AAChD,cAAc,uCAAuC,CAAC;AAItD,cAAc,8BAA8B,CAAC;AAC7C,cAAc,2CAA2C,CAAC;AAC1D,cAAc,iCAAiC,CAAC;AAChD,cAAc,kCAAkC,CAAC;AACjD,cAAc,wCAAwC,CAAC;AACvD,cAAc,4CAA4C,CAAC;AAC3D,cAAc,0CAA0C,CAAC;AACzD,cAAc,mCAAmC,CAAC;AAClD,cAAc,sCAAsC,CAAC;AACrD,cAAc,uCAAuC,CAAC;AACtD,cAAc,gCAAgC,CAAC;AAC/C,cAAc,+BAA+B,CAAC;AAC9C,cAAc,kCAAkC,CAAC;AACjD,cAAc,6BAA6B,CAAC;AAC5C,cAAc,4BAA4B,CAAC;AAC3C,cAAc,kCAAkC,CAAC;AACjD,cAAc,gCAAgC,CAAC;AAC/C,cAAc,iCAAiC,CAAC;AAChD,cAAc,2BAA2B,CAAC;AAC1C,cAAc,0BAA0B,CAAC;AACzC,cAAc,wCAAwC,CAAC;AACvD,cAAc,mCAAmC,CAAC;AAClD,cAAc,gCAAgC,CAAC;AAC/C,cAAc,iCAAiC,CAAC;AAChD,cAAc,iCAAiC,CAAC;AAChD,cAAc,oCAAoC,CAAC;AACnD,cAAc,yCAAyC,CAAC;AACxD,cAAc,qCAAqC,CAAC;AACpD,cAAc,qCAAqC,CAAC;AACpD,cAAc,8BAA8B,CAAC;AAC7C,cAAc,wBAAwB,CAAC;AACvC,cAAc,0BAA0B,CAAC"}
|
package/dist/index.js
CHANGED
|
@@ -36,7 +36,16 @@ export * from "./model/api.js";
|
|
|
36
36
|
export * from "./model/position.js";
|
|
37
37
|
export * from "./model/selection-source.js";
|
|
38
38
|
// ---------------------------------------------------------------------------
|
|
39
|
-
// 10.
|
|
39
|
+
// 10. Score file-format models (codecs live in @sudobility/music_codecs)
|
|
40
|
+
// ---------------------------------------------------------------------------
|
|
41
|
+
// ---------------------------------------------------------------------------
|
|
42
|
+
// Playhead — the one stateful service here; see position/music-position.ts
|
|
43
|
+
// ---------------------------------------------------------------------------
|
|
44
|
+
export * from "./position/index.js";
|
|
45
|
+
export * from "./bytes/index.js";
|
|
46
|
+
export * from "./formats/index.js";
|
|
47
|
+
// ---------------------------------------------------------------------------
|
|
48
|
+
// 11. Platform interfaces (implementations live in @sudobility/music_io)
|
|
40
49
|
// ---------------------------------------------------------------------------
|
|
41
50
|
export * from "./platform/index.js";
|
|
42
51
|
// ---------------------------------------------------------------------------
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AAEH,8EAA8E;AAC9E,+CAA+C;AAC/C,EAAE;AACF,8EAA8E;AAC9E,sEAAsE;AACtE,iEAAiE;AACjE,8EAA8E;AAC9E,cAAc,kBAAkB,CAAC;AACjC,cAAc,oBAAoB,CAAC;AACnC,cAAc,uBAAuB,CAAC;AACtC,cAAc,gBAAgB,CAAC;AAC/B,cAAc,qBAAqB,CAAC;AACpC,cAAc,6BAA6B,CAAC;AAE5C,8EAA8E;AAC9E,yEAAyE;AACzE,8EAA8E;AAC9E,cAAc,qBAAqB,CAAC;AAEpC,8EAA8E;AAC9E,8DAA8D;AAC9D,8EAA8E;AAE9E,cAAc,yBAAyB,CAAC;AACxC,cAAc,6BAA6B,CAAC;AAC5C,cAAc,kCAAkC,CAAC;AACjD,cAAc,mCAAmC,CAAC;AAClD,cAAc,2BAA2B,CAAC;AAC1C,cAAc,4BAA4B,CAAC;AAC3C,cAAc,uBAAuB,CAAC;AACtC,cAAc,2BAA2B,CAAC;AAC1C,cAAc,wBAAwB,CAAC;AACvC,cAAc,6BAA6B,CAAC;AAC5C,cAAc,4BAA4B,CAAC;AAC3C,cAAc,2BAA2B,CAAC;AAC1C,cAAc,4BAA4B,CAAC;AAC3C,cAAc,wBAAwB,CAAC;AACvC,cAAc,8BAA8B,CAAC;AAC7C,cAAc,+BAA+B,CAAC;AAC9C,cAAc,kCAAkC,CAAC;AAEjD,8EAA8E;AAC9E,iFAAiF;AACjF,8EAA8E;AAE9E,cAAc,4BAA4B,CAAC;AAC3C,cAAc,6BAA6B,CAAC;AAC5C,cAAc,+BAA+B,CAAC;AAC9C,cAAc,yCAAyC,CAAC;AACxD,cAAc,qCAAqC,CAAC;AACpD,cAAc,sCAAsC,CAAC;AACrD,cAAc,oCAAoC,CAAC;AACnD,cAAc,iCAAiC,CAAC;AAChD,cAAc,oCAAoC,CAAC;AACnD,cAAc,wCAAwC,CAAC;AACvD,cAAc,sCAAsC,CAAC;AACrD,cAAc,4BAA4B,CAAC;AAC3C,cAAc,gCAAgC,CAAC;AAC/C,cAAc,kCAAkC,CAAC;AACjD,cAAc,sCAAsC,CAAC;AACrD,cAAc,0CAA0C,CAAC;AACzD,cAAc,uCAAuC,CAAC;AACtD,cAAc,iCAAiC,CAAC;AAChD,cAAc,uCAAuC,CAAC;AAEtD,yEAAyE;AACzE,yCAAyC;AACzC,cAAc,8BAA8B,CAAC;AAC7C,cAAc,2CAA2C,CAAC;AAC1D,cAAc,iCAAiC,CAAC;AAChD,cAAc,kCAAkC,CAAC;AACjD,cAAc,wCAAwC,CAAC;AACvD,cAAc,4CAA4C,CAAC;AAC3D,cAAc,0CAA0C,CAAC;AACzD,cAAc,mCAAmC,CAAC;AAClD,cAAc,sCAAsC,CAAC;AACrD,cAAc,uCAAuC,CAAC;AACtD,cAAc,gCAAgC,CAAC;AAC/C,cAAc,+BAA+B,CAAC;AAC9C,cAAc,kCAAkC,CAAC;AACjD,cAAc,6BAA6B,CAAC;AAC5C,cAAc,4BAA4B,CAAC;AAC3C,cAAc,kCAAkC,CAAC;AACjD,cAAc,gCAAgC,CAAC;AAC/C,cAAc,iCAAiC,CAAC;AAChD,cAAc,2BAA2B,CAAC;AAC1C,cAAc,0BAA0B,CAAC;AACzC,cAAc,wCAAwC,CAAC;AACvD,cAAc,mCAAmC,CAAC;AAClD,cAAc,gCAAgC,CAAC;AAC/C,cAAc,iCAAiC,CAAC;AAChD,cAAc,iCAAiC,CAAC;AAChD,cAAc,oCAAoC,CAAC;AACnD,cAAc,yCAAyC,CAAC;AACxD,cAAc,qCAAqC,CAAC;AACpD,cAAc,qCAAqC,CAAC;AACpD,cAAc,8BAA8B,CAAC;AAC7C,cAAc,wBAAwB,CAAC;AACvC,cAAc,0BAA0B,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
|
package/dist/platform/index.d.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/platform/index.ts"],"names":[],"mappings":"AAAA,cAAc,YAAY,CAAC;AAC3B;;;;GAIG;AACH,cAAc,eAAe,CAAC;AAC9B,cAAc,UAAU,CAAC;AACzB,cAAc,WAAW,CAAC;AAC1B,cAAc,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/platform/index.ts"],"names":[],"mappings":"AAAA,cAAc,YAAY,CAAC;AAC3B;;;;GAIG;AACH,cAAc,eAAe,CAAC;AAC9B,cAAc,UAAU,CAAC;AACzB,cAAc,WAAW,CAAC;AAC1B,cAAc,iBAAiB,CAAC"}
|
package/dist/platform/index.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/platform/index.ts"],"names":[],"mappings":"AAAA,cAAc,YAAY,CAAC;AAC3B;;;;GAIG;AACH,cAAc,eAAe,CAAC;AAC9B,cAAc,UAAU,CAAC;AACzB,cAAc,WAAW,CAAC;AAC1B,cAAc,
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/platform/index.ts"],"names":[],"mappings":"AAAA,cAAc,YAAY,CAAC;AAC3B;;;;GAIG;AACH,cAAc,eAAe,CAAC;AAC9B,cAAc,UAAU,CAAC;AACzB,cAAc,WAAW,CAAC;AAC1B,cAAc,iBAAiB,CAAC"}
|
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Playing notes in from a MIDI keyboard.
|
|
3
3
|
*
|
|
4
|
-
* Separate from
|
|
5
|
-
*
|
|
4
|
+
* Separate from the `.mid` file codec in `@sudobility/music_codecs`: reading a
|
|
5
|
+
* file is byte arithmetic that runs anywhere, while this is a live input
|
|
6
|
+
* device that only a platform can offer. The two have nothing in common but
|
|
7
|
+
* the word MIDI, which is why only this one is a capability.
|
|
6
8
|
*
|
|
7
9
|
* A capability rather than a direct Web MIDI call, for the same reason
|
|
8
10
|
* playback is one: the browser has `navigator.requestMIDIAccess`, React Native
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"midi-input.d.ts","sourceRoot":"","sources":["../../src/platform/midi-input.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"midi-input.d.ts","sourceRoot":"","sources":["../../src/platform/midi-input.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAEH,+BAA+B;AAC/B,MAAM,MAAM,eAAe,GAAG;IAC5B,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;CACd,CAAC;AAEF;;;;;;GAMG;AACH,MAAM,MAAM,cAAc,GACtB;IAAE,IAAI,EAAE,IAAI,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAA;CAAE,GAC9C;IAAE,IAAI,EAAE,KAAK,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,CAAC;AAElC,MAAM,MAAM,gBAAgB,GAAG,CAAC,KAAK,EAAE,cAAc,KAAK,IAAI,CAAC;AAE/D,MAAM,MAAM,SAAS,GAAG;IACtB,yDAAyD;IACzD,WAAW,IAAI,OAAO,CAAC;IAEvB;;;;;;OAMG;IACH,WAAW,IAAI,OAAO,CAAC,eAAe,EAAE,CAAC,CAAC;IAE1C;;;;;;OAMG;IACH,SAAS,CAAC,OAAO,EAAE,gBAAgB,EAAE,QAAQ,CAAC,EAAE,MAAM,GAAG,MAAM,IAAI,CAAC;CACrE,CAAC"}
|
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Playing notes in from a MIDI keyboard.
|
|
3
3
|
*
|
|
4
|
-
* Separate from
|
|
5
|
-
*
|
|
4
|
+
* Separate from the `.mid` file codec in `@sudobility/music_codecs`: reading a
|
|
5
|
+
* file is byte arithmetic that runs anywhere, while this is a live input
|
|
6
|
+
* device that only a platform can offer. The two have nothing in common but
|
|
7
|
+
* the word MIDI, which is why only this one is a capability.
|
|
6
8
|
*
|
|
7
9
|
* A capability rather than a direct Web MIDI call, for the same reason
|
|
8
10
|
* playback is one: the browser has `navigator.requestMIDIAccess`, React Native
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"midi-input.js","sourceRoot":"","sources":["../../src/platform/midi-input.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"midi-input.js","sourceRoot":"","sources":["../../src/platform/midi-input.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG"}
|
|
@@ -7,7 +7,6 @@
|
|
|
7
7
|
* things that import an audio library; everything that depends on playback
|
|
8
8
|
* depends on this file instead, so the engine stays swappable and mockable.
|
|
9
9
|
*/
|
|
10
|
-
import type { ScoreRange } from "../index.js";
|
|
11
10
|
import type { RenderTrack } from "./audio.js";
|
|
12
11
|
export type TransportPlaybackState = "stopped" | "playing" | "paused";
|
|
13
12
|
/**
|
|
@@ -24,29 +23,6 @@ export type SoundingNote = {
|
|
|
24
23
|
/** Its sounding MIDI pitch, so a keyboard can light a key without resolving the event. */
|
|
25
24
|
midi: number;
|
|
26
25
|
};
|
|
27
|
-
export type PlaybackObserver = {
|
|
28
|
-
onPositionTick(tick: number): void;
|
|
29
|
-
/**
|
|
30
|
-
* The notes currently sounding, whenever that set changes.
|
|
31
|
-
*
|
|
32
|
-
* Resolved rather than bare ids. The scheduler already knows each note's
|
|
33
|
-
* track and pitch — it scheduled them — and emitting only the id forced every
|
|
34
|
-
* consumer to search the whole score to get it back: `playingPitchesForTrack`
|
|
35
|
-
* was an O(score) scan per sounding note, twenty times a second.
|
|
36
|
-
*/
|
|
37
|
-
onActiveNotes(notes: SoundingNote[]): void;
|
|
38
|
-
onStateChange(state: TransportPlaybackState): void;
|
|
39
|
-
/**
|
|
40
|
-
* How far along the engine is in becoming able to make a sound.
|
|
41
|
-
*
|
|
42
|
-
* Optional because not every engine has anything to load. The soundfont
|
|
43
|
-
* engine does: tens of megabytes to fetch and several seconds for the synth
|
|
44
|
-
* to digest, all on the first press of Play. Without this the transport
|
|
45
|
-
* simply looks broken for that whole time — which is exactly how it read
|
|
46
|
-
* before there was anywhere to report it.
|
|
47
|
-
*/
|
|
48
|
-
onLoadStateChange?(state: PlaybackLoadState): void;
|
|
49
|
-
};
|
|
50
26
|
/**
|
|
51
27
|
* The engine's readiness to play, for a progress indicator.
|
|
52
28
|
*
|
|
@@ -77,20 +53,6 @@ export type PlaybackTrack = RenderTrack & {
|
|
|
77
53
|
muted: boolean;
|
|
78
54
|
solo: boolean;
|
|
79
55
|
};
|
|
80
|
-
/**
|
|
81
|
-
* A voice to audition, resolved from the GM tables **by the caller**.
|
|
82
|
-
*
|
|
83
|
-
* The engine cannot resolve it: a percussion voice's program addresses a drum
|
|
84
|
-
* kit, and only the GM tables know which kit an arbitrary address falls in.
|
|
85
|
-
* Resolving here is what lets the platform layer keep no catalogue of its own.
|
|
86
|
-
*/
|
|
87
|
-
export type AuditionVoice = {
|
|
88
|
-
/** The kit's program on a percussion voice, the instrument's own otherwise. */
|
|
89
|
-
program: number;
|
|
90
|
-
/** The GM catalogue name for `program`. */
|
|
91
|
-
name: string;
|
|
92
|
-
isPercussion: boolean;
|
|
93
|
-
};
|
|
94
56
|
/** One playback-ready note, in score ticks, carrying the ids playback reports back. */
|
|
95
57
|
export type PlaybackNote = {
|
|
96
58
|
tick: number;
|
|
@@ -135,6 +97,15 @@ export type PerformanceTimeline = {
|
|
|
135
97
|
segments: readonly TimelineSegment[];
|
|
136
98
|
durationTicks: number;
|
|
137
99
|
};
|
|
100
|
+
/**
|
|
101
|
+
* The engine contract — `PlaybackEngine`, `PlaybackObserver` and
|
|
102
|
+
* `AuditionVoice` — lives in `@sudobility/music_player`, which is the only
|
|
103
|
+
* thing that implements it.
|
|
104
|
+
*
|
|
105
|
+
* The *plan* stays here, and must: `PlaybackPlan` composes `PerformanceTimeline`,
|
|
106
|
+
* which `performanceTimeline()` in `domain/score/` produces. Moving it would
|
|
107
|
+
* make this package import from music_player.
|
|
108
|
+
*/
|
|
138
109
|
export type PlaybackPlan = {
|
|
139
110
|
tracks: readonly PlaybackTrack[];
|
|
140
111
|
notes: readonly PlaybackNote[];
|
|
@@ -156,56 +127,4 @@ export type PlaybackLoadState = {
|
|
|
156
127
|
status: "failed";
|
|
157
128
|
message: string;
|
|
158
129
|
};
|
|
159
|
-
export interface PlaybackEngine {
|
|
160
|
-
initialize(): Promise<void>;
|
|
161
|
-
/** Adopts a plan. The engine is handed music, never a score. */
|
|
162
|
-
load(plan: PlaybackPlan): Promise<void>;
|
|
163
|
-
play(fromTick?: number): Promise<void>;
|
|
164
|
-
pause(): void;
|
|
165
|
-
stop(): void;
|
|
166
|
-
seek(tick: number): void;
|
|
167
|
-
setTempoMultiplier(multiplier: number): void;
|
|
168
|
-
setLoop(range: ScoreRange | null): void;
|
|
169
|
-
setTrackMute(trackId: string, muted: boolean): void;
|
|
170
|
-
setTrackSolo(trackId: string, solo: boolean): void;
|
|
171
|
-
/**
|
|
172
|
-
* Re-reads every track's volume, pan, mute and solo and pushes them,
|
|
173
|
-
* touching nothing that is scheduled.
|
|
174
|
-
*
|
|
175
|
-
* The mixing counterpart to `load`. Mute and solo had setters; volume and pan
|
|
176
|
-
* did not, and a track's volume was only ever read at load time — so once a
|
|
177
|
-
* mix change stopped reloading the score (the playback edit lock, see
|
|
178
|
-
* `music_lib`'s `score-slice`), moving a fader during playback moved the
|
|
179
|
-
* fader and not the sound.
|
|
180
|
-
*
|
|
181
|
-
* Takes only the tracks, so changing a gain does not rebuild every note —
|
|
182
|
-
* which is the whole reason this is separate from `load`. Idempotent, and
|
|
183
|
-
* takes them all rather than one property: the caller says "the mix changed,
|
|
184
|
-
* here it is" and does not work out which property it was.
|
|
185
|
-
*/
|
|
186
|
-
applyMix(tracks: readonly PlaybackTrack[]): void;
|
|
187
|
-
/** Toggles the metronome click. */
|
|
188
|
-
setMetronome(enabled: boolean): void;
|
|
189
|
-
/** Sets overall output level, 0-1 linear gain. */
|
|
190
|
-
setMasterVolume(volume: number): void;
|
|
191
|
-
/**
|
|
192
|
-
* Sounds `midi` immediately on `program`'s voice and holds it, independent of
|
|
193
|
-
* the transport — for auditioning a key while editing.
|
|
194
|
-
*
|
|
195
|
-
* `voice.isPercussion` mirrors what playback does with a percussion-clef
|
|
196
|
-
* track: without it, tapping a note on a drum track auditioned a pitched
|
|
197
|
-
* instrument while the same note played back as a drum.
|
|
198
|
-
*
|
|
199
|
-
* Separate from `play` because the two answer different questions: `play`
|
|
200
|
-
* renders the written score along a timeline, this makes a sound *now*, for
|
|
201
|
-
* as long as the caller holds it, whether or not a score is even loaded.
|
|
202
|
-
* Implementations must not disturb transport state.
|
|
203
|
-
*/
|
|
204
|
-
noteOn(midi: number, voice: AuditionVoice): void;
|
|
205
|
-
/** Releases a pitch started by `noteOn`. Silent no-op if it is not sounding. */
|
|
206
|
-
noteOff(midi: number): void;
|
|
207
|
-
/** Registers (or, with `null`, clears) the single observer receiving position/active-note/state updates. */
|
|
208
|
-
setObserver(observer: PlaybackObserver | null): void;
|
|
209
|
-
dispose(): void;
|
|
210
|
-
}
|
|
211
130
|
//# sourceMappingURL=playback.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"playback.d.ts","sourceRoot":"","sources":["../../src/platform/playback.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AACH,OAAO,KAAK,EAAE,
|
|
1
|
+
{"version":3,"file":"playback.d.ts","sourceRoot":"","sources":["../../src/platform/playback.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AACH,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAE9C,MAAM,MAAM,sBAAsB,GAAG,SAAS,GAAG,SAAS,GAAG,QAAQ,CAAC;AAEtE;;;;;GAKG;AACH,MAAM,MAAM,YAAY,GAAG;IACzB,0EAA0E;IAC1E,MAAM,EAAE,MAAM,CAAC;IACf,kFAAkF;IAClF,OAAO,EAAE,MAAM,CAAC;IAChB,0FAA0F;IAC1F,IAAI,EAAE,MAAM,CAAC;CACd,CAAC;AAGF;;;;;;GAMG;AACH;;;;;;;GAOG;AACH,MAAM,WAAW,eAAe;IAC9B,cAAc,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAAC;IACrC,cAAc,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAAC;CACzC;AAED;;;;;;GAMG;AACH,MAAM,MAAM,aAAa,GAAG,WAAW,GAAG;IACxC,KAAK,EAAE,OAAO,CAAC;IACf,IAAI,EAAE,OAAO,CAAC;CACf,CAAC;AAGF,uFAAuF;AACvF,MAAM,MAAM,YAAY,GAAG;IACzB,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,MAAM,CAAC;IACjB,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,EAAE,MAAM,CAAC;CAChB,CAAC;AAEF,iEAAiE;AACjE,MAAM,MAAM,cAAc,GAAG;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,OAAO,CAAA;CAAE,CAAC;AAE/D;;;;;;;GAOG;AACH,yEAAyE;AACzE,MAAM,MAAM,eAAe,GAAG;IAC5B,eAAe,EAAE,MAAM,CAAC;IACxB,UAAU,EAAE,MAAM,CAAC;IACnB,aAAa,EAAE,MAAM,CAAC;CACvB,CAAC;AAEF;;;;;;;;;;;GAWG;AACH,MAAM,MAAM,mBAAmB,GAAG;IAChC,QAAQ,EAAE,SAAS,eAAe,EAAE,CAAC;IACrC,aAAa,EAAE,MAAM,CAAC;CACvB,CAAC;AAEF;;;;;;;;GAQG;AACH,MAAM,MAAM,YAAY,GAAG;IACzB,MAAM,EAAE,SAAS,aAAa,EAAE,CAAC;IACjC,KAAK,EAAE,SAAS,YAAY,EAAE,CAAC;IAC/B,MAAM,EAAE,SAAS,cAAc,EAAE,CAAC;IAClC,KAAK,EAAE,eAAe,CAAC;IACvB,mEAAmE;IACnE,QAAQ,EAAE,mBAAmB,CAAC;IAC9B,2DAA2D;IAC3D,aAAa,EAAE,MAAM,CAAC;CACvB,CAAC;AAEF,MAAM,MAAM,iBAAiB,GACzB;IAAE,MAAM,EAAE,MAAM,CAAA;CAAE,GAClB;IAAE,MAAM,EAAE,SAAS,CAAC;IAAC,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAA;CAAE,GAC9C;IAAE,MAAM,EAAE,OAAO,CAAA;CAAE,GACnB;IAAE,MAAM,EAAE,QAAQ,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/position/index.ts"],"names":[],"mappings":"AAAA,cAAc,qBAAqB,CAAC;AACpC,cAAc,gBAAgB,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/position/index.ts"],"names":[],"mappings":"AAAA,cAAc,qBAAqB,CAAC;AACpC,cAAc,gBAAgB,CAAC"}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The one place that knows where playback is.
|
|
3
|
+
*
|
|
4
|
+
* **The first stateful singleton service in music_types, and deliberately so.**
|
|
5
|
+
* This package is otherwise model and primitives. The playhead earns the
|
|
6
|
+
* exception because it has exactly one writer — `@sudobility/music_player` —
|
|
7
|
+
* and readers in every other package: the caret, the note highlighting and the
|
|
8
|
+
* piano keyboard. Any other home creates a dependency edge that exists only to
|
|
9
|
+
* reach it. It still obeys the four rules this package keeps: it works on both
|
|
10
|
+
* the frontend and the backend, adds no dependency, contains no hooks and
|
|
11
|
+
* contains no async code.
|
|
12
|
+
*
|
|
13
|
+
* Implements `IMusicPosition` — see that interface for why this exists at all.
|
|
14
|
+
* The short version: the caret, the note highlighting and the piano keyboard
|
|
15
|
+
* used to derive the playhead three different ways and drifted apart under
|
|
16
|
+
* load. Now they read one number.
|
|
17
|
+
*
|
|
18
|
+
* **The smoothing lives here, not in the caret.** That is the load-bearing
|
|
19
|
+
* decision. The engine reports position about thirty times a second, and those
|
|
20
|
+
* reports arrive in clumps because they come off the audio scheduler's
|
|
21
|
+
* lookahead loop rather than a wall clock — driving anything straight off them
|
|
22
|
+
* leaves it stalled on most frames and jumping when it does move. So the
|
|
23
|
+
* playhead is dead-reckoned forward from the last report, and because that
|
|
24
|
+
* happens *inside the single source of truth*, every consumer that asks gets
|
|
25
|
+
* the same smoothed answer at the same instant. When the caret did its own
|
|
26
|
+
* dead-reckoning, it was smoothing a number nobody else was smoothing.
|
|
27
|
+
*
|
|
28
|
+
* The anchor is the engine's own clock reading, not `performance.now()` at the
|
|
29
|
+
* moment the report was handled. Anchoring on receipt folds event-loop latency
|
|
30
|
+
* into the playhead: under load the anchor is stamped late and everything
|
|
31
|
+
* derived from it lags the audio, which is exactly the drift this replaces.
|
|
32
|
+
*/
|
|
33
|
+
import type { IMusicPositionSource, UnsubscribePosition } from '../model/position.js';
|
|
34
|
+
export declare class MusicPosition implements IMusicPositionSource {
|
|
35
|
+
private readonly listeners;
|
|
36
|
+
/** The last authoritative report, and the clock reading it belongs to. */
|
|
37
|
+
private anchorTick;
|
|
38
|
+
private anchorSeconds;
|
|
39
|
+
private playing;
|
|
40
|
+
private ticksPerSecond;
|
|
41
|
+
get isPlaying(): boolean;
|
|
42
|
+
/**
|
|
43
|
+
* The playhead now.
|
|
44
|
+
*
|
|
45
|
+
* While stopped this is exactly the last reported tick — a paused caret must
|
|
46
|
+
* not creep. While playing it is the last report projected forward by the
|
|
47
|
+
* time since it was taken, which is what makes motion even however unevenly
|
|
48
|
+
* the reports land.
|
|
49
|
+
*/
|
|
50
|
+
get tick(): number;
|
|
51
|
+
report(tick: number, atSeconds?: number): void;
|
|
52
|
+
/**
|
|
53
|
+
* Update the tick rate without disturbing the anchor.
|
|
54
|
+
*
|
|
55
|
+
* Deliberately not folded into `report`: re-anchoring is `report`'s job and
|
|
56
|
+
* a rate change must not do it, or a tempo change mid-bar would restart the
|
|
57
|
+
* projection from wherever the last report left off.
|
|
58
|
+
*/
|
|
59
|
+
setRate(ticksPerSecond: number): void;
|
|
60
|
+
setPlaying(playing: boolean, ticksPerSecond?: number): void;
|
|
61
|
+
subscribe(listener: (tick: number) => void): UnsubscribePosition;
|
|
62
|
+
}
|
|
63
|
+
//# sourceMappingURL=music-position.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"music-position.d.ts","sourceRoot":"","sources":["../../src/position/music-position.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+BG;AACH,OAAO,KAAK,EACV,oBAAoB,EACpB,mBAAmB,EACpB,MAAM,sBAAsB,CAAC;AAS9B,qBAAa,aAAc,YAAW,oBAAoB;IACxD,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAqC;IAE/D,0EAA0E;IAC1E,OAAO,CAAC,UAAU,CAAK;IACvB,OAAO,CAAC,aAAa,CAAgB;IAErC,OAAO,CAAC,OAAO,CAAS;IACxB,OAAO,CAAC,cAAc,CAAK;IAE3B,IAAI,SAAS,IAAI,OAAO,CAEvB;IAED;;;;;;;OAOG;IACH,IAAI,IAAI,IAAI,MAAM,CASjB;IAED,MAAM,CAAC,IAAI,EAAE,MAAM,EAAE,SAAS,CAAC,EAAE,MAAM,GAAG,IAAI;IAM9C;;;;;;OAMG;IACH,OAAO,CAAC,cAAc,EAAE,MAAM,GAAG,IAAI;IAIrC,UAAU,CAAC,OAAO,EAAE,OAAO,EAAE,cAAc,CAAC,EAAE,MAAM,GAAG,IAAI;IAY3D,SAAS,CAAC,QAAQ,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,GAAG,mBAAmB;CAIjE"}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
/** Wall-clock seconds, monotonic where the platform offers it. */
|
|
2
|
+
function nowSeconds() {
|
|
3
|
+
return typeof performance !== 'undefined'
|
|
4
|
+
? performance.now() / 1000
|
|
5
|
+
: Date.now() / 1000;
|
|
6
|
+
}
|
|
7
|
+
export class MusicPosition {
|
|
8
|
+
constructor() {
|
|
9
|
+
this.listeners = new Set();
|
|
10
|
+
/** The last authoritative report, and the clock reading it belongs to. */
|
|
11
|
+
this.anchorTick = 0;
|
|
12
|
+
this.anchorSeconds = nowSeconds();
|
|
13
|
+
this.playing = false;
|
|
14
|
+
this.ticksPerSecond = 0;
|
|
15
|
+
}
|
|
16
|
+
get isPlaying() {
|
|
17
|
+
return this.playing;
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* The playhead now.
|
|
21
|
+
*
|
|
22
|
+
* While stopped this is exactly the last reported tick — a paused caret must
|
|
23
|
+
* not creep. While playing it is the last report projected forward by the
|
|
24
|
+
* time since it was taken, which is what makes motion even however unevenly
|
|
25
|
+
* the reports land.
|
|
26
|
+
*/
|
|
27
|
+
get tick() {
|
|
28
|
+
if (!this.playing || this.ticksPerSecond <= 0)
|
|
29
|
+
return this.anchorTick;
|
|
30
|
+
const elapsed = nowSeconds() - this.anchorSeconds;
|
|
31
|
+
// Never backwards: a report that arrives late would otherwise pull the
|
|
32
|
+
// playhead back a few ticks, which reads as a stutter.
|
|
33
|
+
return Math.max(this.anchorTick, this.anchorTick + elapsed * this.ticksPerSecond);
|
|
34
|
+
}
|
|
35
|
+
report(tick, atSeconds) {
|
|
36
|
+
this.anchorTick = tick;
|
|
37
|
+
this.anchorSeconds = atSeconds ?? nowSeconds();
|
|
38
|
+
for (const listener of this.listeners)
|
|
39
|
+
listener(tick);
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Update the tick rate without disturbing the anchor.
|
|
43
|
+
*
|
|
44
|
+
* Deliberately not folded into `report`: re-anchoring is `report`'s job and
|
|
45
|
+
* a rate change must not do it, or a tempo change mid-bar would restart the
|
|
46
|
+
* projection from wherever the last report left off.
|
|
47
|
+
*/
|
|
48
|
+
setRate(ticksPerSecond) {
|
|
49
|
+
this.ticksPerSecond = Math.max(0, ticksPerSecond);
|
|
50
|
+
}
|
|
51
|
+
setPlaying(playing, ticksPerSecond) {
|
|
52
|
+
// Re-anchor on the transition, or the first `tick` read after resuming
|
|
53
|
+
// would project forward from however long the transport sat paused.
|
|
54
|
+
if (playing !== this.playing) {
|
|
55
|
+
this.anchorTick = this.tick;
|
|
56
|
+
this.anchorSeconds = nowSeconds();
|
|
57
|
+
}
|
|
58
|
+
this.playing = playing;
|
|
59
|
+
if (ticksPerSecond !== undefined)
|
|
60
|
+
this.ticksPerSecond = ticksPerSecond;
|
|
61
|
+
for (const listener of this.listeners)
|
|
62
|
+
listener(this.anchorTick);
|
|
63
|
+
}
|
|
64
|
+
subscribe(listener) {
|
|
65
|
+
this.listeners.add(listener);
|
|
66
|
+
return () => this.listeners.delete(listener);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
//# sourceMappingURL=music-position.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"music-position.js","sourceRoot":"","sources":["../../src/position/music-position.ts"],"names":[],"mappings":"AAqCA,kEAAkE;AAClE,SAAS,UAAU;IACjB,OAAO,OAAO,WAAW,KAAK,WAAW;QACvC,CAAC,CAAC,WAAW,CAAC,GAAG,EAAE,GAAG,IAAI;QAC1B,CAAC,CAAC,IAAI,CAAC,GAAG,EAAE,GAAG,IAAI,CAAC;AACxB,CAAC;AAED,MAAM,OAAO,aAAa;IAA1B;QACmB,cAAS,GAAG,IAAI,GAAG,EAA0B,CAAC;QAE/D,0EAA0E;QAClE,eAAU,GAAG,CAAC,CAAC;QACf,kBAAa,GAAG,UAAU,EAAE,CAAC;QAE7B,YAAO,GAAG,KAAK,CAAC;QAChB,mBAAc,GAAG,CAAC,CAAC;IA0D7B,CAAC;IAxDC,IAAI,SAAS;QACX,OAAO,IAAI,CAAC,OAAO,CAAC;IACtB,CAAC;IAED;;;;;;;OAOG;IACH,IAAI,IAAI;QACN,IAAI,CAAC,IAAI,CAAC,OAAO,IAAI,IAAI,CAAC,cAAc,IAAI,CAAC;YAAE,OAAO,IAAI,CAAC,UAAU,CAAC;QACtE,MAAM,OAAO,GAAG,UAAU,EAAE,GAAG,IAAI,CAAC,aAAa,CAAC;QAClD,uEAAuE;QACvE,uDAAuD;QACvD,OAAO,IAAI,CAAC,GAAG,CACb,IAAI,CAAC,UAAU,EACf,IAAI,CAAC,UAAU,GAAG,OAAO,GAAG,IAAI,CAAC,cAAc,CAChD,CAAC;IACJ,CAAC;IAED,MAAM,CAAC,IAAY,EAAE,SAAkB;QACrC,IAAI,CAAC,UAAU,GAAG,IAAI,CAAC;QACvB,IAAI,CAAC,aAAa,GAAG,SAAS,IAAI,UAAU,EAAE,CAAC;QAC/C,KAAK,MAAM,QAAQ,IAAI,IAAI,CAAC,SAAS;YAAE,QAAQ,CAAC,IAAI,CAAC,CAAC;IACxD,CAAC;IAED;;;;;;OAMG;IACH,OAAO,CAAC,cAAsB;QAC5B,IAAI,CAAC,cAAc,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,cAAc,CAAC,CAAC;IACpD,CAAC;IAED,UAAU,CAAC,OAAgB,EAAE,cAAuB;QAClD,uEAAuE;QACvE,oEAAoE;QACpE,IAAI,OAAO,KAAK,IAAI,CAAC,OAAO,EAAE,CAAC;YAC7B,IAAI,CAAC,UAAU,GAAG,IAAI,CAAC,IAAI,CAAC;YAC5B,IAAI,CAAC,aAAa,GAAG,UAAU,EAAE,CAAC;QACpC,CAAC;QACD,IAAI,CAAC,OAAO,GAAG,OAAO,CAAC;QACvB,IAAI,cAAc,KAAK,SAAS;YAAE,IAAI,CAAC,cAAc,GAAG,cAAc,CAAC;QACvE,KAAK,MAAM,QAAQ,IAAI,IAAI,CAAC,SAAS;YAAE,QAAQ,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC;IACnE,CAAC;IAED,SAAS,CAAC,QAAgC;QACxC,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC;QAC7B,OAAO,GAAG,EAAE,CAAC,IAAI,CAAC,SAAS,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC;IAC/C,CAAC;CACF"}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The music-position singleton.
|
|
3
|
+
*
|
|
4
|
+
* Same shape as the rest of the family's services (see `@sudobility/di`'s
|
|
5
|
+
* storage and network singletons): initialise once at start-up, read it
|
|
6
|
+
* everywhere, reset it in tests.
|
|
7
|
+
*
|
|
8
|
+
* A singleton because the playhead *is* global to a running transport — there
|
|
9
|
+
* is one piece of music playing, and the whole point of this type is that
|
|
10
|
+
* everything following it reads the same number. Two instances would
|
|
11
|
+
* reintroduce exactly the disagreement it exists to prevent.
|
|
12
|
+
*/
|
|
13
|
+
import type { IMusicPosition, IMusicPositionSource } from '../model/position.js';
|
|
14
|
+
/**
|
|
15
|
+
* Creates the singleton if it does not exist.
|
|
16
|
+
*
|
|
17
|
+
* Idempotent, so a second call from a re-mounting composition root does not
|
|
18
|
+
* silently swap the playhead out from under everything subscribed to it.
|
|
19
|
+
*/
|
|
20
|
+
export declare function initializeMusicPosition(override?: IMusicPositionSource): IMusicPositionSource;
|
|
21
|
+
/**
|
|
22
|
+
* The writable playhead, for whatever is driving the transport.
|
|
23
|
+
*
|
|
24
|
+
* Auto-initialises rather than throwing: unlike a storage service there is no
|
|
25
|
+
* configuration to get wrong, and a playhead that refuses to exist until
|
|
26
|
+
* somebody remembers a start-up call is a worse failure than one that starts
|
|
27
|
+
* at zero.
|
|
28
|
+
*/
|
|
29
|
+
export declare function getMusicPositionSource(): IMusicPositionSource;
|
|
30
|
+
/** The read-only playhead, for anything that follows the music. */
|
|
31
|
+
export declare function getMusicPosition(): IMusicPosition;
|
|
32
|
+
/** Drops the singleton. Tests only — a suite must not inherit the last one's playhead. */
|
|
33
|
+
export declare function resetMusicPosition(): void;
|
|
34
|
+
//# sourceMappingURL=singleton.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"singleton.d.ts","sourceRoot":"","sources":["../../src/position/singleton.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AACH,OAAO,KAAK,EACV,cAAc,EACd,oBAAoB,EACrB,MAAM,sBAAsB,CAAC;AAK9B;;;;;GAKG;AACH,wBAAgB,uBAAuB,CACrC,QAAQ,CAAC,EAAE,oBAAoB,GAC9B,oBAAoB,CAGtB;AAED;;;;;;;GAOG;AACH,wBAAgB,sBAAsB,IAAI,oBAAoB,CAE7D;AAED,mEAAmE;AACnE,wBAAgB,gBAAgB,IAAI,cAAc,CAEjD;AAED,0FAA0F;AAC1F,wBAAgB,kBAAkB,IAAI,IAAI,CAEzC"}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { MusicPosition } from './music-position.js';
|
|
2
|
+
let instance = null;
|
|
3
|
+
/**
|
|
4
|
+
* Creates the singleton if it does not exist.
|
|
5
|
+
*
|
|
6
|
+
* Idempotent, so a second call from a re-mounting composition root does not
|
|
7
|
+
* silently swap the playhead out from under everything subscribed to it.
|
|
8
|
+
*/
|
|
9
|
+
export function initializeMusicPosition(override) {
|
|
10
|
+
if (!instance)
|
|
11
|
+
instance = override ?? new MusicPosition();
|
|
12
|
+
return instance;
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* The writable playhead, for whatever is driving the transport.
|
|
16
|
+
*
|
|
17
|
+
* Auto-initialises rather than throwing: unlike a storage service there is no
|
|
18
|
+
* configuration to get wrong, and a playhead that refuses to exist until
|
|
19
|
+
* somebody remembers a start-up call is a worse failure than one that starts
|
|
20
|
+
* at zero.
|
|
21
|
+
*/
|
|
22
|
+
export function getMusicPositionSource() {
|
|
23
|
+
return initializeMusicPosition();
|
|
24
|
+
}
|
|
25
|
+
/** The read-only playhead, for anything that follows the music. */
|
|
26
|
+
export function getMusicPosition() {
|
|
27
|
+
return initializeMusicPosition();
|
|
28
|
+
}
|
|
29
|
+
/** Drops the singleton. Tests only — a suite must not inherit the last one's playhead. */
|
|
30
|
+
export function resetMusicPosition() {
|
|
31
|
+
instance = null;
|
|
32
|
+
}
|
|
33
|
+
//# sourceMappingURL=singleton.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"singleton.js","sourceRoot":"","sources":["../../src/position/singleton.ts"],"names":[],"mappings":"AAgBA,OAAO,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AAEpD,IAAI,QAAQ,GAAgC,IAAI,CAAC;AAEjD;;;;;GAKG;AACH,MAAM,UAAU,uBAAuB,CACrC,QAA+B;IAE/B,IAAI,CAAC,QAAQ;QAAE,QAAQ,GAAG,QAAQ,IAAI,IAAI,aAAa,EAAE,CAAC;IAC1D,OAAO,QAAQ,CAAC;AAClB,CAAC;AAED;;;;;;;GAOG;AACH,MAAM,UAAU,sBAAsB;IACpC,OAAO,uBAAuB,EAAE,CAAC;AACnC,CAAC;AAED,mEAAmE;AACnE,MAAM,UAAU,gBAAgB;IAC9B,OAAO,uBAAuB,EAAE,CAAC;AACnC,CAAC;AAED,0FAA0F;AAC1F,MAAM,UAAU,kBAAkB;IAChC,QAAQ,GAAG,IAAI,CAAC;AAClB,CAAC"}
|
package/package.json
CHANGED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"midi.d.ts","sourceRoot":"","sources":["../../src/platform/midi.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,MAAM,MAAM,QAAQ,GAAG;IACrB,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;IACd,aAAa,EAAE,MAAM,CAAC;IACtB,QAAQ,EAAE,MAAM,CAAC;CAClB,CAAC;AACF,MAAM,MAAM,iBAAiB,GAAG;IAC9B,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,MAAM,CAAC;CACf,CAAC;AACF,MAAM,MAAM,cAAc,GAAG;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,GAAG,EAAE,MAAM,CAAA;CAAE,CAAC;AAC5D,MAAM,MAAM,sBAAsB,GAAG;IACnC,KAAK,EAAE,MAAM,CAAC;IACd,aAAa,EAAE,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CACjC,CAAC;AAEF,MAAM,MAAM,aAAa,GAAG;IAC1B,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;IAChB,UAAU,EAAE;QAAE,MAAM,EAAE,MAAM,CAAC;QAAC,IAAI,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;IAC9C,KAAK,EAAE,QAAQ,EAAE,CAAC;IAClB;;;;;OAKG;IACH,cAAc,EAAE,MAAM,CAAC,MAAM,EAAE,iBAAiB,EAAE,CAAC,CAAC;IACpD,aAAa,EAAE,MAAM,CAAC;IACtB,8EAA8E;IAC9E,eAAe,EAAE,MAAM,CAAC;CACzB,CAAC;AAEF,MAAM,MAAM,QAAQ,GAAG;IACrB,MAAM,EAAE;QACN,GAAG,EAAE,MAAM,CAAC;QACZ,IAAI,CAAC,EAAE,MAAM,CAAC;QACd,MAAM,EAAE,cAAc,EAAE,CAAC;QACzB,cAAc,EAAE,sBAAsB,EAAE,CAAC;KAC1C,CAAC;IACF,MAAM,EAAE,aAAa,EAAE,CAAC;IACxB,yCAAyC;IACzC,QAAQ,EAAE,MAAM,CAAC;CAClB,CAAC;AAEF,MAAM,WAAW,SAAS;IACxB,MAAM,CAAC,IAAI,EAAE,WAAW,GAAG,QAAQ,CAAC;IACpC,MAAM,CAAC,IAAI,EAAE,QAAQ,GAAG,UAAU,CAAC;CACpC"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"mod.d.ts","sourceRoot":"","sources":["../../src/platform/mod.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAEH,MAAM,MAAM,aAAa,GAAG,KAAK,GAAG,KAAK,GAAG,IAAI,GAAG,IAAI,GAAG,KAAK,GAAG,MAAM,CAAC;AAEzE,2GAA2G;AAC3G,MAAM,MAAM,iBAAiB,GAAG;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,CAAC;AAEhE;;;;;;;;;;;;GAYG;AACH,MAAM,MAAM,WAAW,GAAG;IACxB,gEAAgE;IAChE,UAAU,EAAE,MAAM,CAAC;IACnB,0FAA0F;IAC1F,IAAI,EAAE,MAAM,GAAG,KAAK,GAAG,IAAI,CAAC;IAC5B,iDAAiD;IACjD,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,oDAAoD;IACpD,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,+CAA+C;IAC/C,YAAY,CAAC,EAAE,OAAO,CAAC;CACxB,CAAC;AAEF,MAAM,MAAM,aAAa,GAAG;IAC1B,MAAM,EAAE,aAAa,CAAC;IACtB,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,MAAM,CAAC;IACjB,WAAW,EAAE,iBAAiB,EAAE,CAAC;IACjC,2FAA2F;IAC3F,KAAK,EAAE,MAAM,EAAE,CAAC;IAChB,mGAAmG;IACnG,QAAQ,EAAE,WAAW,EAAE,EAAE,EAAE,CAAC;CAC7B,CAAC;AAEF;;;;;;GAMG;AACH,MAAM,WAAW,YAAY;IAC3B,uJAAuJ;IACvJ,MAAM,CAAC,KAAK,EAAE,WAAW,GAAG,aAAa,CAAC;IAC1C;;;;;;;OAOG;IACH,MAAM,CAAC,MAAM,EAAE,aAAa,GAAG,WAAW,CAAC;CAC5C"}
|
package/dist/platform/mod.js.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"mod.js","sourceRoot":"","sources":["../../src/platform/mod.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG"}
|
|
File without changes
|
|
File without changes
|