@datamoc/mw_games 0.22.0 → 0.24.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +78 -2
- package/README.md +42 -0
- package/dist/ai/Neural.d.ts +65 -0
- package/dist/ai/Neural.d.ts.map +1 -0
- package/dist/ai/Neural.js +169 -0
- package/dist/ai/Neural.js.map +1 -0
- package/dist/ai/index.d.ts +2 -0
- package/dist/ai/index.d.ts.map +1 -1
- package/dist/ai/index.js +1 -0
- package/dist/ai/index.js.map +1 -1
- package/dist/audio/Midi.d.ts +24 -0
- package/dist/audio/Midi.d.ts.map +1 -1
- package/dist/audio/Midi.js +21 -0
- package/dist/audio/Midi.js.map +1 -1
- package/dist/audio/humanize/Groove.d.ts +80 -0
- package/dist/audio/humanize/Groove.d.ts.map +1 -0
- package/dist/audio/humanize/Groove.js +102 -0
- package/dist/audio/humanize/Groove.js.map +1 -0
- package/dist/audio/humanize/GrooveConverter.d.ts +49 -0
- package/dist/audio/humanize/GrooveConverter.d.ts.map +1 -0
- package/dist/audio/humanize/GrooveConverter.js +344 -0
- package/dist/audio/humanize/GrooveConverter.js.map +1 -0
- package/dist/audio/humanize/GrooveExtractor.d.ts +49 -0
- package/dist/audio/humanize/GrooveExtractor.d.ts.map +1 -0
- package/dist/audio/humanize/GrooveExtractor.js +179 -0
- package/dist/audio/humanize/GrooveExtractor.js.map +1 -0
- package/dist/audio/humanize/GrooveHumanizer.d.ts +34 -0
- package/dist/audio/humanize/GrooveHumanizer.d.ts.map +1 -0
- package/dist/audio/humanize/GrooveHumanizer.js +37 -0
- package/dist/audio/humanize/GrooveHumanizer.js.map +1 -0
- package/dist/audio/humanize/Humanize.d.ts +54 -0
- package/dist/audio/humanize/Humanize.d.ts.map +1 -0
- package/dist/audio/humanize/Humanize.js +239 -0
- package/dist/audio/humanize/Humanize.js.map +1 -0
- package/dist/audio/humanize/HumanizerFactory.d.ts +68 -0
- package/dist/audio/humanize/HumanizerFactory.d.ts.map +1 -0
- package/dist/audio/humanize/HumanizerFactory.js +84 -0
- package/dist/audio/humanize/HumanizerFactory.js.map +1 -0
- package/dist/audio/humanize/LiteHumanizer.d.ts +30 -0
- package/dist/audio/humanize/LiteHumanizer.d.ts.map +1 -0
- package/dist/audio/humanize/LiteHumanizer.js +35 -0
- package/dist/audio/humanize/LiteHumanizer.js.map +1 -0
- package/dist/audio/humanize/Onsets.d.ts +26 -0
- package/dist/audio/humanize/Onsets.d.ts.map +1 -0
- package/dist/audio/humanize/Onsets.js +164 -0
- package/dist/audio/humanize/Onsets.js.map +1 -0
- package/dist/audio/index.d.ts +11 -0
- package/dist/audio/index.d.ts.map +1 -1
- package/dist/audio/index.js +6 -0
- package/dist/audio/index.js.map +1 -1
- package/dist/board/tourney.d.ts +8 -1
- package/dist/board/tourney.d.ts.map +1 -1
- package/dist/board/tourney.js +8 -2
- package/dist/board/tourney.js.map +1 -1
- package/dist/core/Random.d.ts +1 -0
- package/dist/core/Random.d.ts.map +1 -1
- package/dist/core/Random.js +14 -17
- package/dist/core/Random.js.map +1 -1
- package/dist/mw_games.global.js +108 -108
- package/dist/rpg/MoveRoute.d.ts +35 -4
- package/dist/rpg/MoveRoute.d.ts.map +1 -1
- package/dist/rpg/MoveRoute.js +40 -11
- package/dist/rpg/MoveRoute.js.map +1 -1
- package/dist/simulation/Training.d.ts +143 -0
- package/dist/simulation/Training.d.ts.map +1 -0
- package/dist/simulation/Training.js +279 -0
- package/dist/simulation/Training.js.map +1 -0
- package/dist/simulation/index.d.ts +2 -0
- package/dist/simulation/index.d.ts.map +1 -1
- package/dist/simulation/index.js +1 -0
- package/dist/simulation/index.js.map +1 -1
- package/dist/two-d/render/RpgmAutotile.d.ts +37 -0
- package/dist/two-d/render/RpgmAutotile.d.ts.map +1 -1
- package/dist/two-d/render/RpgmAutotile.js +67 -1
- package/dist/two-d/render/RpgmAutotile.js.map +1 -1
- package/dist/two-d/render/TileMap.d.ts +36 -0
- package/dist/two-d/render/TileMap.d.ts.map +1 -1
- package/dist/two-d/render/TileMap.js +89 -1
- package/dist/two-d/render/TileMap.js.map +1 -1
- package/dist/two-d/render/index.d.ts +3 -3
- package/dist/two-d/render/index.d.ts.map +1 -1
- package/dist/two-d/render/index.js +1 -1
- package/dist/two-d/render/index.js.map +1 -1
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +15 -6
- package/sbom.cdx.json +194 -32
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Groove templates: a quantized performance carried as data. Every slot of one bar says
|
|
3
|
+
* how far its notes sit from the grid and how loud they are relative to usual, so a
|
|
4
|
+
* template can be extracted from a performance, exchanged, and played back into another.
|
|
5
|
+
*
|
|
6
|
+
* The offsets are dense and ordered by construction - slot 0 through the last slot of the
|
|
7
|
+
* bar - which is what lets a consumer look a note's slot up directly instead of searching,
|
|
8
|
+
* and what lets an import recognise a truncated or hand-edited file by its length alone.
|
|
9
|
+
* `timeSignature` and `subdivision` are part of the data rather than outside context: the
|
|
10
|
+
* same offsets mean different things against a bar of 4/4 and a bar of 6/8, and a template
|
|
11
|
+
* moved between the two without saying so is a silent misreading.
|
|
12
|
+
*/
|
|
13
|
+
/** how finely a bar is cut up: an eighth, sixteenth or thirty-second grid */
|
|
14
|
+
export type GridSubdivision = '8n' | '16n' | '32n';
|
|
15
|
+
export interface GrooveOffset {
|
|
16
|
+
/** which slot of the bar this describes, counting from 0 */
|
|
17
|
+
subdivisionIndex: number;
|
|
18
|
+
/** how far the slot is played from its grid position, in ticks; negative is early */
|
|
19
|
+
timeOffsetTicks: number;
|
|
20
|
+
/** velocity multiplier for the slot: 1 unchanged, 1.1 a tenth louder, 0 muted */
|
|
21
|
+
velocityFactor: number;
|
|
22
|
+
}
|
|
23
|
+
export interface GrooveTemplate {
|
|
24
|
+
/** a stable name: the file it came from, or whatever a DAW calls it */
|
|
25
|
+
name: string;
|
|
26
|
+
/** numerator and denominator of the bar the slots divide up, e.g. [4, 4] */
|
|
27
|
+
timeSignature: [number, number];
|
|
28
|
+
/** ticks per quarter note, the same quantity `MidiFile.ticksPerQuarter` reports */
|
|
29
|
+
ppq: number;
|
|
30
|
+
/** the tempo the offsets were measured at; applying it elsewhere rescales them in time */
|
|
31
|
+
tempoBpm: number;
|
|
32
|
+
subdivision: GridSubdivision;
|
|
33
|
+
/** one entry per slot of the bar, in slot order */
|
|
34
|
+
offsets: GrooveOffset[];
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* How many slots a bar of this signature has on this grid: the number of subdivisions
|
|
38
|
+
* that fit in `beats * (4 / beatValue)` quarters. It throws when the grid does not tile
|
|
39
|
+
* the bar exactly (an eighth grid against a bar of three sixteenths), because a template
|
|
40
|
+
* with a fractional slot count would wrap onto a position no note can land on.
|
|
41
|
+
*
|
|
42
|
+
* @example
|
|
43
|
+
* ```ts
|
|
44
|
+
* import { subdivisionCount } from '@datamoc/mw_games/audio';
|
|
45
|
+
*
|
|
46
|
+
* console.log(subdivisionCount([4, 4], '16n')); // 16: a bar of 4/4 cut into sixteenths
|
|
47
|
+
* console.log(subdivisionCount([6, 8], '8n')); // 6: a bar of 6/8 cut into eighths
|
|
48
|
+
* ```
|
|
49
|
+
*/
|
|
50
|
+
export declare function subdivisionCount(timeSignature: [number, number], subdivision: GridSubdivision): number;
|
|
51
|
+
/** ticks between two grid slots of this subdivision in a file with this ppq (480 and '16n' is 120) */
|
|
52
|
+
export declare function subdivisionTicks(pq: number, subdivision: GridSubdivision): number;
|
|
53
|
+
/**
|
|
54
|
+
* Checks every invariant of a `GrooveTemplate` at once, so a template read from a file or
|
|
55
|
+
* handed in from outside fails here rather than as a misaligned grid several frames later.
|
|
56
|
+
* The engines call it before applying a template, and the JSON import calls it before
|
|
57
|
+
* returning one.
|
|
58
|
+
*
|
|
59
|
+
* @example
|
|
60
|
+
* ```ts
|
|
61
|
+
* import { assertGrooveTemplate, subdivisionCount } from '@datamoc/mw_games/audio';
|
|
62
|
+
*
|
|
63
|
+
* const template = {
|
|
64
|
+
* name: 'four-on-the-floor',
|
|
65
|
+
* timeSignature: [4, 4] as [number, number],
|
|
66
|
+
* ppq: 480,
|
|
67
|
+
* tempoBpm: 120,
|
|
68
|
+
* subdivision: '16n' as const,
|
|
69
|
+
* offsets: Array.from({ length: subdivisionCount([4, 4], '16n') }, (_, index) => ({
|
|
70
|
+
* subdivisionIndex: index,
|
|
71
|
+
* timeOffsetTicks: 0,
|
|
72
|
+
* velocityFactor: 1,
|
|
73
|
+
* })),
|
|
74
|
+
* };
|
|
75
|
+
*
|
|
76
|
+
* assertGrooveTemplate(template); // throws when a slot is missing or a tick is fractional
|
|
77
|
+
* ```
|
|
78
|
+
*/
|
|
79
|
+
export declare function assertGrooveTemplate(template: GrooveTemplate): void;
|
|
80
|
+
//# sourceMappingURL=Groove.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"Groove.d.ts","sourceRoot":"","sources":["../../../src/audio/humanize/Groove.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,6EAA6E;AAC7E,MAAM,MAAM,eAAe,GAAG,IAAI,GAAG,KAAK,GAAG,KAAK,CAAC;AAEnD,MAAM,WAAW,YAAY;IAC5B,4DAA4D;IAC5D,gBAAgB,EAAE,MAAM,CAAC;IACzB,qFAAqF;IACrF,eAAe,EAAE,MAAM,CAAC;IACxB,iFAAiF;IACjF,cAAc,EAAE,MAAM,CAAC;CACvB;AAED,MAAM,WAAW,cAAc;IAC9B,uEAAuE;IACvE,IAAI,EAAE,MAAM,CAAC;IACb,4EAA4E;IAC5E,aAAa,EAAE,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAChC,mFAAmF;IACnF,GAAG,EAAE,MAAM,CAAC;IACZ,0FAA0F;IAC1F,QAAQ,EAAE,MAAM,CAAC;IACjB,WAAW,EAAE,eAAe,CAAC;IAC7B,mDAAmD;IACnD,OAAO,EAAE,YAAY,EAAE,CAAC;CACxB;AAKD;;;;;;;;;;;;;GAaG;AACH,wBAAgB,gBAAgB,CAAC,aAAa,EAAE,CAAC,MAAM,EAAE,MAAM,CAAC,EAAE,WAAW,EAAE,eAAe,GAAG,MAAM,CAQtG;AAED,sGAAsG;AACtG,wBAAgB,gBAAgB,CAAC,EAAE,EAAE,MAAM,EAAE,WAAW,EAAE,eAAe,GAAG,MAAM,CAEjF;AAED;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AACH,wBAAgB,oBAAoB,CAAC,QAAQ,EAAE,cAAc,GAAG,IAAI,CAqDnE"}
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Groove templates: a quantized performance carried as data. Every slot of one bar says
|
|
3
|
+
* how far its notes sit from the grid and how loud they are relative to usual, so a
|
|
4
|
+
* template can be extracted from a performance, exchanged, and played back into another.
|
|
5
|
+
*
|
|
6
|
+
* The offsets are dense and ordered by construction - slot 0 through the last slot of the
|
|
7
|
+
* bar - which is what lets a consumer look a note's slot up directly instead of searching,
|
|
8
|
+
* and what lets an import recognise a truncated or hand-edited file by its length alone.
|
|
9
|
+
* `timeSignature` and `subdivision` are part of the data rather than outside context: the
|
|
10
|
+
* same offsets mean different things against a bar of 4/4 and a bar of 6/8, and a template
|
|
11
|
+
* moved between the two without saying so is a silent misreading.
|
|
12
|
+
*/
|
|
13
|
+
/** subdivisions that fit in one quarter note: a quarter is two eighths, four sixteenths */
|
|
14
|
+
const SUBDIVISIONS_PER_QUARTER = { '8n': 2, '16n': 4, '32n': 8 };
|
|
15
|
+
/**
|
|
16
|
+
* How many slots a bar of this signature has on this grid: the number of subdivisions
|
|
17
|
+
* that fit in `beats * (4 / beatValue)` quarters. It throws when the grid does not tile
|
|
18
|
+
* the bar exactly (an eighth grid against a bar of three sixteenths), because a template
|
|
19
|
+
* with a fractional slot count would wrap onto a position no note can land on.
|
|
20
|
+
*
|
|
21
|
+
* @example
|
|
22
|
+
* ```ts
|
|
23
|
+
* import { subdivisionCount } from '@datamoc/mw_games/audio';
|
|
24
|
+
*
|
|
25
|
+
* console.log(subdivisionCount([4, 4], '16n')); // 16: a bar of 4/4 cut into sixteenths
|
|
26
|
+
* console.log(subdivisionCount([6, 8], '8n')); // 6: a bar of 6/8 cut into eighths
|
|
27
|
+
* ```
|
|
28
|
+
*/
|
|
29
|
+
export function subdivisionCount(timeSignature, subdivision) {
|
|
30
|
+
const [beats, beatValue] = timeSignature;
|
|
31
|
+
const slots = (beats * 4 * SUBDIVISIONS_PER_QUARTER[subdivision]) / beatValue;
|
|
32
|
+
if (!Number.isInteger(slots) || slots < 1)
|
|
33
|
+
throw new Error(`a ${subdivision} grid does not cut a ${beats}/${beatValue} bar into whole slots (it needs ${slots})`);
|
|
34
|
+
return slots;
|
|
35
|
+
}
|
|
36
|
+
/** ticks between two grid slots of this subdivision in a file with this ppq (480 and '16n' is 120) */
|
|
37
|
+
export function subdivisionTicks(pq, subdivision) {
|
|
38
|
+
return pq / SUBDIVISIONS_PER_QUARTER[subdivision];
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Checks every invariant of a `GrooveTemplate` at once, so a template read from a file or
|
|
42
|
+
* handed in from outside fails here rather than as a misaligned grid several frames later.
|
|
43
|
+
* The engines call it before applying a template, and the JSON import calls it before
|
|
44
|
+
* returning one.
|
|
45
|
+
*
|
|
46
|
+
* @example
|
|
47
|
+
* ```ts
|
|
48
|
+
* import { assertGrooveTemplate, subdivisionCount } from '@datamoc/mw_games/audio';
|
|
49
|
+
*
|
|
50
|
+
* const template = {
|
|
51
|
+
* name: 'four-on-the-floor',
|
|
52
|
+
* timeSignature: [4, 4] as [number, number],
|
|
53
|
+
* ppq: 480,
|
|
54
|
+
* tempoBpm: 120,
|
|
55
|
+
* subdivision: '16n' as const,
|
|
56
|
+
* offsets: Array.from({ length: subdivisionCount([4, 4], '16n') }, (_, index) => ({
|
|
57
|
+
* subdivisionIndex: index,
|
|
58
|
+
* timeOffsetTicks: 0,
|
|
59
|
+
* velocityFactor: 1,
|
|
60
|
+
* })),
|
|
61
|
+
* };
|
|
62
|
+
*
|
|
63
|
+
* assertGrooveTemplate(template); // throws when a slot is missing or a tick is fractional
|
|
64
|
+
* ```
|
|
65
|
+
*/
|
|
66
|
+
export function assertGrooveTemplate(template) {
|
|
67
|
+
if (typeof template !== 'object' || template === null)
|
|
68
|
+
throw new Error('a groove template must be an object');
|
|
69
|
+
const { name, timeSignature, ppq, tempoBpm, subdivision, offsets } = template;
|
|
70
|
+
if (typeof name !== 'string' || name.length === 0)
|
|
71
|
+
throw new Error('a groove template needs a non-empty name');
|
|
72
|
+
if (!Array.isArray(timeSignature) || timeSignature.length !== 2)
|
|
73
|
+
throw new Error(`groove template "${name}": timeSignature must be [beats, beatValue]`);
|
|
74
|
+
const [beats, beatValue] = timeSignature;
|
|
75
|
+
if (!Number.isInteger(beats) || beats < 1)
|
|
76
|
+
throw new Error(`groove template "${name}": time signature numerator must be a positive integer, got ${String(beats)}`);
|
|
77
|
+
if (!Number.isInteger(beatValue) || beatValue < 1 || (beatValue & (beatValue - 1)) !== 0)
|
|
78
|
+
throw new Error(`groove template "${name}": time signature denominator must be a power of two, got ${String(beatValue)}`);
|
|
79
|
+
if (!Number.isInteger(ppq) || ppq < 1)
|
|
80
|
+
throw new Error(`groove template "${name}": ppq must be a positive integer, got ${String(ppq)}`);
|
|
81
|
+
if (!Number.isFinite(tempoBpm) || tempoBpm <= 0)
|
|
82
|
+
throw new Error(`groove template "${name}": tempoBpm must be greater than 0, got ${String(tempoBpm)}`);
|
|
83
|
+
if (subdivision !== '8n' && subdivision !== '16n' && subdivision !== '32n')
|
|
84
|
+
throw new Error(`groove template "${name}": subdivision must be 8n, 16n or 32n, got ${String(subdivision)}`);
|
|
85
|
+
const tickSpan = subdivisionTicks(ppq, subdivision);
|
|
86
|
+
if (!Number.isInteger(tickSpan) || tickSpan < 1)
|
|
87
|
+
throw new Error(`groove template "${name}": ${ppq} ppq does not cut into whole ${subdivision} ticks (it needs ${String(tickSpan)})`);
|
|
88
|
+
const slots = subdivisionCount(timeSignature, subdivision);
|
|
89
|
+
if (!Array.isArray(offsets) || offsets.length !== slots)
|
|
90
|
+
throw new Error(`groove template "${name}": a ${subdivision} grid in ${beats}/${beatValue} has ${slots} slots, this carries ${Array.isArray(offsets) ? offsets.length : String(offsets)}`);
|
|
91
|
+
offsets.forEach((offset, index) => {
|
|
92
|
+
if (typeof offset !== 'object' || offset === null)
|
|
93
|
+
throw new Error(`groove template "${name}": slot ${index} must be an object`);
|
|
94
|
+
if (offset.subdivisionIndex !== index)
|
|
95
|
+
throw new Error(`groove template "${name}": slots must run in order, slot ${index} carries ${String(offset.subdivisionIndex)}`);
|
|
96
|
+
if (!Number.isInteger(offset.timeOffsetTicks))
|
|
97
|
+
throw new Error(`groove template "${name}": slot ${index} has a fractional timeOffsetTicks (${String(offset.timeOffsetTicks)}); ticks are whole numbers`);
|
|
98
|
+
if (!Number.isFinite(offset.velocityFactor) || offset.velocityFactor < 0)
|
|
99
|
+
throw new Error(`groove template "${name}": slot ${index} has velocityFactor ${String(offset.velocityFactor)}, which must be a finite number of at least 0`);
|
|
100
|
+
});
|
|
101
|
+
}
|
|
102
|
+
//# sourceMappingURL=Groove.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"Groove.js","sourceRoot":"","sources":["../../../src/audio/humanize/Groove.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { type GrooveTemplate } from './Groove.ts';
|
|
2
|
+
/**
|
|
3
|
+
* Moving a `GrooveTemplate` in and out of the two formats that can actually carry one:
|
|
4
|
+
* JSON, which carries every field exactly, and a Standard MIDI File, which cannot hold a
|
|
5
|
+
* fractional slot or an unbounded velocity and says so instead of rounding quietly.
|
|
6
|
+
*
|
|
7
|
+
* The MIDI encoding is one note per slot, with a note one subdivision ahead of slot 0 as
|
|
8
|
+
* an anchor. That anchor is why the writer demands `|timeOffsetTicks| < slotTicks`: the
|
|
9
|
+
* reader recovers each offset as *its own grid position*, so an offset a full slot long
|
|
10
|
+
* would be indistinguishable from a slot shift. Velocity rides on the note-on velocity at
|
|
11
|
+
* 1/64 per unit of `velocityFactor`, which fixes the valid factor range to
|
|
12
|
+
* `[1/64, 127/64]` - both ends are enforced rather than clamped.
|
|
13
|
+
*
|
|
14
|
+
* `.agr` and `.reaper-groove` are names in the format union, not shipped readers: both
|
|
15
|
+
* are undocumented proprietary containers, and a half-working guess would silently load
|
|
16
|
+
* the wrong groove rather than fail.
|
|
17
|
+
*/
|
|
18
|
+
/** what a template can be written to and read back from */
|
|
19
|
+
export type GrooveFormat = 'json' | 'midi' | 'ableton-agr' | 'reaper-groove';
|
|
20
|
+
export interface GrooveConverterEngine {
|
|
21
|
+
/** `json` and `midi` return bytes only where the format needs them (MIDI); JSON is text */
|
|
22
|
+
exportFormat(template: GrooveTemplate, format: GrooveFormat): Promise<Uint8Array | string>;
|
|
23
|
+
importFormat(data: Uint8Array | string, format: GrooveFormat): Promise<GrooveTemplate>;
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Writes a template out and reads one back, through JSON (exact) or MIDI (encoded, with
|
|
27
|
+
* the bounds above enforced on the way in). Asking for `.agr` or `.reaper-groove` throws
|
|
28
|
+
* naming what is missing rather than returning something that merely resembles them.
|
|
29
|
+
*
|
|
30
|
+
* @example
|
|
31
|
+
* ```ts
|
|
32
|
+
* import { GrooveConverter, type GrooveTemplate } from '@datamoc/mw_games/audio';
|
|
33
|
+
*
|
|
34
|
+
* declare const template: GrooveTemplate;
|
|
35
|
+
*
|
|
36
|
+
* const converter = new GrooveConverter();
|
|
37
|
+
* const text = await converter.exportFormat(template, 'json');
|
|
38
|
+
* const copy = await converter.importFormat(text, 'json'); // same offsets, same name
|
|
39
|
+
*
|
|
40
|
+
* const bytes = await converter.exportFormat(template, 'midi');
|
|
41
|
+
* const fromMidi = await converter.importFormat(bytes, 'midi');
|
|
42
|
+
* console.log(fromMidi.tempoBpm === template.tempoBpm); // true: tempo travels as microseconds
|
|
43
|
+
* ```
|
|
44
|
+
*/
|
|
45
|
+
export declare class GrooveConverter implements GrooveConverterEngine {
|
|
46
|
+
exportFormat(template: GrooveTemplate, format: GrooveFormat): Promise<Uint8Array | string>;
|
|
47
|
+
importFormat(data: Uint8Array | string, format: GrooveFormat): Promise<GrooveTemplate>;
|
|
48
|
+
}
|
|
49
|
+
//# sourceMappingURL=GrooveConverter.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"GrooveConverter.d.ts","sourceRoot":"","sources":["../../../src/audio/humanize/GrooveConverter.ts"],"names":[],"mappings":"AAAA,OAAO,EAMN,KAAK,cAAc,EACnB,MAAM,aAAa,CAAC;AAErB;;;;;;;;;;;;;;;GAeG;AAEH,2DAA2D;AAC3D,MAAM,MAAM,YAAY,GAAG,MAAM,GAAG,MAAM,GAAG,aAAa,GAAG,eAAe,CAAC;AAE7E,MAAM,WAAW,qBAAqB;IACrC,2FAA2F;IAC3F,YAAY,CAAC,QAAQ,EAAE,cAAc,EAAE,MAAM,EAAE,YAAY,GAAG,OAAO,CAAC,UAAU,GAAG,MAAM,CAAC,CAAC;IAC3F,YAAY,CAAC,IAAI,EAAE,UAAU,GAAG,MAAM,EAAE,MAAM,EAAE,YAAY,GAAG,OAAO,CAAC,cAAc,CAAC,CAAC;CACvF;AAyTD;;;;;;;;;;;;;;;;;;;GAmBG;AACH,qBAAa,eAAgB,YAAW,qBAAqB;IACtD,YAAY,CAAC,QAAQ,EAAE,cAAc,EAAE,MAAM,EAAE,YAAY,GAAG,OAAO,CAAC,UAAU,GAAG,MAAM,CAAC,CAa/F;IAEK,YAAY,CAAC,IAAI,EAAE,UAAU,GAAG,MAAM,EAAE,MAAM,EAAE,YAAY,GAAG,OAAO,CAAC,cAAc,CAAC,CAY3F;CACD"}
|
|
@@ -0,0 +1,344 @@
|
|
|
1
|
+
import { assertGrooveTemplate, subdivisionCount, subdivisionTicks, } from './Groove.js';
|
|
2
|
+
/** every text field in a groove MIDI starts with this, so a stray song cannot impersonate one */
|
|
3
|
+
const GROOVE_PREFIX = 'mwg-groove:';
|
|
4
|
+
/** General MIDI's bass drum, chosen as the first slot's pitch so slots run up from it */
|
|
5
|
+
const FIRST_SLOT_PITCH = 36;
|
|
6
|
+
/** how many velocity units one unit of velocityFactor buys; fixes the whole valid range */
|
|
7
|
+
const VELOCITY_SCALE = 64;
|
|
8
|
+
const GRIDS = ['8n', '16n', '32n'];
|
|
9
|
+
const encoder = new TextEncoder();
|
|
10
|
+
function notShipped(format) {
|
|
11
|
+
throw new Error(`the ${format} format is not shipped: it is an undocumented proprietary container, and a guessed reader could load a different groove than the file holds`);
|
|
12
|
+
}
|
|
13
|
+
function ascii(text) {
|
|
14
|
+
return [...text].map((character) => character.charCodeAt(0) & 0x7f);
|
|
15
|
+
}
|
|
16
|
+
/** MIDI variable-length quantity: seven bits per byte, high bit set while more follow */
|
|
17
|
+
function vlq(value) {
|
|
18
|
+
const bytes = [value & 0x7f];
|
|
19
|
+
let rest = value >>> 7;
|
|
20
|
+
while (rest > 0) {
|
|
21
|
+
bytes.unshift((rest & 0x7f) | 0x80);
|
|
22
|
+
rest >>>= 7;
|
|
23
|
+
}
|
|
24
|
+
return bytes;
|
|
25
|
+
}
|
|
26
|
+
function metaEvent(type, body) {
|
|
27
|
+
return [0xff, type, ...vlq(body.length), ...body];
|
|
28
|
+
}
|
|
29
|
+
function writeGrooveMidi(template) {
|
|
30
|
+
assertGrooveTemplate(template);
|
|
31
|
+
const { name, timeSignature, ppq, subdivision, offsets } = template;
|
|
32
|
+
const slots = subdivisionCount(timeSignature, subdivision);
|
|
33
|
+
const slotTicks = subdivisionTicks(ppq, subdivision);
|
|
34
|
+
if (ppq > 0x7fff)
|
|
35
|
+
throw new Error(`groove template "${name}": ppq ${ppq} does not fit MIDI's 15-bit division`);
|
|
36
|
+
if (FIRST_SLOT_PITCH + slots - 1 > 127)
|
|
37
|
+
throw new Error(`groove template "${name}": ${slots} slots need pitches up to ${FIRST_SLOT_PITCH + slots - 1}, past MIDI's top note of 127`);
|
|
38
|
+
const microseconds = Math.round(60_000_000 / template.tempoBpm);
|
|
39
|
+
if (microseconds < 1 || microseconds > 0xffffff)
|
|
40
|
+
throw new Error(`groove template "${name}": tempoBpm ${template.tempoBpm} does not fit MIDI's three-byte tempo (${microseconds} microseconds)`);
|
|
41
|
+
offsets.forEach((offset, index) => {
|
|
42
|
+
if (Math.abs(offset.timeOffsetTicks) >= slotTicks)
|
|
43
|
+
throw new Error(`groove template "${name}": slot ${index} is offset by ${offset.timeOffsetTicks} ticks, outside the +/-${slotTicks} the MIDI encoding's one-slot anchor can tell from a slot shift`);
|
|
44
|
+
const velocity = Math.round(VELOCITY_SCALE * offset.velocityFactor);
|
|
45
|
+
if (velocity < 1 || velocity > 127)
|
|
46
|
+
throw new Error(`groove template "${name}": slot ${index} has velocityFactor ${offset.velocityFactor}, outside the ${1 / VELOCITY_SCALE} to ${127 / VELOCITY_SCALE} the MIDI encoding can store at ${1 / VELOCITY_SCALE} per velocity unit`);
|
|
47
|
+
});
|
|
48
|
+
const wires = [
|
|
49
|
+
{
|
|
50
|
+
tick: 0,
|
|
51
|
+
bytes: metaEvent(0x51, [(microseconds >> 16) & 0xff, (microseconds >> 8) & 0xff, microseconds & 0xff]),
|
|
52
|
+
},
|
|
53
|
+
{ tick: 0, bytes: metaEvent(0x58, [timeSignature[0], Math.log2(timeSignature[1]), 24, 8]) },
|
|
54
|
+
{ tick: 0, bytes: metaEvent(0x01, [...encoder.encode(GROOVE_PREFIX + name)]) },
|
|
55
|
+
];
|
|
56
|
+
offsets.forEach((offset, index) => {
|
|
57
|
+
//the anchor occupies the first subdivision, so slot i's grid position is one slot
|
|
58
|
+
//later than the anchor plus i more; the offset is measured from that position
|
|
59
|
+
const on = slotTicks * (index + 1) + offset.timeOffsetTicks;
|
|
60
|
+
const off = on + Math.max(1, Math.floor(slotTicks / 4));
|
|
61
|
+
wires.push({
|
|
62
|
+
tick: on,
|
|
63
|
+
bytes: [0x90, FIRST_SLOT_PITCH + index, Math.round(VELOCITY_SCALE * offset.velocityFactor)],
|
|
64
|
+
});
|
|
65
|
+
wires.push({ tick: off, bytes: [0x80, FIRST_SLOT_PITCH + index, 0] });
|
|
66
|
+
});
|
|
67
|
+
wires.sort((a, b) => a.tick - b.tick);
|
|
68
|
+
const track = [];
|
|
69
|
+
let previous = 0;
|
|
70
|
+
for (const wire of wires) {
|
|
71
|
+
track.push(...vlq(wire.tick - previous), ...wire.bytes);
|
|
72
|
+
previous = wire.tick;
|
|
73
|
+
}
|
|
74
|
+
track.push(0, 0xff, 0x2f, 0x00);
|
|
75
|
+
return new Uint8Array([
|
|
76
|
+
...ascii('MThd'),
|
|
77
|
+
0,
|
|
78
|
+
0,
|
|
79
|
+
0,
|
|
80
|
+
6,
|
|
81
|
+
0,
|
|
82
|
+
0,
|
|
83
|
+
0,
|
|
84
|
+
1,
|
|
85
|
+
(ppq >> 8) & 0xff,
|
|
86
|
+
ppq & 0xff,
|
|
87
|
+
...ascii('MTrk'),
|
|
88
|
+
(track.length >>> 24) & 0xff,
|
|
89
|
+
(track.length >>> 16) & 0xff,
|
|
90
|
+
(track.length >>> 8) & 0xff,
|
|
91
|
+
track.length & 0xff,
|
|
92
|
+
...track,
|
|
93
|
+
]);
|
|
94
|
+
}
|
|
95
|
+
/** which grid gives a bar of this signature exactly this many slots */
|
|
96
|
+
function subdivisionFor(timeSignature, slots) {
|
|
97
|
+
for (const candidate of GRIDS) {
|
|
98
|
+
try {
|
|
99
|
+
if (subdivisionCount(timeSignature, candidate) === slots)
|
|
100
|
+
return candidate;
|
|
101
|
+
}
|
|
102
|
+
catch {
|
|
103
|
+
//a grid that does not tile this bar cannot be the answer
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
throw new Error(`a ${slots}-slot groove in ${timeSignature[0]}/${timeSignature[1]} is not one of the 8n, 16n or 32n grids`);
|
|
107
|
+
}
|
|
108
|
+
/**
|
|
109
|
+
* Reads a groove back out of Standard MIDI File bytes.
|
|
110
|
+
*
|
|
111
|
+
* This is a dedicated reader rather than `parseMidi` over the same bytes: a round trip
|
|
112
|
+
* needs the tempo, time-signature and name metas, which `parseMidi` deliberately drops
|
|
113
|
+
* when it flattens a file into gameplay events.
|
|
114
|
+
*/
|
|
115
|
+
function readGrooveMidi(data) {
|
|
116
|
+
const bytes = typeof data === 'string' ? encoder.encode(data) : data;
|
|
117
|
+
const view = new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength);
|
|
118
|
+
let cursor = 0;
|
|
119
|
+
const need = (count, what) => {
|
|
120
|
+
if (cursor + count > bytes.length)
|
|
121
|
+
throw new Error(`truncated MIDI file while reading ${what}`);
|
|
122
|
+
};
|
|
123
|
+
const readAscii = (length, what) => {
|
|
124
|
+
need(length, what);
|
|
125
|
+
let text = '';
|
|
126
|
+
for (let i = 0; i < length; i++)
|
|
127
|
+
text += String.fromCharCode(bytes[cursor + i]);
|
|
128
|
+
cursor += length;
|
|
129
|
+
return text;
|
|
130
|
+
};
|
|
131
|
+
const readU8 = (what) => {
|
|
132
|
+
need(1, what);
|
|
133
|
+
return bytes[cursor++];
|
|
134
|
+
};
|
|
135
|
+
const readU16 = (what) => {
|
|
136
|
+
need(2, what);
|
|
137
|
+
const value = view.getUint16(cursor);
|
|
138
|
+
cursor += 2;
|
|
139
|
+
return value;
|
|
140
|
+
};
|
|
141
|
+
const readU32 = (what) => {
|
|
142
|
+
need(4, what);
|
|
143
|
+
const value = view.getUint32(cursor);
|
|
144
|
+
cursor += 4;
|
|
145
|
+
return value;
|
|
146
|
+
};
|
|
147
|
+
const readVlq = (what) => {
|
|
148
|
+
let value = 0;
|
|
149
|
+
for (let i = 0; i < 4; i++) {
|
|
150
|
+
const byte = readU8(what);
|
|
151
|
+
value = (value << 7) | (byte & 0x7f);
|
|
152
|
+
if ((byte & 0x80) === 0)
|
|
153
|
+
return value;
|
|
154
|
+
}
|
|
155
|
+
throw new Error(`a variable-length quantity in ${what} runs past four bytes`);
|
|
156
|
+
};
|
|
157
|
+
if (readAscii(4, 'the file header') !== 'MThd')
|
|
158
|
+
throw new Error('not a Standard MIDI File: the MThd header is missing');
|
|
159
|
+
const headerLength = readU32('the header length');
|
|
160
|
+
if (headerLength < 6)
|
|
161
|
+
throw new Error(`MThd is ${headerLength} bytes, expected at least 6`);
|
|
162
|
+
const format = readU16('the format');
|
|
163
|
+
const trackCount = readU16('the track count');
|
|
164
|
+
const division = readU16('the division');
|
|
165
|
+
cursor += headerLength - 6;
|
|
166
|
+
if (format > 1)
|
|
167
|
+
throw new Error(`MIDI format ${format} is not a file this reader understands`);
|
|
168
|
+
if ((division & 0x8000) !== 0)
|
|
169
|
+
throw new Error('SMPTE time in the division carries no tick grid to read a groove from');
|
|
170
|
+
if (trackCount !== 1)
|
|
171
|
+
throw new Error(`an mwg groove file holds one track, this one has ${trackCount}`);
|
|
172
|
+
if (readAscii(4, 'the track header') !== 'MTrk')
|
|
173
|
+
throw new Error('the MTrk track header is missing');
|
|
174
|
+
const trackLength = readU32('the track length');
|
|
175
|
+
const trackEnd = cursor + trackLength;
|
|
176
|
+
if (trackEnd > bytes.length)
|
|
177
|
+
throw new Error('truncated MIDI file: the track runs past the end of the data');
|
|
178
|
+
let tick = 0;
|
|
179
|
+
let running = 0;
|
|
180
|
+
let microseconds = null;
|
|
181
|
+
let timeSignature = null;
|
|
182
|
+
let grooveName = null;
|
|
183
|
+
let sawEnd = false;
|
|
184
|
+
const onsets = new Map();
|
|
185
|
+
while (cursor < trackEnd) {
|
|
186
|
+
tick += readVlq('a delta time');
|
|
187
|
+
let status = bytes[cursor];
|
|
188
|
+
if (status < 0x80) {
|
|
189
|
+
if (running === 0)
|
|
190
|
+
throw new Error(`data at tick ${tick} follows a delta time with no running status to read it under`);
|
|
191
|
+
//the byte is data, not a status: the channel message still stands from before
|
|
192
|
+
status = running;
|
|
193
|
+
}
|
|
194
|
+
else {
|
|
195
|
+
cursor++;
|
|
196
|
+
if (status < 0xf0)
|
|
197
|
+
running = status;
|
|
198
|
+
}
|
|
199
|
+
if (status === 0xff) {
|
|
200
|
+
const type = readU8('a meta type');
|
|
201
|
+
const length = readVlq('a meta length');
|
|
202
|
+
need(length, 'a meta event');
|
|
203
|
+
const body = cursor;
|
|
204
|
+
if (type === 0x51 && length === 3)
|
|
205
|
+
microseconds = (bytes[body] << 16) | (bytes[body + 1] << 8) | bytes[body + 2];
|
|
206
|
+
else if (type === 0x58 && length >= 2)
|
|
207
|
+
timeSignature = [bytes[body], 2 ** bytes[body + 1]];
|
|
208
|
+
else if ((type === 0x01 || type === 0x03) && length > 0) {
|
|
209
|
+
let text = '';
|
|
210
|
+
for (let i = 0; i < length; i++)
|
|
211
|
+
text += String.fromCharCode(bytes[body + i]);
|
|
212
|
+
if (text.startsWith(GROOVE_PREFIX))
|
|
213
|
+
grooveName = text.slice(GROOVE_PREFIX.length);
|
|
214
|
+
}
|
|
215
|
+
cursor += length;
|
|
216
|
+
if (type === 0x2f)
|
|
217
|
+
sawEnd = true;
|
|
218
|
+
running = 0;
|
|
219
|
+
continue;
|
|
220
|
+
}
|
|
221
|
+
if (status === 0xf0 || status === 0xf7) {
|
|
222
|
+
cursor += readVlq('a sysex length');
|
|
223
|
+
running = 0;
|
|
224
|
+
continue;
|
|
225
|
+
}
|
|
226
|
+
const kind = status & 0xf0;
|
|
227
|
+
if (kind === 0x90 || kind === 0x80) {
|
|
228
|
+
const note = readU8('a note number');
|
|
229
|
+
const velocity = readU8('a note velocity');
|
|
230
|
+
if (kind === 0x90 && velocity > 0) {
|
|
231
|
+
if (onsets.has(note))
|
|
232
|
+
throw new Error(`pitch ${note} carries more than one note-on, which no groove encodes`);
|
|
233
|
+
onsets.set(note, { tick, velocity });
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
else if (kind === 0xb0 || kind === 0xe0) {
|
|
237
|
+
need(2, 'a controller or bend event');
|
|
238
|
+
cursor += 2;
|
|
239
|
+
}
|
|
240
|
+
else if (kind === 0xc0 || kind === 0xd0) {
|
|
241
|
+
need(1, 'a program or channel-aftertouch event');
|
|
242
|
+
cursor += 1;
|
|
243
|
+
}
|
|
244
|
+
else
|
|
245
|
+
throw new Error(`unexpected status byte 0x${status.toString(16)} at tick ${tick}`);
|
|
246
|
+
}
|
|
247
|
+
if (!sawEnd)
|
|
248
|
+
throw new Error('the track has no end-of-track meta, so the file is incomplete');
|
|
249
|
+
if (grooveName === null)
|
|
250
|
+
throw new Error(`no ${GROOVE_PREFIX} text meta: this file was not written as a groove`);
|
|
251
|
+
if (timeSignature === null)
|
|
252
|
+
throw new Error('no time-signature meta: an mwg groove file always carries one');
|
|
253
|
+
const ppq = division;
|
|
254
|
+
const slots = onsets.size;
|
|
255
|
+
const subdivision = subdivisionFor(timeSignature, slots);
|
|
256
|
+
const slotTicks = subdivisionTicks(ppq, subdivision);
|
|
257
|
+
const offsets = [];
|
|
258
|
+
for (let index = 0; index < slots; index++) {
|
|
259
|
+
const onset = onsets.get(FIRST_SLOT_PITCH + index);
|
|
260
|
+
if (!onset)
|
|
261
|
+
throw new Error(`slot ${index} is missing: expected a note on pitch ${FIRST_SLOT_PITCH + index}`);
|
|
262
|
+
offsets.push({
|
|
263
|
+
subdivisionIndex: index,
|
|
264
|
+
//slot i's grid position is the anchor plus i subdivisions: the anchor itself
|
|
265
|
+
//is what makes an offset a whole slot long unreadable, hence the writer's bound
|
|
266
|
+
timeOffsetTicks: onset.tick - slotTicks * (index + 1),
|
|
267
|
+
velocityFactor: onset.velocity / VELOCITY_SCALE,
|
|
268
|
+
});
|
|
269
|
+
}
|
|
270
|
+
const template = {
|
|
271
|
+
name: grooveName,
|
|
272
|
+
timeSignature,
|
|
273
|
+
ppq,
|
|
274
|
+
tempoBpm: microseconds === null ? 120 : Math.round((60_000_000 / microseconds) * 100) / 100,
|
|
275
|
+
subdivision,
|
|
276
|
+
offsets,
|
|
277
|
+
};
|
|
278
|
+
assertGrooveTemplate(template);
|
|
279
|
+
return template;
|
|
280
|
+
}
|
|
281
|
+
function readGrooveJson(data) {
|
|
282
|
+
const text = typeof data === 'string' ? data : new TextDecoder().decode(data);
|
|
283
|
+
let parsed;
|
|
284
|
+
try {
|
|
285
|
+
parsed = JSON.parse(text);
|
|
286
|
+
}
|
|
287
|
+
catch (error) {
|
|
288
|
+
throw new Error(`not valid JSON: ${error instanceof Error ? error.message : String(error)}`);
|
|
289
|
+
}
|
|
290
|
+
if (typeof parsed !== 'object' || parsed === null || Array.isArray(parsed))
|
|
291
|
+
throw new Error('a groove JSON export must be an object');
|
|
292
|
+
assertGrooveTemplate(parsed);
|
|
293
|
+
return parsed;
|
|
294
|
+
}
|
|
295
|
+
/**
|
|
296
|
+
* Writes a template out and reads one back, through JSON (exact) or MIDI (encoded, with
|
|
297
|
+
* the bounds above enforced on the way in). Asking for `.agr` or `.reaper-groove` throws
|
|
298
|
+
* naming what is missing rather than returning something that merely resembles them.
|
|
299
|
+
*
|
|
300
|
+
* @example
|
|
301
|
+
* ```ts
|
|
302
|
+
* import { GrooveConverter, type GrooveTemplate } from '@datamoc/mw_games/audio';
|
|
303
|
+
*
|
|
304
|
+
* declare const template: GrooveTemplate;
|
|
305
|
+
*
|
|
306
|
+
* const converter = new GrooveConverter();
|
|
307
|
+
* const text = await converter.exportFormat(template, 'json');
|
|
308
|
+
* const copy = await converter.importFormat(text, 'json'); // same offsets, same name
|
|
309
|
+
*
|
|
310
|
+
* const bytes = await converter.exportFormat(template, 'midi');
|
|
311
|
+
* const fromMidi = await converter.importFormat(bytes, 'midi');
|
|
312
|
+
* console.log(fromMidi.tempoBpm === template.tempoBpm); // true: tempo travels as microseconds
|
|
313
|
+
* ```
|
|
314
|
+
*/
|
|
315
|
+
export class GrooveConverter {
|
|
316
|
+
async exportFormat(template, format) {
|
|
317
|
+
switch (format) {
|
|
318
|
+
case 'json':
|
|
319
|
+
assertGrooveTemplate(template);
|
|
320
|
+
return JSON.stringify(template, null, 2);
|
|
321
|
+
case 'midi':
|
|
322
|
+
return writeGrooveMidi(template);
|
|
323
|
+
case 'ableton-agr':
|
|
324
|
+
case 'reaper-groove':
|
|
325
|
+
return notShipped(format);
|
|
326
|
+
default:
|
|
327
|
+
throw new Error(`unknown groove format: ${String(format)}`);
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
async importFormat(data, format) {
|
|
331
|
+
switch (format) {
|
|
332
|
+
case 'json':
|
|
333
|
+
return readGrooveJson(data);
|
|
334
|
+
case 'midi':
|
|
335
|
+
return readGrooveMidi(data);
|
|
336
|
+
case 'ableton-agr':
|
|
337
|
+
case 'reaper-groove':
|
|
338
|
+
return notShipped(format);
|
|
339
|
+
default:
|
|
340
|
+
throw new Error(`unknown groove format: ${String(format)}`);
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
//# sourceMappingURL=GrooveConverter.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"GrooveConverter.js","sourceRoot":"","sources":["../../../src/audio/humanize/GrooveConverter.ts"],"names":[],"mappings":"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"}
|