@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.
Files changed (87) hide show
  1. package/CHANGELOG.md +78 -2
  2. package/README.md +42 -0
  3. package/dist/ai/Neural.d.ts +65 -0
  4. package/dist/ai/Neural.d.ts.map +1 -0
  5. package/dist/ai/Neural.js +169 -0
  6. package/dist/ai/Neural.js.map +1 -0
  7. package/dist/ai/index.d.ts +2 -0
  8. package/dist/ai/index.d.ts.map +1 -1
  9. package/dist/ai/index.js +1 -0
  10. package/dist/ai/index.js.map +1 -1
  11. package/dist/audio/Midi.d.ts +24 -0
  12. package/dist/audio/Midi.d.ts.map +1 -1
  13. package/dist/audio/Midi.js +21 -0
  14. package/dist/audio/Midi.js.map +1 -1
  15. package/dist/audio/humanize/Groove.d.ts +80 -0
  16. package/dist/audio/humanize/Groove.d.ts.map +1 -0
  17. package/dist/audio/humanize/Groove.js +102 -0
  18. package/dist/audio/humanize/Groove.js.map +1 -0
  19. package/dist/audio/humanize/GrooveConverter.d.ts +49 -0
  20. package/dist/audio/humanize/GrooveConverter.d.ts.map +1 -0
  21. package/dist/audio/humanize/GrooveConverter.js +344 -0
  22. package/dist/audio/humanize/GrooveConverter.js.map +1 -0
  23. package/dist/audio/humanize/GrooveExtractor.d.ts +49 -0
  24. package/dist/audio/humanize/GrooveExtractor.d.ts.map +1 -0
  25. package/dist/audio/humanize/GrooveExtractor.js +179 -0
  26. package/dist/audio/humanize/GrooveExtractor.js.map +1 -0
  27. package/dist/audio/humanize/GrooveHumanizer.d.ts +34 -0
  28. package/dist/audio/humanize/GrooveHumanizer.d.ts.map +1 -0
  29. package/dist/audio/humanize/GrooveHumanizer.js +37 -0
  30. package/dist/audio/humanize/GrooveHumanizer.js.map +1 -0
  31. package/dist/audio/humanize/Humanize.d.ts +54 -0
  32. package/dist/audio/humanize/Humanize.d.ts.map +1 -0
  33. package/dist/audio/humanize/Humanize.js +239 -0
  34. package/dist/audio/humanize/Humanize.js.map +1 -0
  35. package/dist/audio/humanize/HumanizerFactory.d.ts +68 -0
  36. package/dist/audio/humanize/HumanizerFactory.d.ts.map +1 -0
  37. package/dist/audio/humanize/HumanizerFactory.js +84 -0
  38. package/dist/audio/humanize/HumanizerFactory.js.map +1 -0
  39. package/dist/audio/humanize/LiteHumanizer.d.ts +30 -0
  40. package/dist/audio/humanize/LiteHumanizer.d.ts.map +1 -0
  41. package/dist/audio/humanize/LiteHumanizer.js +35 -0
  42. package/dist/audio/humanize/LiteHumanizer.js.map +1 -0
  43. package/dist/audio/humanize/Onsets.d.ts +26 -0
  44. package/dist/audio/humanize/Onsets.d.ts.map +1 -0
  45. package/dist/audio/humanize/Onsets.js +164 -0
  46. package/dist/audio/humanize/Onsets.js.map +1 -0
  47. package/dist/audio/index.d.ts +11 -0
  48. package/dist/audio/index.d.ts.map +1 -1
  49. package/dist/audio/index.js +6 -0
  50. package/dist/audio/index.js.map +1 -1
  51. package/dist/board/tourney.d.ts +8 -1
  52. package/dist/board/tourney.d.ts.map +1 -1
  53. package/dist/board/tourney.js +8 -2
  54. package/dist/board/tourney.js.map +1 -1
  55. package/dist/core/Random.d.ts +1 -0
  56. package/dist/core/Random.d.ts.map +1 -1
  57. package/dist/core/Random.js +14 -17
  58. package/dist/core/Random.js.map +1 -1
  59. package/dist/mw_games.global.js +108 -108
  60. package/dist/rpg/MoveRoute.d.ts +35 -4
  61. package/dist/rpg/MoveRoute.d.ts.map +1 -1
  62. package/dist/rpg/MoveRoute.js +40 -11
  63. package/dist/rpg/MoveRoute.js.map +1 -1
  64. package/dist/simulation/Training.d.ts +143 -0
  65. package/dist/simulation/Training.d.ts.map +1 -0
  66. package/dist/simulation/Training.js +279 -0
  67. package/dist/simulation/Training.js.map +1 -0
  68. package/dist/simulation/index.d.ts +2 -0
  69. package/dist/simulation/index.d.ts.map +1 -1
  70. package/dist/simulation/index.js +1 -0
  71. package/dist/simulation/index.js.map +1 -1
  72. package/dist/two-d/render/RpgmAutotile.d.ts +37 -0
  73. package/dist/two-d/render/RpgmAutotile.d.ts.map +1 -1
  74. package/dist/two-d/render/RpgmAutotile.js +67 -1
  75. package/dist/two-d/render/RpgmAutotile.js.map +1 -1
  76. package/dist/two-d/render/TileMap.d.ts +36 -0
  77. package/dist/two-d/render/TileMap.d.ts.map +1 -1
  78. package/dist/two-d/render/TileMap.js +89 -1
  79. package/dist/two-d/render/TileMap.js.map +1 -1
  80. package/dist/two-d/render/index.d.ts +3 -3
  81. package/dist/two-d/render/index.d.ts.map +1 -1
  82. package/dist/two-d/render/index.js +1 -1
  83. package/dist/two-d/render/index.js.map +1 -1
  84. package/dist/version.d.ts +1 -1
  85. package/dist/version.js +1 -1
  86. package/package.json +15 -6
  87. package/sbom.cdx.json +194 -32
