@sudobility/music_player 0.1.0 → 0.1.2

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 (159) hide show
  1. package/CLAUDE.md +335 -0
  2. package/dist/core.d.ts +33 -0
  3. package/dist/core.d.ts.map +1 -0
  4. package/dist/core.js +33 -0
  5. package/dist/core.js.map +1 -0
  6. package/dist/engine.d.ts +103 -0
  7. package/dist/engine.d.ts.map +1 -0
  8. package/dist/engine.js +2 -0
  9. package/dist/engine.js.map +1 -0
  10. package/dist/mocks/index.d.ts +46 -0
  11. package/dist/mocks/index.d.ts.map +1 -0
  12. package/dist/mocks/index.js +93 -0
  13. package/dist/mocks/index.js.map +1 -0
  14. package/dist/player.d.ts +81 -0
  15. package/dist/player.d.ts.map +1 -0
  16. package/dist/player.js +209 -0
  17. package/dist/player.js.map +1 -0
  18. package/dist/rn/audio/offline-render.d.ts +29 -0
  19. package/dist/rn/audio/offline-render.d.ts.map +1 -0
  20. package/dist/rn/audio/offline-render.js +134 -0
  21. package/dist/rn/audio/offline-render.js.map +1 -0
  22. package/dist/rn/index.d.ts +50 -0
  23. package/dist/rn/index.d.ts.map +1 -0
  24. package/dist/rn/index.js +39 -0
  25. package/dist/rn/index.js.map +1 -0
  26. package/dist/rn/playback/audio-api.d.ts +93 -0
  27. package/dist/rn/playback/audio-api.d.ts.map +1 -0
  28. package/dist/rn/playback/audio-api.js +24 -0
  29. package/dist/rn/playback/audio-api.js.map +1 -0
  30. package/dist/rn/playback/expression-table.d.ts +22 -0
  31. package/dist/rn/playback/expression-table.d.ts.map +1 -0
  32. package/dist/rn/playback/expression-table.js +926 -0
  33. package/dist/rn/playback/expression-table.js.map +1 -0
  34. package/dist/rn/playback/expression.d.ts +48 -0
  35. package/dist/rn/playback/expression.d.ts.map +1 -0
  36. package/dist/rn/playback/expression.js +120 -0
  37. package/dist/rn/playback/expression.js.map +1 -0
  38. package/dist/rn/playback/gm-pack-name.d.ts +40 -0
  39. package/dist/rn/playback/gm-pack-name.d.ts.map +1 -0
  40. package/dist/rn/playback/gm-pack-name.js +61 -0
  41. package/dist/rn/playback/gm-pack-name.js.map +1 -0
  42. package/dist/rn/playback/pack-library.d.ts +44 -0
  43. package/dist/rn/playback/pack-library.d.ts.map +1 -0
  44. package/dist/rn/playback/pack-library.js +85 -0
  45. package/dist/rn/playback/pack-library.js.map +1 -0
  46. package/dist/rn/playback/pack-names.json +130 -0
  47. package/dist/rn/playback/sample-engine.d.ts +216 -0
  48. package/dist/rn/playback/sample-engine.d.ts.map +1 -0
  49. package/dist/rn/playback/sample-engine.js +669 -0
  50. package/dist/rn/playback/sample-engine.js.map +1 -0
  51. package/dist/rn/playback/sample-pack.d.ts +71 -0
  52. package/dist/rn/playback/sample-pack.d.ts.map +1 -0
  53. package/dist/rn/playback/sample-pack.js +112 -0
  54. package/dist/rn/playback/sample-pack.js.map +1 -0
  55. package/dist/rn/playback/sustain-loop.d.ts +24 -0
  56. package/dist/rn/playback/sustain-loop.d.ts.map +1 -0
  57. package/dist/rn/playback/sustain-loop.js +111 -0
  58. package/dist/rn/playback/sustain-loop.js.map +1 -0
  59. package/dist/rn/playback/voice-plan.d.ts +60 -0
  60. package/dist/rn/playback/voice-plan.d.ts.map +1 -0
  61. package/dist/rn/playback/voice-plan.js +38 -0
  62. package/dist/rn/playback/voice-plan.js.map +1 -0
  63. package/dist/shared/bus.d.ts +73 -0
  64. package/dist/shared/bus.d.ts.map +1 -0
  65. package/dist/shared/bus.js +83 -0
  66. package/dist/shared/bus.js.map +1 -0
  67. package/dist/shared/midi.d.ts +18 -0
  68. package/dist/shared/midi.d.ts.map +1 -0
  69. package/dist/shared/midi.js +25 -0
  70. package/dist/shared/midi.js.map +1 -0
  71. package/dist/shared/mix.d.ts +75 -0
  72. package/dist/shared/mix.d.ts.map +1 -0
  73. package/dist/shared/mix.js +115 -0
  74. package/dist/shared/mix.js.map +1 -0
  75. package/dist/shared/note-queue.d.ts +43 -0
  76. package/dist/shared/note-queue.d.ts.map +1 -0
  77. package/dist/shared/note-queue.js +54 -0
  78. package/dist/shared/note-queue.js.map +1 -0
  79. package/dist/shared/plan.d.ts +34 -0
  80. package/dist/shared/plan.d.ts.map +1 -0
  81. package/dist/shared/plan.js +134 -0
  82. package/dist/shared/plan.js.map +1 -0
  83. package/dist/shared/pump-window.d.ts +38 -0
  84. package/dist/shared/pump-window.d.ts.map +1 -0
  85. package/dist/shared/pump-window.js +13 -0
  86. package/dist/shared/pump-window.js.map +1 -0
  87. package/dist/shared/render-events.d.ts +25 -0
  88. package/dist/shared/render-events.d.ts.map +1 -0
  89. package/dist/shared/render-events.js +79 -0
  90. package/dist/shared/render-events.js.map +1 -0
  91. package/dist/shared/sounding-set.d.ts +45 -0
  92. package/dist/shared/sounding-set.d.ts.map +1 -0
  93. package/dist/shared/sounding-set.js +81 -0
  94. package/dist/shared/sounding-set.js.map +1 -0
  95. package/dist/shared/test-plan.d.ts +36 -0
  96. package/dist/shared/test-plan.d.ts.map +1 -0
  97. package/dist/shared/test-plan.js +83 -0
  98. package/dist/shared/test-plan.js.map +1 -0
  99. package/dist/singleton.d.ts +23 -0
  100. package/dist/singleton.d.ts.map +1 -0
  101. package/dist/singleton.js +26 -0
  102. package/dist/singleton.js.map +1 -0
  103. package/dist/types.d.ts +84 -0
  104. package/dist/types.d.ts.map +1 -0
  105. package/dist/types.js +2 -0
  106. package/dist/types.js.map +1 -0
  107. package/dist/web/audio/offline-synth.d.ts +24 -0
  108. package/dist/web/audio/offline-synth.d.ts.map +1 -0
  109. package/dist/web/audio/offline-synth.js +92 -0
  110. package/dist/web/audio/offline-synth.js.map +1 -0
  111. package/dist/web/audio/soundfont-render.d.ts +31 -0
  112. package/dist/web/audio/soundfont-render.d.ts.map +1 -0
  113. package/dist/web/audio/soundfont-render.js +130 -0
  114. package/dist/web/audio/soundfont-render.js.map +1 -0
  115. package/dist/web/audio/synth-types.d.ts +24 -0
  116. package/dist/web/audio/synth-types.d.ts.map +1 -0
  117. package/dist/web/audio/synth-types.js +2 -0
  118. package/dist/web/audio/synth-types.js.map +1 -0
  119. package/dist/web/index.d.ts +40 -0
  120. package/dist/web/index.d.ts.map +1 -0
  121. package/dist/web/index.js +59 -0
  122. package/dist/web/index.js.map +1 -0
  123. package/dist/web/playback/channel-allocator.d.ts +54 -0
  124. package/dist/web/playback/channel-allocator.d.ts.map +1 -0
  125. package/dist/web/playback/channel-allocator.js +72 -0
  126. package/dist/web/playback/channel-allocator.js.map +1 -0
  127. package/dist/web/playback/click.d.ts +9 -0
  128. package/dist/web/playback/click.d.ts.map +1 -0
  129. package/dist/web/playback/click.js +61 -0
  130. package/dist/web/playback/click.js.map +1 -0
  131. package/dist/web/playback/clock.d.ts +36 -0
  132. package/dist/web/playback/clock.d.ts.map +1 -0
  133. package/dist/web/playback/clock.js +64 -0
  134. package/dist/web/playback/clock.js.map +1 -0
  135. package/dist/web/playback/governor.d.ts +45 -0
  136. package/dist/web/playback/governor.d.ts.map +1 -0
  137. package/dist/web/playback/governor.js +33 -0
  138. package/dist/web/playback/governor.js.map +1 -0
  139. package/dist/web/playback/quiet-stub-notices.d.ts +55 -0
  140. package/dist/web/playback/quiet-stub-notices.d.ts.map +1 -0
  141. package/dist/web/playback/quiet-stub-notices.js +93 -0
  142. package/dist/web/playback/quiet-stub-notices.js.map +1 -0
  143. package/dist/web/playback/soundfont-engine.d.ts +281 -0
  144. package/dist/web/playback/soundfont-engine.d.ts.map +1 -0
  145. package/dist/web/playback/soundfont-engine.js +735 -0
  146. package/dist/web/playback/soundfont-engine.js.map +1 -0
  147. package/dist/web/playback/soundfont-loader.d.ts +30 -0
  148. package/dist/web/playback/soundfont-loader.d.ts.map +1 -0
  149. package/dist/web/playback/soundfont-loader.js +68 -0
  150. package/dist/web/playback/soundfont-loader.js.map +1 -0
  151. package/dist/web/playback/synth-host.d.ts +142 -0
  152. package/dist/web/playback/synth-host.d.ts.map +1 -0
  153. package/dist/web/playback/synth-host.js +293 -0
  154. package/dist/web/playback/synth-host.js.map +1 -0
  155. package/package.json +18 -3
  156. package/dist/index.d.ts +0 -14
  157. package/dist/index.d.ts.map +0 -1
  158. package/dist/index.js +0 -2
  159. package/dist/index.js.map +0 -1
