@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,68 @@
1
+ import type { MidiFile } from '../Midi.ts';
2
+ import { type HumanizationOptions, type HumanizerTier, type MidiHumanizerEngine } from './Humanize.ts';
3
+ /**
4
+ * One entry point for "humanize this file", so a game never names the class that does the
5
+ * work. The tier decides the engine; everything else about the request is the same for
6
+ * both shipped tiers, which is why the request extends the options rather than wrapping
7
+ * them.
8
+ *
9
+ * The engines load on demand through dynamic import. That is a size decision, not a
10
+ * style one: `GrooveHumanizer`, the extractor and the converter are all reachable from a
11
+ * game that only ever wanted the lite tier, and none of them should be in the bundle
12
+ * merely for living in the same module. The magenta tier is the case where loading would
13
+ * never succeed at all: it needs `@magenta/music` and a PerformanceRNN checkpoint, and a
14
+ * `file://` build can fetch neither, so asking for it fails by name instead of hanging on
15
+ * a request that cannot resolve.
16
+ *
17
+ * @example
18
+ * ```ts
19
+ * import { HumanizerFactory, humanizeMidi, parseMidi } from '@datamoc/mw_games/audio';
20
+ *
21
+ * declare const midiBytes: ArrayBuffer;
22
+ *
23
+ * const file = parseMidi(midiBytes);
24
+ *
25
+ * //the convenience path: the tier follows from the request
26
+ * const lite = await humanizeMidi(file, { intensity: 0.5, seed: 1 });
27
+ *
28
+ * //or hold the engine, when several files go through the same one
29
+ * const engine = await HumanizerFactory.create('lite');
30
+ * const again = await engine.process(lite, { intensity: 0.5, seed: 2 });
31
+ *
32
+ * console.log(engine.tierName, engine.approximateSizeMb); // 'lite' 0.05
33
+ * ```
34
+ */
35
+ export declare class HumanizerFactory {
36
+ /** resolves the engine for a tier and initializes it, so `process` can be called at once */
37
+ static create(tier: HumanizerTier): Promise<MidiHumanizerEngine>;
38
+ private static load;
39
+ }
40
+ export interface HumanizeRequest extends HumanizationOptions {
41
+ /**
42
+ * Which engine runs the request. Omitted, a request carrying a groove template picks
43
+ * `'groove'` and anything else picks `'lite'`, so the common case needs no tier at all.
44
+ */
45
+ tier?: HumanizerTier;
46
+ }
47
+ /**
48
+ * Humanizes in one call: resolves the tier, runs it, returns the new file.
49
+ *
50
+ * @example
51
+ * ```ts
52
+ * import { humanizeMidi, parseMidi, type GrooveTemplate } from '@datamoc/mw_games/audio';
53
+ *
54
+ * declare const midiBytes: ArrayBuffer;
55
+ * declare const template: GrooveTemplate;
56
+ *
57
+ * const file = parseMidi(midiBytes);
58
+ * const swung = await humanizeMidi(file, {
59
+ * tier: 'groove',
60
+ * intensity: 0.7,
61
+ * grooveTemplate: template,
62
+ * seed: 99,
63
+ * onWarn: (message) => console.log(message), // a template from another tempo says so
64
+ * });
65
+ * ```
66
+ */
67
+ export declare function humanizeMidi(midi: MidiFile, request: HumanizeRequest): Promise<MidiFile>;
68
+ //# sourceMappingURL=HumanizerFactory.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"HumanizerFactory.d.ts","sourceRoot":"","sources":["../../../src/audio/humanize/HumanizerFactory.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AAC3C,OAAO,EAAE,KAAK,mBAAmB,EAAE,KAAK,aAAa,EAAE,KAAK,mBAAmB,EAAE,MAAM,eAAe,CAAC;AAEvG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+BG;AACH,qBAAa,gBAAgB;IAC5B,4FAA4F;IAC5F,OAAa,MAAM,CAAC,IAAI,EAAE,aAAa,GAAG,OAAO,CAAC,mBAAmB,CAAC,CAIrE;mBAEoB,IAAI;CAkBzB;AAED,MAAM,WAAW,eAAgB,SAAQ,mBAAmB;IAC3D;;;OAGG;IACH,IAAI,CAAC,EAAE,aAAa,CAAC;CACrB;AAED;;;;;;;;;;;;;;;;;;;GAmBG;AACH,wBAAsB,YAAY,CAAC,IAAI,EAAE,QAAQ,EAAE,OAAO,EAAE,eAAe,GAAG,OAAO,CAAC,QAAQ,CAAC,CAK9F"}
@@ -0,0 +1,84 @@
1
+ import {} from './Humanize.js';
2
+ /**
3
+ * One entry point for "humanize this file", so a game never names the class that does the
4
+ * work. The tier decides the engine; everything else about the request is the same for
5
+ * both shipped tiers, which is why the request extends the options rather than wrapping
6
+ * them.
7
+ *
8
+ * The engines load on demand through dynamic import. That is a size decision, not a
9
+ * style one: `GrooveHumanizer`, the extractor and the converter are all reachable from a
10
+ * game that only ever wanted the lite tier, and none of them should be in the bundle
11
+ * merely for living in the same module. The magenta tier is the case where loading would
12
+ * never succeed at all: it needs `@magenta/music` and a PerformanceRNN checkpoint, and a
13
+ * `file://` build can fetch neither, so asking for it fails by name instead of hanging on
14
+ * a request that cannot resolve.
15
+ *
16
+ * @example
17
+ * ```ts
18
+ * import { HumanizerFactory, humanizeMidi, parseMidi } from '@datamoc/mw_games/audio';
19
+ *
20
+ * declare const midiBytes: ArrayBuffer;
21
+ *
22
+ * const file = parseMidi(midiBytes);
23
+ *
24
+ * //the convenience path: the tier follows from the request
25
+ * const lite = await humanizeMidi(file, { intensity: 0.5, seed: 1 });
26
+ *
27
+ * //or hold the engine, when several files go through the same one
28
+ * const engine = await HumanizerFactory.create('lite');
29
+ * const again = await engine.process(lite, { intensity: 0.5, seed: 2 });
30
+ *
31
+ * console.log(engine.tierName, engine.approximateSizeMb); // 'lite' 0.05
32
+ * ```
33
+ */
34
+ export class HumanizerFactory {
35
+ /** resolves the engine for a tier and initializes it, so `process` can be called at once */
36
+ static async create(tier) {
37
+ const engine = await HumanizerFactory.load(tier);
38
+ await engine.initialize();
39
+ return engine;
40
+ }
41
+ static async load(tier) {
42
+ switch (tier) {
43
+ case 'lite': {
44
+ const module = await import('./LiteHumanizer.js');
45
+ return new module.LiteHumanizer();
46
+ }
47
+ case 'groove': {
48
+ const module = await import('./GrooveHumanizer.js');
49
+ return new module.GrooveHumanizer();
50
+ }
51
+ case 'magenta':
52
+ throw new Error("the 'magenta' tier is not shipped: it needs @magenta/music and a PerformanceRNN checkpoint, and neither can be fetched from a file:// build");
53
+ default:
54
+ throw new Error(`unknown humanizer tier: ${String(tier)}`);
55
+ }
56
+ }
57
+ }
58
+ /**
59
+ * Humanizes in one call: resolves the tier, runs it, returns the new file.
60
+ *
61
+ * @example
62
+ * ```ts
63
+ * import { humanizeMidi, parseMidi, type GrooveTemplate } from '@datamoc/mw_games/audio';
64
+ *
65
+ * declare const midiBytes: ArrayBuffer;
66
+ * declare const template: GrooveTemplate;
67
+ *
68
+ * const file = parseMidi(midiBytes);
69
+ * const swung = await humanizeMidi(file, {
70
+ * tier: 'groove',
71
+ * intensity: 0.7,
72
+ * grooveTemplate: template,
73
+ * seed: 99,
74
+ * onWarn: (message) => console.log(message), // a template from another tempo says so
75
+ * });
76
+ * ```
77
+ */
78
+ export async function humanizeMidi(midi, request) {
79
+ const { tier, ...options } = request;
80
+ const chosen = tier ?? (options.grooveTemplate ? 'groove' : 'lite');
81
+ const engine = await HumanizerFactory.create(chosen);
82
+ return engine.process(midi, options);
83
+ }
84
+ //# sourceMappingURL=HumanizerFactory.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"HumanizerFactory.js","sourceRoot":"","sources":["../../../src/audio/humanize/HumanizerFactory.ts"],"names":[],"mappings":"AACA,OAAO,EAA0E,MAAM,eAAe,CAAC;AAEvG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+BG;AACH,MAAM,OAAO,gBAAgB;IAC5B,4FAA4F;IAC5F,MAAM,CAAC,KAAK,CAAC,MAAM,CAAC,IAAmB;QACtC,MAAM,MAAM,GAAG,MAAM,gBAAgB,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QACjD,MAAM,MAAM,CAAC,UAAU,EAAE,CAAC;QAC1B,OAAO,MAAM,CAAC;IACf,CAAC;IAEO,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,IAAmB;QAC5C,QAAQ,IAAI,EAAE,CAAC;YACd,KAAK,MAAM,EAAE,CAAC;gBACb,MAAM,MAAM,GAAG,MAAM,MAAM,CAAC,oBAAoB,CAAC,CAAC;gBAClD,OAAO,IAAI,MAAM,CAAC,aAAa,EAAE,CAAC;YACnC,CAAC;YACD,KAAK,QAAQ,EAAE,CAAC;gBACf,MAAM,MAAM,GAAG,MAAM,MAAM,CAAC,sBAAsB,CAAC,CAAC;gBACpD,OAAO,IAAI,MAAM,CAAC,eAAe,EAAE,CAAC;YACrC,CAAC;YACD,KAAK,SAAS;gBACb,MAAM,IAAI,KAAK,CACd,6IAA6I,CAC7I,CAAC;YACH;gBACC,MAAM,IAAI,KAAK,CAAC,2BAA2B,MAAM,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;QAC7D,CAAC;IACF,CAAC;CACD;AAUD;;;;;;;;;;;;;;;;;;;GAmBG;AACH,MAAM,CAAC,KAAK,UAAU,YAAY,CAAC,IAAc,EAAE,OAAwB;IAC1E,MAAM,EAAE,IAAI,EAAE,GAAG,OAAO,EAAE,GAAG,OAAO,CAAC;IACrC,MAAM,MAAM,GAAG,IAAI,IAAI,CAAC,OAAO,CAAC,cAAc,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC;IACpE,MAAM,MAAM,GAAG,MAAM,gBAAgB,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC;IACrD,OAAO,MAAM,CAAC,OAAO,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC;AACtC,CAAC"}
@@ -0,0 +1,30 @@
1
+ import type { MidiFile } from '../Midi.ts';
2
+ import { type HumanizationOptions, type MidiHumanizerEngine } from './Humanize.ts';
3
+ /**
4
+ * Tier 1: every note gets a seeded gaussian nudge plus a slow correlated drift, and
5
+ * nothing else. No grid, no model, no I/O - which is exactly why it is the tier a shipped
6
+ * game can afford to call, and why it has to refuse a groove template rather than quietly
7
+ * ignore one: a template passed here is a caller who picked the wrong tier.
8
+ *
9
+ * @example
10
+ * ```ts
11
+ * import { parseMidi, LiteHumanizer } from '@datamoc/mw_games/audio';
12
+ *
13
+ * declare const midiBytes: ArrayBuffer;
14
+ *
15
+ * const file = parseMidi(midiBytes);
16
+ * const humanized = await new LiteHumanizer().process(file, {
17
+ * intensity: 0.6,
18
+ * timingVarianceMs: 6,
19
+ * seed: 42, // the same seed gives back the same performance every time
20
+ * });
21
+ * ```
22
+ */
23
+ export declare class LiteHumanizer implements MidiHumanizerEngine {
24
+ readonly tierName = "lite";
25
+ readonly approximateSizeMb = 0.05;
26
+ /** nothing heavy to load: the tier is the pass, already in the bundle */
27
+ initialize(): Promise<void>;
28
+ process(midi: MidiFile, options: HumanizationOptions): Promise<MidiFile>;
29
+ }
30
+ //# sourceMappingURL=LiteHumanizer.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"LiteHumanizer.d.ts","sourceRoot":"","sources":["../../../src/audio/humanize/LiteHumanizer.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;;;;;;;;;;;;;;;;;;;GAmBG;AACH,qBAAa,aAAc,YAAW,mBAAmB;IACxD,QAAQ,CAAC,QAAQ,UAAU;IAC3B,QAAQ,CAAC,iBAAiB,QAAQ;IAElC,yEAAyE;IACnE,UAAU,IAAI,OAAO,CAAC,IAAI,CAAC,CAAG;IAE9B,OAAO,CAAC,IAAI,EAAE,QAAQ,EAAE,OAAO,EAAE,mBAAmB,GAAG,OAAO,CAAC,QAAQ,CAAC,CAM7E;CACD"}
@@ -0,0 +1,35 @@
1
+ import { applyHumanization } from './Humanize.js';
2
+ /**
3
+ * Tier 1: every note gets a seeded gaussian nudge plus a slow correlated drift, and
4
+ * nothing else. No grid, no model, no I/O - which is exactly why it is the tier a shipped
5
+ * game can afford to call, and why it has to refuse a groove template rather than quietly
6
+ * ignore one: a template passed here is a caller who picked the wrong tier.
7
+ *
8
+ * @example
9
+ * ```ts
10
+ * import { parseMidi, LiteHumanizer } from '@datamoc/mw_games/audio';
11
+ *
12
+ * declare const midiBytes: ArrayBuffer;
13
+ *
14
+ * const file = parseMidi(midiBytes);
15
+ * const humanized = await new LiteHumanizer().process(file, {
16
+ * intensity: 0.6,
17
+ * timingVarianceMs: 6,
18
+ * seed: 42, // the same seed gives back the same performance every time
19
+ * });
20
+ * ```
21
+ */
22
+ export class LiteHumanizer {
23
+ constructor() {
24
+ this.tierName = 'lite';
25
+ this.approximateSizeMb = 0.05;
26
+ }
27
+ /** nothing heavy to load: the tier is the pass, already in the bundle */
28
+ async initialize() { }
29
+ async process(midi, options) {
30
+ if (options.grooveTemplate)
31
+ throw new Error('the lite tier applies no groove grid: ask HumanizerFactory for "groove" when a template is in play');
32
+ return applyHumanization(midi, options, 'lite');
33
+ }
34
+ }
35
+ //# sourceMappingURL=LiteHumanizer.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"LiteHumanizer.js","sourceRoot":"","sources":["../../../src/audio/humanize/LiteHumanizer.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,iBAAiB,EAAsD,MAAM,eAAe,CAAC;AAEtG;;;;;;;;;;;;;;;;;;;GAmBG;AACH,MAAM,OAAO,aAAa;IAA1B;QACU,aAAQ,GAAG,MAAM,CAAC;QAClB,sBAAiB,GAAG,IAAI,CAAC;IAYnC,CAAC;IAVA,yEAAyE;IACzE,KAAK,CAAC,UAAU,KAAmB,CAAC;IAEpC,KAAK,CAAC,OAAO,CAAC,IAAc,EAAE,OAA4B;QACzD,IAAI,OAAO,CAAC,cAAc;YACzB,MAAM,IAAI,KAAK,CACd,oGAAoG,CACpG,CAAC;QACH,OAAO,iBAAiB,CAAC,IAAI,EAAE,OAAO,EAAE,MAAM,CAAC,CAAC;IACjD,CAAC;CACD"}
@@ -0,0 +1,26 @@
1
+ /**
2
+ * Onset detection, for the audio path of `GrooveExtractor`: when a groove has to come
3
+ * from a recording rather than from a pair of MIDI files, something has to find the hits
4
+ * first. Spectral flux over a short sliding window is the usual measure (what aubio and
5
+ * Essentia reach for), and it is cheap enough to hand-roll: a radix-2 FFT, the rise in
6
+ * magnitude bin by bin, then a local threshold with a minimum gap so one attack is not
7
+ * reported again by its own decay.
8
+ *
9
+ * The timings are what a groove lives or dies on, so the frame geometry is worth stating.
10
+ * Frames are padded by half a window either side, which is what lets a hit at sample 0 be
11
+ * measured with the window's full weight instead of its zeroed edge. A Hann window makes
12
+ * flux peak a quarter of a window plus half a hop after the hit it is reacting to; the
13
+ * detector subtracts that constant, leaving only the half-hop of frame quantization.
14
+ *
15
+ * Flux alone is not enough to call something an onset, which is what `ENERGY_RISE` below
16
+ * is for: everything else in this file is about locating a hit, that gate is about
17
+ * refusing to find one where there is none.
18
+ */
19
+ /**
20
+ * When each onset in a mono signal happens, in seconds from its start.
21
+ *
22
+ * Returns an empty array rather than throwing when there is nothing to find; the caller
23
+ * decides whether silence is a valid extraction result (it is not, and it says so).
24
+ */
25
+ export declare function detectOnsets(samples: Float32Array, sampleRate: number): number[];
26
+ //# sourceMappingURL=Onsets.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Onsets.d.ts","sourceRoot":"","sources":["../../../src/audio/humanize/Onsets.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AA0EH;;;;;GAKG;AACH,wBAAgB,YAAY,CAAC,OAAO,EAAE,YAAY,EAAE,UAAU,EAAE,MAAM,GAAG,MAAM,EAAE,CAwEhF"}
@@ -0,0 +1,164 @@
1
+ /**
2
+ * Onset detection, for the audio path of `GrooveExtractor`: when a groove has to come
3
+ * from a recording rather than from a pair of MIDI files, something has to find the hits
4
+ * first. Spectral flux over a short sliding window is the usual measure (what aubio and
5
+ * Essentia reach for), and it is cheap enough to hand-roll: a radix-2 FFT, the rise in
6
+ * magnitude bin by bin, then a local threshold with a minimum gap so one attack is not
7
+ * reported again by its own decay.
8
+ *
9
+ * The timings are what a groove lives or dies on, so the frame geometry is worth stating.
10
+ * Frames are padded by half a window either side, which is what lets a hit at sample 0 be
11
+ * measured with the window's full weight instead of its zeroed edge. A Hann window makes
12
+ * flux peak a quarter of a window plus half a hop after the hit it is reacting to; the
13
+ * detector subtracts that constant, leaving only the half-hop of frame quantization.
14
+ *
15
+ * Flux alone is not enough to call something an onset, which is what `ENERGY_RISE` below
16
+ * is for: everything else in this file is about locating a hit, that gate is about
17
+ * refusing to find one where there is none.
18
+ */
19
+ const FRAME = 256;
20
+ const HOP = 64;
21
+ /** frames either side of a peak that make up its local mean and spread */
22
+ const NEIGHBOURHOOD = 10;
23
+ /**
24
+ * How far above its neighbourhood a peak has to sit. Two is the balance point: an attack
25
+ * lands well above it however loud the rest of the file is, while three lets the
26
+ * neighbourhood of a sparse attack (mostly zeros) put the peak back within reach.
27
+ */
28
+ const FLUX_SIGMAS = 2;
29
+ /**
30
+ * An onset has to multiply its frame's energy by this much. This is what tells a hit apart
31
+ * from a sustained note: flux alone reports the beating of a steady tone as a train of
32
+ * onsets, because the tone's energy sloshes between bins while its own envelope is flat. A
33
+ * real onset multiplies the frame's energy many times over; a steady tone moves it by a
34
+ * couple of per cent. The previous frame may carry the rise, since flux compares against
35
+ * the frame it came from and so trails the energy by up to one frame.
36
+ */
37
+ const ENERGY_RISE = 0.05;
38
+ /** minimum gap between two onsets, in frames: one attack, not its own ringing */
39
+ const MIN_GAP_FRAMES = 4;
40
+ /** where a Hann window's derivative puts the flux peak behind the hit: FRAME/4 + HOP/2 */
41
+ const FLUX_PEAK_DELAY = FRAME / 4 + HOP / 2;
42
+ /** in-place iterative radix-2 FFT; `real` and `imaginary` must be a power of two long */
43
+ function fft(real, imaginary) {
44
+ const length = real.length;
45
+ let j = 0;
46
+ for (let i = 1; i < length; i++) {
47
+ let bit = length >> 1;
48
+ while (j & bit) {
49
+ j ^= bit;
50
+ bit >>= 1;
51
+ }
52
+ j ^= bit;
53
+ if (i < j) {
54
+ let swap = real[i];
55
+ real[i] = real[j];
56
+ real[j] = swap;
57
+ swap = imaginary[i];
58
+ imaginary[i] = imaginary[j];
59
+ imaginary[j] = swap;
60
+ }
61
+ }
62
+ for (let size = 2; size <= length; size <<= 1) {
63
+ const angle = (-2 * Math.PI) / size;
64
+ const stepReal = Math.cos(angle);
65
+ const stepImaginary = Math.sin(angle);
66
+ const half = size >> 1;
67
+ for (let start = 0; start < length; start += size) {
68
+ let weightReal = 1;
69
+ let weightImaginary = 0;
70
+ for (let k = 0; k < half; k++) {
71
+ const even = start + k;
72
+ const odd = even + half;
73
+ const oddReal = real[odd] * weightReal - imaginary[odd] * weightImaginary;
74
+ const oddImaginary = real[odd] * weightImaginary + imaginary[odd] * weightReal;
75
+ real[odd] = real[even] - oddReal;
76
+ imaginary[odd] = imaginary[even] - oddImaginary;
77
+ real[even] += oddReal;
78
+ imaginary[even] += oddImaginary;
79
+ const nextReal = weightReal * stepReal - weightImaginary * stepImaginary;
80
+ weightImaginary = weightReal * stepImaginary + weightImaginary * stepReal;
81
+ weightReal = nextReal;
82
+ }
83
+ }
84
+ }
85
+ }
86
+ /**
87
+ * When each onset in a mono signal happens, in seconds from its start.
88
+ *
89
+ * Returns an empty array rather than throwing when there is nothing to find; the caller
90
+ * decides whether silence is a valid extraction result (it is not, and it says so).
91
+ */
92
+ export function detectOnsets(samples, sampleRate) {
93
+ if (samples.length === 0 || sampleRate <= 0)
94
+ return [];
95
+ const pad = FRAME / 2;
96
+ const padded = new Float32Array(samples.length + pad * 2);
97
+ padded.set(samples, pad);
98
+ const window = new Float64Array(FRAME);
99
+ for (let i = 0; i < FRAME; i++)
100
+ window[i] = 0.5 - 0.5 * Math.cos((2 * Math.PI * i) / (FRAME - 1));
101
+ const real = new Float64Array(FRAME);
102
+ const imaginary = new Float64Array(FRAME);
103
+ const previous = new Float64Array(FRAME / 2);
104
+ const flux = [];
105
+ const energyRise = [];
106
+ let previousEnergy = 0;
107
+ for (let start = 0; start + FRAME <= padded.length; start += HOP) {
108
+ let energy = 0;
109
+ for (let i = 0; i < FRAME; i++) {
110
+ real[i] = padded[start + i] * window[i];
111
+ imaginary[i] = 0;
112
+ energy += real[i] * real[i];
113
+ }
114
+ //nothing against nothing is no change, but a frame that finally carries signal is
115
+ //an unbounded one, which is what the first frame of any file is
116
+ energyRise.push(previousEnergy === 0 ? (energy === 0 ? 0 : Infinity) : energy / previousEnergy - 1);
117
+ previousEnergy = energy;
118
+ fft(real, imaginary);
119
+ let rise = 0;
120
+ for (let k = 0; k < FRAME / 2; k++) {
121
+ const magnitude = Math.hypot(real[k], imaginary[k]);
122
+ const difference = magnitude - previous[k];
123
+ if (difference > 0)
124
+ rise += difference;
125
+ previous[k] = magnitude;
126
+ }
127
+ flux.push(rise);
128
+ }
129
+ const onsets = [];
130
+ let lastFrame = -Infinity;
131
+ for (let i = 0; i < flux.length; i++) {
132
+ const from = Math.max(0, i - NEIGHBOURHOOD);
133
+ const to = Math.min(flux.length - 1, i + NEIGHBOURHOOD);
134
+ const span = to - from + 1;
135
+ let mean = 0;
136
+ for (let k = from; k <= to; k++)
137
+ mean += flux[k];
138
+ mean /= span;
139
+ let variance = 0;
140
+ for (let k = from; k <= to; k++)
141
+ variance += (flux[k] - mean) ** 2;
142
+ const threshold = mean + FLUX_SIGMAS * Math.sqrt(variance / span);
143
+ //`<= 0` first: a silent file has flux of exactly zero everywhere, and its last
144
+ //frame would otherwise pass every later test and report an onset at the end
145
+ if (flux[i] <= 0 || flux[i] < threshold)
146
+ continue;
147
+ //and an attack has to have moved the envelope, not just the bin balance
148
+ if (energyRise[i] < ENERGY_RISE && (i === 0 || energyRise[i - 1] < ENERGY_RISE))
149
+ continue;
150
+ // the last frame of a flat top wins, so the plateau an impulse leaves behind
151
+ // resolves to one onset rather than the first frame of the plateau
152
+ if (i > 0 && flux[i - 1] > flux[i])
153
+ continue;
154
+ if (i + 1 < flux.length && flux[i + 1] >= flux[i])
155
+ continue;
156
+ if (i - lastFrame < MIN_GAP_FRAMES)
157
+ continue;
158
+ const time = (i * HOP + pad - FLUX_PEAK_DELAY) / sampleRate;
159
+ onsets.push(Math.max(0, time));
160
+ lastFrame = i;
161
+ }
162
+ return onsets;
163
+ }
164
+ //# sourceMappingURL=Onsets.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Onsets.js","sourceRoot":"","sources":["../../../src/audio/humanize/Onsets.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AAEH,MAAM,KAAK,GAAG,GAAG,CAAC;AAClB,MAAM,GAAG,GAAG,EAAE,CAAC;AACf,0EAA0E;AAC1E,MAAM,aAAa,GAAG,EAAE,CAAC;AACzB;;;;GAIG;AACH,MAAM,WAAW,GAAG,CAAC,CAAC;AACtB;;;;;;;GAOG;AACH,MAAM,WAAW,GAAG,IAAI,CAAC;AACzB,iFAAiF;AACjF,MAAM,cAAc,GAAG,CAAC,CAAC;AACzB,0FAA0F;AAC1F,MAAM,eAAe,GAAG,KAAK,GAAG,CAAC,GAAG,GAAG,GAAG,CAAC,CAAC;AAE5C,yFAAyF;AACzF,SAAS,GAAG,CAAC,IAAkB,EAAE,SAAuB;IACvD,MAAM,MAAM,GAAG,IAAI,CAAC,MAAM,CAAC;IAE3B,IAAI,CAAC,GAAG,CAAC,CAAC;IACV,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;QACjC,IAAI,GAAG,GAAG,MAAM,IAAI,CAAC,CAAC;QACtB,OAAO,CAAC,GAAG,GAAG,EAAE,CAAC;YAChB,CAAC,IAAI,GAAG,CAAC;YACT,GAAG,KAAK,CAAC,CAAC;QACX,CAAC;QACD,CAAC,IAAI,GAAG,CAAC;QACT,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC;YACX,IAAI,IAAI,GAAG,IAAI,CAAC,CAAC,CAAC,CAAC;YACnB,IAAI,CAAC,CAAC,CAAC,GAAG,IAAI,CAAC,CAAC,CAAC,CAAC;YAClB,IAAI,CAAC,CAAC,CAAC,GAAG,IAAI,CAAC;YACf,IAAI,GAAG,SAAS,CAAC,CAAC,CAAC,CAAC;YACpB,SAAS,CAAC,CAAC,CAAC,GAAG,SAAS,CAAC,CAAC,CAAC,CAAC;YAC5B,SAAS,CAAC,CAAC,CAAC,GAAG,IAAI,CAAC;QACrB,CAAC;IACF,CAAC;IAED,KAAK,IAAI,IAAI,GAAG,CAAC,EAAE,IAAI,IAAI,MAAM,EAAE,IAAI,KAAK,CAAC,EAAE,CAAC;QAC/C,MAAM,KAAK,GAAG,CAAC,CAAC,CAAC,GAAG,IAAI,CAAC,EAAE,CAAC,GAAG,IAAI,CAAC;QACpC,MAAM,QAAQ,GAAG,IAAI,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC;QACjC,MAAM,aAAa,GAAG,IAAI,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC;QACtC,MAAM,IAAI,GAAG,IAAI,IAAI,CAAC,CAAC;QACvB,KAAK,IAAI,KAAK,GAAG,CAAC,EAAE,KAAK,GAAG,MAAM,EAAE,KAAK,IAAI,IAAI,EAAE,CAAC;YACnD,IAAI,UAAU,GAAG,CAAC,CAAC;YACnB,IAAI,eAAe,GAAG,CAAC,CAAC;YACxB,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,IAAI,EAAE,CAAC,EAAE,EAAE,CAAC;gBAC/B,MAAM,IAAI,GAAG,KAAK,GAAG,CAAC,CAAC;gBACvB,MAAM,GAAG,GAAG,IAAI,GAAG,IAAI,CAAC;gBACxB,MAAM,OAAO,GAAG,IAAI,CAAC,GAAG,CAAC,GAAG,UAAU,GAAG,SAAS,CAAC,GAAG,CAAC,GAAG,eAAe,CAAC;gBAC1E,MAAM,YAAY,GAAG,IAAI,CAAC,GAAG,CAAC,GAAG,eAAe,GAAG,SAAS,CAAC,GAAG,CAAC,GAAG,UAAU,CAAC;gBAC/E,IAAI,CAAC,GAAG,CAAC,GAAG,IAAI,CAAC,IAAI,CAAC,GAAG,OAAO,CAAC;gBACjC,SAAS,CAAC,GAAG,CAAC,GAAG,SAAS,CAAC,IAAI,CAAC,GAAG,YAAY,CAAC;gBAChD,IAAI,CAAC,IAAI,CAAC,IAAI,OAAO,CAAC;gBACtB,SAAS,CAAC,IAAI,CAAC,IAAI,YAAY,CAAC;gBAEhC,MAAM,QAAQ,GAAG,UAAU,GAAG,QAAQ,GAAG,eAAe,GAAG,aAAa,CAAC;gBACzE,eAAe,GAAG,UAAU,GAAG,aAAa,GAAG,eAAe,GAAG,QAAQ,CAAC;gBAC1E,UAAU,GAAG,QAAQ,CAAC;YACvB,CAAC;QACF,CAAC;IACF,CAAC;AACF,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,YAAY,CAAC,OAAqB,EAAE,UAAkB;IACrE,IAAI,OAAO,CAAC,MAAM,KAAK,CAAC,IAAI,UAAU,IAAI,CAAC;QAAE,OAAO,EAAE,CAAC;IAEvD,MAAM,GAAG,GAAG,KAAK,GAAG,CAAC,CAAC;IACtB,MAAM,MAAM,GAAG,IAAI,YAAY,CAAC,OAAO,CAAC,MAAM,GAAG,GAAG,GAAG,CAAC,CAAC,CAAC;IAC1D,MAAM,CAAC,GAAG,CAAC,OAAO,EAAE,GAAG,CAAC,CAAC;IAEzB,MAAM,MAAM,GAAG,IAAI,YAAY,CAAC,KAAK,CAAC,CAAC;IACvC,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,KAAK,EAAE,CAAC,EAAE;QAAE,MAAM,CAAC,CAAC,CAAC,GAAG,GAAG,GAAG,GAAG,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,GAAG,IAAI,CAAC,EAAE,GAAG,CAAC,CAAC,GAAG,CAAC,KAAK,GAAG,CAAC,CAAC,CAAC,CAAC;IAElG,MAAM,IAAI,GAAG,IAAI,YAAY,CAAC,KAAK,CAAC,CAAC;IACrC,MAAM,SAAS,GAAG,IAAI,YAAY,CAAC,KAAK,CAAC,CAAC;IAC1C,MAAM,QAAQ,GAAG,IAAI,YAAY,CAAC,KAAK,GAAG,CAAC,CAAC,CAAC;IAC7C,MAAM,IAAI,GAAa,EAAE,CAAC;IAC1B,MAAM,UAAU,GAAa,EAAE,CAAC;IAChC,IAAI,cAAc,GAAG,CAAC,CAAC;IAEvB,KAAK,IAAI,KAAK,GAAG,CAAC,EAAE,KAAK,GAAG,KAAK,IAAI,MAAM,CAAC,MAAM,EAAE,KAAK,IAAI,GAAG,EAAE,CAAC;QAClE,IAAI,MAAM,GAAG,CAAC,CAAC;QACf,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,KAAK,EAAE,CAAC,EAAE,EAAE,CAAC;YAChC,IAAI,CAAC,CAAC,CAAC,GAAG,MAAM,CAAC,KAAK,GAAG,CAAC,CAAC,GAAG,MAAM,CAAC,CAAC,CAAC,CAAC;YACxC,SAAS,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC;YACjB,MAAM,IAAI,IAAI,CAAC,CAAC,CAAC,GAAG,IAAI,CAAC,CAAC,CAAC,CAAC;QAC7B,CAAC;QACD,kFAAkF;QAClF,gEAAgE;QAChE,UAAU,CAAC,IAAI,CAAC,cAAc,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,MAAM,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,MAAM,GAAG,cAAc,GAAG,CAAC,CAAC,CAAC;QACpG,cAAc,GAAG,MAAM,CAAC;QAExB,GAAG,CAAC,IAAI,EAAE,SAAS,CAAC,CAAC;QAErB,IAAI,IAAI,GAAG,CAAC,CAAC;QACb,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,KAAK,GAAG,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC;YACpC,MAAM,SAAS,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,SAAS,CAAC,CAAC,CAAC,CAAC,CAAC;YACpD,MAAM,UAAU,GAAG,SAAS,GAAG,QAAQ,CAAC,CAAC,CAAC,CAAC;YAC3C,IAAI,UAAU,GAAG,CAAC;gBAAE,IAAI,IAAI,UAAU,CAAC;YACvC,QAAQ,CAAC,CAAC,CAAC,GAAG,SAAS,CAAC;QACzB,CAAC;QACD,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IACjB,CAAC;IAED,MAAM,MAAM,GAAa,EAAE,CAAC;IAC5B,IAAI,SAAS,GAAG,CAAC,QAAQ,CAAC;IAC1B,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,IAAI,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;QACtC,MAAM,IAAI,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,CAAC,GAAG,aAAa,CAAC,CAAC;QAC5C,MAAM,EAAE,GAAG,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC,GAAG,aAAa,CAAC,CAAC;QACxD,MAAM,IAAI,GAAG,EAAE,GAAG,IAAI,GAAG,CAAC,CAAC;QAE3B,IAAI,IAAI,GAAG,CAAC,CAAC;QACb,KAAK,IAAI,CAAC,GAAG,IAAI,EAAE,CAAC,IAAI,EAAE,EAAE,CAAC,EAAE;YAAE,IAAI,IAAI,IAAI,CAAC,CAAC,CAAC,CAAC;QACjD,IAAI,IAAI,IAAI,CAAC;QAEb,IAAI,QAAQ,GAAG,CAAC,CAAC;QACjB,KAAK,IAAI,CAAC,GAAG,IAAI,EAAE,CAAC,IAAI,EAAE,EAAE,CAAC,EAAE;YAAE,QAAQ,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,GAAG,IAAI,CAAC,IAAI,CAAC,CAAC;QACnE,MAAM,SAAS,GAAG,IAAI,GAAG,WAAW,GAAG,IAAI,CAAC,IAAI,CAAC,QAAQ,GAAG,IAAI,CAAC,CAAC;QAElE,+EAA+E;QAC/E,4EAA4E;QAC5E,IAAI,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,IAAI,CAAC,CAAC,CAAC,GAAG,SAAS;YAAE,SAAS;QAClD,wEAAwE;QACxE,IAAI,UAAU,CAAC,CAAC,CAAC,GAAG,WAAW,IAAI,CAAC,CAAC,KAAK,CAAC,IAAI,UAAU,CAAC,CAAC,GAAG,CAAC,CAAC,GAAG,WAAW,CAAC;YAAE,SAAS;QAC1F,6EAA6E;QAC7E,mEAAmE;QACnE,IAAI,CAAC,GAAG,CAAC,IAAI,IAAI,CAAC,CAAC,GAAG,CAAC,CAAC,GAAG,IAAI,CAAC,CAAC,CAAC;YAAE,SAAS;QAC7C,IAAI,CAAC,GAAG,CAAC,GAAG,IAAI,CAAC,MAAM,IAAI,IAAI,CAAC,CAAC,GAAG,CAAC,CAAC,IAAI,IAAI,CAAC,CAAC,CAAC;YAAE,SAAS;QAC5D,IAAI,CAAC,GAAG,SAAS,GAAG,cAAc;YAAE,SAAS;QAE7C,MAAM,IAAI,GAAG,CAAC,CAAC,GAAG,GAAG,GAAG,GAAG,GAAG,eAAe,CAAC,GAAG,UAAU,CAAC;QAC5D,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,CAAC,CAAC;QAC/B,SAAS,GAAG,CAAC,CAAC;IACf,CAAC;IACD,OAAO,MAAM,CAAC;AACf,CAAC"}
@@ -23,4 +23,15 @@ export type { LoopRegion, ChannelPlayOptions, ChannelState, BusMixerLevel } from
23
23
  export type { BusKind, BusTrack, SavedBusTrack, LoadedTrack, BusLoader, BusSnapshot, AudioBusOptions, } from './Channels.ts';
