@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.
- package/CHANGELOG.md +45 -0
- 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/mw_games.global.js +112 -112
- 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/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 +6 -6
- 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":"AAAA,OAAO,EACN,oBAAoB,EACpB,gBAAgB,EAChB,gBAAgB,GAIhB,MAAM,aAAa,CAAC;AA4BrB,iGAAiG;AACjG,MAAM,aAAa,GAAG,aAAa,CAAC;AAEpC,yFAAyF;AACzF,MAAM,gBAAgB,GAAG,EAAE,CAAC;AAE5B,2FAA2F;AAC3F,MAAM,cAAc,GAAG,EAAE,CAAC;AAE1B,MAAM,KAAK,GAA+B,CAAC,IAAI,EAAE,KAAK,EAAE,KAAK,CAAC,CAAC;AAE/D,MAAM,OAAO,GAAG,IAAI,WAAW,EAAE,CAAC;AAElC,SAAS,UAAU,CAAC,MAAoB;IACvC,MAAM,IAAI,KAAK,CACd,OAAO,MAAM,6IAA6I,CAC1J,CAAC;AACH,CAAC;AAED,SAAS,KAAK,CAAC,IAAY;IAC1B,OAAO,CAAC,GAAG,IAAI,CAAC,CAAC,GAAG,CAAC,CAAC,SAAS,EAAE,EAAE,CAAC,SAAS,CAAC,UAAU,CAAC,CAAC,CAAC,GAAG,IAAI,CAAC,CAAC;AACrE,CAAC;AAED,yFAAyF;AACzF,SAAS,GAAG,CAAC,KAAa;IACzB,MAAM,KAAK,GAAG,CAAC,KAAK,GAAG,IAAI,CAAC,CAAC;IAC7B,IAAI,IAAI,GAAG,KAAK,KAAK,CAAC,CAAC;IACvB,OAAO,IAAI,GAAG,CAAC,EAAE,CAAC;QACjB,KAAK,CAAC,OAAO,CAAC,CAAC,IAAI,GAAG,IAAI,CAAC,GAAG,IAAI,CAAC,CAAC;QACpC,IAAI,MAAM,CAAC,CAAC;IACb,CAAC;IACD,OAAO,KAAK,CAAC;AACd,CAAC;AAED,SAAS,SAAS,CAAC,IAAY,EAAE,IAAc;IAC9C,OAAO,CAAC,IAAI,EAAE,IAAI,EAAE,GAAG,GAAG,CAAC,IAAI,CAAC,MAAM,CAAC,EAAE,GAAG,IAAI,CAAC,CAAC;AACnD,CAAC;AAED,SAAS,eAAe,CAAC,QAAwB;IAChD,oBAAoB,CAAC,QAAQ,CAAC,CAAC;IAC/B,MAAM,EAAE,IAAI,EAAE,aAAa,EAAE,GAAG,EAAE,WAAW,EAAE,OAAO,EAAE,GAAG,QAAQ,CAAC;IAEpE,MAAM,KAAK,GAAG,gBAAgB,CAAC,aAAa,EAAE,WAAW,CAAC,CAAC;IAC3D,MAAM,SAAS,GAAG,gBAAgB,CAAC,GAAG,EAAE,WAAW,CAAC,CAAC;IACrD,IAAI,GAAG,GAAG,MAAM;QAAE,MAAM,IAAI,KAAK,CAAC,oBAAoB,IAAI,UAAU,GAAG,sCAAsC,CAAC,CAAC;IAC/G,IAAI,gBAAgB,GAAG,KAAK,GAAG,CAAC,GAAG,GAAG;QACrC,MAAM,IAAI,KAAK,CACd,oBAAoB,IAAI,MAAM,KAAK,6BAA6B,gBAAgB,GAAG,KAAK,GAAG,CAAC,+BAA+B,CAC3H,CAAC;IAEH,MAAM,YAAY,GAAG,IAAI,CAAC,KAAK,CAAC,UAAU,GAAG,QAAQ,CAAC,QAAQ,CAAC,CAAC;IAChE,IAAI,YAAY,GAAG,CAAC,IAAI,YAAY,GAAG,QAAQ;QAC9C,MAAM,IAAI,KAAK,CACd,oBAAoB,IAAI,eAAe,QAAQ,CAAC,QAAQ,0CAA0C,YAAY,gBAAgB,CAC9H,CAAC;IAEH,OAAO,CAAC,OAAO,CAAC,CAAC,MAAM,EAAE,KAAK,EAAE,EAAE;QACjC,IAAI,IAAI,CAAC,GAAG,CAAC,MAAM,CAAC,eAAe,CAAC,IAAI,SAAS;YAChD,MAAM,IAAI,KAAK,CACd,oBAAoB,IAAI,WAAW,KAAK,iBAAiB,MAAM,CAAC,eAAe,0BAA0B,SAAS,iEAAiE,CACnL,CAAC;QACH,MAAM,QAAQ,GAAG,IAAI,CAAC,KAAK,CAAC,cAAc,GAAG,MAAM,CAAC,cAAc,CAAC,CAAC;QACpE,IAAI,QAAQ,GAAG,CAAC,IAAI,QAAQ,GAAG,GAAG;YACjC,MAAM,IAAI,KAAK,CACd,oBAAoB,IAAI,WAAW,KAAK,uBAAuB,MAAM,CAAC,cAAc,iBAAiB,CAAC,GAAG,cAAc,OAAO,GAAG,GAAG,cAAc,mCAAmC,CAAC,GAAG,cAAc,oBAAoB,CAC3N,CAAC;IACJ,CAAC,CAAC,CAAC;IAEH,MAAM,KAAK,GAAwC;QAClD;YACC,IAAI,EAAE,CAAC;YACP,KAAK,EAAE,SAAS,CAAC,IAAI,EAAE,CAAC,CAAC,YAAY,IAAI,EAAE,CAAC,GAAG,IAAI,EAAE,CAAC,YAAY,IAAI,CAAC,CAAC,GAAG,IAAI,EAAE,YAAY,GAAG,IAAI,CAAC,CAAC;SACtG;QACD,EAAE,IAAI,EAAE,CAAC,EAAE,KAAK,EAAE,SAAS,CAAC,IAAI,EAAE,CAAC,aAAa,CAAC,CAAC,CAAC,EAAE,IAAI,CAAC,IAAI,CAAC,aAAa,CAAC,CAAC,CAAC,CAAC,EAAE,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE;QAC3F,EAAE,IAAI,EAAE,CAAC,EAAE,KAAK,EAAE,SAAS,CAAC,IAAI,EAAE,CAAC,GAAG,OAAO,CAAC,MAAM,CAAC,aAAa,GAAG,IAAI,CAAC,CAAC,CAAC,EAAE;KAC9E,CAAC;IAEF,OAAO,CAAC,OAAO,CAAC,CAAC,MAAM,EAAE,KAAK,EAAE,EAAE;QACjC,kFAAkF;QAClF,8EAA8E;QAC9E,MAAM,EAAE,GAAG,SAAS,GAAG,CAAC,KAAK,GAAG,CAAC,CAAC,GAAG,MAAM,CAAC,eAAe,CAAC;QAC5D,MAAM,GAAG,GAAG,EAAE,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,KAAK,CAAC,SAAS,GAAG,CAAC,CAAC,CAAC,CAAC;QACxD,KAAK,CAAC,IAAI,CAAC;YACV,IAAI,EAAE,EAAE;YACR,KAAK,EAAE,CAAC,IAAI,EAAE,gBAAgB,GAAG,KAAK,EAAE,IAAI,CAAC,KAAK,CAAC,cAAc,GAAG,MAAM,CAAC,cAAc,CAAC,CAAC;SAC3F,CAAC,CAAC;QACH,KAAK,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,GAAG,EAAE,KAAK,EAAE,CAAC,IAAI,EAAE,gBAAgB,GAAG,KAAK,EAAE,CAAC,CAAC,EAAE,CAAC,CAAC;IACvE,CAAC,CAAC,CAAC;IAEH,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,GAAG,CAAC,CAAC,IAAI,CAAC,CAAC;IAEtC,MAAM,KAAK,GAAa,EAAE,CAAC;IAC3B,IAAI,QAAQ,GAAG,CAAC,CAAC;IACjB,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;QAC1B,KAAK,CAAC,IAAI,CAAC,GAAG,GAAG,CAAC,IAAI,CAAC,IAAI,GAAG,QAAQ,CAAC,EAAE,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC;QACxD,QAAQ,GAAG,IAAI,CAAC,IAAI,CAAC;IACtB,CAAC;IACD,KAAK,CAAC,IAAI,CAAC,CAAC,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,CAAC,CAAC;IAEhC,OAAO,IAAI,UAAU,CAAC;QACrB,GAAG,KAAK,CAAC,MAAM,CAAC;QAChB,CAAC;QACD,CAAC;QACD,CAAC;QACD,CAAC;QACD,CAAC;QACD,CAAC;QACD,CAAC;QACD,CAAC;QACD,CAAC,GAAG,IAAI,CAAC,CAAC,GAAG,IAAI;QACjB,GAAG,GAAG,IAAI;QACV,GAAG,KAAK,CAAC,MAAM,CAAC;QAChB,CAAC,KAAK,CAAC,MAAM,KAAK,EAAE,CAAC,GAAG,IAAI;QAC5B,CAAC,KAAK,CAAC,MAAM,KAAK,EAAE,CAAC,GAAG,IAAI;QAC5B,CAAC,KAAK,CAAC,MAAM,KAAK,CAAC,CAAC,GAAG,IAAI;QAC3B,KAAK,CAAC,MAAM,GAAG,IAAI;QACnB,GAAG,KAAK;KACR,CAAC,CAAC;AACJ,CAAC;AAED,uEAAuE;AACvE,SAAS,cAAc,CAAC,aAA+B,EAAE,KAAa;IACrE,KAAK,MAAM,SAAS,IAAI,KAAK,EAAE,CAAC;QAC/B,IAAI,CAAC;YACJ,IAAI,gBAAgB,CAAC,aAAa,EAAE,SAAS,CAAC,KAAK,KAAK;gBAAE,OAAO,SAAS,CAAC;QAC5E,CAAC;QAAC,MAAM,CAAC;YACR,yDAAyD;QAC1D,CAAC;IACF,CAAC;IACD,MAAM,IAAI,KAAK,CACd,KAAK,KAAK,mBAAmB,aAAa,CAAC,CAAC,CAAC,IAAI,aAAa,CAAC,CAAC,CAAC,yCAAyC,CAC1G,CAAC;AACH,CAAC;AAED;;;;;;GAMG;AACH,SAAS,cAAc,CAAC,IAAyB;IAChD,MAAM,KAAK,GAAG,OAAO,IAAI,KAAK,QAAQ,CAAC,CAAC,CAAC,OAAO,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC;IACrE,MAAM,IAAI,GAAG,IAAI,QAAQ,CAAC,KAAK,CAAC,MAAM,EAAE,KAAK,CAAC,UAAU,EAAE,KAAK,CAAC,UAAU,CAAC,CAAC;IAC5E,IAAI,MAAM,GAAG,CAAC,CAAC;IAEf,MAAM,IAAI,GAAG,CAAC,KAAa,EAAE,IAAY,EAAQ,EAAE;QAClD,IAAI,MAAM,GAAG,KAAK,GAAG,KAAK,CAAC,MAAM;YAAE,MAAM,IAAI,KAAK,CAAC,qCAAqC,IAAI,EAAE,CAAC,CAAC;IACjG,CAAC,CAAC;IACF,MAAM,SAAS,GAAG,CAAC,MAAc,EAAE,IAAY,EAAU,EAAE;QAC1D,IAAI,CAAC,MAAM,EAAE,IAAI,CAAC,CAAC;QACnB,IAAI,IAAI,GAAG,EAAE,CAAC;QACd,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,MAAM,EAAE,CAAC,EAAE;YAAE,IAAI,IAAI,MAAM,CAAC,YAAY,CAAC,KAAK,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC;QAChF,MAAM,IAAI,MAAM,CAAC;QACjB,OAAO,IAAI,CAAC;IACb,CAAC,CAAC;IACF,MAAM,MAAM,GAAG,CAAC,IAAY,EAAU,EAAE;QACvC,IAAI,CAAC,CAAC,EAAE,IAAI,CAAC,CAAC;QACd,OAAO,KAAK,CAAC,MAAM,EAAE,CAAC,CAAC;IACxB,CAAC,CAAC;IACF,MAAM,OAAO,GAAG,CAAC,IAAY,EAAU,EAAE;QACxC,IAAI,CAAC,CAAC,EAAE,IAAI,CAAC,CAAC;QACd,MAAM,KAAK,GAAG,IAAI,CAAC,SAAS,CAAC,MAAM,CAAC,CAAC;QACrC,MAAM,IAAI,CAAC,CAAC;QACZ,OAAO,KAAK,CAAC;IACd,CAAC,CAAC;IACF,MAAM,OAAO,GAAG,CAAC,IAAY,EAAU,EAAE;QACxC,IAAI,CAAC,CAAC,EAAE,IAAI,CAAC,CAAC;QACd,MAAM,KAAK,GAAG,IAAI,CAAC,SAAS,CAAC,MAAM,CAAC,CAAC;QACrC,MAAM,IAAI,CAAC,CAAC;QACZ,OAAO,KAAK,CAAC;IACd,CAAC,CAAC;IACF,MAAM,OAAO,GAAG,CAAC,IAAY,EAAU,EAAE;QACxC,IAAI,KAAK,GAAG,CAAC,CAAC;QACd,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC;YAC5B,MAAM,IAAI,GAAG,MAAM,CAAC,IAAI,CAAC,CAAC;YAC1B,KAAK,GAAG,CAAC,KAAK,IAAI,CAAC,CAAC,GAAG,CAAC,IAAI,GAAG,IAAI,CAAC,CAAC;YACrC,IAAI,CAAC,IAAI,GAAG,IAAI,CAAC,KAAK,CAAC;gBAAE,OAAO,KAAK,CAAC;QACvC,CAAC;QACD,MAAM,IAAI,KAAK,CAAC,iCAAiC,IAAI,uBAAuB,CAAC,CAAC;IAC/E,CAAC,CAAC;IAEF,IAAI,SAAS,CAAC,CAAC,EAAE,iBAAiB,CAAC,KAAK,MAAM;QAC7C,MAAM,IAAI,KAAK,CAAC,sDAAsD,CAAC,CAAC;IACzE,MAAM,YAAY,GAAG,OAAO,CAAC,mBAAmB,CAAC,CAAC;IAClD,IAAI,YAAY,GAAG,CAAC;QAAE,MAAM,IAAI,KAAK,CAAC,WAAW,YAAY,6BAA6B,CAAC,CAAC;IAC5F,MAAM,MAAM,GAAG,OAAO,CAAC,YAAY,CAAC,CAAC;IACrC,MAAM,UAAU,GAAG,OAAO,CAAC,iBAAiB,CAAC,CAAC;IAC9C,MAAM,QAAQ,GAAG,OAAO,CAAC,cAAc,CAAC,CAAC;IACzC,MAAM,IAAI,YAAY,GAAG,CAAC,CAAC;IAC3B,IAAI,MAAM,GAAG,CAAC;QAAE,MAAM,IAAI,KAAK,CAAC,eAAe,MAAM,wCAAwC,CAAC,CAAC;IAC/F,IAAI,CAAC,QAAQ,GAAG,MAAM,CAAC,KAAK,CAAC;QAC5B,MAAM,IAAI,KAAK,CAAC,uEAAuE,CAAC,CAAC;IAC1F,IAAI,UAAU,KAAK,CAAC;QAAE,MAAM,IAAI,KAAK,CAAC,oDAAoD,UAAU,EAAE,CAAC,CAAC;IACxG,IAAI,SAAS,CAAC,CAAC,EAAE,kBAAkB,CAAC,KAAK,MAAM;QAAE,MAAM,IAAI,KAAK,CAAC,kCAAkC,CAAC,CAAC;IACrG,MAAM,WAAW,GAAG,OAAO,CAAC,kBAAkB,CAAC,CAAC;IAChD,MAAM,QAAQ,GAAG,MAAM,GAAG,WAAW,CAAC;IACtC,IAAI,QAAQ,GAAG,KAAK,CAAC,MAAM;QAAE,MAAM,IAAI,KAAK,CAAC,8DAA8D,CAAC,CAAC;IAE7G,IAAI,IAAI,GAAG,CAAC,CAAC;IACb,IAAI,OAAO,GAAG,CAAC,CAAC;IAChB,IAAI,YAAY,GAAkB,IAAI,CAAC;IACvC,IAAI,aAAa,GAA4B,IAAI,CAAC;IAClD,IAAI,UAAU,GAAkB,IAAI,CAAC;IACrC,IAAI,MAAM,GAAG,KAAK,CAAC;IACnB,MAAM,MAAM,GAAG,IAAI,GAAG,EAA8C,CAAC;IAErE,OAAO,MAAM,GAAG,QAAQ,EAAE,CAAC;QAC1B,IAAI,IAAI,OAAO,CAAC,cAAc,CAAC,CAAC;QAChC,IAAI,MAAM,GAAG,KAAK,CAAC,MAAM,CAAC,CAAC;QAC3B,IAAI,MAAM,GAAG,IAAI,EAAE,CAAC;YACnB,IAAI,OAAO,KAAK,CAAC;gBAChB,MAAM,IAAI,KAAK,CAAC,gBAAgB,IAAI,+DAA+D,CAAC,CAAC;YACtG,8EAA8E;YAC9E,MAAM,GAAG,OAAO,CAAC;QAClB,CAAC;aAAM,CAAC;YACP,MAAM,EAAE,CAAC;YACT,IAAI,MAAM,GAAG,IAAI;gBAAE,OAAO,GAAG,MAAM,CAAC;QACrC,CAAC;QAED,IAAI,MAAM,KAAK,IAAI,EAAE,CAAC;YACrB,MAAM,IAAI,GAAG,MAAM,CAAC,aAAa,CAAC,CAAC;YACnC,MAAM,MAAM,GAAG,OAAO,CAAC,eAAe,CAAC,CAAC;YACxC,IAAI,CAAC,MAAM,EAAE,cAAc,CAAC,CAAC;YAC7B,MAAM,IAAI,GAAG,MAAM,CAAC;YACpB,IAAI,IAAI,KAAK,IAAI,IAAI,MAAM,KAAK,CAAC;gBAChC,YAAY,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC,IAAI,EAAE,CAAC,GAAG,CAAC,KAAK,CAAC,IAAI,GAAG,CAAC,CAAC,IAAI,CAAC,CAAC,GAAG,KAAK,CAAC,IAAI,GAAG,CAAC,CAAC,CAAC;iBAC1E,IAAI,IAAI,KAAK,IAAI,IAAI,MAAM,IAAI,CAAC;gBAAE,aAAa,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC,IAAI,KAAK,CAAC,IAAI,GAAG,CAAC,CAAC,CAAC,CAAC;iBACtF,IAAI,CAAC,IAAI,KAAK,IAAI,IAAI,IAAI,KAAK,IAAI,CAAC,IAAI,MAAM,GAAG,CAAC,EAAE,CAAC;gBACzD,IAAI,IAAI,GAAG,EAAE,CAAC;gBACd,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,MAAM,EAAE,CAAC,EAAE;oBAAE,IAAI,IAAI,MAAM,CAAC,YAAY,CAAC,KAAK,CAAC,IAAI,GAAG,CAAC,CAAC,CAAC,CAAC;gBAC9E,IAAI,IAAI,CAAC,UAAU,CAAC,aAAa,CAAC;oBAAE,UAAU,GAAG,IAAI,CAAC,KAAK,CAAC,aAAa,CAAC,MAAM,CAAC,CAAC;YACnF,CAAC;YACD,MAAM,IAAI,MAAM,CAAC;YACjB,IAAI,IAAI,KAAK,IAAI;gBAAE,MAAM,GAAG,IAAI,CAAC;YACjC,OAAO,GAAG,CAAC,CAAC;YACZ,SAAS;QACV,CAAC;QAED,IAAI,MAAM,KAAK,IAAI,IAAI,MAAM,KAAK,IAAI,EAAE,CAAC;YACxC,MAAM,IAAI,OAAO,CAAC,gBAAgB,CAAC,CAAC;YACpC,OAAO,GAAG,CAAC,CAAC;YACZ,SAAS;QACV,CAAC;QAED,MAAM,IAAI,GAAG,MAAM,GAAG,IAAI,CAAC;QAC3B,IAAI,IAAI,KAAK,IAAI,IAAI,IAAI,KAAK,IAAI,EAAE,CAAC;YACpC,MAAM,IAAI,GAAG,MAAM,CAAC,eAAe,CAAC,CAAC;YACrC,MAAM,QAAQ,GAAG,MAAM,CAAC,iBAAiB,CAAC,CAAC;YAC3C,IAAI,IAAI,KAAK,IAAI,IAAI,QAAQ,GAAG,CAAC,EAAE,CAAC;gBACnC,IAAI,MAAM,CAAC,GAAG,CAAC,IAAI,CAAC;oBACnB,MAAM,IAAI,KAAK,CAAC,SAAS,IAAI,yDAAyD,CAAC,CAAC;gBACzF,MAAM,CAAC,GAAG,CAAC,IAAI,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE,CAAC,CAAC;YACtC,CAAC;QACF,CAAC;aAAM,IAAI,IAAI,KAAK,IAAI,IAAI,IAAI,KAAK,IAAI,EAAE,CAAC;YAC3C,IAAI,CAAC,CAAC,EAAE,4BAA4B,CAAC,CAAC;YACtC,MAAM,IAAI,CAAC,CAAC;QACb,CAAC;aAAM,IAAI,IAAI,KAAK,IAAI,IAAI,IAAI,KAAK,IAAI,EAAE,CAAC;YAC3C,IAAI,CAAC,CAAC,EAAE,uCAAuC,CAAC,CAAC;YACjD,MAAM,IAAI,CAAC,CAAC;QACb,CAAC;;YAAM,MAAM,IAAI,KAAK,CAAC,4BAA4B,MAAM,CAAC,QAAQ,CAAC,EAAE,CAAC,YAAY,IAAI,EAAE,CAAC,CAAC;IAC3F,CAAC;IAED,IAAI,CAAC,MAAM;QAAE,MAAM,IAAI,KAAK,CAAC,+DAA+D,CAAC,CAAC;IAC9F,IAAI,UAAU,KAAK,IAAI;QAAE,MAAM,IAAI,KAAK,CAAC,MAAM,aAAa,mDAAmD,CAAC,CAAC;IACjH,IAAI,aAAa,KAAK,IAAI;QAAE,MAAM,IAAI,KAAK,CAAC,+DAA+D,CAAC,CAAC;IAE7G,MAAM,GAAG,GAAG,QAAQ,CAAC;IACrB,MAAM,KAAK,GAAG,MAAM,CAAC,IAAI,CAAC;IAC1B,MAAM,WAAW,GAAG,cAAc,CAAC,aAAa,EAAE,KAAK,CAAC,CAAC;IACzD,MAAM,SAAS,GAAG,gBAAgB,CAAC,GAAG,EAAE,WAAW,CAAC,CAAC;IAErD,MAAM,OAAO,GAAmB,EAAE,CAAC;IACnC,KAAK,IAAI,KAAK,GAAG,CAAC,EAAE,KAAK,GAAG,KAAK,EAAE,KAAK,EAAE,EAAE,CAAC;QAC5C,MAAM,KAAK,GAAG,MAAM,CAAC,GAAG,CAAC,gBAAgB,GAAG,KAAK,CAAC,CAAC;QACnD,IAAI,CAAC,KAAK;YAAE,MAAM,IAAI,KAAK,CAAC,QAAQ,KAAK,yCAAyC,gBAAgB,GAAG,KAAK,EAAE,CAAC,CAAC;QAC9G,OAAO,CAAC,IAAI,CAAC;YACZ,gBAAgB,EAAE,KAAK;YACvB,6EAA6E;YAC7E,gFAAgF;YAChF,eAAe,EAAE,KAAK,CAAC,IAAI,GAAG,SAAS,GAAG,CAAC,KAAK,GAAG,CAAC,CAAC;YACrD,cAAc,EAAE,KAAK,CAAC,QAAQ,GAAG,cAAc;SAC/C,CAAC,CAAC;IACJ,CAAC;IAED,MAAM,QAAQ,GAAmB;QAChC,IAAI,EAAE,UAAU;QAChB,aAAa;QACb,GAAG;QACH,QAAQ,EAAE,YAAY,KAAK,IAAI,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,UAAU,GAAG,YAAY,CAAC,GAAG,GAAG,CAAC,GAAG,GAAG;QAC3F,WAAW;QACX,OAAO;KACP,CAAC;IACF,oBAAoB,CAAC,QAAQ,CAAC,CAAC;IAC/B,OAAO,QAAQ,CAAC;AACjB,CAAC;AAED,SAAS,cAAc,CAAC,IAAyB;IAChD,MAAM,IAAI,GAAG,OAAO,IAAI,KAAK,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,WAAW,EAAE,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;IAC9E,IAAI,MAAe,CAAC;IACpB,IAAI,CAAC;QACJ,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;IAC3B,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QAChB,MAAM,IAAI,KAAK,CAAC,mBAAmB,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC;IAC9F,CAAC;IACD,IAAI,OAAO,MAAM,KAAK,QAAQ,IAAI,MAAM,KAAK,IAAI,IAAI,KAAK,CAAC,OAAO,CAAC,MAAM,CAAC;QACzE,MAAM,IAAI,KAAK,CAAC,wCAAwC,CAAC,CAAC;IAC3D,oBAAoB,CAAC,MAAwB,CAAC,CAAC;IAC/C,OAAO,MAAwB,CAAC;AACjC,CAAC;AAED;;;;;;;;;;;;;;;;;;;GAmBG;AACH,MAAM,OAAO,eAAe;IAC3B,KAAK,CAAC,YAAY,CAAC,QAAwB,EAAE,MAAoB;QAChE,QAAQ,MAAM,EAAE,CAAC;YAChB,KAAK,MAAM;gBACV,oBAAoB,CAAC,QAAQ,CAAC,CAAC;gBAC/B,OAAO,IAAI,CAAC,SAAS,CAAC,QAAQ,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC;YAC1C,KAAK,MAAM;gBACV,OAAO,eAAe,CAAC,QAAQ,CAAC,CAAC;YAClC,KAAK,aAAa,CAAC;YACnB,KAAK,eAAe;gBACnB,OAAO,UAAU,CAAC,MAAM,CAAC,CAAC;YAC3B;gBACC,MAAM,IAAI,KAAK,CAAC,0BAA0B,MAAM,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC;QAC9D,CAAC;IACF,CAAC;IAED,KAAK,CAAC,YAAY,CAAC,IAAyB,EAAE,MAAoB;QACjE,QAAQ,MAAM,EAAE,CAAC;YAChB,KAAK,MAAM;gBACV,OAAO,cAAc,CAAC,IAAI,CAAC,CAAC;YAC7B,KAAK,MAAM;gBACV,OAAO,cAAc,CAAC,IAAI,CAAC,CAAC;YAC7B,KAAK,aAAa,CAAC;YACnB,KAAK,eAAe;gBACnB,OAAO,UAAU,CAAC,MAAM,CAAC,CAAC;YAC3B;gBACC,MAAM,IAAI,KAAK,CAAC,0BAA0B,MAAM,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC;QAC9D,CAAC;IACF,CAAC;CACD"}
|
|
@@ -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"}
|