@@ -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"}
@@ -0,0 +1,37 @@
1
+ import { applyHumanization } from './Humanize.js';
2
+ /**
3
+ * Tier 2: the lite pass plus the groove grid and two musical rules the lite tier has no
4
+ * opinion about. The template supplies the per-slot deviations, rescaled from the tempo it
5
+ * was measured at to the tempo of the file being played, so a groove stays the same shape
6
+ * in time rather than the same number of ticks.
7
+ *
8
+ * The two rules are what keep a groove musical instead of merely uneven: kick and bass
9
+ * carry the pulse and stay inside 5 ms however loose the run, while a melody's local peaks
10
+ * are lifted a little so the line still speaks. Without a template the slot term is empty
11
+ * and the run is those rules over the same noise the lite tier uses - this tier is a
12
+ * superset of it, never a different pass.
13
+ *
14
+ * @example
15
+ * ```ts
16
+ * import { parseMidi, GrooveHumanizer, type GrooveTemplate } from '@datamoc/mw_games/audio';
17
+ *
18
+ * declare const midiBytes: ArrayBuffer;
19
+ * declare const template: GrooveTemplate;
20
+ *
21
+ * const file = parseMidi(midiBytes);
22
+ * const engine = new GrooveHumanizer();
23
+ * await engine.initialize(); // idempotent; the factory already does this for you
24
+ * const swung = await engine.process(file, { intensity: 0.8, grooveTemplate: template, seed: 7 });
25
+ * ```
26
+ */
27
+ export class GrooveHumanizer {
28
+ constructor() {
29
+ this.tierName = 'groove';
30
+ this.approximateSizeMb = 0.5;
31
+ }
32
+ async initialize() { }
33
+ async process(midi, options) {
34
+ return applyHumanization(midi, options, 'groove');
35
+ }
36
+ }
37
+ //# sourceMappingURL=GrooveHumanizer.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"GrooveHumanizer.js","sourceRoot":"","sources":["../../../src/audio/humanize/GrooveHumanizer.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,iBAAiB,EAAsD,MAAM,eAAe,CAAC;AAEtG;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AACH,MAAM,OAAO,eAAe;IAA5B;QACU,aAAQ,GAAG,QAAQ,CAAC;QACpB,sBAAiB,GAAG,GAAG,CAAC;IAOlC,CAAC;IALA,KAAK,CAAC,UAAU,KAAmB,CAAC;IAEpC,KAAK,CAAC,OAAO,CAAC,IAAc,EAAE,OAA4B;QACzD,OAAO,iBAAiB,CAAC,IAAI,EAAE,OAAO,EAAE,QAAQ,CAAC,CAAC;IACnD,CAAC;CACD"}
@@ -0,0 +1,54 @@
1
+ import type { MidiFile } from '../Midi.ts';
2
+ import { type GrooveTemplate } from './Groove.ts';
3
+ /**
4
+ * The two tiers share this file: the option and engine types, plus the one pass that
5
+ * actually moves notes. The tiers differ only in what they hand it, which is the point -
6
+ * a tier is a policy over the same pass, not a second implementation of it.
7
+ *
8
+ * Two rules shape everything below. The input `MidiFile` is never mutated: every event the
9
+ * pass touches is a fresh object, so a caller can humanize a file and still play the
10
+ * original. And only note-on/note-off events move: tempo, controller, program and pitch
11
+ * bend events are copied through untouched, because shifting them would change what the
12
+ * performance *means* rather than how it is played.
13
+ */
14
+ /** which humanizer to run: the two shipped tiers plus the one the spec reserves for a model */
15
+ export type HumanizerTier = 'lite' | 'groove' | 'magenta';
16
+ /** musical styles scale the timing and velocity spread; they change how wide, not what shape */
17
+ export type HumanizeStyle = 'jazz' | 'rock' | 'classical' | 'funk';
18
+ export interface HumanizationOptions {
19
+ /** 0 to 1: how much of the template and the noise to apply; 0 returns the file unchanged */
20
+ intensity: number;
21
+ /** standard deviation of the per-note timing noise, in milliseconds (default 8) */
22
+ timingVarianceMs?: number;
23
+ /** standard deviation of the per-note velocity noise, in MIDI velocity units (default 10) */
24
+ velocityVariance?: number;
25
+ /** per-slot deviations to replay; only `GrooveHumanizer` applies one */
26
+ grooveTemplate?: GrooveTemplate;
27
+ /** scales the two spreads: jazz runs looser, classical tighter (default: no scaling) */
28
+ style?: HumanizeStyle;
29
+ /** makes the noise reproducible; left out, the result differs on every call */
30
+ seed?: number;
31
+ /**
32
+ * Reports what a request needs saying out loud - today, a template whose tempo is far
33
+ * from the file's. Defaults to `console.warn`, so the signal is never silent by default.
34
+ */
35
+ onWarn?: (message: string) => void;
36
+ }
37
+ export interface MidiHumanizerEngine {
38
+ /** the tier this engine implements, as `HumanizerFactory.create` spells it */
39
+ readonly tierName: string;
40
+ /** roughly what the tier costs a bundle, in megabytes: lite is code, magenta is a model */
41
+ readonly approximateSizeMb: number;
42
+ /** loads anything heavy; idempotent, and already called for you by the factory */
43
+ initialize(): Promise<void>;
44
+ /** humanizes a file and returns the result, leaving `midi` untouched */
45
+ process(midi: MidiFile, options: HumanizationOptions): Promise<MidiFile>;
46
+ }
47
+ /**
48
+ * The pass both tiers run: pick a slot, build a timing delta from the template plus the
49
+ * noise, clamp it so no note lands before tick 0, shake the velocity inside 1-127, and
50
+ * carry the note's own delta onto its note-off so every duration survives intact.
51
+ * Returns a fresh file sorted back into tick order; `midi` is only ever read.
52
+ */
53
+ export declare function applyHumanization(midi: MidiFile, options: HumanizationOptions, tier: 'lite' | 'groove'): MidiFile;
54
+ //# sourceMappingURL=Humanize.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Humanize.d.ts","sourceRoot":"","sources":["../../../src/audio/humanize/Humanize.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAa,QAAQ,EAAiB,MAAM,YAAY,CAAC;AACrE,OAAO,EAA4D,KAAK,cAAc,EAAE,MAAM,aAAa,CAAC;AAE5G;;;;;;;;;;GAUG;AAEH,+FAA+F;AAC/F,MAAM,MAAM,aAAa,GAAG,MAAM,GAAG,QAAQ,GAAG,SAAS,CAAC;AAE1D,gGAAgG;AAChG,MAAM,MAAM,aAAa,GAAG,MAAM,GAAG,MAAM,GAAG,WAAW,GAAG,MAAM,CAAC;AAEnE,MAAM,WAAW,mBAAmB;IACnC,4FAA4F;IAC5F,SAAS,EAAE,MAAM,CAAC;IAClB,mFAAmF;IACnF,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,6FAA6F;IAC7F,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,wEAAwE;IACxE,cAAc,CAAC,EAAE,cAAc,CAAC;IAChC,wFAAwF;IACxF,KAAK,CAAC,EAAE,aAAa,CAAC;IACtB,+EAA+E;IAC/E,IAAI,CAAC,EAAE,MAAM,CAAC;IACd;;;OAGG;IACH,MAAM,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,CAAC;CACnC;AAED,MAAM,WAAW,mBAAmB;IACnC,8EAA8E;IAC9E,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,2FAA2F;IAC3F,QAAQ,CAAC,iBAAiB,EAAE,MAAM,CAAC;IACnC,kFAAkF;IAClF,UAAU,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;IAC5B,wEAAwE;IACxE,OAAO,CAAC,IAAI,EAAE,QAAQ,EAAE,OAAO,EAAE,mBAAmB,GAAG,OAAO,CAAC,QAAQ,CAAC,CAAC;CACzE;AA8MD;;;;;GAKG;AACH,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,QAAQ,EAAE,OAAO,EAAE,mBAAmB,EAAE,IAAI,EAAE,MAAM,GAAG,QAAQ,GAAG,QAAQ,CA0EjH"}
@@ -0,0 +1,239 @@
1
+ import { Generator } from '../../core/Random.js';
2
+ import { assertGrooveTemplate, subdivisionCount, subdivisionTicks } from './Groove.js';
3
+ /** how often the drift wanders, in cycles per quarter note: one cycle across four beats */
4
+ const DRIFT_SCALE = 0.25;
5
+ /** share of the timing spread the drift takes; the rest is the independent per-note draw */
6
+ const DRIFT_RATIO = 0.5;
7
+ /** how much a melody's local peaks are lifted, as a fraction of the intensity */
8
+ const PEAK_BOOST = 0.15;
9
+ /** a template this far from the file's tempo says so instead of silently rescaling */
10
+ const TEMPO_TOLERANCE_BPM = 1;
11
+ /** Tier 2 keeps kick and bass timing inside this, in milliseconds, however loose the run is */
12
+ const ANCHOR_TIMING_MS = 5;
13
+ /** General MIDI puts the kit on channel 10, which is channel 9 counted from zero */
14
+ const DRUM_CHANNEL = 9;
15
+ /** acoustic and bass drum */
16
+ const KICK_NOTES = [35, 36];
17
+ /** below C3 reads as the bass line rather than as the melody being ornamented */
18
+ const BASS_CEILING_NOTE = 48;
19
+ const DEFAULT_TIMING_VARIANCE_MS = 8;
20
+ const DEFAULT_VELOCITY_VARIANCE = 10;
21
+ const STYLE_FACTORS = {
22
+ jazz: { timing: 1.3, velocity: 1.1 },
23
+ rock: { timing: 0.8, velocity: 1.2 },
24
+ classical: { timing: 0.6, velocity: 1.4 },
25
+ funk: { timing: 1.0, velocity: 1.3 },
26
+ };
27
+ const IDENTITY_FACTORS = { timing: 1, velocity: 1 };
28
+ function resolveOptions(options) {
29
+ const intensity = options.intensity;
30
+ if (!Number.isFinite(intensity) || intensity < 0 || intensity > 1)
31
+ throw new Error(`humanization intensity must be between 0 and 1, got ${String(options.intensity)}`);
32
+ const timingMs = options.timingVarianceMs ?? DEFAULT_TIMING_VARIANCE_MS;
33
+ if (!Number.isFinite(timingMs) || timingMs < 0)
34
+ throw new Error(`timingVarianceMs must be a finite number of at least 0, got ${String(options.timingVarianceMs)}`);
35
+ const velocityVariance = options.velocityVariance ?? DEFAULT_VELOCITY_VARIANCE;
36
+ if (!Number.isFinite(velocityVariance) || velocityVariance < 0)
37
+ throw new Error(`velocityVariance must be a finite number of at least 0, got ${String(options.velocityVariance)}`);
38
+ const style = options.style ? STYLE_FACTORS[options.style] : IDENTITY_FACTORS;
39
+ const template = options.grooveTemplate ?? null;
40
+ if (template)
41
+ assertGrooveTemplate(template);
42
+ return {
43
+ intensity,
44
+ timingMs,
45
+ velocityVariance,
46
+ timingScale: style.timing,
47
+ velocityScale: style.velocity,
48
+ template,
49
+ warn: options.onWarn ?? ((message) => console.warn(message)),
50
+ };
51
+ }
52
+ /** the file's tempo changes in tick order, with the default tempo standing before the first */
53
+ function tempoSegments(events) {
54
+ const changes = [];
55
+ for (const event of events) {
56
+ if (event.type === 'tempo')
57
+ changes.push({ tick: event.tick, microsecondsPerQuarter: event.microsecondsPerQuarter });
58
+ }
59
+ changes.sort((a, b) => a.tick - b.tick);
60
+ const segments = [{ tick: 0, microsecondsPerQuarter: 500000 }];
61
+ for (const change of changes) {
62
+ if (change.tick === segments[segments.length - 1].tick)
63
+ segments[segments.length - 1] = change;
64
+ else
65
+ segments.push(change);
66
+ }
67
+ return segments;
68
+ }
69
+ /** beats per minute in effect at a tick, by the last tempo change at or before it */
70
+ function bpmAt(segments, tick) {
71
+ let low = 0;
72
+ let high = segments.length - 1;
73
+ let found = 0;
74
+ while (low <= high) {
75
+ const middle = (low + high) >> 1;
76
+ if (segments[middle].tick <= tick) {
77
+ found = middle;
78
+ low = middle + 1;
79
+ }
80
+ else
81
+ high = middle - 1;
82
+ }
83
+ return 60_000_000 / segments[found].microsecondsPerQuarter;
84
+ }
85
+ function msToTicks(ms, ticksPerQuarter, bpm) {
86
+ return (ms * ticksPerQuarter * bpm) / 60_000;
87
+ }
88
+ function ticksToMs(ticks, ticksPerQuarter, bpm) {
89
+ return (ticks * 60_000) / (ticksPerQuarter * bpm);
90
+ }
91
+ /** Box-Muller over the seeded generator, keeping the second variate for the next call */
92
+ function createGaussian(rng) {
93
+ let spare = null;
94
+ return () => {
95
+ if (spare !== null) {
96
+ const value = spare;
97
+ spare = null;
98
+ return value;
99
+ }
100
+ const radius = Math.sqrt(-2 * Math.log(1 - rng.float()));
101
+ const angle = 2 * Math.PI * rng.float();
102
+ spare = radius * Math.sin(angle);
103
+ return radius * Math.cos(angle);
104
+ };
105
+ }
106
+ /**
107
+ * A seeded one-dimensional gradient (Perlin) noise, which is what the correlated drift in
108
+ * the spec's `Gaussian + drift` is for: the second term is a slow wander that follows the
109
+ * first rather than a second independent draw, the way a player speeds up across a phrase.
110
+ */
111
+ function createDriftNoise(rng) {
112
+ const table = new Uint8Array(256);
113
+ for (let i = 0; i < 256; i++)
114
+ table[i] = i;
115
+ for (let i = 255; i > 0; i--) {
116
+ const j = rng.int(i + 1);
117
+ const swap = table[i];
118
+ table[i] = table[j];
119
+ table[j] = swap;
120
+ }
121
+ const gradient = (hash, x) => (hash & 1 ? x : -x);
122
+ return (x) => {
123
+ const cell = Math.floor(x);
124
+ const fraction = x - cell;
125
+ const fade = fraction * fraction * fraction * (fraction * (fraction * 6 - 15) + 10);
126
+ const from = gradient(table[cell & 255], fraction);
127
+ const to = gradient(table[(cell + 1) & 255], fraction - 1);
128
+ return from + fade * (to - from);
129
+ };
130
+ }
131
+ /** template offsets indexed by slot, already converted out of ticks and into milliseconds */
132
+ function slotTable(template) {
133
+ const table = new Map();
134
+ for (const offset of template.offsets) {
135
+ table.set(offset.subdivisionIndex, {
136
+ ms: ticksToMs(offset.timeOffsetTicks, template.ppq, template.tempoBpm),
137
+ factor: offset.velocityFactor,
138
+ });
139
+ }
140
+ return table;
141
+ }
142
+ /** Tier 2's melody rule needs to see the whole line before it can spot a local peak */
143
+ function markMelodyPeaks(events) {
144
+ const peaks = new Uint8Array(events.length);
145
+ const byChannel = new Map();
146
+ for (let index = 0; index < events.length; index++) {
147
+ const event = events[index];
148
+ if (event.type !== 'noteOn' || event.channel === DRUM_CHANNEL)
149
+ continue;
150
+ const line = byChannel.get(event.channel);
151
+ if (line)
152
+ line.push({ index, note: event.note });
153
+ else
154
+ byChannel.set(event.channel, [{ index, note: event.note }]);
155
+ }
156
+ for (const line of byChannel.values()) {
157
+ for (let i = 1; i < line.length - 1; i++) {
158
+ if (line[i].note > line[i - 1].note && line[i].note > line[i + 1].note)
159
+ peaks[line[i].index] = 1;
160
+ }
161
+ }
162
+ return peaks;
163
+ }
164
+ /** kick and bass carry the pulse, so Tier 2 refuses to smear them as far as the rest */
165
+ function anchorsTiming(event) {
166
+ if (event.channel === DRUM_CHANNEL)
167
+ return KICK_NOTES.includes(event.note);
168
+ return event.note < BASS_CEILING_NOTE;
169
+ }
170
+ const noteKey = (event) => `${event.channel}:${event.note}`;
171
+ /**
172
+ * The pass both tiers run: pick a slot, build a timing delta from the template plus the
173
+ * noise, clamp it so no note lands before tick 0, shake the velocity inside 1-127, and
174
+ * carry the note's own delta onto its note-off so every duration survives intact.
175
+ * Returns a fresh file sorted back into tick order; `midi` is only ever read.
176
+ */
177
+ export function applyHumanization(midi, options, tier) {
178
+ const resolved = resolveOptions(options);
179
+ const rng = new Generator(options.seed);
180
+ const gaussian = createGaussian(rng);
181
+ const drift = createDriftNoise(rng);
182
+ const ticksPerQuarter = midi.ticksPerQuarter;
183
+ const tempos = tempoSegments(midi.events);
184
+ const slots = resolved.template ? slotTable(resolved.template) : null;
185
+ const gridTicks = resolved.template ? subdivisionTicks(ticksPerQuarter, resolved.template.subdivision) : 0;
186
+ const slotCount = resolved.template
187
+ ? subdivisionCount(resolved.template.timeSignature, resolved.template.subdivision)
188
+ : 0;
189
+ const peaks = tier === 'groove' ? markMelodyPeaks(midi.events) : null;
190
+ const events = [];
191
+ /** a note's delta, queued for the note-off that closes it; FIFO so overlaps stay paired */
192
+ const openNotes = new Map();
193
+ let warnedTempo = false;
194
+ for (let index = 0; index < midi.events.length; index++) {
195
+ const event = midi.events[index];
196
+ if (event.type === 'noteOff') {
197
+ const queue = openNotes.get(noteKey(event));
198
+ const delta = queue && queue.length > 0 ? queue.shift() : 0;
199
+ events.push(delta === 0 ? { ...event } : { ...event, tick: event.tick + delta });
200
+ continue;
201
+ }
202
+ if (event.type !== 'noteOn') {
203
+ events.push({ ...event });
204
+ continue;
205
+ }
206
+ const bpm = bpmAt(tempos, event.tick);
207
+ if (resolved.template && !warnedTempo && Math.abs(bpm - resolved.template.tempoBpm) > TEMPO_TOLERANCE_BPM) {
208
+ warnedTempo = true;
209
+ resolved.warn(`groove template "${resolved.template.name}" is written at ${resolved.template.tempoBpm} BPM but this file is at ${bpm.toFixed(1)} BPM by tick ${event.tick}: its offsets are rescaled to keep the same timing in milliseconds`);
210
+ }
211
+ const sigma = msToTicks(resolved.timingMs * resolved.timingScale, ticksPerQuarter, bpm);
212
+ let noise = gaussian() * sigma + drift((event.tick / ticksPerQuarter) * DRIFT_SCALE) * sigma * DRIFT_RATIO;
213
+ if (tier === 'groove' && anchorsTiming(event)) {
214
+ const limit = msToTicks(ANCHOR_TIMING_MS, ticksPerQuarter, bpm);
215
+ noise = Math.min(limit, Math.max(-limit, noise));
216
+ }
217
+ let slot;
218
+ if (slots)
219
+ slot = slots.get(Math.floor(event.tick / gridTicks) % slotCount);
220
+ let delta = Math.round(resolved.intensity * ((slot ? msToTicks(slot.ms, ticksPerQuarter, bpm) : 0) + noise));
221
+ if (event.tick + delta < 0)
222
+ delta = -event.tick;
223
+ let velocity = Math.round(event.velocity * (1 + resolved.intensity * ((slot ? slot.factor : 1) - 1)) +
224
+ resolved.intensity * gaussian() * resolved.velocityVariance * resolved.velocityScale);
225
+ if (peaks && peaks[index] === 1)
226
+ velocity = Math.round(velocity * (1 + resolved.intensity * PEAK_BOOST));
227
+ velocity = Math.min(127, Math.max(1, velocity));
228
+ const key = noteKey(event);
229
+ const queue = openNotes.get(key);
230
+ if (queue)
231
+ queue.push(delta);
232
+ else
233
+ openNotes.set(key, [delta]);
234
+ events.push({ ...event, tick: event.tick + delta, velocity });
235
+ }
236
+ events.sort((a, b) => a.tick - b.tick);
237
+ return { ticksPerQuarter, events, loopStartTick: midi.loopStartTick };
238
+ }
239
+ //# sourceMappingURL=Humanize.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Humanize.js","sourceRoot":"","sources":["../../../src/audio/humanize/Humanize.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,sBAAsB,CAAC;AAEjD,OAAO,EAAE,oBAAoB,EAAE,gBAAgB,EAAE,gBAAgB,EAAuB,MAAM,aAAa,CAAC;AAmD5G,2FAA2F;AAC3F,MAAM,WAAW,GAAG,IAAI,CAAC;AACzB,4FAA4F;AAC5F,MAAM,WAAW,GAAG,GAAG,CAAC;AACxB,iFAAiF;AACjF,MAAM,UAAU,GAAG,IAAI,CAAC;AACxB,sFAAsF;AACtF,MAAM,mBAAmB,GAAG,CAAC,CAAC;AAC9B,+FAA+F;AAC/F,MAAM,gBAAgB,GAAG,CAAC,CAAC;AAC3B,oFAAoF;AACpF,MAAM,YAAY,GAAG,CAAC,CAAC;AACvB,6BAA6B;AAC7B,MAAM,UAAU,GAAG,CAAC,EAAE,EAAE,EAAE,CAAC,CAAC;AAC5B,iFAAiF;AACjF,MAAM,iBAAiB,GAAG,EAAE,CAAC;AAE7B,MAAM,0BAA0B,GAAG,CAAC,CAAC;AACrC,MAAM,yBAAyB,GAAG,EAAE,CAAC;AAErC,MAAM,aAAa,GAAgE;IAClF,IAAI,EAAE,EAAE,MAAM,EAAE,GAAG,EAAE,QAAQ,EAAE,GAAG,EAAE;IACpC,IAAI,EAAE,EAAE,MAAM,EAAE,GAAG,EAAE,QAAQ,EAAE,GAAG,EAAE;IACpC,SAAS,EAAE,EAAE,MAAM,EAAE,GAAG,EAAE,QAAQ,EAAE,GAAG,EAAE;IACzC,IAAI,EAAE,EAAE,MAAM,EAAE,GAAG,EAAE,QAAQ,EAAE,GAAG,EAAE;CACpC,CAAC;AAEF,MAAM,gBAAgB,GAAG,EAAE,MAAM,EAAE,CAAC,EAAE,QAAQ,EAAE,CAAC,EAAE,CAAC;AAYpD,SAAS,cAAc,CAAC,OAA4B;IACnD,MAAM,SAAS,GAAG,OAAO,CAAC,SAAS,CAAC;IACpC,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,SAAS,CAAC,IAAI,SAAS,GAAG,CAAC,IAAI,SAAS,GAAG,CAAC;QAChE,MAAM,IAAI,KAAK,CAAC,uDAAuD,MAAM,CAAC,OAAO,CAAC,SAAS,CAAC,EAAE,CAAC,CAAC;IAErG,MAAM,QAAQ,GAAG,OAAO,CAAC,gBAAgB,IAAI,0BAA0B,CAAC;IACxE,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,QAAQ,CAAC,IAAI,QAAQ,GAAG,CAAC;QAC7C,MAAM,IAAI,KAAK,CACd,+DAA+D,MAAM,CAAC,OAAO,CAAC,gBAAgB,CAAC,EAAE,CACjG,CAAC;IAEH,MAAM,gBAAgB,GAAG,OAAO,CAAC,gBAAgB,IAAI,yBAAyB,CAAC;IAC/E,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,gBAAgB,CAAC,IAAI,gBAAgB,GAAG,CAAC;QAC7D,MAAM,IAAI,KAAK,CACd,+DAA+D,MAAM,CAAC,OAAO,CAAC,gBAAgB,CAAC,EAAE,CACjG,CAAC;IAEH,MAAM,KAAK,GAAG,OAAO,CAAC,KAAK,CAAC,CAAC,CAAC,aAAa,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,gBAAgB,CAAC;IAC9E,MAAM,QAAQ,GAAG,OAAO,CAAC,cAAc,IAAI,IAAI,CAAC;IAChD,IAAI,QAAQ;QAAE,oBAAoB,CAAC,QAAQ,CAAC,CAAC;IAE7C,OAAO;QACN,SAAS;QACT,QAAQ;QACR,gBAAgB;QAChB,WAAW,EAAE,KAAK,CAAC,MAAM;QACzB,aAAa,EAAE,KAAK,CAAC,QAAQ;QAC7B,QAAQ;QACR,IAAI,EAAE,OAAO,CAAC,MAAM,IAAI,CAAC,CAAC,OAAe,EAAE,EAAE,CAAC,OAAO,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;KACpE,CAAC;AACH,CAAC;AAOD,+FAA+F;AAC/F,SAAS,aAAa,CAAC,MAA4B;IAClD,MAAM,OAAO,GAAmB,EAAE,CAAC;IACnC,KAAK,MAAM,KAAK,IAAI,MAAM,EAAE,CAAC;QAC5B,IAAI,KAAK,CAAC,IAAI,KAAK,OAAO;YACzB,OAAO,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,KAAK,CAAC,IAAI,EAAE,sBAAsB,EAAE,KAAK,CAAC,sBAAsB,EAAE,CAAC,CAAC;IAC3F,CAAC;IACD,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,GAAG,CAAC,CAAC,IAAI,CAAC,CAAC;IAExC,MAAM,QAAQ,GAAmB,CAAC,EAAE,IAAI,EAAE,CAAC,EAAE,sBAAsB,EAAE,MAAM,EAAE,CAAC,CAAC;IAC/E,KAAK,MAAM,MAAM,IAAI,OAAO,EAAE,CAAC;QAC9B,IAAI,MAAM,CAAC,IAAI,KAAK,QAAQ,CAAC,QAAQ,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,IAAI;YAAE,QAAQ,CAAC,QAAQ,CAAC,MAAM,GAAG,CAAC,CAAC,GAAG,MAAM,CAAC;;YAC1F,QAAQ,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;IAC5B,CAAC;IACD,OAAO,QAAQ,CAAC;AACjB,CAAC;AAED,qFAAqF;AACrF,SAAS,KAAK,CAAC,QAAiC,EAAE,IAAY;IAC7D,IAAI,GAAG,GAAG,CAAC,CAAC;IACZ,IAAI,IAAI,GAAG,QAAQ,CAAC,MAAM,GAAG,CAAC,CAAC;IAC/B,IAAI,KAAK,GAAG,CAAC,CAAC;IACd,OAAO,GAAG,IAAI,IAAI,EAAE,CAAC;QACpB,MAAM,MAAM,GAAG,CAAC,GAAG,GAAG,IAAI,CAAC,IAAI,CAAC,CAAC;QACjC,IAAI,QAAQ,CAAC,MAAM,CAAC,CAAC,IAAI,IAAI,IAAI,EAAE,CAAC;YACnC,KAAK,GAAG,MAAM,CAAC;YACf,GAAG,GAAG,MAAM,GAAG,CAAC,CAAC;QAClB,CAAC;;YAAM,IAAI,GAAG,MAAM,GAAG,CAAC,CAAC;IAC1B,CAAC;IACD,OAAO,UAAU,GAAG,QAAQ,CAAC,KAAK,CAAC,CAAC,sBAAsB,CAAC;AAC5D,CAAC;AAED,SAAS,SAAS,CAAC,EAAU,EAAE,eAAuB,EAAE,GAAW;IAClE,OAAO,CAAC,EAAE,GAAG,eAAe,GAAG,GAAG,CAAC,GAAG,MAAM,CAAC;AAC9C,CAAC;AAED,SAAS,SAAS,CAAC,KAAa,EAAE,eAAuB,EAAE,GAAW;IACrE,OAAO,CAAC,KAAK,GAAG,MAAM,CAAC,GAAG,CAAC,eAAe,GAAG,GAAG,CAAC,CAAC;AACnD,CAAC;AAED,yFAAyF;AACzF,SAAS,cAAc,CAAC,GAAc;IACrC,IAAI,KAAK,GAAkB,IAAI,CAAC;IAChC,OAAO,GAAG,EAAE;QACX,IAAI,KAAK,KAAK,IAAI,EAAE,CAAC;YACpB,MAAM,KAAK,GAAG,KAAK,CAAC;YACpB,KAAK,GAAG,IAAI,CAAC;YACb,OAAO,KAAK,CAAC;QACd,CAAC;QACD,MAAM,MAAM,GAAG,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,GAAG,GAAG,CAAC,KAAK,EAAE,CAAC,CAAC,CAAC;QACzD,MAAM,KAAK,GAAG,CAAC,GAAG,IAAI,CAAC,EAAE,GAAG,GAAG,CAAC,KAAK,EAAE,CAAC;QACxC,KAAK,GAAG,MAAM,GAAG,IAAI,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC;QACjC,OAAO,MAAM,GAAG,IAAI,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC;IACjC,CAAC,CAAC;AACH,CAAC;AAED;;;;GAIG;AACH,SAAS,gBAAgB,CAAC,GAAc;IACvC,MAAM,KAAK,GAAG,IAAI,UAAU,CAAC,GAAG,CAAC,CAAC;IAClC,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,GAAG,EAAE,CAAC,EAAE;QAAE,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC;IAC3C,KAAK,IAAI,CAAC,GAAG,GAAG,EAAE,CAAC,GAAG,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC;QAC9B,MAAM,CAAC,GAAG,GAAG,CAAC,GAAG,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC;QACzB,MAAM,IAAI,GAAG,KAAK,CAAC,CAAC,CAAC,CAAC;QACtB,KAAK,CAAC,CAAC,CAAC,GAAG,KAAK,CAAC,CAAC,CAAC,CAAC;QACpB,KAAK,CAAC,CAAC,CAAC,GAAG,IAAI,CAAC;IACjB,CAAC;IACD,MAAM,QAAQ,GAAG,CAAC,IAAY,EAAE,CAAS,EAAU,EAAE,CAAC,CAAC,IAAI,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;IAC1E,OAAO,CAAC,CAAS,EAAU,EAAE;QAC5B,MAAM,IAAI,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;QAC3B,MAAM,QAAQ,GAAG,CAAC,GAAG,IAAI,CAAC;QAC1B,MAAM,IAAI,GAAG,QAAQ,GAAG,QAAQ,GAAG,QAAQ,GAAG,CAAC,QAAQ,GAAG,CAAC,QAAQ,GAAG,CAAC,GAAG,EAAE,CAAC,GAAG,EAAE,CAAC,CAAC;QACpF,MAAM,IAAI,GAAG,QAAQ,CAAC,KAAK,CAAC,IAAI,GAAG,GAAG,CAAC,EAAE,QAAQ,CAAC,CAAC;QACnD,MAAM,EAAE,GAAG,QAAQ,CAAC,KAAK,CAAC,CAAC,IAAI,GAAG,CAAC,CAAC,GAAG,GAAG,CAAC,EAAE,QAAQ,GAAG,CAAC,CAAC,CAAC;QAC3D,OAAO,IAAI,GAAG,IAAI,GAAG,CAAC,EAAE,GAAG,IAAI,CAAC,CAAC;IAClC,CAAC,CAAC;AACH,CAAC;AAQD,6FAA6F;AAC7F,SAAS,SAAS,CAAC,QAAwB;IAC1C,MAAM,KAAK,GAAG,IAAI,GAAG,EAAgB,CAAC;IACtC,KAAK,MAAM,MAAM,IAAI,QAAQ,CAAC,OAAO,EAAE,CAAC;QACvC,KAAK,CAAC,GAAG,CAAC,MAAM,CAAC,gBAAgB,EAAE;YAClC,EAAE,EAAE,SAAS,CAAC,MAAM,CAAC,eAAe,EAAE,QAAQ,CAAC,GAAG,EAAE,QAAQ,CAAC,QAAQ,CAAC;YACtE,MAAM,EAAE,MAAM,CAAC,cAAc;SAC7B,CAAC,CAAC;IACJ,CAAC;IACD,OAAO,KAAK,CAAC;AACd,CAAC;AAED,uFAAuF;AACvF,SAAS,eAAe,CAAC,MAA4B;IACpD,MAAM,KAAK,GAAG,IAAI,UAAU,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC;IAC5C,MAAM,SAAS,GAAG,IAAI,GAAG,EAA6C,CAAC;IAEvE,KAAK,IAAI,KAAK,GAAG,CAAC,EAAE,KAAK,GAAG,MAAM,CAAC,MAAM,EAAE,KAAK,EAAE,EAAE,CAAC;QACpD,MAAM,KAAK,GAAG,MAAM,CAAC,KAAK,CAAC,CAAC;QAC5B,IAAI,KAAK,CAAC,IAAI,KAAK,QAAQ,IAAI,KAAK,CAAC,OAAO,KAAK,YAAY;YAAE,SAAS;QACxE,MAAM,IAAI,GAAG,SAAS,CAAC,GAAG,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC;QAC1C,IAAI,IAAI;YAAE,IAAI,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,IAAI,EAAE,KAAK,CAAC,IAAI,EAAE,CAAC,CAAC;;YAC5C,SAAS,CAAC,GAAG,CAAC,KAAK,CAAC,OAAO,EAAE,CAAC,EAAE,KAAK,EAAE,IAAI,EAAE,KAAK,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC;IAClE,CAAC;IAED,KAAK,MAAM,IAAI,IAAI,SAAS,CAAC,MAAM,EAAE,EAAE,CAAC;QACvC,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,IAAI,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC;YAC1C,IAAI,IAAI,CAAC,CAAC,CAAC,CAAC,IAAI,GAAG,IAAI,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,IAAI,IAAI,CAAC,CAAC,CAAC,CAAC,IAAI,GAAG,IAAI,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI;gBAAE,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;QAClG,CAAC;IACF,CAAC;IACD,OAAO,KAAK,CAAC;AACd,CAAC;AAED,wFAAwF;AACxF,SAAS,aAAa,CAAC,KAAoB;IAC1C,IAAI,KAAK,CAAC,OAAO,KAAK,YAAY;QAAE,OAAO,UAAU,CAAC,QAAQ,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;IAC3E,OAAO,KAAK,CAAC,IAAI,GAAG,iBAAiB,CAAC;AACvC,CAAC;AAED,MAAM,OAAO,GAAG,CAAC,KAAoB,EAAU,EAAE,CAAC,GAAG,KAAK,CAAC,OAAO,IAAI,KAAK,CAAC,IAAI,EAAE,CAAC;AAEnF;;;;;GAKG;AACH,MAAM,UAAU,iBAAiB,CAAC,IAAc,EAAE,OAA4B,EAAE,IAAuB;IACtG,MAAM,QAAQ,GAAG,cAAc,CAAC,OAAO,CAAC,CAAC;IACzC,MAAM,GAAG,GAAG,IAAI,SAAS,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC;IACxC,MAAM,QAAQ,GAAG,cAAc,CAAC,GAAG,CAAC,CAAC;IACrC,MAAM,KAAK,GAAG,gBAAgB,CAAC,GAAG,CAAC,CAAC;IACpC,MAAM,eAAe,GAAG,IAAI,CAAC,eAAe,CAAC;IAC7C,MAAM,MAAM,GAAG,aAAa,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;IAE1C,MAAM,KAAK,GAAG,QAAQ,CAAC,QAAQ,CAAC,CAAC,CAAC,SAAS,CAAC,QAAQ,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC;IACtE,MAAM,SAAS,GAAG,QAAQ,CAAC,QAAQ,CAAC,CAAC,CAAC,gBAAgB,CAAC,eAAe,EAAE,QAAQ,CAAC,QAAQ,CAAC,WAAW,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;IAC3G,MAAM,SAAS,GAAG,QAAQ,CAAC,QAAQ;QAClC,CAAC,CAAC,gBAAgB,CAAC,QAAQ,CAAC,QAAQ,CAAC,aAAa,EAAE,QAAQ,CAAC,QAAQ,CAAC,WAAW,CAAC;QAClF,CAAC,CAAC,CAAC,CAAC;IACL,MAAM,KAAK,GAAG,IAAI,KAAK,QAAQ,CAAC,CAAC,CAAC,eAAe,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC;IAEtE,MAAM,MAAM,GAAgB,EAAE,CAAC;IAC/B,2FAA2F;IAC3F,MAAM,SAAS,GAAG,IAAI,GAAG,EAAoB,CAAC;IAC9C,IAAI,WAAW,GAAG,KAAK,CAAC;IAExB,KAAK,IAAI,KAAK,GAAG,CAAC,EAAE,KAAK,GAAG,IAAI,CAAC,MAAM,CAAC,MAAM,EAAE,KAAK,EAAE,EAAE,CAAC;QACzD,MAAM,KAAK,GAAG,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;QAEjC,IAAI,KAAK,CAAC,IAAI,KAAK,SAAS,EAAE,CAAC;YAC9B,MAAM,KAAK,GAAG,SAAS,CAAC,GAAG,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,CAAC;YAC5C,MAAM,KAAK,GAAG,KAAK,IAAI,KAAK,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAE,KAAK,CAAC,KAAK,EAAa,CAAC,CAAC,CAAC,CAAC,CAAC;YACxE,MAAM,CAAC,IAAI,CAAC,KAAK,KAAK,CAAC,CAAC,CAAC,CAAC,EAAE,GAAG,KAAK,EAAE,CAAC,CAAC,CAAC,EAAE,GAAG,KAAK,EAAE,IAAI,EAAE,KAAK,CAAC,IAAI,GAAG,KAAK,EAAE,CAAC,CAAC;YACjF,SAAS;QACV,CAAC;QACD,IAAI,KAAK,CAAC,IAAI,KAAK,QAAQ,EAAE,CAAC;YAC7B,MAAM,CAAC,IAAI,CAAC,EAAE,GAAG,KAAK,EAAE,CAAC,CAAC;YAC1B,SAAS;QACV,CAAC;QAED,MAAM,GAAG,GAAG,KAAK,CAAC,MAAM,EAAE,KAAK,CAAC,IAAI,CAAC,CAAC;QACtC,IAAI,QAAQ,CAAC,QAAQ,IAAI,CAAC,WAAW,IAAI,IAAI,CAAC,GAAG,CAAC,GAAG,GAAG,QAAQ,CAAC,QAAQ,CAAC,QAAQ,CAAC,GAAG,mBAAmB,EAAE,CAAC;YAC3G,WAAW,GAAG,IAAI,CAAC;YACnB,QAAQ,CAAC,IAAI,CACZ,oBAAoB,QAAQ,CAAC,QAAQ,CAAC,IAAI,mBAAmB,QAAQ,CAAC,QAAQ,CAAC,QAAQ,4BAA4B,GAAG,CAAC,OAAO,CAC7H,CAAC,CACD,gBAAgB,KAAK,CAAC,IAAI,oEAAoE,CAC/F,CAAC;QACH,CAAC;QAED,MAAM,KAAK,GAAG,SAAS,CAAC,QAAQ,CAAC,QAAQ,GAAG,QAAQ,CAAC,WAAW,EAAE,eAAe,EAAE,GAAG,CAAC,CAAC;QACxF,IAAI,KAAK,GAAG,QAAQ,EAAE,GAAG,KAAK,GAAG,KAAK,CAAC,CAAC,KAAK,CAAC,IAAI,GAAG,eAAe,CAAC,GAAG,WAAW,CAAC,GAAG,KAAK,GAAG,WAAW,CAAC;QAC3G,IAAI,IAAI,KAAK,QAAQ,IAAI,aAAa,CAAC,KAAK,CAAC,EAAE,CAAC;YAC/C,MAAM,KAAK,GAAG,SAAS,CAAC,gBAAgB,EAAE,eAAe,EAAE,GAAG,CAAC,CAAC;YAChE,KAAK,GAAG,IAAI,CAAC,GAAG,CAAC,KAAK,EAAE,IAAI,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,KAAK,CAAC,CAAC,CAAC;QAClD,CAAC;QAED,IAAI,IAAsB,CAAC;QAC3B,IAAI,KAAK;YAAE,IAAI,GAAG,KAAK,CAAC,GAAG,CAAC,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,IAAI,GAAG,SAAS,CAAC,GAAG,SAAS,CAAC,CAAC;QAE5E,IAAI,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC,QAAQ,CAAC,SAAS,GAAG,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,SAAS,CAAC,IAAI,CAAC,EAAE,EAAE,eAAe,EAAE,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,KAAK,CAAC,CAAC,CAAC;QAC7G,IAAI,KAAK,CAAC,IAAI,GAAG,KAAK,GAAG,CAAC;YAAE,KAAK,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC;QAEhD,IAAI,QAAQ,GAAG,IAAI,CAAC,KAAK,CACxB,KAAK,CAAC,QAAQ,GAAG,CAAC,CAAC,GAAG,QAAQ,CAAC,SAAS,GAAG,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC;YACzE,QAAQ,CAAC,SAAS,GAAG,QAAQ,EAAE,GAAG,QAAQ,CAAC,gBAAgB,GAAG,QAAQ,CAAC,aAAa,CACrF,CAAC;QACF,IAAI,KAAK,IAAI,KAAK,CAAC,KAAK,CAAC,KAAK,CAAC;YAAE,QAAQ,GAAG,IAAI,CAAC,KAAK,CAAC,QAAQ,GAAG,CAAC,CAAC,GAAG,QAAQ,CAAC,SAAS,GAAG,UAAU,CAAC,CAAC,CAAC;QACzG,QAAQ,GAAG,IAAI,CAAC,GAAG,CAAC,GAAG,EAAE,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,QAAQ,CAAC,CAAC,CAAC;QAEhD,MAAM,GAAG,GAAG,OAAO,CAAC,KAAK,CAAC,CAAC;QAC3B,MAAM,KAAK,GAAG,SAAS,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;QACjC,IAAI,KAAK;YAAE,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;;YACxB,SAAS,CAAC,GAAG,CAAC,GAAG,EAAE,CAAC,KAAK,CAAC,CAAC,CAAC;QAEjC,MAAM,CAAC,IAAI,CAAC,EAAE,GAAG,KAAK,EAAE,IAAI,EAAE,KAAK,CAAC,IAAI,GAAG,KAAK,EAAE,QAAQ,EAAE,CAAC,CAAC;IAC/D,CAAC;IAED,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,GAAG,CAAC,CAAC,IAAI,CAAC,CAAC;IACvC,OAAO,EAAE,eAAe,EAAE,MAAM,EAAE,aAAa,EAAE,IAAI,CAAC,aAAa,EAAE,CAAC;AACvE,CAAC"}