24
24
  export { AudioListener, SoundSource, audioGain, audioPan } from './Positional.ts';
25
25
  export type { AudioPoint, AudioFalloff, SoundSourceOptions } from './Positional.ts';
26
+ export { subdivisionCount, assertGrooveTemplate } from './humanize/Groove.ts';
27
+ export type { GridSubdivision, GrooveOffset, GrooveTemplate } from './humanize/Groove.ts';
28
+ export { LiteHumanizer } from './humanize/LiteHumanizer.ts';
29
+ export { GrooveHumanizer } from './humanize/GrooveHumanizer.ts';
30
+ export { GrooveExtractor } from './humanize/GrooveExtractor.ts';
31
+ export type { GrooveExtractorEngine } from './humanize/GrooveExtractor.ts';
32
+ export { GrooveConverter } from './humanize/GrooveConverter.ts';
33
+ export type { GrooveConverterEngine, GrooveFormat } from './humanize/GrooveConverter.ts';
34
+ export { HumanizerFactory, humanizeMidi } from './humanize/HumanizerFactory.ts';
35
+ export type { HumanizeRequest } from './humanize/HumanizerFactory.ts';
36
+ export type { HumanizationOptions, HumanizeStyle, HumanizerTier, MidiHumanizerEngine } from './humanize/Humanize.ts';
26
37
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/audio/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,MAAM,YAAY,CAAC;AACnC,YAAY,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAC/C,OAAO,EAAE,SAAS,EAAE,MAAM,eAAe,CAAC;AAC1C,YAAY,EAAE,YAAY,EAAE,MAAM,eAAe,CAAC;AAClD,OAAO,EAAE,KAAK,EAAE,MAAM,YAAY,CAAC;AACnC,YAAY,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAC/C,OAAO,EAAE,WAAW,EAAE,MAAM,eAAe,CAAC;AAC5C,YAAY,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AAC9C,OAAO,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;AACjD,YAAY,EAAE,iBAAiB,EAAE,MAAM,mBAAmB,CAAC;AAC3D,OAAO,EAAE,cAAc,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AACtD,YAAY,EAAE,QAAQ,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AACxD,OAAO,EAAE,SAAS,EAAE,YAAY,EAAE,eAAe,EAAE,aAAa,EAAE,UAAU,EAAE,MAAM,WAAW,CAAC;AAChG,YAAY,EACX,QAAQ,EACR,SAAS,EACT,aAAa,EACb,cAAc,EACd,gBAAgB,EAChB,gBAAgB,EAChB,kBAAkB,EAClB,SAAS,EACT,aAAa,EACb,iBAAiB,GACjB,MAAM,WAAW,CAAC;AACnB,OAAO,EAAE,kBAAkB,EAAE,uBAAuB,EAAE,MAAM,iBAAiB,CAAC;AAC9E,YAAY,EAAE,iBAAiB,EAAE,sBAAsB,EAAE,YAAY,EAAE,MAAM,iBAAiB,CAAC;AAC/F,OAAO,EAAE,qBAAqB,EAAE,cAAc,EAAE,eAAe,EAAE,MAAM,gBAAgB,CAAC;AACxF,YAAY,EAAE,SAAS,EAAE,gBAAgB,EAAE,eAAe,EAAE,cAAc,EAAE,MAAM,gBAAgB,CAAC;AACnG,OAAO,EAAE,OAAO,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AAClD,OAAO,EAAE,mBAAmB,EAAE,kBAAkB,EAAE,MAAM,eAAe,CAAC;AACxE,YAAY,EAAE,cAAc,EAAE,MAAM,eAAe,CAAC;AACpD,YAAY,EAAE,UAAU,EAAE,kBAAkB,EAAE,YAAY,EAAE,aAAa,EAAE,MAAM,eAAe,CAAC;AACjG,YAAY,EACX,OAAO,EACP,QAAQ,EACR,aAAa,EACb,WAAW,EACX,SAAS,EACT,WAAW,EACX,eAAe,GACf,MAAM,eAAe,CAAC;AACvB,OAAO,EAAE,aAAa,EAAE,WAAW,EAAE,SAAS,EAAE,QAAQ,EAAE,MAAM,iBAAiB,CAAC;AAClF,YAAY,EAAE,UAAU,EAAE,YAAY,EAAE,kBAAkB,EAAE,MAAM,iBAAiB,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/audio/index.ts"],"names":[],"mappings":"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"}
@@ -10,4 +10,10 @@ export { collectSoundFontUsage, parseSoundFont, subsetSoundFont } from './SoundF
10
10
  export { Channel, AudioBus } from './Channels.js';