@@ -0,0 +1 @@
1
+ {"version":3,"file":"click.js","sourceRoot":"","sources":["../../../src/web/playback/click.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AACH,MAAM,SAAS,GAAG,IAAI,CAAC;AACvB,MAAM,OAAO,GAAG,IAAI,CAAC;AACrB,MAAM,aAAa,GAAG,IAAI,CAAC;AAC3B,wEAAwE;AACxE,MAAM,UAAU,GAAG,IAAI,CAAC;AAUxB,MAAM,UAAU,aAAa,CAC3B,OAAyB,EACzB,WAAsB,EACtB,SAAiB,EACjB,MAAe;IAEf,MAAM,UAAU,GAAG,OAAO,CAAC,gBAAgB,EAAE,CAAC;IAC9C,MAAM,IAAI,GAAG,OAAO,CAAC,UAAU,EAAE,CAAC;IAClC,UAAU,CAAC,SAAS,CAAC,KAAK,GAAG,MAAM,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,OAAO,CAAC;IAC1D,UAAU,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC;IACzB,IAAI,CAAC,OAAO,CAAC,WAAW,CAAC,CAAC;IAE1B,6EAA6E;IAC7E,2BAA2B;IAC3B,MAAM,GAAG,GAAG,SAAS,GAAG,aAAa,CAAC;IACtC,IAAI,CAAC,IAAI,CAAC,cAAc,CAAC,UAAU,EAAE,SAAS,CAAC,CAAC;IAChD,IAAI,CAAC,IAAI,CAAC,4BAA4B,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;IAEpD,UAAU,CAAC,KAAK,CAAC,SAAS,CAAC,CAAC;IAC5B,UAAU,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;IACrB,IAAI,QAAQ,GAAG,KAAK,CAAC;IACrB,UAAU,CAAC,OAAO,GAAG,GAAG,EAAE;QACxB,QAAQ,GAAG,IAAI,CAAC;QAChB,UAAU,CAAC,UAAU,EAAE,CAAC;QACxB,IAAI,CAAC,UAAU,EAAE,CAAC;IACpB,CAAC,CAAC;IAEF,OAAO;QACL,MAAM,EAAE,GAAG;QACX,MAAM,CAAC,IAAY;YACjB,oEAAoE;YACpE,yEAAyE;YACzE,kEAAkE;YAClE,IAAI,QAAQ,IAAI,IAAI,IAAI,GAAG;gBAAE,OAAO;YACpC,qEAAqE;YACrE,wDAAwD;YACxD,IAAI,CAAC,IAAI,CAAC,qBAAqB,CAAC,IAAI,CAAC,CAAC;YACtC,IAAI,CAAC,IAAI,CAAC,cAAc,CAAC,CAAC,EAAE,IAAI,CAAC,CAAC;YAClC,6DAA6D;YAC7D,UAAU,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QACxB,CAAC;KACF,CAAC;AACJ,CAAC"}
@@ -0,0 +1,36 @@
1
+ /**
2
+ * Playback position, owned here rather than by Tone's Transport.
3
+ *
4
+ * `now` is injected: the engine passes the AudioContext's `currentTime`, so
5
+ * position is measured on the very clock the audio is rendered against, and
6
+ * tests pass a variable they control instead of waiting for real time.
7
+ *
8
+ * Everything works by anchoring — a position and the moment it was true —
9
+ * rather than by accumulating deltas. Accumulation drifts, and drift in the
10
+ * playback clock is what silenced tracks in the engine this replaces.
11
+ */
12
+ export declare class PlaybackClock {
13
+ private readonly now;
14
+ private running;
15
+ private anchorNow;
16
+ private anchorPosition;
17
+ private rate;
18
+ constructor(now: () => number);
19
+ /** Starts, or resumes from wherever it was paused. `fromSeconds` overrides that. */
20
+ start(fromSeconds?: number): void;
21
+ pause(): void;
22
+ stop(): void;
23
+ /** Jumps to `seconds`, keeping whatever running state the clock had. */
24
+ seek(seconds: number): void;
25
+ /**
26
+ * Scales elapsed time — the playback-speed control.
27
+ *
28
+ * Banks the current position first, so a speed change applies from here on
29
+ * rather than retroactively rescaling everything already played, which would
30
+ * jump the caret.
31
+ */
32
+ setRate(rate: number): void;
33
+ get positionSeconds(): number;
34
+ get isRunning(): boolean;
35
+ }
36
+ //# sourceMappingURL=clock.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"clock.d.ts","sourceRoot":"","sources":["../../../src/web/playback/clock.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AACH,qBAAa,aAAa;IAMZ,OAAO,CAAC,QAAQ,CAAC,GAAG;IALhC,OAAO,CAAC,OAAO,CAAS;IACxB,OAAO,CAAC,SAAS,CAAK;IACtB,OAAO,CAAC,cAAc,CAAK;IAC3B,OAAO,CAAC,IAAI,CAAK;gBAEY,GAAG,EAAE,MAAM,MAAM;IAE9C,oFAAoF;IACpF,KAAK,CAAC,WAAW,CAAC,EAAE,MAAM,GAAG,IAAI;IAMjC,KAAK,IAAI,IAAI;IAMb,IAAI,IAAI,IAAI;IAMZ,wEAAwE;IACxE,IAAI,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI;IAK3B;;;;;;OAMG;IACH,OAAO,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI;IAM3B,IAAI,eAAe,IAAI,MAAM,CAG5B;IAED,IAAI,SAAS,IAAI,OAAO,CAEvB;CACF"}
@@ -0,0 +1,64 @@
1
+ /**
2
+ * Playback position, owned here rather than by Tone's Transport.
3
+ *
4
+ * `now` is injected: the engine passes the AudioContext's `currentTime`, so
5
+ * position is measured on the very clock the audio is rendered against, and
6
+ * tests pass a variable they control instead of waiting for real time.
7
+ *
8
+ * Everything works by anchoring — a position and the moment it was true —
9
+ * rather than by accumulating deltas. Accumulation drifts, and drift in the
10
+ * playback clock is what silenced tracks in the engine this replaces.
11
+ */
12
+ export class PlaybackClock {
13
+ now;
14
+ running = false;
15
+ anchorNow = 0;
16
+ anchorPosition = 0;
17
+ rate = 1;
18
+ constructor(now) {
19
+ this.now = now;
20
+ }
21
+ /** Starts, or resumes from wherever it was paused. `fromSeconds` overrides that. */
22
+ start(fromSeconds) {
23
+ this.anchorPosition = fromSeconds ?? this.positionSeconds;
24
+ this.anchorNow = this.now();
25
+ this.running = true;
26
+ }
27
+ pause() {
28
+ if (!this.running)
29
+ return;
30
+ this.anchorPosition = this.positionSeconds;
31
+ this.running = false;
32
+ }
33
+ stop() {
34
+ this.running = false;
35
+ this.anchorPosition = 0;
36
+ this.anchorNow = this.now();
37
+ }
38
+ /** Jumps to `seconds`, keeping whatever running state the clock had. */
39
+ seek(seconds) {
40
+ this.anchorPosition = seconds;
41
+ this.anchorNow = this.now();
42
+ }
43
+ /**
44
+ * Scales elapsed time — the playback-speed control.
45
+ *
46
+ * Banks the current position first, so a speed change applies from here on
47
+ * rather than retroactively rescaling everything already played, which would
48
+ * jump the caret.
49
+ */
50
+ setRate(rate) {
51
+ this.anchorPosition = this.positionSeconds;
52
+ this.anchorNow = this.now();
53
+ this.rate = rate;
54
+ }
55
+ get positionSeconds() {
56
+ if (!this.running)
57
+ return this.anchorPosition;
58
+ return this.anchorPosition + (this.now() - this.anchorNow) * this.rate;
59
+ }
60
+ get isRunning() {
61
+ return this.running;
62
+ }
63
+ }
64
+ //# sourceMappingURL=clock.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"clock.js","sourceRoot":"","sources":["../../../src/web/playback/clock.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AACH,MAAM,OAAO,aAAa;IAMK;IALrB,OAAO,GAAG,KAAK,CAAC;IAChB,SAAS,GAAG,CAAC,CAAC;IACd,cAAc,GAAG,CAAC,CAAC;IACnB,IAAI,GAAG,CAAC,CAAC;IAEjB,YAA6B,GAAiB;QAAjB,QAAG,GAAH,GAAG,CAAc;IAAG,CAAC;IAElD,oFAAoF;IACpF,KAAK,CAAC,WAAoB;QACxB,IAAI,CAAC,cAAc,GAAG,WAAW,IAAI,IAAI,CAAC,eAAe,CAAC;QAC1D,IAAI,CAAC,SAAS,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;QAC5B,IAAI,CAAC,OAAO,GAAG,IAAI,CAAC;IACtB,CAAC;IAED,KAAK;QACH,IAAI,CAAC,IAAI,CAAC,OAAO;YAAE,OAAO;QAC1B,IAAI,CAAC,cAAc,GAAG,IAAI,CAAC,eAAe,CAAC;QAC3C,IAAI,CAAC,OAAO,GAAG,KAAK,CAAC;IACvB,CAAC;IAED,IAAI;QACF,IAAI,CAAC,OAAO,GAAG,KAAK,CAAC;QACrB,IAAI,CAAC,cAAc,GAAG,CAAC,CAAC;QACxB,IAAI,CAAC,SAAS,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;IAC9B,CAAC;IAED,wEAAwE;IACxE,IAAI,CAAC,OAAe;QAClB,IAAI,CAAC,cAAc,GAAG,OAAO,CAAC;QAC9B,IAAI,CAAC,SAAS,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;IAC9B,CAAC;IAED;;;;;;OAMG;IACH,OAAO,CAAC,IAAY;QAClB,IAAI,CAAC,cAAc,GAAG,IAAI,CAAC,eAAe,CAAC;QAC3C,IAAI,CAAC,SAAS,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;QAC5B,IAAI,CAAC,IAAI,GAAG,IAAI,CAAC;IACnB,CAAC;IAED,IAAI,eAAe;QACjB,IAAI,CAAC,IAAI,CAAC,OAAO;YAAE,OAAO,IAAI,CAAC,cAAc,CAAC;QAC9C,OAAO,IAAI,CAAC,cAAc,GAAG,CAAC,IAAI,CAAC,GAAG,EAAE,GAAG,IAAI,CAAC,SAAS,CAAC,GAAG,IAAI,CAAC,IAAI,CAAC;IACzE,CAAC;IAED,IAAI,SAAS;QACX,OAAO,IAAI,CAAC,OAAO,CAAC;IACtB,CAAC;CACF"}
@@ -0,0 +1,45 @@
1
+ /**
2
+ * Adaptive quality — currently measuring, not acting.
3
+ *
4
+ * This used to step interpolation order down after ten consecutive pump frames
5
+ * more than 100ms late, on the reasoning that a starved pump meant a starved
6
+ * synth. `setInterpolation(0)` renders the same passage in 53% of the time, so
7
+ * the knob is real, and timbre is the right thing to shed first.
8
+ *
9
+ * The scheduling horizon killed the reasoning. The pump no longer delivers
10
+ * notes on time — it tops up a four-second buffer in the worklet, and the
11
+ * synth renders from that buffer on the audio thread. The main thread can now
12
+ * stall for a second with nothing audible happening at all, so lateness here
13
+ * says only that the main thread is busy. Degrading timbre for every listener
14
+ * because a notation redraw took 119ms is a cost with no benefit.
15
+ *
16
+ * So the count is kept and nothing acts on it. It is free, and it is the shape
17
+ * a real audio-thread load signal would take. `AudioWorkletNode` exposes no
18
+ * such signal today, and `AudioContext` offers only latency figures that do not
19
+ * move under voice pressure; when one exists, it plugs in here.
20
+ *
21
+ * Polyphony was to be the other rung. `js-synthesizer` has no runtime setter
22
+ * for it — `ISynthesizer` has `setInterpolation` and `setGain` and nothing else
23
+ * — so the ceiling is chosen once at init in `synth-host.ts` and fluidsynth's
24
+ * own overflow priority does the stealing above it.
25
+ */
26
+ export type GovernorOptions = {
27
+ onChange?: (interpolationOrder: number) => void;
28
+ };
29
+ export declare class Governor {
30
+ private readonly options;
31
+ private lateFrames;
32
+ constructor(options?: GovernorOptions);
33
+ get interpolation(): number;
34
+ /** Consecutive frames measured late. Diagnostic; nothing acts on it. */
35
+ get stalledFrames(): number;
36
+ /**
37
+ * One pump frame's lateness, in seconds.
38
+ *
39
+ * Counted, never acted on — see this module's doc for why. `options.onChange`
40
+ * is therefore never invoked; it is kept so the caller's wiring survives
41
+ * whatever replaces this signal.
42
+ */
43
+ record(lateBySeconds: number): void;
44
+ }
45
+ //# sourceMappingURL=governor.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"governor.d.ts","sourceRoot":"","sources":["../../../src/web/playback/governor.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AACH,MAAM,MAAM,eAAe,GAAG;IAC5B,QAAQ,CAAC,EAAE,CAAC,kBAAkB,EAAE,MAAM,KAAK,IAAI,CAAC;CACjD,CAAC;AAOF,qBAAa,QAAQ;IAGP,OAAO,CAAC,QAAQ,CAAC,OAAO;IAFpC,OAAO,CAAC,UAAU,CAAK;gBAEM,OAAO,GAAE,eAAoB;IAE1D,IAAI,aAAa,IAAI,MAAM,CAE1B;IAED,wEAAwE;IACxE,IAAI,aAAa,IAAI,MAAM,CAE1B;IAED;;;;;;OAMG;IACH,MAAM,CAAC,aAAa,EAAE,MAAM,GAAG,IAAI;CAOpC"}
@@ -0,0 +1,33 @@
1
+ /** fluidsynth's interpolation orders, best first. */
2
+ const LADDER = [7, 4, 1, 0];
3
+ /** How late a pump frame must be to count against the budget. */
4
+ const LATE_THRESHOLD_SECONDS = 0.1;
5
+ export class Governor {
6
+ options;
7
+ lateFrames = 0;
8
+ constructor(options = {}) {
9
+ this.options = options;
10
+ }
11
+ get interpolation() {
12
+ return LADDER[0];
13
+ }
14
+ /** Consecutive frames measured late. Diagnostic; nothing acts on it. */
15
+ get stalledFrames() {
16
+ return this.lateFrames;
17
+ }
18
+ /**
19
+ * One pump frame's lateness, in seconds.
20
+ *
21
+ * Counted, never acted on — see this module's doc for why. `options.onChange`
22
+ * is therefore never invoked; it is kept so the caller's wiring survives
23
+ * whatever replaces this signal.
24
+ */
25
+ record(lateBySeconds) {
26
+ if (lateBySeconds > LATE_THRESHOLD_SECONDS) {
27
+ this.lateFrames += 1;
28
+ return;
29
+ }
30
+ this.lateFrames = 0;
31
+ }
32
+ }
33
+ //# sourceMappingURL=governor.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"governor.js","sourceRoot":"","sources":["../../../src/web/playback/governor.ts"],"names":[],"mappings":"AA6BA,qDAAqD;AACrD,MAAM,MAAM,GAAG,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC;AAC5B,iEAAiE;AACjE,MAAM,sBAAsB,GAAG,GAAG,CAAC;AAEnC,MAAM,OAAO,QAAQ;IAGU;IAFrB,UAAU,GAAG,CAAC,CAAC;IAEvB,YAA6B,UAA2B,EAAE;QAA7B,YAAO,GAAP,OAAO,CAAsB;IAAG,CAAC;IAE9D,IAAI,aAAa;QACf,OAAO,MAAM,CAAC,CAAC,CAAC,CAAC;IACnB,CAAC;IAED,wEAAwE;IACxE,IAAI,aAAa;QACf,OAAO,IAAI,CAAC,UAAU,CAAC;IACzB,CAAC;IAED;;;;;;OAMG;IACH,MAAM,CAAC,aAAqB;QAC1B,IAAI,aAAa,GAAG,sBAAsB,EAAE,CAAC;YAC3C,IAAI,CAAC,UAAU,IAAI,CAAC,CAAC;YACrB,OAAO;QACT,CAAC;QACD,IAAI,CAAC,UAAU,GAAG,CAAC,CAAC;IACtB,CAAC;CACF"}
@@ -0,0 +1,55 @@
1
+ /**
2
+ * Silences libfluidsynth's build-stub notices, and nothing else.
3
+ *
4
+ * The WASM build stubs out filesystem calls it does not need and reports each
5
+ * one at *error* level every time a soundfont is loaded:
6
+ *
7
+ * fluidsynth: error: function fluid_stat is a stub, always returning -1
8
+ * fluidsynth: error: function fluid_file_test is a stub, always returning true
9
+ *
10
+ * They are build notices, not failures — audio is verified working with them
11
+ * firing. They also cannot be suppressed through `js-synthesizer`'s
12
+ * `disableLogging`, which controls fluidsynth's *log callback*: these come out
13
+ * of the C code's stderr through emscripten's stdio instead, as the stack shows
14
+ * (`put_char → write → doWritev → _fd_write`).
15
+ *
16
+ * Two different hooks are needed, because the two paths load the module
17
+ * differently.
18
+ *
19
+ * On the **main thread** the build is injected as a classic script, so its
20
+ * `var Module = typeof Module != "undefined" ? Module : {}` sees a global we
21
+ * seeded first, and `Module.printErr` redirects the stream.
22
+ *
23
+ * In an **AudioWorklet** that does not work: `addModule` loads module scripts,
24
+ * so `var Module` is module-scoped and a global seed is invisible. The hook
25
+ * there is the other half of the same line — `Module["printErr"] ||
26
+ * console.error.bind(console)` binds whatever `console.error` *is at module
27
+ * init*. Replacing it in the worklet's own scope beforehand therefore catches
28
+ * the stream, and affects nothing outside that worklet.
29
+ *
30
+ * This matters more than tidiness: fluidsynth calls the stubs once per sample
31
+ * while loading, which measured at over a thousand console writes per font —
32
+ * each capturing a stack, during the load this engine is already slow at.
33
+ *
34
+ * Deliberately a pattern match rather than blanket suppression: anything that
35
+ * is not a stub notice is passed straight through to `console.error`, so a real
36
+ * fluidsynth failure still reaches the console.
37
+ */
38
+ /** Matches only "function <name> is a stub, ..." — the emscripten build notices. */
39
+ export declare const STUB_NOTICE_PATTERN: RegExp;
40
+ export declare function isStubNotice(text: unknown): boolean;
41
+ /**
42
+ * A module URL that filters the stub notices inside an AudioWorklet.
43
+ *
44
+ * Patches the worklet's own `console.error` rather than seeding `Module`, for
45
+ * the module-scoping reason above. Scoped to the worklet, so the page console
46
+ * is untouched.
47
+ *
48
+ * A blob rather than a hosted file: it is a few lines, and making an app host
49
+ * another asset just to quiet a log would be a poor trade. Returns `null` where
50
+ * blobs are unavailable, in which case the notices simply remain.
51
+ */
52
+ export declare function createWorkletQuietModuleUrl(): string | null;
53
+ /** Seeds the quiet `Module` on the main thread, before libfluidsynth is injected. */
54
+ export declare function seedMainThreadQuietModule(): void;
55
+ //# sourceMappingURL=quiet-stub-notices.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"quiet-stub-notices.d.ts","sourceRoot":"","sources":["../../../src/web/playback/quiet-stub-notices.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAoCG;AAEH,oFAAoF;AACpF,eAAO,MAAM,mBAAmB,QAA2B,CAAC;AAE5D,wBAAgB,YAAY,CAAC,IAAI,EAAE,OAAO,GAAG,OAAO,CAEnD;AA0BD;;;;;;;;;;GAUG;AACH,wBAAgB,2BAA2B,IAAI,MAAM,GAAG,IAAI,CAG3D;AAED,qFAAqF;AACrF,wBAAgB,yBAAyB,IAAI,IAAI,CAQhD"}
@@ -0,0 +1,93 @@
1
+ /**
2
+ * Silences libfluidsynth's build-stub notices, and nothing else.
3
+ *
4
+ * The WASM build stubs out filesystem calls it does not need and reports each
5
+ * one at *error* level every time a soundfont is loaded:
6
+ *
7
+ * fluidsynth: error: function fluid_stat is a stub, always returning -1
8
+ * fluidsynth: error: function fluid_file_test is a stub, always returning true
9
+ *
10
+ * They are build notices, not failures — audio is verified working with them
11
+ * firing. They also cannot be suppressed through `js-synthesizer`'s
12
+ * `disableLogging`, which controls fluidsynth's *log callback*: these come out
13
+ * of the C code's stderr through emscripten's stdio instead, as the stack shows
14
+ * (`put_char → write → doWritev → _fd_write`).
15
+ *
16
+ * Two different hooks are needed, because the two paths load the module
17
+ * differently.
18
+ *
19
+ * On the **main thread** the build is injected as a classic script, so its
20
+ * `var Module = typeof Module != "undefined" ? Module : {}` sees a global we
21
+ * seeded first, and `Module.printErr` redirects the stream.
22
+ *
23
+ * In an **AudioWorklet** that does not work: `addModule` loads module scripts,
24
+ * so `var Module` is module-scoped and a global seed is invisible. The hook
25
+ * there is the other half of the same line — `Module["printErr"] ||
26
+ * console.error.bind(console)` binds whatever `console.error` *is at module
27
+ * init*. Replacing it in the worklet's own scope beforehand therefore catches
28
+ * the stream, and affects nothing outside that worklet.
29
+ *
30
+ * This matters more than tidiness: fluidsynth calls the stubs once per sample
31
+ * while loading, which measured at over a thousand console writes per font —
32
+ * each capturing a stack, during the load this engine is already slow at.
33
+ *
34
+ * Deliberately a pattern match rather than blanket suppression: anything that
35
+ * is not a stub notice is passed straight through to `console.error`, so a real
36
+ * fluidsynth failure still reaches the console.
37
+ */
38
+ /** Matches only "function <name> is a stub, ..." — the emscripten build notices. */
39
+ export const STUB_NOTICE_PATTERN = /function \w+ is a stub/;
40
+ export function isStubNotice(text) {
41
+ return STUB_NOTICE_PATTERN.test(String(text));
42
+ }
43
+ /**
44
+ * The seed, as source, so it can run in a worklet where our modules cannot
45
+ * reach. Kept in sync with `isStubNotice` by construction: both use the same
46
+ * pattern, written once here.
47
+ */
48
+ function seedSource() {
49
+ // Must run before libfluidsynth: emscripten binds console.error at init.
50
+ return `(function () {
51
+ var pattern = ${STUB_NOTICE_PATTERN.toString()};
52
+ var original = console.error.bind(console);
53
+ console.error = function () {
54
+ for (var i = 0; i < arguments.length; i++) {
55
+ if (typeof arguments[i] === 'string' && pattern.test(arguments[i])) return;
56
+ }
57
+ original.apply(null, arguments);
58
+ };
59
+ globalThis.Module = Object.assign({}, globalThis.Module, {
60
+ printErr: function (text) {
61
+ if (!pattern.test(String(text))) original(text);
62
+ },
63
+ });
64
+ })();`;
65
+ }
66
+ /**
67
+ * A module URL that filters the stub notices inside an AudioWorklet.
68
+ *
69
+ * Patches the worklet's own `console.error` rather than seeding `Module`, for
70
+ * the module-scoping reason above. Scoped to the worklet, so the page console
71
+ * is untouched.
72
+ *
73
+ * A blob rather than a hosted file: it is a few lines, and making an app host
74
+ * another asset just to quiet a log would be a poor trade. Returns `null` where
75
+ * blobs are unavailable, in which case the notices simply remain.
76
+ */
77
+ export function createWorkletQuietModuleUrl() {
78
+ if (typeof Blob === 'undefined' || typeof URL?.createObjectURL !== 'function')
79
+ return null;
80
+ return URL.createObjectURL(new Blob([seedSource()], { type: 'text/javascript' }));
81
+ }
82
+ /** Seeds the quiet `Module` on the main thread, before libfluidsynth is injected. */
83
+ export function seedMainThreadQuietModule() {
84
+ const scope = globalThis;
85
+ scope.Module = {
86
+ ...scope.Module,
87
+ printErr: (text) => {
88
+ if (!isStubNotice(text))
89
+ console.error(text);
90
+ },
91
+ };
92
+ }
93
+ //# sourceMappingURL=quiet-stub-notices.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"quiet-stub-notices.js","sourceRoot":"","sources":["../../../src/web/playback/quiet-stub-notices.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAoCG;AAEH,oFAAoF;AACpF,MAAM,CAAC,MAAM,mBAAmB,GAAG,wBAAwB,CAAC;AAE5D,MAAM,UAAU,YAAY,CAAC,IAAa;IACxC,OAAO,mBAAmB,CAAC,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC;AAChD,CAAC;AAED;;;;GAIG;AACH,SAAS,UAAU;IACjB,yEAAyE;IACzE,OAAO;kBACS,mBAAmB,CAAC,QAAQ,EAAE;;;;;;;;;;;;;MAa1C,CAAC;AACP,CAAC;AAED;;;;;;;;;;GAUG;AACH,MAAM,UAAU,2BAA2B;IACzC,IAAI,OAAO,IAAI,KAAK,WAAW,IAAI,OAAO,GAAG,EAAE,eAAe,KAAK,UAAU;QAAE,OAAO,IAAI,CAAC;IAC3F,OAAO,GAAG,CAAC,eAAe,CAAC,IAAI,IAAI,CAAC,CAAC,UAAU,EAAE,CAAC,EAAE,EAAE,IAAI,EAAE,iBAAiB,EAAE,CAAC,CAAC,CAAC;AACpF,CAAC;AAED,qFAAqF;AACrF,MAAM,UAAU,yBAAyB;IACvC,MAAM,KAAK,GAAG,UAA6D,CAAC;IAC5E,KAAK,CAAC,MAAM,GAAG;QACb,GAAG,KAAK,CAAC,MAAM;QACf,QAAQ,EAAE,CAAC,IAAa,EAAE,EAAE;YAC1B,IAAI,CAAC,YAAY,CAAC,IAAI,CAAC;gBAAE,OAAO,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;QAC/C,CAAC;KACF,CAAC;AACJ,CAAC"}
@@ -0,0 +1,281 @@
1
+ /**
2
+ * `PlaybackEngine` over a worklet-hosted soundfont synth.
3
+ *
4
+ * The shape is: a note queue with a cursor, a clock we own, and a pump that
5
+ * keeps a multi-second horizon of timed notes topped up in the worklet's
6
+ * sequencer. Timing therefore lives on the audio thread, and the pump's only
7
+ * job — refilling a buffer — is one that tolerates being late. Voice
8
+ * allocation and stealing belong to fluidsynth, which is the point: every
9
+ * note-loss bug in the engine this replaces came from our own voice management
10
+ * refusing a note.
11
+ *
12
+ * Everything time-related is injected (`now`, `startPump`, `createContext`,
13
+ * `loadFont`) so the transport can be tested without an audio device or real
14
+ * time. The alternative is an engine verifiable only by ear, which is how the
15
+ * last one accumulated four silent-failure bugs.
16
+ */
17
+ import type { PlaybackPlan, PlaybackTrack, ScoreRange } from '@sudobility/music_types';
18
+ import type { AuditionVoice, PlaybackEngine, PlaybackObserver } from '../../engine.js';
19
+ import type { LoadProgress } from './soundfont-loader.js';
20
+ /** The slice of `SynthHost` this engine drives, so tests can pass a stub. */
21
+ export type SynthHostLike = {
22
+ init(context: BaseAudioContext, options: {
23
+ fluidsynthModuleUrl: string;
24
+ workletModuleUrl: string;
25
+ soundfont: ArrayBuffer;
26
+ instanceCount: number;
27
+ }): Promise<void>;
28
+ ensureInstances(count: number): Promise<void>;
29
+ setChannelPercussion(instance: number, channel: number, kit?: number): void;
30
+ programSelect(instance: number, channel: number, program: number): void;
31
+ noteOn(instance: number, channel: number, midi: number, velocity: number): void;
32
+ noteAt(instance: number, channel: number, midi: number, velocity: number, delaySeconds: number, durationSeconds: number): void;
33
+ noteOff(instance: number, channel: number, midi: number): void;
34
+ controlChange(instance: number, channel: number, control: number, value: number): void;
35
+ allSoundOff(): void;
36
+ setInterpolation(order: number): void;
37
+ setMasterVolume(volume: number): void;
38
+ setTrackCount(count: number): void;
39
+ dispose(): void;
40
+ };
41
+ export type SoundfontEngineDeps = {
42
+ host: SynthHostLike;
43
+ moduleUrls: {
44
+ fluidsynth: string;
45
+ worklet: string;
46
+ };
47
+ fontUrl: string;
48
+ loadFont?: (url: string, onProgress?: (progress: LoadProgress) => void) => Promise<ArrayBuffer>;
49
+ createContext?: () => AudioContext;
50
+ /** Defaults to the audio context's own clock, which is what the audio is rendered against. */
51
+ now?: () => number;
52
+ /** Starts the pump and returns a function that stops it. */
53
+ startPump?: (tick: () => void, intervalMs: number) => () => void;
54
+ };
55
+ export declare class SoundfontPlaybackEngine implements PlaybackEngine {
56
+ private readonly queue;
57
+ private readonly governor;
58
+ /** When the pump was last expected to run, for measuring how late it is. */
59
+ private nextPumpDueAt;
60
+ private readonly clock;
61
+ private readonly deps;
62
+ private context;
63
+ private plan;
64
+ /** Replaced on every `load`. The default is 120 BPM at ppq 480, as before. */
65
+ private tempo;
66
+ private tracks;
67
+ private observer;
68
+ private stopPump;
69
+ private readonly sounding;
70
+ /**
71
+ * When the last note of the loaded score finishes.
72
+ *
73
+ * The transport used to know the piece was over by the note queue being
74
+ * exhausted *and* no note-offs being outstanding. Releases now happen in the
75
+ * worklet, so there is nothing outstanding to count — this is what stops
76
+ * playback ending on the last note's attack instead of its release.
77
+ */
78
+ private lastNoteEndSeconds;
79
+ private loopRange;
80
+ private lastReportedAt;
81
+ private initialized;
82
+ /** In-flight bring-up, so concurrent callers share one attempt. */
83
+ private initializing;
84
+ private loadState;
85
+ /** Counted so a pump that fails every tick reports once, not sixty times a second. */
86
+ private tickFailures;
87
+ /**
88
+ * Where playback should begin once the pump actually runs.
89
+ *
90
+ * The clock is *not* started by `play()`. Starting it there loses the opening
91
+ * of the piece: the first pump tick can be seconds late while the main thread
92
+ * finishes worklet and soundfont setup, and by then the clock says the music
93
+ * is already seconds in — so every note before that point is past its grace
94
+ * window and gets skipped. Measured at 2.3s of lost opening on a real load.
95
+ * Anchoring on the first tick instead means the piece starts where it should,
96
+ * however busy the thread was.
97
+ */
98
+ private startAtSeconds;
99
+ private metronomeEnabled;
100
+ /** Click positions in ticks, with a cursor, mirroring how notes are drained. */
101
+ private clicks;
102
+ private clickCursor;
103
+ /**
104
+ * Clicks already placed on the audio graph, so the transport can take them
105
+ * back.
106
+ *
107
+ * They are scheduled a whole horizon ahead and do not go through the synth,
108
+ * so `allSoundOff` says nothing to them: pausing used to leave the room
109
+ * ticking for four seconds after the music stopped.
110
+ */
111
+ private pendingClicks;
112
+ /** Audition voices are held on their own channel so they never disturb a track. */
113
+ private auditionHeld;
114
+ /** How many synths the current score's channel assignment needs. */
115
+ private instanceCount;
116
+ private tempoMultiplier;
117
+ /**
118
+ * The channel auditions borrow — recomputed per score so it is one no track owns.
119
+ *
120
+ * It used to be channel 15 flat. That channel belongs to a track on any score
121
+ * with fifteen pitched parts, so tapping a key selected the audition's program
122
+ * on it and left that part playing the wrong instrument until the next edit
123
+ * reloaded the score.
124
+ */
125
+ private auditionChannel;
126
+ constructor(deps: SoundfontEngineDeps);
127
+ private now;
128
+ private secondsForTick;
129
+ /**
130
+ * Brings up the audio context and the synth, once.
131
+ *
132
+ * Resuming comes first, and the guard after it is the whole point. A browser
133
+ * starts an `AudioContext` **suspended** unless it was created during a user
134
+ * gesture, and a suspended context never runs its `AudioWorklet`. The synth's
135
+ * font load round-trips through that worklet, so attempting it on a suspended
136
+ * context does not fail — it simply never returns. That is what happened:
137
+ * `load` ran at load time, long before any click, and left a
138
+ * promise pending for good. Every later `play()` awaited the same promise,
139
+ * so the transport sat at "stopped" with nothing in the console to explain it.
140
+ *
141
+ * So when the context cannot be got running, this does the cheap part and
142
+ * returns. `play()` and `noteOn()` are user gestures and will get it running.
143
+ */
144
+ initialize(): Promise<void>;
145
+ private bringUp;
146
+ /** Only on change, so a progress stream does not become a store-update stream. */
147
+ private reportLoad;
148
+ /** Best effort: without a user gesture behind it this is allowed to fail. */
149
+ private resumeContext;
150
+ /** A stub context in a test has no `state`; only a real suspended one blocks. */
151
+ private contextCanRun;
152
+ load(plan: PlaybackPlan): Promise<void>;
153
+ /**
154
+ * A channel no track owns, searched from the top down because tracks fill
155
+ * from the bottom.
156
+ *
157
+ * Channel 9 is skipped: it is General MIDI's drum channel, so borrowing it
158
+ * would make every audition a drum hit. Percussion auditions get their drum
159
+ * type switched on instead, which is the same route a second drum track takes.
160
+ *
161
+ * Where a score owns every channel of every open instance there is nothing to
162
+ * borrow, and it falls back to sharing the last one — an audition that
163
+ * disturbs one track's program beats a keyboard that makes no sound.
164
+ */
165
+ private pickAuditionChannel;
166
+ /** Tells the host each track's program, percussion flag, pan and level. */
167
+ private applyPlanToHost;
168
+ /** Volume, mute and solo all resolve to one CC7 value per channel. */
169
+ private applyTrackLevels;
170
+ /**
171
+ * Starts the transport — reporting "playing" only once it is true.
172
+ *
173
+ * The first press of Play has to load a 23MB soundfont and hand it to
174
+ * fluidsynth, which takes seconds. This used to report "playing" up front so
175
+ * the button would not look dead through that wait, on the reasoning that it
176
+ * cost nothing in accuracy: the clock does not start until the pump's first
177
+ * tick, so no part of the piece is skipped however long the load takes.
178
+ *
179
+ * It cost something in accuracy elsewhere. The **caret** does not follow the
180
+ * engine's position while playing — it interpolates, dead-reckoning from the
181
+ * last 30Hz report with elapsed real time, because those reports arrive in
182
+ * clumps. During the load there are no reports at all, so the caret projected
183
+ * forward from a standing start and glided silently through several bars,
184
+ * then snapped back when the music finally began at the beginning. Two
185
+ * separately-sound optimisations that could not both be right.
186
+ *
187
+ * So the transport now says what is true, and `PlaybackLoadState` — which the
188
+ * bar renders as "Preparing instruments 45%" — says why the wait is happening.
189
+ * That indicator did not exist when the early report was written; it is what
190
+ * makes honesty affordable here.
191
+ *
192
+ * Only a cold engine waits. Once the synth is up this reports synchronously,
193
+ * which is every press after the first.
194
+ */
195
+ play(fromTick?: number): Promise<void>;
196
+ pause(): void;
197
+ stop(): void;
198
+ seek(tick: number): void;
199
+ /**
200
+ * Changes playback speed, rebuilding what is already queued.
201
+ *
202
+ * Every note in the sequencer carries a delay worked out at the speed that
203
+ * was in force when it was posted, and the horizon is four seconds deep — so
204
+ * on its own a speed change moved the caret at once and left the music at the
205
+ * old tempo for a whole horizon, then jumped. Re-seeking where we stand drops
206
+ * that backlog, and the next tick refills it at the new speed. The gap is one
207
+ * pump interval.
208
+ */
209
+ setTempoMultiplier(multiplier: number): void;
210
+ setLoop(range: ScoreRange | null): void;
211
+ setTrackMute(trackId: string, muted: boolean): void;
212
+ setTrackSolo(trackId: string, solo: boolean): void;
213
+ /**
214
+ * Re-reads the mix off `score` and pushes it, scheduling nothing.
215
+ *
216
+ * `setTrackMute`/`setTrackSolo` covered two of the four mix properties.
217
+ * Volume was read once, in `load`, and pan only in `applyPlanToHost` —
218
+ * so with the playback edit lock in place, where a mix change deliberately
219
+ * does *not* reload, moving a fader mid-playback moved the fader and left the
220
+ * sound where it was.
221
+ *
222
+ * A track the engine does not know is skipped rather than throwing: a mix
223
+ * change cannot add a track (that would be content), so an unknown id means
224
+ * the engine is holding an older plan and the next `load` settles it.
225
+ */
226
+ applyMix(tracks: readonly PlaybackTrack[]): void;
227
+ setMetronome(enabled: boolean): void;
228
+ setMasterVolume(volume: number): void;
229
+ noteOn(midi: number, voice: AuditionVoice): void;
230
+ noteOff(midi: number): void;
231
+ setObserver(observer: PlaybackObserver | null): void;
232
+ dispose(): void;
233
+ private startPump;
234
+ private endPump;
235
+ private tick;
236
+ private dispatchTick;
237
+ /**
238
+ * The far edge of what this tick may schedule, in ticks.
239
+ *
240
+ * Clamped to just inside a loop, because the wrap only happens on the pump
241
+ * tick *after* the end is reached: without this a note or click a few
242
+ * milliseconds past the loop end was already in the sequencer and sounded
243
+ * before the loop came round, on every pass. The end tick itself belongs to
244
+ * the next pass, where it is the loop's own first beat.
245
+ */
246
+ private horizonTick;
247
+ /**
248
+ * How late this frame ran against when it was due.
249
+ *
250
+ * Measured on the audio clock rather than wall time, because that is the
251
+ * clock the audio is rendered against and the one that matters if the thread
252
+ * is being starved.
253
+ */
254
+ private measureLateness;
255
+ /**
256
+ * Clicks due in this window, scheduled ahead at their exact moment.
257
+ *
258
+ * Unlike notes, these can be placed precisely: an oscillator started with
259
+ * `start(at)` sounds at that sample however busy the main thread is, so the
260
+ * click stays steady even when the pump runs late.
261
+ */
262
+ private pumpMetronome;
263
+ /** Takes back every click still queued — a stop, a seek, or the switch going off. */
264
+ private cancelPendingClicks;
265
+ /** Inverse of `secondsForTick`, for turning the lookahead window back into ticks. */
266
+ private tickForSeconds;
267
+ private get playbackSpeed();
268
+ private applyLoop;
269
+ /**
270
+ * Position and active notes, both at 30Hz.
271
+ *
272
+ * The engine this replaces called `onActiveNotes` on every note-on *and*
273
+ * note-off — some 11,500 store updates for a three-minute piece, on the same
274
+ * thread the scheduler runs on. Coalescing is what keeps that work off the
275
+ * pump's back.
276
+ */
277
+ private report;
278
+ /** Drops the sounding set and says so, for a seek, stop or teardown. */
279
+ private clearSounding;
280
+ }
281
+ //# sourceMappingURL=soundfont-engine.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"soundfont-engine.d.ts","sourceRoot":"","sources":["../../../src/web/playback/soundfont-engine.ts"],"names":[],"mappings":"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"}