@sudobility/music_types 0.12.1 → 0.13.2

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 (49) hide show
  1. package/CLAUDE.md +20 -0
  2. package/dist/bytes/base64.d.ts +40 -0
  3. package/dist/bytes/base64.d.ts.map +1 -0
  4. package/dist/bytes/base64.js +101 -0
  5. package/dist/bytes/base64.js.map +1 -0
  6. package/dist/bytes/index.d.ts +2 -0
  7. package/dist/bytes/index.d.ts.map +1 -0
  8. package/dist/bytes/index.js +2 -0
  9. package/dist/bytes/index.js.map +1 -0
  10. package/dist/domain/commands/repair-commands.d.ts +10 -0
  11. package/dist/domain/commands/repair-commands.d.ts.map +1 -0
  12. package/dist/domain/commands/repair-commands.js +25 -0
  13. package/dist/domain/commands/repair-commands.js.map +1 -0
  14. package/dist/domain/validation/limits.d.ts +32 -0
  15. package/dist/domain/validation/limits.d.ts.map +1 -0
  16. package/dist/domain/validation/limits.js +32 -0
  17. package/dist/domain/validation/limits.js.map +1 -0
  18. package/dist/domain/validation/repair.d.ts +48 -0
  19. package/dist/domain/validation/repair.d.ts.map +1 -0
  20. package/dist/domain/validation/repair.js +389 -0
  21. package/dist/domain/validation/repair.js.map +1 -0
  22. package/dist/domain/validation/validator.d.ts.map +1 -1
  23. package/dist/domain/validation/validator.js +1 -22
  24. package/dist/domain/validation/validator.js.map +1 -1
  25. package/dist/index.d.ts +5 -0
  26. package/dist/index.d.ts.map +1 -1
  27. package/dist/index.js +8 -0
  28. package/dist/index.js.map +1 -1
  29. package/dist/model/api.d.ts +16 -0
  30. package/dist/model/api.d.ts.map +1 -1
  31. package/dist/model/api.js +6 -0
  32. package/dist/model/api.js.map +1 -1
  33. package/dist/model/position.d.ts +16 -0
  34. package/dist/model/position.d.ts.map +1 -1
  35. package/dist/platform/playback.d.ts +9 -90
  36. package/dist/platform/playback.d.ts.map +1 -1
  37. package/dist/position/index.d.ts +3 -0
  38. package/dist/position/index.d.ts.map +1 -0
  39. package/dist/position/index.js +3 -0
  40. package/dist/position/index.js.map +1 -0
  41. package/dist/position/music-position.d.ts +69 -0
  42. package/dist/position/music-position.d.ts.map +1 -0
  43. package/dist/position/music-position.js +99 -0
  44. package/dist/position/music-position.js.map +1 -0
  45. package/dist/position/singleton.d.ts +34 -0
  46. package/dist/position/singleton.d.ts.map +1 -0
  47. package/dist/position/singleton.js +33 -0
  48. package/dist/position/singleton.js.map +1 -0
  49. package/package.json +1 -1
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,2 @@
1
+ export * from "./base64.js";
2
+ //# sourceMappingURL=index.d.ts.map
@@ -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,2 @@
1
+ export * from "./base64.js";
2
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/bytes/index.ts"],"names":[],"mappings":"AAAA,cAAc,aAAa,CAAC"}
@@ -0,0 +1,10 @@
1
+ import type { ScoreCommand } from "./types.js";
2
+ /**
3
+ * Repairs every validation issue that has an unambiguous fix.
4
+ *
5
+ * A no-op on a clean score: `repairScore` returns its input by reference, so
6
+ * the command produces an identical score and the caller can compare
7
+ * identities to decide whether anything happened.
8
+ */
9
+ export declare function repairScoreCommand(label: string): ScoreCommand;
10
+ //# sourceMappingURL=repair-commands.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"repair-commands.d.ts","sourceRoot":"","sources":["../../../src/domain/commands/repair-commands.ts"],"names":[],"mappings":"AAaA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAG/C;;;;;;GAMG;AACH,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,MAAM,GAAG,YAAY,CAE9D"}
@@ -0,0 +1,25 @@
1
+ /**
2
+ * The "fix every issue you can" edit, as one undoable command.
3
+ *
4
+ * One command rather than one per rule, because that is what the reader asked
5
+ * for: the issue list is a single list and clearing it is a single decision.
6
+ * Splitting it would also make undo wrong — stepping back through twelve
7
+ * separate repairs to get the score you had before pressing one button.
8
+ *
9
+ * `kind` is left at the default `content`, so this is refused while the
10
+ * transport is playing like every other edit. Rewriting notes out from under
11
+ * a running engine is exactly what that lock exists to prevent.
12
+ */
13
+ import { transformCommand } from "./snapshot.js";
14
+ import { repairScore } from "../validation/repair.js";
15
+ /**
16
+ * Repairs every validation issue that has an unambiguous fix.
17
+ *
18
+ * A no-op on a clean score: `repairScore` returns its input by reference, so
19
+ * the command produces an identical score and the caller can compare
20
+ * identities to decide whether anything happened.
21
+ */
22
+ export function repairScoreCommand(label) {
23
+ return transformCommand(label, (score) => repairScore(score).score);
24
+ }
25
+ //# sourceMappingURL=repair-commands.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"repair-commands.js","sourceRoot":"","sources":["../../../src/domain/commands/repair-commands.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AACH,OAAO,EAAE,gBAAgB,EAAE,MAAM,eAAe,CAAC;AAEjD,OAAO,EAAE,WAAW,EAAE,MAAM,yBAAyB,CAAC;AAEtD;;;;;;GAMG;AACH,MAAM,UAAU,kBAAkB,CAAC,KAAa;IAC9C,OAAO,gBAAgB,CAAC,KAAK,EAAE,CAAC,KAAK,EAAE,EAAE,CAAC,WAAW,CAAC,KAAK,CAAC,CAAC,KAAK,CAAC,CAAC;AACtE,CAAC"}
@@ -0,0 +1,32 @@
1
+ /**
2
+ * The numeric bounds every validation rule is written against.
3
+ *
4
+ * Extracted so `validator.ts` and `repair.ts` cannot drift: a repair that
5
+ * clamped to its own idea of "valid" would hand back a score the validator
6
+ * still rejects, and the Fix button would appear to do nothing. One
7
+ * definition means a clamp to these bounds provably clears the rule that
8
+ * cited them.
9
+ */
10
+ export declare const MIN_MIDI = 0;
11
+ export declare const MAX_MIDI = 127;
12
+ export declare const MIN_VELOCITY = 0;
13
+ export declare const MAX_VELOCITY = 127;
14
+ export declare const MIN_MIDI_PROGRAM = 0;
15
+ export declare const MAX_MIDI_PROGRAM = 127;
16
+ export declare const MIN_MIDI_CHANNEL = 0;
17
+ export declare const MAX_MIDI_CHANNEL = 15;
18
+ export declare const VALID_TIME_SIG_DENOMINATORS: Set<number>;
19
+ export declare const MIN_FIFTHS = -7;
20
+ export declare const MAX_FIFTHS = 7;
21
+ export declare const MIN_BPM = 20;
22
+ export declare const MAX_BPM = 400;
23
+ /**
24
+ * Maximum number of notes sounding at once (within a single track) before a
25
+ * "too many simultaneous notes" readability warning fires. Spec §23 names
26
+ * this rule but doesn't give a number; 10 is a deliberate implementer
27
+ * default (roughly as many notes as a two-hand piano voicing on one staff
28
+ * can render legibly), documented here so a later task can make it
29
+ * configurable if that turns out to be too strict/loose in practice.
30
+ */
31
+ export declare const MAX_SIMULTANEOUS_NOTES = 10;
32
+ //# sourceMappingURL=limits.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"limits.d.ts","sourceRoot":"","sources":["../../../src/domain/validation/limits.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AACH,eAAO,MAAM,QAAQ,IAAI,CAAC;AAC1B,eAAO,MAAM,QAAQ,MAAM,CAAC;AAC5B,eAAO,MAAM,YAAY,IAAI,CAAC;AAC9B,eAAO,MAAM,YAAY,MAAM,CAAC;AAChC,eAAO,MAAM,gBAAgB,IAAI,CAAC;AAClC,eAAO,MAAM,gBAAgB,MAAM,CAAC;AACpC,eAAO,MAAM,gBAAgB,IAAI,CAAC;AAClC,eAAO,MAAM,gBAAgB,KAAK,CAAC;AACnC,eAAO,MAAM,2BAA2B,aAAgC,CAAC;AACzE,eAAO,MAAM,UAAU,KAAK,CAAC;AAC7B,eAAO,MAAM,UAAU,IAAI,CAAC;AAC5B,eAAO,MAAM,OAAO,KAAK,CAAC;AAC1B,eAAO,MAAM,OAAO,MAAM,CAAC;AAE3B;;;;;;;GAOG;AACH,eAAO,MAAM,sBAAsB,KAAK,CAAC"}
@@ -0,0 +1,32 @@
1
+ /**
2
+ * The numeric bounds every validation rule is written against.
3
+ *
4
+ * Extracted so `validator.ts` and `repair.ts` cannot drift: a repair that
5
+ * clamped to its own idea of "valid" would hand back a score the validator
6
+ * still rejects, and the Fix button would appear to do nothing. One
7
+ * definition means a clamp to these bounds provably clears the rule that
8
+ * cited them.
9
+ */
10
+ export const MIN_MIDI = 0;
11
+ export const MAX_MIDI = 127;
12
+ export const MIN_VELOCITY = 0;
13
+ export const MAX_VELOCITY = 127;
14
+ export const MIN_MIDI_PROGRAM = 0;
15
+ export const MAX_MIDI_PROGRAM = 127;
16
+ export const MIN_MIDI_CHANNEL = 0;
17
+ export const MAX_MIDI_CHANNEL = 15;
18
+ export const VALID_TIME_SIG_DENOMINATORS = new Set([1, 2, 4, 8, 16, 32]);
19
+ export const MIN_FIFTHS = -7;
20
+ export const MAX_FIFTHS = 7;
21
+ export const MIN_BPM = 20;
22
+ export const MAX_BPM = 400;
23
+ /**
24
+ * Maximum number of notes sounding at once (within a single track) before a
25
+ * "too many simultaneous notes" readability warning fires. Spec §23 names
26
+ * this rule but doesn't give a number; 10 is a deliberate implementer
27
+ * default (roughly as many notes as a two-hand piano voicing on one staff
28
+ * can render legibly), documented here so a later task can make it
29
+ * configurable if that turns out to be too strict/loose in practice.
30
+ */
31
+ export const MAX_SIMULTANEOUS_NOTES = 10;
32
+ //# sourceMappingURL=limits.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"limits.js","sourceRoot":"","sources":["../../../src/domain/validation/limits.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AACH,MAAM,CAAC,MAAM,QAAQ,GAAG,CAAC,CAAC;AAC1B,MAAM,CAAC,MAAM,QAAQ,GAAG,GAAG,CAAC;AAC5B,MAAM,CAAC,MAAM,YAAY,GAAG,CAAC,CAAC;AAC9B,MAAM,CAAC,MAAM,YAAY,GAAG,GAAG,CAAC;AAChC,MAAM,CAAC,MAAM,gBAAgB,GAAG,CAAC,CAAC;AAClC,MAAM,CAAC,MAAM,gBAAgB,GAAG,GAAG,CAAC;AACpC,MAAM,CAAC,MAAM,gBAAgB,GAAG,CAAC,CAAC;AAClC,MAAM,CAAC,MAAM,gBAAgB,GAAG,EAAE,CAAC;AACnC,MAAM,CAAC,MAAM,2BAA2B,GAAG,IAAI,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,EAAE,EAAE,EAAE,CAAC,CAAC,CAAC;AACzE,MAAM,CAAC,MAAM,UAAU,GAAG,CAAC,CAAC,CAAC;AAC7B,MAAM,CAAC,MAAM,UAAU,GAAG,CAAC,CAAC;AAC5B,MAAM,CAAC,MAAM,OAAO,GAAG,EAAE,CAAC;AAC1B,MAAM,CAAC,MAAM,OAAO,GAAG,GAAG,CAAC;AAE3B;;;;;;;GAOG;AACH,MAAM,CAAC,MAAM,sBAAsB,GAAG,EAAE,CAAC"}
@@ -0,0 +1,48 @@
1
+ /**
2
+ * Automatic repair for the rules `validateScore` checks (`./validator.ts`).
3
+ *
4
+ * The validator's inverse, and deliberately its neighbour: a repair that
5
+ * clamped to its own idea of "valid" would hand back a score the validator
6
+ * still rejects, so both read their bounds from `./limits.ts` and this module
7
+ * *measures* its own result rather than asserting it — `repairScore`
8
+ * re-validates at the end and reports the before/after counts per code. That
9
+ * is what makes "Fix" honest about the issues it could not resolve instead of
10
+ * quietly claiming all of them.
11
+ *
12
+ * Pure: never mutates its input, and returns a fresh `Score`.
13
+ *
14
+ * Each pass repairs toward what the notation *means*, not toward whatever
15
+ * silences the rule fastest:
16
+ *
17
+ * - Two same-pitch notes that overlap are one note struck twice, so the
18
+ * earlier one is truncated to end where the later begins. An exact
19
+ * duplicate — same tick, same length, same spelling — cannot be played
20
+ * twice at once and is simply dropped.
21
+ * - A pitch out of MIDI range moves by whole **octaves**, keeping its pitch
22
+ * class, for the reason the tracker exporter does it: a bassline pushed to
23
+ * the boundary stops being that bassline.
24
+ * - A misordered measure is moved by shifting its events with it, because an
25
+ * event's `startTick` is absolute; rewriting the measure's own start alone
26
+ * would strand every note it contains outside it.
27
+ * - The count of notes sounding at once is left alone. There is no
28
+ * non-arbitrary choice of which note to delete, and it is a readability
29
+ * warning rather than a broken score.
30
+ */
31
+ import type { Score } from "../../index.js";
32
+ export type ScoreRepair = {
33
+ /** The repaired score. Identical by reference to the input when nothing needed fixing. */
34
+ score: Score;
35
+ /** How many issues of each code the repair actually cleared, measured by re-validating. */
36
+ fixed: Record<string, number>;
37
+ /** What the repaired score still reports, by code — the rules this cannot resolve. */
38
+ remaining: Record<string, number>;
39
+ };
40
+ /**
41
+ * Repairs every rule this can, and reports what it actually achieved.
42
+ *
43
+ * Returns the input score by reference when it was already clean, so a caller
44
+ * dispatching this as a command can tell "nothing to do" from "something
45
+ * changed" without diffing.
46
+ */
47
+ export declare function repairScore(score: Score): ScoreRepair;
48
+ //# sourceMappingURL=repair.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"repair.d.ts","sourceRoot":"","sources":["../../../src/domain/validation/repair.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;AACH,OAAO,KAAK,EAIV,KAAK,EAIN,MAAM,gBAAgB,CAAC;AAsBxB,MAAM,MAAM,WAAW,GAAG;IACxB,0FAA0F;IAC1F,KAAK,EAAE,KAAK,CAAC;IACb,2FAA2F;IAC3F,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC9B,sFAAsF;IACtF,SAAS,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CACnC,CAAC;AAuWF;;;;;;GAMG;AACH,wBAAgB,WAAW,CAAC,KAAK,EAAE,KAAK,GAAG,WAAW,CA2DrD"}