@datamoc/mw_games 0.22.0 → 0.23.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.
Files changed (62) hide show
  1. package/CHANGELOG.md +45 -0
  2. package/dist/audio/Midi.d.ts +24 -0
  3. package/dist/audio/Midi.d.ts.map +1 -1
  4. package/dist/audio/Midi.js +21 -0
  5. package/dist/audio/Midi.js.map +1 -1
  6. package/dist/audio/humanize/Groove.d.ts +80 -0
  7. package/dist/audio/humanize/Groove.d.ts.map +1 -0
  8. package/dist/audio/humanize/Groove.js +102 -0
  9. package/dist/audio/humanize/Groove.js.map +1 -0
  10. package/dist/audio/humanize/GrooveConverter.d.ts +49 -0
  11. package/dist/audio/humanize/GrooveConverter.d.ts.map +1 -0
  12. package/dist/audio/humanize/GrooveConverter.js +344 -0
  13. package/dist/audio/humanize/GrooveConverter.js.map +1 -0
  14. package/dist/audio/humanize/GrooveExtractor.d.ts +49 -0
  15. package/dist/audio/humanize/GrooveExtractor.d.ts.map +1 -0
  16. package/dist/audio/humanize/GrooveExtractor.js +179 -0
  17. package/dist/audio/humanize/GrooveExtractor.js.map +1 -0
  18. package/dist/audio/humanize/GrooveHumanizer.d.ts +34 -0
  19. package/dist/audio/humanize/GrooveHumanizer.d.ts.map +1 -0
  20. package/dist/audio/humanize/GrooveHumanizer.js +37 -0
  21. package/dist/audio/humanize/GrooveHumanizer.js.map +1 -0
  22. package/dist/audio/humanize/Humanize.d.ts +54 -0
  23. package/dist/audio/humanize/Humanize.d.ts.map +1 -0
  24. package/dist/audio/humanize/Humanize.js +239 -0
  25. package/dist/audio/humanize/Humanize.js.map +1 -0
  26. package/dist/audio/humanize/HumanizerFactory.d.ts +68 -0
  27. package/dist/audio/humanize/HumanizerFactory.d.ts.map +1 -0
  28. package/dist/audio/humanize/HumanizerFactory.js +84 -0
  29. package/dist/audio/humanize/HumanizerFactory.js.map +1 -0
  30. package/dist/audio/humanize/LiteHumanizer.d.ts +30 -0
  31. package/dist/audio/humanize/LiteHumanizer.d.ts.map +1 -0
  32. package/dist/audio/humanize/LiteHumanizer.js +35 -0
  33. package/dist/audio/humanize/LiteHumanizer.js.map +1 -0
  34. package/dist/audio/humanize/Onsets.d.ts +26 -0
  35. package/dist/audio/humanize/Onsets.d.ts.map +1 -0
  36. package/dist/audio/humanize/Onsets.js +164 -0
  37. package/dist/audio/humanize/Onsets.js.map +1 -0
  38. package/dist/audio/index.d.ts +11 -0
  39. package/dist/audio/index.d.ts.map +1 -1
  40. package/dist/audio/index.js +6 -0
  41. package/dist/audio/index.js.map +1 -1
  42. package/dist/mw_games.global.js +112 -112
  43. package/dist/rpg/MoveRoute.d.ts +35 -4
  44. package/dist/rpg/MoveRoute.d.ts.map +1 -1
  45. package/dist/rpg/MoveRoute.js +40 -11
  46. package/dist/rpg/MoveRoute.js.map +1 -1
  47. package/dist/two-d/render/RpgmAutotile.d.ts +37 -0
  48. package/dist/two-d/render/RpgmAutotile.d.ts.map +1 -1
  49. package/dist/two-d/render/RpgmAutotile.js +67 -1
  50. package/dist/two-d/render/RpgmAutotile.js.map +1 -1
  51. package/dist/two-d/render/TileMap.d.ts +36 -0
  52. package/dist/two-d/render/TileMap.d.ts.map +1 -1
  53. package/dist/two-d/render/TileMap.js +89 -1
  54. package/dist/two-d/render/TileMap.js.map +1 -1
  55. package/dist/two-d/render/index.d.ts +3 -3
  56. package/dist/two-d/render/index.d.ts.map +1 -1
  57. package/dist/two-d/render/index.js +1 -1
  58. package/dist/two-d/render/index.js.map +1 -1
  59. package/dist/version.d.ts +1 -1
  60. package/dist/version.js +1 -1
  61. package/package.json +6 -6
  62. package/sbom.cdx.json +194 -32