11
11
  export { parseVorbisLoopTags, loopRegionFromTags } from './LoopTags.js';
12
12
  export { AudioListener, SoundSource, audioGain, audioPan } from './Positional.js';
13
+ export { subdivisionCount, assertGrooveTemplate } from './humanize/Groove.js';
14
+ export { LiteHumanizer } from './humanize/LiteHumanizer.js';
15
+ export { GrooveHumanizer } from './humanize/GrooveHumanizer.js';
16
+ export { GrooveExtractor } from './humanize/GrooveExtractor.js';
17
+ export { GrooveConverter } from './humanize/GrooveConverter.js';
18
+ export { HumanizerFactory, humanizeMidi } from './humanize/HumanizerFactory.js';
13
19
  //# sourceMappingURL=index.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/audio/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,MAAM,YAAY,CAAC;AAEnC,OAAO,EAAE,SAAS,EAAE,MAAM,eAAe,CAAC;AAE1C,OAAO,EAAE,KAAK,EAAE,MAAM,YAAY,CAAC;AAEnC,OAAO,EAAE,WAAW,EAAE,MAAM,eAAe,CAAC;AAE5C,OAAO,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;AAEjD,OAAO,EAAE,cAAc,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AAEtD,OAAO,EAAE,SAAS,EAAE,YAAY,EAAE,eAAe,EAAE,aAAa,EAAE,UAAU,EAAE,MAAM,WAAW,CAAC;AAahG,OAAO,EAAE,kBAAkB,EAAE,uBAAuB,EAAE,MAAM,iBAAiB,CAAC;AAE9E,OAAO,EAAE,qBAAqB,EAAE,cAAc,EAAE,eAAe,EAAE,MAAM,gBAAgB,CAAC;AAExF,OAAO,EAAE,OAAO,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AAClD,OAAO,EAAE,mBAAmB,EAAE,kBAAkB,EAAE,MAAM,eAAe,CAAC;AAYxE,OAAO,EAAE,aAAa,EAAE,WAAW,EAAE,SAAS,EAAE,QAAQ,EAAE,MAAM,iBAAiB,CAAC"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/audio/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,MAAM,YAAY,CAAC;AAEnC,OAAO,EAAE,SAAS,EAAE,MAAM,eAAe,CAAC;AAE1C,OAAO,EAAE,KAAK,EAAE,MAAM,YAAY,CAAC;AAEnC,OAAO,EAAE,WAAW,EAAE,MAAM,eAAe,CAAC;AAE5C,OAAO,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;AAEjD,OAAO,EAAE,cAAc,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AAEtD,OAAO,EAAE,SAAS,EAAE,YAAY,EAAE,eAAe,EAAE,aAAa,EAAE,UAAU,EAAE,MAAM,WAAW,CAAC;AAahG,OAAO,EAAE,kBAAkB,EAAE,uBAAuB,EAAE,MAAM,iBAAiB,CAAC;AAE9E,OAAO,EAAE,qBAAqB,EAAE,cAAc,EAAE,eAAe,EAAE,MAAM,gBAAgB,CAAC;AAExF,OAAO,EAAE,OAAO,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AAClD,OAAO,EAAE,mBAAmB,EAAE,kBAAkB,EAAE,MAAM,eAAe,CAAC;AAYxE,OAAO,EAAE,aAAa,EAAE,WAAW,EAAE,SAAS,EAAE,QAAQ,EAAE,MAAM,iBAAiB,CAAC;AAElF,OAAO,EAAE,gBAAgB,EAAE,oBAAoB,EAAE,MAAM,sBAAsB,CAAC;AAE9E,OAAO,EAAE,aAAa,EAAE,MAAM,6BAA6B,CAAC;AAC5D,OAAO,EAAE,eAAe,EAAE,MAAM,+BAA+B,CAAC;AAChE,OAAO,EAAE,eAAe,EAAE,MAAM,+BAA+B,CAAC;AAEhE,OAAO,EAAE,eAAe,EAAE,MAAM,+BAA+B,CAAC;AAEhE,OAAO,EAAE,gBAAgB,EAAE,YAAY,EAAE,MAAM,gCAAgC,CAAC"}
