@sudobility/music_types 0.11.19 → 0.11.21
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/dist/domain/commands/edit-commands.d.ts +66 -0
- package/dist/domain/commands/edit-commands.d.ts.map +1 -0
- package/dist/domain/commands/edit-commands.js +185 -0
- package/dist/domain/commands/edit-commands.js.map +1 -0
- package/dist/domain/commands/note-commands.d.ts +73 -0
- package/dist/domain/commands/note-commands.d.ts.map +1 -0
- package/dist/domain/commands/note-commands.js +292 -0
- package/dist/domain/commands/note-commands.js.map +1 -0
- package/dist/domain/commands/reflow.d.ts +69 -0
- package/dist/domain/commands/reflow.d.ts.map +1 -0
- package/dist/domain/commands/reflow.js +395 -0
- package/dist/domain/commands/reflow.js.map +1 -0
- package/dist/domain/commands/region-commands.d.ts +21 -0
- package/dist/domain/commands/region-commands.d.ts.map +1 -0
- package/dist/domain/commands/region-commands.js +47 -0
- package/dist/domain/commands/region-commands.js.map +1 -0
- package/dist/domain/commands/relocate-commands.d.ts +18 -0
- package/dist/domain/commands/relocate-commands.d.ts.map +1 -0
- package/dist/domain/commands/relocate-commands.js +108 -0
- package/dist/domain/commands/relocate-commands.js.map +1 -0
- package/dist/domain/commands/ripple-commands.d.ts +44 -0
- package/dist/domain/commands/ripple-commands.d.ts.map +1 -0
- package/dist/domain/commands/ripple-commands.js +104 -0
- package/dist/domain/commands/ripple-commands.js.map +1 -0
- package/dist/domain/commands/snapshot.d.ts +38 -0
- package/dist/domain/commands/snapshot.d.ts.map +1 -0
- package/dist/domain/commands/snapshot.js +44 -0
- package/dist/domain/commands/snapshot.js.map +1 -0
- package/dist/domain/commands/structure-commands.d.ts +38 -0
- package/dist/domain/commands/structure-commands.d.ts.map +1 -0
- package/dist/domain/commands/structure-commands.js +220 -0
- package/dist/domain/commands/structure-commands.js.map +1 -0
- package/dist/domain/commands/track-commands.d.ts +15 -0
- package/dist/domain/commands/track-commands.d.ts.map +1 -0
- package/dist/domain/commands/track-commands.js +56 -0
- package/dist/domain/commands/track-commands.js.map +1 -0
- package/dist/domain/commands/types.d.ts +31 -0
- package/dist/domain/commands/types.d.ts.map +1 -0
- package/dist/domain/commands/types.js +2 -0
- package/dist/domain/commands/types.js.map +1 -0
- package/dist/domain/instruments/gm-kit.d.ts +45 -0
- package/dist/domain/instruments/gm-kit.d.ts.map +1 -0
- package/dist/domain/instruments/gm-kit.js +59 -0
- package/dist/domain/instruments/gm-kit.js.map +1 -0
- package/dist/domain/instruments/gm-percussion.d.ts +42 -0
- package/dist/domain/instruments/gm-percussion.d.ts.map +1 -0
- package/dist/domain/instruments/gm-percussion.js +85 -0
- package/dist/domain/instruments/gm-percussion.js.map +1 -0
- package/dist/domain/instruments/gm-polyphony.d.ts +13 -0
- package/dist/domain/instruments/gm-polyphony.d.ts.map +1 -0
- package/dist/domain/instruments/gm-polyphony.js +76 -0
- package/dist/domain/instruments/gm-polyphony.js.map +1 -0
- package/dist/domain/instruments/gm-range.d.ts +15 -0
- package/dist/domain/instruments/gm-range.d.ts.map +1 -0
- package/dist/domain/instruments/gm-range.js +97 -0
- package/dist/domain/instruments/gm-range.js.map +1 -0
- package/dist/domain/instruments/gm-transposition.d.ts +11 -0
- package/dist/domain/instruments/gm-transposition.d.ts.map +1 -0
- package/dist/domain/instruments/gm-transposition.js +69 -0
- package/dist/domain/instruments/gm-transposition.js.map +1 -0
- package/dist/domain/instruments/gm.d.ts +28 -0
- package/dist/domain/instruments/gm.d.ts.map +1 -0
- package/dist/domain/instruments/gm.js +217 -0
- package/dist/domain/instruments/gm.js.map +1 -0
- package/dist/domain/pitch/pitch.d.ts +15 -0
- package/dist/domain/pitch/pitch.d.ts.map +1 -0
- package/dist/domain/pitch/pitch.js +81 -0
- package/dist/domain/pitch/pitch.js.map +1 -0
- package/dist/domain/pitch/transpose.d.ts +37 -0
- package/dist/domain/pitch/transpose.d.ts.map +1 -0
- package/dist/domain/pitch/transpose.js +65 -0
- package/dist/domain/pitch/transpose.js.map +1 -0
- package/dist/domain/quantization/options.d.ts +55 -0
- package/dist/domain/quantization/options.d.ts.map +1 -0
- package/dist/domain/quantization/options.js +2 -0
- package/dist/domain/quantization/options.js.map +1 -0
- package/dist/domain/quantization/quantize.d.ts +13 -0
- package/dist/domain/quantization/quantize.d.ts.map +1 -0
- package/dist/domain/quantization/quantize.js +199 -0
- package/dist/domain/quantization/quantize.js.map +1 -0
- package/dist/domain/score/factory.d.ts +39 -0
- package/dist/domain/score/factory.d.ts.map +1 -0
- package/dist/domain/score/factory.js +148 -0
- package/dist/domain/score/factory.js.map +1 -0
- package/dist/domain/score/fragment.d.ts +28 -0
- package/dist/domain/score/fragment.d.ts.map +1 -0
- package/dist/domain/score/fragment.js +68 -0
- package/dist/domain/score/fragment.js.map +1 -0
- package/dist/domain/score/ids.d.ts +8 -0
- package/dist/domain/score/ids.d.ts.map +1 -0
- package/dist/domain/score/ids.js +38 -0
- package/dist/domain/score/ids.js.map +1 -0
- package/dist/domain/score/queries.d.ts +34 -0
- package/dist/domain/score/queries.d.ts.map +1 -0
- package/dist/domain/score/queries.js +132 -0
- package/dist/domain/score/queries.js.map +1 -0
- package/dist/domain/score/ties.d.ts +73 -0
- package/dist/domain/score/ties.d.ts.map +1 -0
- package/dist/domain/score/ties.js +200 -0
- package/dist/domain/score/ties.js.map +1 -0
- package/dist/domain/selection/selection.d.ts +63 -0
- package/dist/domain/selection/selection.d.ts.map +1 -0
- package/dist/domain/selection/selection.js +129 -0
- package/dist/domain/selection/selection.js.map +1 -0
- package/dist/domain/selection/types.d.ts +10 -0
- package/dist/domain/selection/types.d.ts.map +1 -0
- package/dist/domain/selection/types.js +5 -0
- package/dist/domain/selection/types.js.map +1 -0
- package/dist/domain/time/durations.d.ts +21 -0
- package/dist/domain/time/durations.d.ts.map +1 -0
- package/dist/domain/time/durations.js +53 -0
- package/dist/domain/time/durations.js.map +1 -0
- package/dist/domain/time/fraction.d.ts +19 -0
- package/dist/domain/time/fraction.d.ts.map +1 -0
- package/dist/domain/time/fraction.js +60 -0
- package/dist/domain/time/fraction.js.map +1 -0
- package/dist/domain/time/tempo-map.d.ts +24 -0
- package/dist/domain/time/tempo-map.d.ts.map +1 -0
- package/dist/domain/time/tempo-map.js +85 -0
- package/dist/domain/time/tempo-map.js.map +1 -0
- package/dist/domain/time/ticks.d.ts +34 -0
- package/dist/domain/time/ticks.d.ts.map +1 -0
- package/dist/domain/time/ticks.js +93 -0
- package/dist/domain/time/ticks.js.map +1 -0
- package/dist/domain/validation/issues.d.ts +44 -0
- package/dist/domain/validation/issues.d.ts.map +1 -0
- package/dist/domain/validation/issues.js +34 -0
- package/dist/domain/validation/issues.js.map +1 -0
- package/dist/domain/validation/validator.d.ts +11 -0
- package/dist/domain/validation/validator.d.ts.map +1 -0
- package/dist/domain/validation/validator.js +375 -0
- package/dist/domain/validation/validator.js.map +1 -0
- package/dist/domain/voicing/allocate.d.ts +19 -0
- package/dist/domain/voicing/allocate.d.ts.map +1 -0
- package/dist/domain/voicing/allocate.js +100 -0
- package/dist/domain/voicing/allocate.js.map +1 -0
- package/dist/index.d.ts +46 -4
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +50 -2
- package/dist/index.js.map +1 -1
- package/dist/platform/audio.d.ts +12 -0
- package/dist/platform/audio.d.ts.map +1 -1
- package/dist/platform/playback.d.ts +85 -15
- package/dist/platform/playback.d.ts.map +1 -1
- package/dist/services/regeneration/controller.d.ts +60 -0
- package/dist/services/regeneration/controller.d.ts.map +1 -0
- package/dist/services/regeneration/controller.js +202 -0
- package/dist/services/regeneration/controller.js.map +1 -0
- package/dist/test/fixtures.d.ts +45 -0
- package/dist/test/fixtures.d.ts.map +1 -0
- package/dist/test/fixtures.js +420 -0
- package/dist/test/fixtures.js.map +1 -0
- package/package.json +2 -1
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../../../src/domain/selection/types.ts"],"names":[],"mappings":"AAQA,0EAA0E;AAC1E,MAAM,UAAU,cAAc;IAC5B,OAAO,EAAE,QAAQ,EAAE,EAAE,EAAE,UAAU,EAAE,EAAE,EAAE,QAAQ,EAAE,EAAE,EAAE,CAAC;AACxD,CAAC"}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Greedily decomposes a tick length into the largest renderable (non-triplet,
|
|
3
|
+
* dotted-allowed) note values that fit, largest first. If a remainder is
|
|
4
|
+
* smaller than the smallest available duration, it is emitted as-is so the
|
|
5
|
+
* returned segments always sum back to the original `ticks` (the caller is
|
|
6
|
+
* responsible for deciding how such a remainder is notated/tied).
|
|
7
|
+
*/
|
|
8
|
+
export declare function decomposeDuration(ticks: number, ppq: number): number[];
|
|
9
|
+
export type TickSegment = {
|
|
10
|
+
startTick: number;
|
|
11
|
+
durationTicks: number;
|
|
12
|
+
};
|
|
13
|
+
/**
|
|
14
|
+
* Splits a note spanning `[startTick, startTick + durationTicks)` at every
|
|
15
|
+
* given absolute-tick boundary that falls strictly inside that range (e.g.
|
|
16
|
+
* measure start ticks), returning contiguous segments that together cover
|
|
17
|
+
* the original span. The caller is responsible for tying split segments
|
|
18
|
+
* back together.
|
|
19
|
+
*/
|
|
20
|
+
export declare function splitAtBoundaries(startTick: number, durationTicks: number, boundaries: number[]): TickSegment[];
|
|
21
|
+
//# sourceMappingURL=durations.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"durations.d.ts","sourceRoot":"","sources":["../../../src/domain/time/durations.ts"],"names":[],"mappings":"AAeA;;;;;;GAMG;AACH,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,MAAM,EAAE,CAkBtE;AAED,MAAM,MAAM,WAAW,GAAG;IAAE,SAAS,EAAE,MAAM,CAAC;IAAC,aAAa,EAAE,MAAM,CAAA;CAAE,CAAC;AAEvE;;;;;;GAMG;AACH,wBAAgB,iBAAiB,CAC/B,SAAS,EAAE,MAAM,EACjB,aAAa,EAAE,MAAM,EACrB,UAAU,EAAE,MAAM,EAAE,GACnB,WAAW,EAAE,CAef"}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { DURATIONS, ticksFor } from './ticks.js';
|
|
2
|
+
const NON_TRIPLET_DURATION_NAMES = Object.keys(DURATIONS).filter(name => !name.startsWith('triplet-'));
|
|
3
|
+
/** Distinct, descending tick lengths for every renderable (non-triplet) duration at a given PPQ. */
|
|
4
|
+
function decomposableDurationTicks(ppq) {
|
|
5
|
+
const ticks = new Set(NON_TRIPLET_DURATION_NAMES.map(name => ticksFor(name, ppq)));
|
|
6
|
+
return [...ticks].filter(t => t > 0).sort((a, b) => b - a);
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* Greedily decomposes a tick length into the largest renderable (non-triplet,
|
|
10
|
+
* dotted-allowed) note values that fit, largest first. If a remainder is
|
|
11
|
+
* smaller than the smallest available duration, it is emitted as-is so the
|
|
12
|
+
* returned segments always sum back to the original `ticks` (the caller is
|
|
13
|
+
* responsible for deciding how such a remainder is notated/tied).
|
|
14
|
+
*/
|
|
15
|
+
export function decomposeDuration(ticks, ppq) {
|
|
16
|
+
if (ticks <= 0)
|
|
17
|
+
return [];
|
|
18
|
+
const candidates = decomposableDurationTicks(ppq);
|
|
19
|
+
const result = [];
|
|
20
|
+
let remaining = ticks;
|
|
21
|
+
while (remaining > 0) {
|
|
22
|
+
const next = candidates.find(c => c <= remaining);
|
|
23
|
+
if (next === undefined) {
|
|
24
|
+
result.push(remaining);
|
|
25
|
+
break;
|
|
26
|
+
}
|
|
27
|
+
result.push(next);
|
|
28
|
+
remaining -= next;
|
|
29
|
+
}
|
|
30
|
+
return result;
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Splits a note spanning `[startTick, startTick + durationTicks)` at every
|
|
34
|
+
* given absolute-tick boundary that falls strictly inside that range (e.g.
|
|
35
|
+
* measure start ticks), returning contiguous segments that together cover
|
|
36
|
+
* the original span. The caller is responsible for tying split segments
|
|
37
|
+
* back together.
|
|
38
|
+
*/
|
|
39
|
+
export function splitAtBoundaries(startTick, durationTicks, boundaries) {
|
|
40
|
+
const endTick = startTick + durationTicks;
|
|
41
|
+
const cutPoints = [...new Set(boundaries)]
|
|
42
|
+
.filter(b => b > startTick && b < endTick)
|
|
43
|
+
.sort((a, b) => a - b);
|
|
44
|
+
const segments = [];
|
|
45
|
+
let cursor = startTick;
|
|
46
|
+
for (const cut of cutPoints) {
|
|
47
|
+
segments.push({ startTick: cursor, durationTicks: cut - cursor });
|
|
48
|
+
cursor = cut;
|
|
49
|
+
}
|
|
50
|
+
segments.push({ startTick: cursor, durationTicks: endTick - cursor });
|
|
51
|
+
return segments;
|
|
52
|
+
}
|
|
53
|
+
//# sourceMappingURL=durations.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"durations.js","sourceRoot":"","sources":["../../../src/domain/time/durations.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,SAAS,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AAEjD,MAAM,0BAA0B,GAC9B,MAAM,CAAC,IAAI,CAAC,SAAS,CACtB,CAAC,MAAM,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC,IAAI,CAAC,UAAU,CAAC,UAAU,CAAC,CAAC,CAAC;AAE/C,oGAAoG;AACpG,SAAS,yBAAyB,CAAC,GAAW;IAC5C,MAAM,KAAK,GAAG,IAAI,GAAG,CACnB,0BAA0B,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC,QAAQ,CAAC,IAAI,EAAE,GAAG,CAAC,CAAC,CAC5D,CAAC;IACF,OAAO,CAAC,GAAG,KAAK,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC;AAC7D,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,iBAAiB,CAAC,KAAa,EAAE,GAAW;IAC1D,IAAI,KAAK,IAAI,CAAC;QAAE,OAAO,EAAE,CAAC;IAE1B,MAAM,UAAU,GAAG,yBAAyB,CAAC,GAAG,CAAC,CAAC;IAClD,MAAM,MAAM,GAAa,EAAE,CAAC;IAC5B,IAAI,SAAS,GAAG,KAAK,CAAC;IAEtB,OAAO,SAAS,GAAG,CAAC,EAAE,CAAC;QACrB,MAAM,IAAI,GAAG,UAAU,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,IAAI,SAAS,CAAC,CAAC;QAClD,IAAI,IAAI,KAAK,SAAS,EAAE,CAAC;YACvB,MAAM,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;YACvB,MAAM;QACR,CAAC;QACD,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QAClB,SAAS,IAAI,IAAI,CAAC;IACpB,CAAC;IAED,OAAO,MAAM,CAAC;AAChB,CAAC;AAID;;;;;;GAMG;AACH,MAAM,UAAU,iBAAiB,CAC/B,SAAiB,EACjB,aAAqB,EACrB,UAAoB;IAEpB,MAAM,OAAO,GAAG,SAAS,GAAG,aAAa,CAAC;IAC1C,MAAM,SAAS,GAAG,CAAC,GAAG,IAAI,GAAG,CAAC,UAAU,CAAC,CAAC;SACvC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,GAAG,SAAS,IAAI,CAAC,GAAG,OAAO,CAAC;SACzC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC;IAEzB,MAAM,QAAQ,GAAkB,EAAE,CAAC;IACnC,IAAI,MAAM,GAAG,SAAS,CAAC;IACvB,KAAK,MAAM,GAAG,IAAI,SAAS,EAAE,CAAC;QAC5B,QAAQ,CAAC,IAAI,CAAC,EAAE,SAAS,EAAE,MAAM,EAAE,aAAa,EAAE,GAAG,GAAG,MAAM,EAAE,CAAC,CAAC;QAClE,MAAM,GAAG,GAAG,CAAC;IACf,CAAC;IACD,QAAQ,CAAC,IAAI,CAAC,EAAE,SAAS,EAAE,MAAM,EAAE,aAAa,EAAE,OAAO,GAAG,MAAM,EAAE,CAAC,CAAC;IAEtE,OAAO,QAAQ,CAAC;AAClB,CAAC"}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { Fraction } from '../../index.js';
|
|
2
|
+
/** Constructs a `Fraction` from a numerator and denominator, unnormalized. */
|
|
3
|
+
export declare function fraction(numerator: number, denominator: number): Fraction;
|
|
4
|
+
/**
|
|
5
|
+
* Reduces a fraction to lowest terms with a positive denominator.
|
|
6
|
+
* Zero always normalizes to `0/1`. Throws on a zero denominator.
|
|
7
|
+
*/
|
|
8
|
+
export declare function normalizeFraction(f: Fraction): Fraction;
|
|
9
|
+
export declare function addFractions(a: Fraction, b: Fraction): Fraction;
|
|
10
|
+
export declare function subtractFractions(a: Fraction, b: Fraction): Fraction;
|
|
11
|
+
/** Returns -1, 0, or 1 depending on whether `a` is less than, equal to, or greater than `b`. */
|
|
12
|
+
export declare function compareFractions(a: Fraction, b: Fraction): -1 | 0 | 1;
|
|
13
|
+
/**
|
|
14
|
+
* Converts a fraction of a whole note to integer ticks at the given PPQ.
|
|
15
|
+
* A quarter note (1/4) equals `ppq` ticks, so a whole note equals `4 * ppq`.
|
|
16
|
+
* Rounds to the nearest integer tick.
|
|
17
|
+
*/
|
|
18
|
+
export declare function fractionToTicks(f: Fraction, ppq: number): number;
|
|
19
|
+
//# sourceMappingURL=fraction.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"fraction.d.ts","sourceRoot":"","sources":["../../../src/domain/time/fraction.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,gBAAgB,CAAC;AAY/C,8EAA8E;AAC9E,wBAAgB,QAAQ,CAAC,SAAS,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,GAAG,QAAQ,CAEzE;AAED;;;GAGG;AACH,wBAAgB,iBAAiB,CAAC,CAAC,EAAE,QAAQ,GAAG,QAAQ,CAevD;AAED,wBAAgB,YAAY,CAAC,CAAC,EAAE,QAAQ,EAAE,CAAC,EAAE,QAAQ,GAAG,QAAQ,CAK/D;AAED,wBAAgB,iBAAiB,CAAC,CAAC,EAAE,QAAQ,EAAE,CAAC,EAAE,QAAQ,GAAG,QAAQ,CAKpE;AAED,gGAAgG;AAChG,wBAAgB,gBAAgB,CAAC,CAAC,EAAE,QAAQ,EAAE,CAAC,EAAE,QAAQ,GAAG,CAAC,CAAC,GAAG,CAAC,GAAG,CAAC,CAMrE;AAED;;;;GAIG;AACH,wBAAgB,eAAe,CAAC,CAAC,EAAE,QAAQ,EAAE,GAAG,EAAE,MAAM,GAAG,MAAM,CAEhE"}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
/** Greatest common divisor (non-negative). */
|
|
2
|
+
function gcd(a, b) {
|
|
3
|
+
let x = Math.abs(a);
|
|
4
|
+
let y = Math.abs(b);
|
|
5
|
+
while (y !== 0) {
|
|
6
|
+
[x, y] = [y, x % y];
|
|
7
|
+
}
|
|
8
|
+
return x;
|
|
9
|
+
}
|
|
10
|
+
/** Constructs a `Fraction` from a numerator and denominator, unnormalized. */
|
|
11
|
+
export function fraction(numerator, denominator) {
|
|
12
|
+
return { numerator, denominator };
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Reduces a fraction to lowest terms with a positive denominator.
|
|
16
|
+
* Zero always normalizes to `0/1`. Throws on a zero denominator.
|
|
17
|
+
*/
|
|
18
|
+
export function normalizeFraction(f) {
|
|
19
|
+
if (f.denominator === 0) {
|
|
20
|
+
throw new Error('normalizeFraction: denominator cannot be zero');
|
|
21
|
+
}
|
|
22
|
+
if (f.numerator === 0) {
|
|
23
|
+
return { numerator: 0, denominator: 1 };
|
|
24
|
+
}
|
|
25
|
+
const sign = Math.sign(f.numerator) * Math.sign(f.denominator);
|
|
26
|
+
const divisor = gcd(f.numerator, f.denominator);
|
|
27
|
+
return {
|
|
28
|
+
numerator: sign * (Math.abs(f.numerator) / divisor),
|
|
29
|
+
denominator: Math.abs(f.denominator) / divisor,
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
export function addFractions(a, b) {
|
|
33
|
+
return normalizeFraction({
|
|
34
|
+
numerator: a.numerator * b.denominator + b.numerator * a.denominator,
|
|
35
|
+
denominator: a.denominator * b.denominator,
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
export function subtractFractions(a, b) {
|
|
39
|
+
return normalizeFraction({
|
|
40
|
+
numerator: a.numerator * b.denominator - b.numerator * a.denominator,
|
|
41
|
+
denominator: a.denominator * b.denominator,
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
/** Returns -1, 0, or 1 depending on whether `a` is less than, equal to, or greater than `b`. */
|
|
45
|
+
export function compareFractions(a, b) {
|
|
46
|
+
const left = a.numerator * b.denominator;
|
|
47
|
+
const right = b.numerator * a.denominator;
|
|
48
|
+
const sign = Math.sign(a.denominator) * Math.sign(b.denominator);
|
|
49
|
+
const diff = sign * (left - right);
|
|
50
|
+
return diff < 0 ? -1 : diff > 0 ? 1 : 0;
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Converts a fraction of a whole note to integer ticks at the given PPQ.
|
|
54
|
+
* A quarter note (1/4) equals `ppq` ticks, so a whole note equals `4 * ppq`.
|
|
55
|
+
* Rounds to the nearest integer tick.
|
|
56
|
+
*/
|
|
57
|
+
export function fractionToTicks(f, ppq) {
|
|
58
|
+
return Math.round((f.numerator / f.denominator) * 4 * ppq);
|
|
59
|
+
}
|
|
60
|
+
//# sourceMappingURL=fraction.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"fraction.js","sourceRoot":"","sources":["../../../src/domain/time/fraction.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import type { TempoEvent } from '../../index.js';
|
|
2
|
+
/**
|
|
3
|
+
* Maps between ticks and seconds given a (possibly empty) set of tempo
|
|
4
|
+
* change events. Tempo is piecewise-constant (a bpm value applies from its
|
|
5
|
+
* event's tick until the next event's tick), which makes the tick <-> second
|
|
6
|
+
* mapping piecewise-linear. An empty map behaves as a constant 120 bpm.
|
|
7
|
+
*/
|
|
8
|
+
export declare class TempoMap {
|
|
9
|
+
private readonly breakpoints;
|
|
10
|
+
private readonly ppq;
|
|
11
|
+
constructor(events: TempoEvent[], ppq: number);
|
|
12
|
+
private static buildBreakpoints;
|
|
13
|
+
/** Finds the last breakpoint at or before `tick` (breakpoints[0].tick is always 0). */
|
|
14
|
+
private breakpointForTick;
|
|
15
|
+
/** Finds the last breakpoint whose seconds offset is at or before `seconds`. */
|
|
16
|
+
private breakpointForSeconds;
|
|
17
|
+
/** The bpm in effect at the given tick. */
|
|
18
|
+
bpmAt(tick: number): number;
|
|
19
|
+
/** Converts an integer tick position to seconds. */
|
|
20
|
+
ticksToSeconds(tick: number): number;
|
|
21
|
+
/** Converts a time in seconds to a (possibly fractional) tick position. */
|
|
22
|
+
secondsToTicks(seconds: number): number;
|
|
23
|
+
}
|
|
24
|
+
//# sourceMappingURL=tempo-map.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tempo-map.d.ts","sourceRoot":"","sources":["../../../src/domain/time/tempo-map.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,gBAAgB,CAAC;AAgBjD;;;;;GAKG;AACH,qBAAa,QAAQ;IACnB,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAe;IAC3C,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAS;gBAEjB,MAAM,EAAE,UAAU,EAAE,EAAE,GAAG,EAAE,MAAM;IAK7C,OAAO,CAAC,MAAM,CAAC,gBAAgB;IA6C/B,uFAAuF;IACvF,OAAO,CAAC,iBAAiB;IASzB,gFAAgF;IAChF,OAAO,CAAC,oBAAoB;IAS5B,2CAA2C;IAC3C,KAAK,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM;IAI3B,oDAAoD;IACpD,cAAc,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM;IAKpC,2EAA2E;IAC3E,cAAc,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM;CAKxC"}
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
const DEFAULT_BPM = 120;
|
|
2
|
+
/** Seconds elapsed for a given tick delta at a constant bpm. */
|
|
3
|
+
function deltaTicksToSeconds(deltaTicks, ppq, bpm) {
|
|
4
|
+
return (deltaTicks / ppq) * (60 / bpm);
|
|
5
|
+
}
|
|
6
|
+
/**
|
|
7
|
+
* Maps between ticks and seconds given a (possibly empty) set of tempo
|
|
8
|
+
* change events. Tempo is piecewise-constant (a bpm value applies from its
|
|
9
|
+
* event's tick until the next event's tick), which makes the tick <-> second
|
|
10
|
+
* mapping piecewise-linear. An empty map behaves as a constant 120 bpm.
|
|
11
|
+
*/
|
|
12
|
+
export class TempoMap {
|
|
13
|
+
constructor(events, ppq) {
|
|
14
|
+
this.ppq = ppq;
|
|
15
|
+
this.breakpoints = TempoMap.buildBreakpoints(events, ppq);
|
|
16
|
+
}
|
|
17
|
+
static buildBreakpoints(events, ppq) {
|
|
18
|
+
const sorted = [...events].sort((a, b) => a.tick - b.tick);
|
|
19
|
+
const breakpoints = [];
|
|
20
|
+
let currentTick = 0;
|
|
21
|
+
let currentSeconds = 0;
|
|
22
|
+
let currentBpm = DEFAULT_BPM;
|
|
23
|
+
if (sorted.length === 0 || sorted[0].tick > 0) {
|
|
24
|
+
breakpoints.push({ tick: 0, seconds: 0, bpm: DEFAULT_BPM });
|
|
25
|
+
}
|
|
26
|
+
for (const event of sorted) {
|
|
27
|
+
if (event.tick === currentTick && breakpoints.length > 0) {
|
|
28
|
+
// Event at the same tick as the running breakpoint: it overrides
|
|
29
|
+
// the bpm effective from that tick (no time has elapsed).
|
|
30
|
+
breakpoints[breakpoints.length - 1] = {
|
|
31
|
+
tick: event.tick,
|
|
32
|
+
seconds: currentSeconds,
|
|
33
|
+
bpm: event.bpm,
|
|
34
|
+
};
|
|
35
|
+
currentBpm = event.bpm;
|
|
36
|
+
continue;
|
|
37
|
+
}
|
|
38
|
+
currentSeconds += deltaTicksToSeconds(event.tick - currentTick, ppq, currentBpm);
|
|
39
|
+
currentTick = event.tick;
|
|
40
|
+
breakpoints.push({
|
|
41
|
+
tick: currentTick,
|
|
42
|
+
seconds: currentSeconds,
|
|
43
|
+
bpm: event.bpm,
|
|
44
|
+
});
|
|
45
|
+
currentBpm = event.bpm;
|
|
46
|
+
}
|
|
47
|
+
return breakpoints;
|
|
48
|
+
}
|
|
49
|
+
/** Finds the last breakpoint at or before `tick` (breakpoints[0].tick is always 0). */
|
|
50
|
+
breakpointForTick(tick) {
|
|
51
|
+
let result = this.breakpoints[0];
|
|
52
|
+
for (const bp of this.breakpoints) {
|
|
53
|
+
if (bp.tick > tick)
|
|
54
|
+
break;
|
|
55
|
+
result = bp;
|
|
56
|
+
}
|
|
57
|
+
return result;
|
|
58
|
+
}
|
|
59
|
+
/** Finds the last breakpoint whose seconds offset is at or before `seconds`. */
|
|
60
|
+
breakpointForSeconds(seconds) {
|
|
61
|
+
let result = this.breakpoints[0];
|
|
62
|
+
for (const bp of this.breakpoints) {
|
|
63
|
+
if (bp.seconds > seconds)
|
|
64
|
+
break;
|
|
65
|
+
result = bp;
|
|
66
|
+
}
|
|
67
|
+
return result;
|
|
68
|
+
}
|
|
69
|
+
/** The bpm in effect at the given tick. */
|
|
70
|
+
bpmAt(tick) {
|
|
71
|
+
return this.breakpointForTick(tick).bpm;
|
|
72
|
+
}
|
|
73
|
+
/** Converts an integer tick position to seconds. */
|
|
74
|
+
ticksToSeconds(tick) {
|
|
75
|
+
const bp = this.breakpointForTick(tick);
|
|
76
|
+
return bp.seconds + deltaTicksToSeconds(tick - bp.tick, this.ppq, bp.bpm);
|
|
77
|
+
}
|
|
78
|
+
/** Converts a time in seconds to a (possibly fractional) tick position. */
|
|
79
|
+
secondsToTicks(seconds) {
|
|
80
|
+
const bp = this.breakpointForSeconds(seconds);
|
|
81
|
+
const deltaSeconds = seconds - bp.seconds;
|
|
82
|
+
return bp.tick + deltaSeconds * (bp.bpm / 60) * this.ppq;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
//# sourceMappingURL=tempo-map.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tempo-map.js","sourceRoot":"","sources":["../../../src/domain/time/tempo-map.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import type { DurationName, Fraction, TimeSignature } from '../../index.js';
|
|
2
|
+
/**
|
|
3
|
+
* Duration name -> fraction of a whole note. Values are consumed by
|
|
4
|
+
* `fractionToTicks`, where a quarter note (1/4) equals `ppq` ticks.
|
|
5
|
+
*/
|
|
6
|
+
export declare const DURATIONS: Record<DurationName, Fraction>;
|
|
7
|
+
/** Integer tick length of a named duration at the given PPQ. */
|
|
8
|
+
export declare function ticksFor(duration: DurationName, ppq: number): number;
|
|
9
|
+
/**
|
|
10
|
+
* The name of a duration `ticks` long, or `null` when nothing is exactly that.
|
|
11
|
+
*
|
|
12
|
+
* The inverse of `ticksFor`, and deliberately exact: a note that is 500 ticks
|
|
13
|
+
* at 480 PPQ is not a quarter note, it is 500 ticks, and a control that
|
|
14
|
+
* rounded it to "quarter" would relabel music it cannot represent. Callers
|
|
15
|
+
* asking "what should the duration selector show" want `null` to mean "no
|
|
16
|
+
* single name fits", which is a different answer from any name at all.
|
|
17
|
+
*
|
|
18
|
+
* Plain search rather than a reverse map: PPQ varies per score, so the tick
|
|
19
|
+
* values are not constants and a cached map would have to be keyed by it for
|
|
20
|
+
* the sake of eighteen comparisons.
|
|
21
|
+
*/
|
|
22
|
+
export declare function durationNameForTicks(ticks: number, ppq: number): DurationName | null;
|
|
23
|
+
/** Integer tick length of one full measure in the given time signature. */
|
|
24
|
+
export declare function measureDurationTicks(ts: TimeSignature, ppq: number): number;
|
|
25
|
+
/**
|
|
26
|
+
* Integer tick length of one beat in the given time signature. Simple meters
|
|
27
|
+
* beat on a single denominator-note (e.g. 4/4 -> quarter). Compound meters
|
|
28
|
+
* (numerator a multiple of 3, > 3) beat on a dotted denominator-note grouping
|
|
29
|
+
* three denominator-notes (e.g. 6/8 -> dotted quarter).
|
|
30
|
+
*/
|
|
31
|
+
export declare function beatDurationTicks(ts: TimeSignature, ppq: number): number;
|
|
32
|
+
/** Tick offsets (relative to measure start) of each beat within a measure. */
|
|
33
|
+
export declare function beatBoundaries(ts: TimeSignature, ppq: number): number[];
|
|
34
|
+
//# sourceMappingURL=ticks.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ticks.d.ts","sourceRoot":"","sources":["../../../src/domain/time/ticks.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,YAAY,EACZ,QAAQ,EACR,aAAa,EACd,MAAM,gBAAgB,CAAC;AA0BxB;;;GAGG;AACH,eAAO,MAAM,SAAS,EAAE,MAAM,CAAC,YAAY,EAAE,QAAQ,CAyBpD,CAAC;AAEF,gEAAgE;AAChE,wBAAgB,QAAQ,CAAC,QAAQ,EAAE,YAAY,EAAE,GAAG,EAAE,MAAM,GAAG,MAAM,CAEpE;AAKD;;;;;;;;;;;;GAYG;AACH,wBAAgB,oBAAoB,CAClC,KAAK,EAAE,MAAM,EACb,GAAG,EAAE,MAAM,GACV,YAAY,GAAG,IAAI,CAErB;AAED,2EAA2E;AAC3E,wBAAgB,oBAAoB,CAAC,EAAE,EAAE,aAAa,EAAE,GAAG,EAAE,MAAM,GAAG,MAAM,CAE3E;AAUD;;;;;GAKG;AACH,wBAAgB,iBAAiB,CAAC,EAAE,EAAE,aAAa,EAAE,GAAG,EAAE,MAAM,GAAG,MAAM,CAMxE;AAED,8EAA8E;AAC9E,wBAAgB,cAAc,CAAC,EAAE,EAAE,aAAa,EAAE,GAAG,EAAE,MAAM,GAAG,MAAM,EAAE,CAQvE"}
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import { fraction, fractionToTicks } from './fraction.js';
|
|
2
|
+
/** Multiplies a fraction by a positive integer/rational scalar expressed as a fraction. */
|
|
3
|
+
function scaleFraction(f, scale) {
|
|
4
|
+
return fraction(f.numerator * scale.numerator, f.denominator * scale.denominator);
|
|
5
|
+
}
|
|
6
|
+
const DOTTED_SCALE = fraction(3, 2);
|
|
7
|
+
const TRIPLET_SCALE = fraction(2, 3);
|
|
8
|
+
const BASE_DURATIONS = {
|
|
9
|
+
whole: fraction(1, 1),
|
|
10
|
+
half: fraction(1, 2),
|
|
11
|
+
quarter: fraction(1, 4),
|
|
12
|
+
eighth: fraction(1, 8),
|
|
13
|
+
sixteenth: fraction(1, 16),
|
|
14
|
+
thirtysecond: fraction(1, 32),
|
|
15
|
+
};
|
|
16
|
+
/**
|
|
17
|
+
* Duration name -> fraction of a whole note. Values are consumed by
|
|
18
|
+
* `fractionToTicks`, where a quarter note (1/4) equals `ppq` ticks.
|
|
19
|
+
*/
|
|
20
|
+
export const DURATIONS = {
|
|
21
|
+
whole: BASE_DURATIONS.whole,
|
|
22
|
+
half: BASE_DURATIONS.half,
|
|
23
|
+
quarter: BASE_DURATIONS.quarter,
|
|
24
|
+
eighth: BASE_DURATIONS.eighth,
|
|
25
|
+
sixteenth: BASE_DURATIONS.sixteenth,
|
|
26
|
+
thirtysecond: BASE_DURATIONS.thirtysecond,
|
|
27
|
+
'dotted-whole': scaleFraction(BASE_DURATIONS.whole, DOTTED_SCALE),
|
|
28
|
+
'dotted-half': scaleFraction(BASE_DURATIONS.half, DOTTED_SCALE),
|
|
29
|
+
'dotted-quarter': scaleFraction(BASE_DURATIONS.quarter, DOTTED_SCALE),
|
|
30
|
+
'dotted-eighth': scaleFraction(BASE_DURATIONS.eighth, DOTTED_SCALE),
|
|
31
|
+
'dotted-sixteenth': scaleFraction(BASE_DURATIONS.sixteenth, DOTTED_SCALE),
|
|
32
|
+
'dotted-thirtysecond': scaleFraction(BASE_DURATIONS.thirtysecond, DOTTED_SCALE),
|
|
33
|
+
'triplet-whole': scaleFraction(BASE_DURATIONS.whole, TRIPLET_SCALE),
|
|
34
|
+
'triplet-half': scaleFraction(BASE_DURATIONS.half, TRIPLET_SCALE),
|
|
35
|
+
'triplet-quarter': scaleFraction(BASE_DURATIONS.quarter, TRIPLET_SCALE),
|
|
36
|
+
'triplet-eighth': scaleFraction(BASE_DURATIONS.eighth, TRIPLET_SCALE),
|
|
37
|
+
'triplet-sixteenth': scaleFraction(BASE_DURATIONS.sixteenth, TRIPLET_SCALE),
|
|
38
|
+
'triplet-thirtysecond': scaleFraction(BASE_DURATIONS.thirtysecond, TRIPLET_SCALE),
|
|
39
|
+
};
|
|
40
|
+
/** Integer tick length of a named duration at the given PPQ. */
|
|
41
|
+
export function ticksFor(duration, ppq) {
|
|
42
|
+
return fractionToTicks(DURATIONS[duration], ppq);
|
|
43
|
+
}
|
|
44
|
+
/** Every duration name, in the order the table declares them. */
|
|
45
|
+
const DURATION_NAMES = Object.keys(DURATIONS);
|
|
46
|
+
/**
|
|
47
|
+
* The name of a duration `ticks` long, or `null` when nothing is exactly that.
|
|
48
|
+
*
|
|
49
|
+
* The inverse of `ticksFor`, and deliberately exact: a note that is 500 ticks
|
|
50
|
+
* at 480 PPQ is not a quarter note, it is 500 ticks, and a control that
|
|
51
|
+
* rounded it to "quarter" would relabel music it cannot represent. Callers
|
|
52
|
+
* asking "what should the duration selector show" want `null` to mean "no
|
|
53
|
+
* single name fits", which is a different answer from any name at all.
|
|
54
|
+
*
|
|
55
|
+
* Plain search rather than a reverse map: PPQ varies per score, so the tick
|
|
56
|
+
* values are not constants and a cached map would have to be keyed by it for
|
|
57
|
+
* the sake of eighteen comparisons.
|
|
58
|
+
*/
|
|
59
|
+
export function durationNameForTicks(ticks, ppq) {
|
|
60
|
+
return DURATION_NAMES.find(name => ticksFor(name, ppq) === ticks) ?? null;
|
|
61
|
+
}
|
|
62
|
+
/** Integer tick length of one full measure in the given time signature. */
|
|
63
|
+
export function measureDurationTicks(ts, ppq) {
|
|
64
|
+
return fractionToTicks(fraction(ts.numerator, ts.denominator), ppq);
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* Whether a time signature is compound (the beat subdivides into three,
|
|
68
|
+
* e.g. 6/8, 9/8, 12/8, 6/4): numerator is a multiple of 3 greater than 3.
|
|
69
|
+
*/
|
|
70
|
+
function isCompoundMeter(ts) {
|
|
71
|
+
return ts.numerator % 3 === 0 && ts.numerator > 3;
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* Integer tick length of one beat in the given time signature. Simple meters
|
|
75
|
+
* beat on a single denominator-note (e.g. 4/4 -> quarter). Compound meters
|
|
76
|
+
* (numerator a multiple of 3, > 3) beat on a dotted denominator-note grouping
|
|
77
|
+
* three denominator-notes (e.g. 6/8 -> dotted quarter).
|
|
78
|
+
*/
|
|
79
|
+
export function beatDurationTicks(ts, ppq) {
|
|
80
|
+
const denominatorNoteTicks = fractionToTicks(fraction(1, ts.denominator), ppq);
|
|
81
|
+
return isCompoundMeter(ts) ? 3 * denominatorNoteTicks : denominatorNoteTicks;
|
|
82
|
+
}
|
|
83
|
+
/** Tick offsets (relative to measure start) of each beat within a measure. */
|
|
84
|
+
export function beatBoundaries(ts, ppq) {
|
|
85
|
+
const measureTicks = measureDurationTicks(ts, ppq);
|
|
86
|
+
const beatTicks = beatDurationTicks(ts, ppq);
|
|
87
|
+
const boundaries = [];
|
|
88
|
+
for (let offset = 0; offset < measureTicks; offset += beatTicks) {
|
|
89
|
+
boundaries.push(offset);
|
|
90
|
+
}
|
|
91
|
+
return boundaries;
|
|
92
|
+
}
|
|
93
|
+
//# sourceMappingURL=ticks.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ticks.js","sourceRoot":"","sources":["../../../src/domain/time/ticks.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Validation issue types (spec §23). `validateScore` (`./validator.ts`)
|
|
3
|
+
* returns `ValidationIssue[]`; the UI displays them and, via `objectId`/
|
|
4
|
+
* `trackId`/`measureId`, navigates to the affected object on click.
|
|
5
|
+
*/
|
|
6
|
+
export type ValidationSeverity = 'error' | 'warning';
|
|
7
|
+
export type ValidationIssue = {
|
|
8
|
+
severity: ValidationSeverity;
|
|
9
|
+
code: string;
|
|
10
|
+
message: string;
|
|
11
|
+
objectId?: string;
|
|
12
|
+
trackId?: string;
|
|
13
|
+
measureId?: string;
|
|
14
|
+
};
|
|
15
|
+
/**
|
|
16
|
+
* Stable, machine-readable codes for every rule `validateScore` checks.
|
|
17
|
+
* Kept as a `const` object (not a string-literal union on `ValidationIssue`)
|
|
18
|
+
* so new rules can be added without a breaking type change to consumers
|
|
19
|
+
* that only pattern-match on known codes.
|
|
20
|
+
*/
|
|
21
|
+
export declare const ISSUE_CODES: {
|
|
22
|
+
readonly DUPLICATE_ID: "duplicate-id";
|
|
23
|
+
readonly INVALID_PITCH_RANGE: "invalid-pitch-range";
|
|
24
|
+
readonly NON_POSITIVE_DURATION: "non-positive-duration";
|
|
25
|
+
readonly NEGATIVE_TICK: "negative-tick";
|
|
26
|
+
readonly INVALID_VELOCITY: "invalid-velocity";
|
|
27
|
+
readonly INVALID_MIDI_PROGRAM: "invalid-midi-program";
|
|
28
|
+
readonly INVALID_MIDI_CHANNEL: "invalid-midi-channel";
|
|
29
|
+
readonly INVALID_TIME_SIGNATURE: "invalid-time-signature";
|
|
30
|
+
readonly INVALID_KEY_SIGNATURE: "invalid-key-signature";
|
|
31
|
+
readonly MEASURE_OVERFULL: "measure-overfull";
|
|
32
|
+
readonly MEASURE_UNDERFULL: "measure-underfull";
|
|
33
|
+
readonly EVENT_OUTSIDE_MEASURE: "event-outside-measure";
|
|
34
|
+
readonly MISSING_TIE_TARGET: "missing-tie-target";
|
|
35
|
+
readonly INVALID_TRACK_REFERENCE: "invalid-track-reference";
|
|
36
|
+
readonly INVALID_VOICE_REFERENCE: "invalid-voice-reference";
|
|
37
|
+
readonly OVERLAPPING_SAME_PITCH: "overlapping-same-pitch";
|
|
38
|
+
readonly TOO_MANY_SIMULTANEOUS_NOTES: "too-many-simultaneous-notes";
|
|
39
|
+
readonly TEMPO_MAP_UNSORTED: "tempo-map-unsorted";
|
|
40
|
+
readonly INVALID_TEMPO_BPM: "invalid-tempo-bpm";
|
|
41
|
+
readonly MEASURE_ORDERING: "measure-ordering";
|
|
42
|
+
};
|
|
43
|
+
export type IssueCode = (typeof ISSUE_CODES)[keyof typeof ISSUE_CODES];
|
|
44
|
+
//# sourceMappingURL=issues.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"issues.d.ts","sourceRoot":"","sources":["../../../src/domain/validation/issues.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,MAAM,MAAM,kBAAkB,GAAG,OAAO,GAAG,SAAS,CAAC;AAErD,MAAM,MAAM,eAAe,GAAG;IAC5B,QAAQ,EAAE,kBAAkB,CAAC;IAC7B,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB,CAAC;AAEF;;;;;GAKG;AACH,eAAO,MAAM,WAAW;;;;;;;;;;;;;;;;;;;;;CAqBd,CAAC;AAEX,MAAM,MAAM,SAAS,GAAG,CAAC,OAAO,WAAW,CAAC,CAAC,MAAM,OAAO,WAAW,CAAC,CAAC"}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Validation issue types (spec §23). `validateScore` (`./validator.ts`)
|
|
3
|
+
* returns `ValidationIssue[]`; the UI displays them and, via `objectId`/
|
|
4
|
+
* `trackId`/`measureId`, navigates to the affected object on click.
|
|
5
|
+
*/
|
|
6
|
+
/**
|
|
7
|
+
* Stable, machine-readable codes for every rule `validateScore` checks.
|
|
8
|
+
* Kept as a `const` object (not a string-literal union on `ValidationIssue`)
|
|
9
|
+
* so new rules can be added without a breaking type change to consumers
|
|
10
|
+
* that only pattern-match on known codes.
|
|
11
|
+
*/
|
|
12
|
+
export const ISSUE_CODES = {
|
|
13
|
+
DUPLICATE_ID: 'duplicate-id',
|
|
14
|
+
INVALID_PITCH_RANGE: 'invalid-pitch-range',
|
|
15
|
+
NON_POSITIVE_DURATION: 'non-positive-duration',
|
|
16
|
+
NEGATIVE_TICK: 'negative-tick',
|
|
17
|
+
INVALID_VELOCITY: 'invalid-velocity',
|
|
18
|
+
INVALID_MIDI_PROGRAM: 'invalid-midi-program',
|
|
19
|
+
INVALID_MIDI_CHANNEL: 'invalid-midi-channel',
|
|
20
|
+
INVALID_TIME_SIGNATURE: 'invalid-time-signature',
|
|
21
|
+
INVALID_KEY_SIGNATURE: 'invalid-key-signature',
|
|
22
|
+
MEASURE_OVERFULL: 'measure-overfull',
|
|
23
|
+
MEASURE_UNDERFULL: 'measure-underfull',
|
|
24
|
+
EVENT_OUTSIDE_MEASURE: 'event-outside-measure',
|
|
25
|
+
MISSING_TIE_TARGET: 'missing-tie-target',
|
|
26
|
+
INVALID_TRACK_REFERENCE: 'invalid-track-reference',
|
|
27
|
+
INVALID_VOICE_REFERENCE: 'invalid-voice-reference',
|
|
28
|
+
OVERLAPPING_SAME_PITCH: 'overlapping-same-pitch',
|
|
29
|
+
TOO_MANY_SIMULTANEOUS_NOTES: 'too-many-simultaneous-notes',
|
|
30
|
+
TEMPO_MAP_UNSORTED: 'tempo-map-unsorted',
|
|
31
|
+
INVALID_TEMPO_BPM: 'invalid-tempo-bpm',
|
|
32
|
+
MEASURE_ORDERING: 'measure-ordering',
|
|
33
|
+
};
|
|
34
|
+
//# sourceMappingURL=issues.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"issues.js","sourceRoot":"","sources":["../../../src/domain/validation/issues.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAaH;;;;;GAKG;AACH,MAAM,CAAC,MAAM,WAAW,GAAG;IACzB,YAAY,EAAE,cAAc;IAC5B,mBAAmB,EAAE,qBAAqB;IAC1C,qBAAqB,EAAE,uBAAuB;IAC9C,aAAa,EAAE,eAAe;IAC9B,gBAAgB,EAAE,kBAAkB;IACpC,oBAAoB,EAAE,sBAAsB;IAC5C,oBAAoB,EAAE,sBAAsB;IAC5C,sBAAsB,EAAE,wBAAwB;IAChD,qBAAqB,EAAE,uBAAuB;IAC9C,gBAAgB,EAAE,kBAAkB;IACpC,iBAAiB,EAAE,mBAAmB;IACtC,qBAAqB,EAAE,uBAAuB;IAC9C,kBAAkB,EAAE,oBAAoB;IACxC,uBAAuB,EAAE,yBAAyB;IAClD,uBAAuB,EAAE,yBAAyB;IAClD,sBAAsB,EAAE,wBAAwB;IAChD,2BAA2B,EAAE,6BAA6B;IAC1D,kBAAkB,EAAE,oBAAoB;IACxC,iBAAiB,EAAE,mBAAmB;IACtC,gBAAgB,EAAE,kBAAkB;CAC5B,CAAC"}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Score-validation engine (spec §23). `validateScore` checks a `Score`
|
|
3
|
+
* against every rule in the Task 4 brief and returns `ValidationIssue[]`
|
|
4
|
+
* (empty when the score is fully consistent). Pure and read-only: never
|
|
5
|
+
* mutates its input.
|
|
6
|
+
*/
|
|
7
|
+
import type { Score } from '../../index.js';
|
|
8
|
+
import type { ValidationIssue } from './issues.js';
|
|
9
|
+
/** Validates a `Score` against every rule in spec §23 (Task 4 brief scope). Read-only; never mutates `score`. */
|
|
10
|
+
export declare function validateScore(score: Score): ValidationIssue[];
|
|
11
|
+
//# sourceMappingURL=validator.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"validator.d.ts","sourceRoot":"","sources":["../../../src/domain/validation/validator.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH,OAAO,KAAK,EAGV,KAAK,EAGN,MAAM,gBAAgB,CAAC;AAMxB,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AA0lBnD,iHAAiH;AACjH,wBAAgB,aAAa,CAAC,KAAK,EAAE,KAAK,GAAG,eAAe,EAAE,CAsB7D"}
|