@@ -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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"}
@@ -0,0 +1,49 @@
1
+ import type { MidiFile } from '../Midi.ts';
2
+ import { type GridSubdivision, type GrooveTemplate } from './Groove.ts';
3
+ export interface GrooveExtractorEngine {
4
+ /**
5
+ * Mode A: align two performances of the same material - one played straight, one
6
+ * played - and keep how far each played note sat from its quantized counterpart.
7
+ */
8
+ extractFromMidiPair(quantizedMidi: MidiFile, recordedMidi: MidiFile, gridSubdivision?: GridSubdivision): Promise<GrooveTemplate>;
9
+ /**
10
+ * Mode B: one performance and no reference. The notes themselves are projected onto
11
+ * the nearest grid slot, so what comes back is the residual of a played file against
12
+ * its own implied grid - the deviations a straight quantization would remove.
13
+ */
14
+ extractFromSingleMidi(recordedMidi: MidiFile, gridSubdivision?: GridSubdivision): Promise<GrooveTemplate>;
15
+ /**
16
+ * Mode C: a recording instead of a file. The tempo is an argument rather than
17
+ * something read off the signal, because timing is meaningless without it.
18
+ */
19
+ extractFromAudioBuffer(audioBuffer: AudioBuffer, gridSubdivision?: '8n' | '16n', tempoBpm?: number): Promise<GrooveTemplate>;
20
+ }
21
+ /**
22
+ * Extracts a groove template from a pair of performances of the same material.
23
+ *
24
+ * Every note-on in `recordedMidi` is matched to the closest still-unused note-on of the
25
+ * same channel and pitch in `quantizedMidi`. A match is only taken when it falls inside
26
+ * half a slot, so a recorded part with notes the quantized one never plays is ignored
27
+ * instead of being forced onto a neighbour, and a slot the pair never reaches keeps the
28
+ * neutral offset rather than a value invented from no evidence at all.
29
+ *
30
+ * @example
31
+ * ```ts
32
+ * import { parseMidi, GrooveExtractor, type GrooveTemplate } from '@datamoc/mw_games/audio';
33
+ *
34
+ * declare const straightBytes: ArrayBuffer;
35
+ * declare const playedBytes: ArrayBuffer;
36
+ *
37
+ * const straight = parseMidi(straightBytes);
38
+ * const played = parseMidi(playedBytes);
39
+ *
40
+ * const template = await new GrooveExtractor().extractFromMidiPair(straight, played, '16n');
41
+ * console.log(template.offsets[0].timeOffsetTicks, template.name); // 0 'extracted'
42
+ * ```
43
+ */
44
+ export declare class GrooveExtractor implements GrooveExtractorEngine {
45
+ extractFromMidiPair(quantizedMidi: MidiFile, recordedMidi: MidiFile, gridSubdivision?: GridSubdivision): Promise<GrooveTemplate>;
46
+ extractFromSingleMidi(recordedMidi: MidiFile, gridSubdivision?: GridSubdivision): Promise<GrooveTemplate>;
47
+ extractFromAudioBuffer(audioBuffer: AudioBuffer, gridSubdivision?: '8n' | '16n', tempoBpm?: number): Promise<GrooveTemplate>;
48
+ }
49
+ //# sourceMappingURL=GrooveExtractor.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"GrooveExtractor.d.ts","sourceRoot":"","sources":["../../../src/audio/humanize/GrooveExtractor.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAiB,MAAM,YAAY,CAAC;AAC1D,OAAO,EAAsC,KAAK,eAAe,EAAE,KAAK,cAAc,EAAE,MAAM,aAAa,CAAC;AA8B5G,MAAM,WAAW,qBAAqB;IACrC;;;OAGG;IACH,mBAAmB,CAClB,aAAa,EAAE,QAAQ,EACvB,YAAY,EAAE,QAAQ,EACtB,eAAe,CAAC,EAAE,eAAe,GAC/B,OAAO,CAAC,cAAc,CAAC,CAAC;IAC3B;;;;OAIG;IACH,qBAAqB,CAAC,YAAY,EAAE,QAAQ,EAAE,eAAe,CAAC,EAAE,eAAe,GAAG,OAAO,CAAC,cAAc,CAAC,CAAC;IAC1G;;;OAGG;IACH,sBAAsB,CACrB,WAAW,EAAE,WAAW,EACxB,eAAe,CAAC,EAAE,IAAI,GAAG,KAAK,EAC9B,QAAQ,CAAC,EAAE,MAAM,GACf,OAAO,CAAC,cAAc,CAAC,CAAC;CAC3B;AAyDD;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,qBAAa,eAAgB,YAAW,qBAAqB;IACtD,mBAAmB,CACxB,aAAa,EAAE,QAAQ,EACvB,YAAY,EAAE,QAAQ,EACtB,eAAe,GAAE,eAAuB,GACtC,OAAO,CAAC,cAAc,CAAC,CAiDzB;IAEK,qBAAqB,CAC1B,YAAY,EAAE,QAAQ,EACtB,eAAe,GAAE,eAAuB,GACtC,OAAO,CAAC,cAAc,CAAC,CAmBzB;IAEK,sBAAsB,CAC3B,WAAW,EAAE,WAAW,EACxB,eAAe,GAAE,IAAI,GAAG,KAAa,EACrC,QAAQ,GAAE,MAA0B,GAClC,OAAO,CAAC,cAAc,CAAC,CAgCzB;CACD"}
@@ -0,0 +1,179 @@
1
+ import { subdivisionCount, subdivisionTicks } from './Groove.js';
2
+ import { detectOnsets } from './Onsets.js';
3
+ /**
4
+ * Builds `GrooveTemplate`s from three kinds of evidence, all reduced to the same shape by
5
+ * the same last step: collect a pile of per-slot deviations, take the median of each, and
6
+ * fill the slots nobody played with the neutral offsets (0, 1.0).
7
+ *
8
+ * The median is the whole reason a groove can be recovered from evidence this noisy. A
9
+ * single flubbed note drags a mean off the grid; with medians it is one sample among
10
+ * several and disappears. It also gives a slot with no evidence at all a defensible
11
+ * answer: nothing was measured, so the slot keeps the grid position and the default
12
+ * velocity, rather than inventing a deviation from an empty list.
13
+ */
14
+ /** every extracted template carries the mode's name so a save can say where it came from */
15
+ const EXTRACTED_NAME = 'extracted';
16
+ /** extraction reads one bar of 4/4; a signature is written out and re-checked on import */
17
+ const EXTRACTED_SIGNATURE = [4, 4];
18
+ /** tempo assumed when the recorded file states none, which is the General MIDI default */
19
+ const DEFAULT_BPM = 120;
20
+ /** the tempo Mode C is measured against when the caller does not supply one */
21
+ const AUDIO_DEFAULT_BPM = 120;
22
+ /** ticks per quarter for audio-derived templates, chosen because nothing ties them to a file */
23
+ const AUDIO_PPQ = 480;
24
+ function median(values) {
25
+ const sorted = [...values].sort((a, b) => a - b);
26
+ const middle = sorted.length >> 1;
27
+ return sorted.length % 2 === 1 ? sorted[middle] : (sorted[middle - 1] + sorted[middle]) / 2;
28
+ }
29
+ function roundedMedian(values) {
30
+ return Math.round(median(values));
31
+ }
32
+ /** the file's tempo in beats per minute: its first tempo event, or the General MIDI default */
33
+ function tempoOf(midi) {
34
+ for (const event of midi.events) {
35
+ if (event.type === 'tempo')
36
+ return Math.round((60_000_000 / event.microsecondsPerQuarter) * 100) / 100;
37
+ }
38
+ return DEFAULT_BPM;
39
+ }
40
+ function noteOns(midi) {
41
+ return midi.events.filter((event) => event.type === 'noteOn');
42
+ }
43
+ /** the flat list of note velocities, which Mode B needs a typical value out of */
44
+ function velocitiesOf(midi) {
45
+ return noteOns(midi).map((event) => event.velocity);
46
+ }
47
+ function finish(slots, subdivision, ppq, tempoBpm, velocityFrom) {
48
+ const offsets = slots.map((slot, subdivisionIndex) => ({
49
+ subdivisionIndex,
50
+ timeOffsetTicks: slot.time.length > 0 ? roundedMedian(slot.time) : 0,
51
+ velocityFactor: slot.velocity.length > 0 ? velocityFrom(slot) : 1,
52
+ }));
53
+ return {
54
+ name: EXTRACTED_NAME,
55
+ timeSignature: [...EXTRACTED_SIGNATURE],
56
+ ppq,
57
+ tempoBpm,
58
+ subdivision,
59
+ offsets,
60
+ };
61
+ }
62
+ /**
63
+ * Extracts a groove template from a pair of performances of the same material.
64
+ *
65
+ * Every note-on in `recordedMidi` is matched to the closest still-unused note-on of the
66
+ * same channel and pitch in `quantizedMidi`. A match is only taken when it falls inside
67
+ * half a slot, so a recorded part with notes the quantized one never plays is ignored
68
+ * instead of being forced onto a neighbour, and a slot the pair never reaches keeps the
69
+ * neutral offset rather than a value invented from no evidence at all.
70
+ *
71
+ * @example
72
+ * ```ts
73
+ * import { parseMidi, GrooveExtractor, type GrooveTemplate } from '@datamoc/mw_games/audio';
74
+ *
75
+ * declare const straightBytes: ArrayBuffer;
76
+ * declare const playedBytes: ArrayBuffer;
77
+ *
78
+ * const straight = parseMidi(straightBytes);
79
+ * const played = parseMidi(playedBytes);
80
+ *
81
+ * const template = await new GrooveExtractor().extractFromMidiPair(straight, played, '16n');
82
+ * console.log(template.offsets[0].timeOffsetTicks, template.name); // 0 'extracted'
83
+ * ```
84
+ */
85
+ export class GrooveExtractor {
86
+ async extractFromMidiPair(quantizedMidi, recordedMidi, gridSubdivision = '16n') {
87
+ const ppq = recordedMidi.ticksPerQuarter;
88
+ const slotTicks = subdivisionTicks(ppq, gridSubdivision);
89
+ const slots = subdivisionCount(EXTRACTED_SIGNATURE, gridSubdivision);
90
+ //the reference may have been quantized against a different ppq: scale it into the
91
+ //recorded file's ticks before any of the comparisons below mean anything
92
+ const scale = ppq / quantizedMidi.ticksPerQuarter;
93
+ const reference = noteOns(quantizedMidi).map((event) => ({
94
+ channel: event.channel,
95
+ note: event.note,
96
+ tick: event.tick * scale,
97
+ velocity: event.velocity,
98
+ }));
99
+ const used = new Set();
100
+ //each played note claims its nearest untouched reference of the same channel and
101
+ //pitch; the ratio, not the difference, of the two velocities is what survives, so
102
+ //the template scales a performance instead of reproducing its own absolute levels
103
+ const matches = [];
104
+ for (const played of noteOns(recordedMidi)) {
105
+ let bestIndex = -1;
106
+ let bestDistance = Infinity;
107
+ for (let i = 0; i < reference.length; i++) {
108
+ const candidate = reference[i];
109
+ if (used.has(i) || candidate.channel !== played.channel || candidate.note !== played.note)
110
+ continue;
111
+ const distance = Math.abs(candidate.tick - played.tick);
112
+ if (distance < bestDistance) {
113
+ bestDistance = distance;
114
+ bestIndex = i;
115
+ }
116
+ }
117
+ if (bestIndex < 0 || bestDistance > slotTicks / 2)
118
+ continue;
119
+ used.add(bestIndex);
120
+ matches.push({
121
+ slotTick: reference[bestIndex].tick,
122
+ delta: played.tick - reference[bestIndex].tick,
123
+ factor: reference[bestIndex].velocity > 0 ? played.velocity / reference[bestIndex].velocity : null,
124
+ });
125
+ }
126
+ const buckets = Array.from({ length: slots }, () => ({ time: [], velocity: [] }));
127
+ for (const match of matches) {
128
+ const index = ((Math.round(match.slotTick / slotTicks) % slots) + slots) % slots;
129
+ buckets[index].time.push(match.delta);
130
+ if (match.factor !== null)
131
+ buckets[index].velocity.push(match.factor);
132
+ }
133
+ return finish(buckets, gridSubdivision, ppq, tempoOf(recordedMidi), (slot) => median(slot.velocity));
134
+ }
135
+ async extractFromSingleMidi(recordedMidi, gridSubdivision = '16n') {
136
+ const ppq = recordedMidi.ticksPerQuarter;
137
+ const slotTicks = subdivisionTicks(ppq, gridSubdivision);
138
+ const slots = subdivisionCount(EXTRACTED_SIGNATURE, gridSubdivision);
139
+ const buckets = Array.from({ length: slots }, () => ({ time: [], velocity: [] }));
140
+ for (const event of noteOns(recordedMidi)) {
141
+ //project onto the nearest slot: the residual is how far the performance sat
142
+ //from its own implied grid, which is what a single file can actually show
143
+ const nearest = Math.round(event.tick / slotTicks) * slotTicks;
144
+ const index = ((Math.round(nearest / slotTicks) % slots) + slots) % slots;
145
+ buckets[index].time.push(event.tick - nearest);
146
+ buckets[index].velocity.push(event.velocity);
147
+ }
148
+ const typical = median(velocitiesOf(recordedMidi));
149
+ return finish(buckets, gridSubdivision, ppq, tempoOf(recordedMidi), (slot) => typical <= 0 ? 1 : median(slot.velocity) / typical);
150
+ }
151
+ async extractFromAudioBuffer(audioBuffer, gridSubdivision = '16n', tempoBpm = AUDIO_DEFAULT_BPM) {
152
+ if (!Number.isFinite(tempoBpm) || tempoBpm <= 0)
153
+ throw new Error(`tempoBpm must be greater than 0, got ${String(tempoBpm)}`);
154
+ const channelData = [];
155
+ for (let channel = 0; channel < audioBuffer.numberOfChannels; channel++)
156
+ channelData.push(audioBuffer.getChannelData(channel));
157
+ const mono = new Float32Array(audioBuffer.length);
158
+ for (const data of channelData)
159
+ for (let i = 0; i < data.length; i++)
160
+ mono[i] += data[i] / channelData.length;
161
+ const onsets = detectOnsets(mono, audioBuffer.sampleRate);
162
+ if (onsets.length === 0)
163
+ throw new Error('no onsets detected in the buffer - a groove needs audible attacks, and this audio has none');
164
+ const ppq = AUDIO_PPQ;
165
+ const slotTicks = subdivisionTicks(ppq, gridSubdivision);
166
+ const slots = subdivisionCount(EXTRACTED_SIGNATURE, gridSubdivision);
167
+ const ticksPerSecond = (tempoBpm * ppq) / 60;
168
+ const buckets = Array.from({ length: slots }, () => ({ time: [], velocity: [] }));
169
+ for (const seconds of onsets) {
170
+ const tick = seconds * ticksPerSecond;
171
+ const nearest = Math.round(tick / slotTicks) * slotTicks;
172
+ const index = ((Math.round(nearest / slotTicks) % slots) + slots) % slots;
173
+ buckets[index].time.push(tick - nearest);
174
+ }
175
+ //audio carries no velocity to recover, so every slot stays at the neutral factor
176
+ return finish(buckets, gridSubdivision, ppq, tempoBpm, () => 1);
177
+ }
178
+ }
179
+ //# sourceMappingURL=GrooveExtractor.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"GrooveExtractor.js","sourceRoot":"","sources":["../../../src/audio/humanize/GrooveExtractor.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,34 @@
1
+ import type { MidiFile } from '../Midi.ts';
2
+ import { type HumanizationOptions, type MidiHumanizerEngine } from './Humanize.ts';
3
+ /**
4
+ * Tier 2: the lite pass plus the groove grid and two musical rules the lite tier has no
5
+ * opinion about. The template supplies the per-slot deviations, rescaled from the tempo it
6
+ * was measured at to the tempo of the file being played, so a groove stays the same shape
7
+ * in time rather than the same number of ticks.
8
+ *
9
+ * The two rules are what keep a groove musical instead of merely uneven: kick and bass
10
+ * carry the pulse and stay inside 5 ms however loose the run, while a melody's local peaks
11
+ * are lifted a little so the line still speaks. Without a template the slot term is empty
12
+ * and the run is those rules over the same noise the lite tier uses - this tier is a
13
+ * superset of it, never a different pass.
14
+ *
15
+ * @example
16
+ * ```ts
17
+ * import { parseMidi, GrooveHumanizer, type GrooveTemplate } from '@datamoc/mw_games/audio';
18
+ *
19
+ * declare const midiBytes: ArrayBuffer;
20
+ * declare const template: GrooveTemplate;
21
+ *
22
+ * const file = parseMidi(midiBytes);
23
+ * const engine = new GrooveHumanizer();
24
+ * await engine.initialize(); // idempotent; the factory already does this for you
25
+ * const swung = await engine.process(file, { intensity: 0.8, grooveTemplate: template, seed: 7 });
26
+ * ```
27
+ */
28
+ export declare class GrooveHumanizer implements MidiHumanizerEngine {
29
+ readonly tierName = "groove";
30
+ readonly approximateSizeMb = 0.5;
31
+ initialize(): Promise<void>;
32
+ process(midi: MidiFile, options: HumanizationOptions): Promise<MidiFile>;
33
+ }
34
+ //# sourceMappingURL=GrooveHumanizer.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"GrooveHumanizer.d.ts","sourceRoot":"","sources":["../../../src/audio/humanize/GrooveHumanizer.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AAC3C,OAAO,EAAqB,KAAK,mBAAmB,EAAE,KAAK,mBAAmB,EAAE,MAAM,eAAe,CAAC;AAEtG;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AACH,qBAAa,eAAgB,YAAW,mBAAmB;IAC1D,QAAQ,CAAC,QAAQ,YAAY;IAC7B,QAAQ,CAAC,iBAAiB,OAAO;IAE3B,UAAU,IAAI,OAAO,CAAC,IAAI,CAAC,CAAG;IAE9B,OAAO,CAAC,IAAI,EAAE,QAAQ,EAAE,OAAO,EAAE,mBAAmB,GAAG,OAAO,CAAC,QAAQ,CAAC,CAE7E;CACD"}