@@ -23,6 +23,11 @@ export interface TourneyResult {
23
23
  nodes: number;
24
24
  /** the deepest fully completed iteration */
25
25
  depth: number;
26
+ /** async search only: every legal root move and its score from the side to move's view, in root order */
27
+ rootScores?: Array<{
28
+ move: BitboardMove;
29
+ score: number;
30
+ }>;
26
31
  }
27
32
  /**
28
33
  * The tournament engine's serializable task: a full iterative-deepening search over a
@@ -75,7 +80,9 @@ export declare function searchTourney(state: ChessState | BitboardState, options
75
80
  * is a self-contained `tourneyThink` task over a structured-cloned bitboard, scored
76
81
  * concurrently down a capped lane queue; the best child score from the opponent's
77
82
  * view is the worst for us, so the root plays the move whose child scored lowest,
78
- * with ties going to the earlier root move. An aborting signal rejects through
83
+ * with ties going to the earlier root move. `rootScores` exposes every candidate so
84
+ * games can choose near-best moves without repeating the search on the UI thread.
85
+ * An aborting signal rejects through
79
86
  * `threads.spawn` and terminates the workers.
80
87
  *
81
88
  * @example
@@ -1 +1 @@
1
- {"version":3,"file":"tourney.d.ts","sourceRoot":"","sources":["../../src/board/tourney.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,YAAY,EAAE,aAAa,EAAE,MAAM,eAAe,CAAC;AACjE,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AAG7C,MAAM,WAAW,cAAc;IAC9B,wFAAwF;IACxF,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,sFAAsF;IACtF,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,+EAA+E;IAC/E,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,yFAAyF;IACzF,MAAM,CAAC,EAAE,WAAW,CAAC;CACrB;AAED,MAAM,WAAW,mBAAoB,SAAQ,cAAc;IAC1D,kFAAkF;IAClF,IAAI,CAAC,EAAE,MAAM,CAAC;CACd;AAED,MAAM,WAAW,aAAa;IAC7B,sDAAsD;IACtD,IAAI,EAAE,YAAY,GAAG,IAAI,CAAC;IAC1B,6FAA6F;IAC7F,KAAK,EAAE,MAAM,CAAC;IACd,6CAA6C;IAC7C,KAAK,EAAE,MAAM,CAAC;IACd,4CAA4C;IAC5C,KAAK,EAAE,MAAM,CAAC;CACd;AAED;;;;;;;;;;;;;;;;;;;GAmBG;AACH,wBAAgB,YAAY,CAC3B,QAAQ,EAAE,aAAa,EACvB,OAAO,GAAE;IAAE,KAAK,CAAC,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAAC,MAAM,CAAC,EAAE,MAAM,CAAC;IAAC,MAAM,CAAC,EAAE,WAAW,CAAA;CAAO,GACxF,aAAa,CAo0Bf;AAcD;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAgB,aAAa,CAAC,KAAK,EAAE,UAAU,GAAG,aAAa,EAAE,OAAO,GAAE,cAAmB,GAAG,aAAa,CAO5G;AAuBD;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAsB,kBAAkB,CACvC,KAAK,EAAE,UAAU,GAAG,aAAa,EACjC,OAAO,GAAE,mBAAwB,GAC/B,OAAO,CAAC,aAAa,CAAC,CA+CxB"}
1
+ {"version":3,"file":"tourney.d.ts","sourceRoot":"","sources":["../../src/board/tourney.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,YAAY,EAAE,aAAa,EAAE,MAAM,eAAe,CAAC;AACjE,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AAG7C,MAAM,WAAW,cAAc;IAC9B,wFAAwF;IACxF,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,sFAAsF;IACtF,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,+EAA+E;IAC/E,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,yFAAyF;IACzF,MAAM,CAAC,EAAE,WAAW,CAAC;CACrB;AAED,MAAM,WAAW,mBAAoB,SAAQ,cAAc;IAC1D,kFAAkF;IAClF,IAAI,CAAC,EAAE,MAAM,CAAC;CACd;AAED,MAAM,WAAW,aAAa;IAC7B,sDAAsD;IACtD,IAAI,EAAE,YAAY,GAAG,IAAI,CAAC;IAC1B,6FAA6F;IAC7F,KAAK,EAAE,MAAM,CAAC;IACd,6CAA6C;IAC7C,KAAK,EAAE,MAAM,CAAC;IACd,4CAA4C;IAC5C,KAAK,EAAE,MAAM,CAAC;IACd,yGAAyG;IACzG,UAAU,CAAC,EAAE,KAAK,CAAC;QAAE,IAAI,EAAE,YAAY,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;CAC1D;AAED;;;;;;;;;;;;;;;;;;;GAmBG;AACH,wBAAgB,YAAY,CAC3B,QAAQ,EAAE,aAAa,EACvB,OAAO,GAAE;IAAE,KAAK,CAAC,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAAC,MAAM,CAAC,EAAE,MAAM,CAAC;IAAC,MAAM,CAAC,EAAE,WAAW,CAAA;CAAO,GACxF,aAAa,CAo0Bf;AAcD;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAgB,aAAa,CAAC,KAAK,EAAE,UAAU,GAAG,aAAa,EAAE,OAAO,GAAE,cAAmB,GAAG,aAAa,CAO5G;AAuBD;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAsB,kBAAkB,CACvC,KAAK,EAAE,UAAU,GAAG,aAAa,EACjC,OAAO,GAAE,mBAAwB,GAC/B,OAAO,CAAC,aAAa,CAAC,CAmDxB"}
@@ -952,7 +952,9 @@ function defaultTourneyJobs() {
952
952
  * is a self-contained `tourneyThink` task over a structured-cloned bitboard, scored
953
953
  * concurrently down a capped lane queue; the best child score from the opponent's
954
954
  * view is the worst for us, so the root plays the move whose child scored lowest,
955
- * with ties going to the earlier root move. An aborting signal rejects through
955
+ * with ties going to the earlier root move. `rootScores` exposes every candidate so
956
+ * games can choose near-best moves without repeating the search on the UI thread.
957
+ * An aborting signal rejects through
956
958
  * `threads.spawn` and terminates the workers.
957
959
  *
958
960
  * @example
@@ -974,7 +976,7 @@ export async function searchTourneyAsync(state, options = {}) {
974
976
  const root = toBitboard(state);
975
977
  const moves = bitboardMoves(root);
976
978
  if (!moves.length)
977
- return tourneyThink(root, { depth, maxNodes });
979
+ return { ...tourneyThink(root, { depth, maxNodes }), rootScores: [] };
978
980
  //one chain per lane over the shared move queue, the tools/compress-dist.mjs shape:
979
981
  //scores land by index, so ties break in root order no matter who finishes first
980
982
  const lanes = Math.max(1, Math.min(jobs, moves.length));
@@ -1008,6 +1010,10 @@ export async function searchTourneyAsync(state, options = {}) {
1008
1010
  score: scores[bestIndex] ?? 0,
1009
1011
  nodes,
1010
1012
  depth,
1013
+ rootScores: moves.map((move, index) => ({
1014
+ move: { from: move.from, to: move.to, ...(move.promotion ? { promotion: move.promotion } : {}) },
1015
+ score: scores[index] ?? 0,
1016
+ })),
1011
1017
  };
1012
1018
  }
1013
1019
  //# sourceMappingURL=tourney.js.map