@pixerate/flair 0.28.0 → 0.29.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 (59) hide show
  1. package/CHANGELOG.md +13 -0
  2. package/README.md +48 -6
  3. package/dist/catalog/index.d.ts +11 -6
  4. package/dist/catalog/index.js +1 -1
  5. package/dist/{chunk-4LKAPRFM.js → chunk-6GFOLHCT.js} +920 -34
  6. package/dist/chunk-6GFOLHCT.js.map +1 -0
  7. package/dist/{chunk-JL6TI6II.js → chunk-7KYQ4R76.js} +2 -2
  8. package/dist/chunk-7KYQ4R76.js.map +1 -0
  9. package/dist/{chunk-STKEKXCK.js → chunk-AUH3PEFW.js} +2 -2
  10. package/dist/{chunk-PFGGPIMT.js → chunk-DA4UUSDO.js} +58 -3
  11. package/dist/chunk-DA4UUSDO.js.map +1 -0
  12. package/dist/{chunk-RDU63CDO.js → chunk-HDYBY3PQ.js} +380 -1
  13. package/dist/chunk-HDYBY3PQ.js.map +1 -0
  14. package/dist/{chunk-VLQLRKGW.js → chunk-KZYZX3V5.js} +260 -33
  15. package/dist/chunk-KZYZX3V5.js.map +1 -0
  16. package/dist/{chunk-GXP4YLAP.js → chunk-MBKNTXQX.js} +9 -9
  17. package/dist/chunk-MBKNTXQX.js.map +1 -0
  18. package/dist/{chunk-H6VDFBIO.js → chunk-SMR2MJXB.js} +156 -5
  19. package/dist/chunk-SMR2MJXB.js.map +1 -0
  20. package/dist/{chunk-YBGRXVAE.js → chunk-X3C2VFTM.js} +3 -3
  21. package/dist/{chunk-CK6EV37I.js → chunk-ZULFHADW.js} +69 -6
  22. package/dist/chunk-ZULFHADW.js.map +1 -0
  23. package/dist/cli.js +6 -6
  24. package/dist/editor/index.d.ts +3 -3
  25. package/dist/editor/index.js +3 -3
  26. package/dist/{effect-GGP66JI6.js → effect-YBWDNIYG.js} +10 -10
  27. package/dist/embed/index.d.ts +36 -8
  28. package/dist/embed/index.js +5 -5
  29. package/dist/{frames-VEcqa40g.d.ts → frames-CGJudAbu.d.ts} +11 -1
  30. package/dist/index.d.ts +69 -6
  31. package/dist/index.js +35 -7
  32. package/dist/index.js.map +1 -1
  33. package/dist/node.d.ts +4 -4
  34. package/dist/node.js +9 -9
  35. package/dist/react/index.d.ts +4 -4
  36. package/dist/react/index.js +7 -7
  37. package/dist/react/index.js.map +1 -1
  38. package/dist/{recorder-CDPPTJMG.js → recorder-TYMEZHOT.js} +3 -3
  39. package/dist/{renderer-teaa98RE.d.ts → renderer-tk1nbVkW.d.ts} +252 -2
  40. package/dist/svelte/index.d.ts +4 -4
  41. package/dist/svelte/index.js +7 -7
  42. package/dist/svelte/index.js.map +1 -1
  43. package/dist/{svg-BhRboN9q.d.ts → svg-DaKRPA9v.d.ts} +39 -7
  44. package/dist/{types-DUOMxZaq.d.ts → types-wh28fXW4.d.ts} +2 -2
  45. package/dist/vector/index.d.ts +2 -2
  46. package/dist/vector/index.js +5 -3
  47. package/package.json +1 -1
  48. package/dist/chunk-4LKAPRFM.js.map +0 -1
  49. package/dist/chunk-CK6EV37I.js.map +0 -1
  50. package/dist/chunk-GXP4YLAP.js.map +0 -1
  51. package/dist/chunk-H6VDFBIO.js.map +0 -1
  52. package/dist/chunk-JL6TI6II.js.map +0 -1
  53. package/dist/chunk-PFGGPIMT.js.map +0 -1
  54. package/dist/chunk-RDU63CDO.js.map +0 -1
  55. package/dist/chunk-VLQLRKGW.js.map +0 -1
  56. /package/dist/{chunk-STKEKXCK.js.map → chunk-AUH3PEFW.js.map} +0 -0
  57. /package/dist/{chunk-YBGRXVAE.js.map → chunk-X3C2VFTM.js.map} +0 -0
  58. /package/dist/{effect-GGP66JI6.js.map → effect-YBWDNIYG.js.map} +0 -0
  59. /package/dist/{recorder-CDPPTJMG.js.map → recorder-TYMEZHOT.js.map} +0 -0
@@ -1,5 +1,7 @@
1
1
  import {
2
+ LiveAudio,
2
3
  PointerTracker,
4
+ SPECTRUM_BINS,
3
5
  ShaderCompileError,
4
6
  TextureCache,
5
7
  clicksUniform,
@@ -8,12 +10,69 @@ import {
8
10
  drawFrame,
9
11
  resetGlCaches,
10
12
  trailUniform
11
- } from "./chunk-RDU63CDO.js";
13
+ } from "./chunk-HDYBY3PQ.js";
12
14
  import {
13
15
  imageUrls,
14
16
  parseControls
15
17
  } from "./chunk-VYOMXE6I.js";
16
18
 
19
+ // src/shader/scroll.ts
20
+ var SCROLL_SMOOTHING = 0.12;
21
+ var SCROLL_SETTLE = 0.4;
22
+ var clamp01 = (x) => x <= 0 ? 0 : x >= 1 ? 1 : x;
23
+ var smooth = (t) => t <= 0 ? 0 : t >= 1 ? 1 : t * t * (3 - 2 * t);
24
+ function viewportProgress(top, height, viewport) {
25
+ const travel = viewport + height;
26
+ return travel > 0 ? clamp01((viewport - top) / travel) : 0;
27
+ }
28
+ var ScrollTracker = class {
29
+ progress = 0;
30
+ page = 0;
31
+ offset;
32
+ last;
33
+ velocity = 0;
34
+ movedAt = -Infinity;
35
+ seen = false;
36
+ /**
37
+ * Where things are at `time` seconds: the element's `progress` (see
38
+ * {@link viewportProgress}), the page's scroll share, and the element's
39
+ * top in viewport heights (from which the velocity comes; any steady
40
+ * measure that grows as the page scrolls down will do).
41
+ */
42
+ sample(progress, page, offset, time) {
43
+ const dt = this.last === void 0 ? 0 : time - this.last;
44
+ if (this.offset !== void 0 && dt > 0) {
45
+ const moved = offset - this.offset;
46
+ const speed = moved / dt;
47
+ this.velocity += (speed - this.velocity) * (1 - Math.exp(-dt / SCROLL_SMOOTHING));
48
+ if (Math.abs(moved) > 1e-6) this.movedAt = time;
49
+ }
50
+ if (this.offset !== void 0 && Math.abs(offset - this.offset) > 1e-6 && dt <= 0) this.movedAt = time;
51
+ this.progress = clamp01(progress);
52
+ this.page = clamp01(page);
53
+ this.offset = offset;
54
+ this.last = time;
55
+ this.seen = true;
56
+ }
57
+ /** Whether scrolling has settled (nothing more to animate until it moves). */
58
+ settled(time) {
59
+ return time - this.movedAt > SCROLL_SETTLE && Math.abs(this.velocity) < 2e-3;
60
+ }
61
+ /** `iScroll` at `time` (all zero before the first sample). */
62
+ scroll(time) {
63
+ if (!this.seen) return [0, 0, 0, 0];
64
+ const active = 1 - smooth((time - this.movedAt) / SCROLL_SETTLE);
65
+ return [Math.max(this.progress, 1e-4), this.page, Math.abs(this.velocity) < 2e-3 ? 0 : this.velocity, active];
66
+ }
67
+ /** Forgets the velocity and activity (when the page jumps, say). */
68
+ clear() {
69
+ this.velocity = 0;
70
+ this.movedAt = -Infinity;
71
+ this.offset = void 0;
72
+ this.last = void 0;
73
+ }
74
+ };
75
+
17
76
  // src/shader/renderer.ts
18
77
  var ShaderRenderer = class {
19
78
  canvas;
@@ -39,6 +98,17 @@ var ShaderRenderer = class {
39
98
  mouse = [0, 0, 0, 0];
40
99
  /** `iPointer` and `iClicks`. */
41
100
  pointer = new PointerTracker();
101
+ /** `iAudio`, `iSpectrum` and `iBeat`, from `setAudioSource`. */
102
+ audio = null;
103
+ audioFrame;
104
+ /** `iScroll`. */
105
+ scrollTracker = new ScrollTracker();
106
+ scrollOverride = null;
107
+ scrollMoved = true;
108
+ /** The page clock's latest reading, in seconds, for `iScroll`'s easing. */
109
+ scrollNow = 0;
110
+ /** `iScroll` as last drawn, so a paused preview redraws only when it changes. */
111
+ drawnScroll = [0, 0, 0, 0];
42
112
  disposed = false;
43
113
  /** The latest source's status. */
44
114
  status = { ok: true, errors: [] };
@@ -109,6 +179,7 @@ var ShaderRenderer = class {
109
179
  canvas.addEventListener("pointerenter", this.onPointerEnter);
110
180
  canvas.addEventListener("pointerleave", this.onPointerLeave);
111
181
  window.addEventListener("pointerup", this.onPointerUp);
182
+ window.addEventListener("scroll", this.onScroll, { capture: true, passive: true });
112
183
  this.resize();
113
184
  this.raf = requestAnimationFrame(this.tick);
114
185
  }
@@ -133,6 +204,7 @@ var ShaderRenderer = class {
133
204
  this.shader = result.shader;
134
205
  this.controls = controls;
135
206
  this.status = { ok: true, errors: [] };
207
+ if (result.shader.builtins.iScroll) this.sampleScroll(performance.now() / 1e3);
136
208
  } else {
137
209
  this.status = { ok: false, errors: result.errors };
138
210
  }
@@ -166,6 +238,42 @@ var ShaderRenderer = class {
166
238
  this.textures?.setLive(name, source);
167
239
  if (!this.playing) this.render();
168
240
  }
241
+ /**
242
+ * Feeds `iAudio`, `iSpectrum` and `iBeat` from a sound (`null` stops):
243
+ * - an `<audio>` or `<video>` element: it keeps playing to the speakers,
244
+ * now through Web Audio (an element can only be wired in once, so the
245
+ * same node is reused by every renderer that listens to it);
246
+ * - a `MediaStream`, such as a microphone from `getUserMedia`: listened
247
+ * to, not played;
248
+ * - any Web Audio node (an oscillator, a gain, a player's output), in its
249
+ * own context.
250
+ *
251
+ * The `AudioContext` and `AnalyserNode` are made on first use (see
252
+ * `sharedAudioContext`). Browsers keep a new context suspended until the
253
+ * page has a click or key press (autoplay policy): call this from one, or
254
+ * the context resumes on the first. While a source is set, a paused
255
+ * preview still follows the sound. A media element from another site needs
256
+ * `crossOrigin = "anonymous"` and CORS, or the browser feeds silence.
257
+ */
258
+ setAudioSource(source) {
259
+ this.audio?.dispose();
260
+ this.audio = source ? new LiveAudio(source) : null;
261
+ this.audioFrame = void 0;
262
+ if (!this.playing) this.render();
263
+ }
264
+ /**
265
+ * Pins `iScroll` to a scroll position of your own (0–1, for x and y; the
266
+ * velocity and "scrolling" follow its changes), for scrubbers and hosts
267
+ * that scroll in their own way; `null` goes back to following the page.
268
+ */
269
+ setScrollProgress(progress) {
270
+ this.scrollOverride = progress === null ? null : Math.min(1, Math.max(0, progress));
271
+ this.scrollMoved = true;
272
+ if (!this.playing) {
273
+ this.sampleScroll(performance.now() / 1e3);
274
+ this.render();
275
+ }
276
+ }
169
277
  /**
170
278
  * Settles once the images the current controls show have loaded, and
171
279
  * every live video has a frame. Rejects with an {@link ImageLoadError}
@@ -238,6 +346,9 @@ var ShaderRenderer = class {
238
346
  this.canvas.removeEventListener("pointerenter", this.onPointerEnter);
239
347
  this.canvas.removeEventListener("pointerleave", this.onPointerLeave);
240
348
  window.removeEventListener("pointerup", this.onPointerUp);
349
+ window.removeEventListener("scroll", this.onScroll, { capture: true });
350
+ this.audio?.dispose();
351
+ this.audio = null;
241
352
  if (this.gl && this.shader) this.gl.deleteProgram(this.shader.program);
242
353
  this.textures?.dispose();
243
354
  this.textures = null;
@@ -270,7 +381,9 @@ var ShaderRenderer = class {
270
381
  mouse: this.mouse,
271
382
  pointer: this.pointer.pointer(this.time),
272
383
  clicks: this.pointer.clicksAt(this.time),
273
- trail: this.pointer.trailAt(this.time)
384
+ trail: this.pointer.trailAt(this.time),
385
+ ...this.audioFrame ?? {},
386
+ scroll: this.drawnScroll = this.scrollTracker.scroll(this.scrollNow)
274
387
  },
275
388
  this.textures ?? void 0
276
389
  );
@@ -295,12 +408,25 @@ var ShaderRenderer = class {
295
408
  this.raf = requestAnimationFrame(this.tick);
296
409
  const delta = this.lastNow ? Math.min((now - this.lastNow) / 1e3, 0.1) : 0;
297
410
  this.lastNow = now;
411
+ const seconds = now / 1e3;
412
+ let follow = false;
413
+ const b = this.shader?.builtins;
414
+ if (this.audio && b && (b.iAudio || b.iSpectrum || b.iBeat)) {
415
+ this.audioFrame = this.audio.read(seconds);
416
+ follow = true;
417
+ }
418
+ this.scrollNow = seconds;
419
+ if (b?.iScroll) {
420
+ if (this.scrollMoved || this.scrollOverride === null || !this.scrollTracker.settled(seconds)) this.sampleScroll(seconds);
421
+ const scroll = this.scrollTracker.scroll(seconds);
422
+ if (scroll.some((v, i) => v !== this.drawnScroll[i])) follow = true;
423
+ }
298
424
  if (this.playing) {
299
425
  this.time += delta * this.speed;
300
426
  this.frame += 1;
301
427
  this.render(delta * this.speed);
302
428
  this.fpsFrames += 1;
303
- } else if (this.textures?.liveChanged()) {
429
+ } else if (this.textures?.liveChanged() || follow) {
304
430
  this.render();
305
431
  }
306
432
  if (now - this.fpsSince >= 500) {
@@ -319,6 +445,24 @@ var ShaderRenderer = class {
319
445
  height: this.canvas.height
320
446
  });
321
447
  }
448
+ /** Where the canvas is in the viewport (or the pinned progress), into the scroll tracker. */
449
+ sampleScroll(seconds) {
450
+ this.scrollMoved = false;
451
+ this.scrollNow = seconds;
452
+ if (this.scrollOverride !== null) {
453
+ const p = this.scrollOverride;
454
+ this.scrollTracker.sample(p, p, p * 2, seconds);
455
+ return;
456
+ }
457
+ const rect = this.canvas.getBoundingClientRect();
458
+ const viewport = window.innerHeight || document.documentElement.clientHeight || 1;
459
+ const page = document.scrollingElement ?? document.documentElement;
460
+ const range = page.scrollHeight - page.clientHeight;
461
+ this.scrollTracker.sample(viewportProgress(rect.top, rect.height, viewport), range > 0 ? page.scrollTop / range : 0, -rect.top / viewport, seconds);
462
+ }
463
+ onScroll = () => {
464
+ this.scrollMoved = true;
465
+ };
322
466
  /** Pointer position in shader pixels (origin bottom-left, like gl_FragCoord). */
323
467
  toPixels(e) {
324
468
  const rect = this.canvas.getBoundingClientRect();
@@ -372,6 +516,7 @@ var ShaderRenderer = class {
372
516
  };
373
517
 
374
518
  // src/shader/frames.ts
519
+ var SILENT = new Float32Array(SPECTRUM_BINS);
375
520
  var ShaderFrames = class {
376
521
  canvas;
377
522
  width;
@@ -445,7 +590,9 @@ var ShaderFrames = class {
445
590
  mouse: options.mouse ?? [0, 0, 0, 0],
446
591
  pointer: options.pointer,
447
592
  clicks: options.clicks && clicksUniform(options.clicks, time),
448
- trail: options.trail && trailUniform(options.trail, time)
593
+ trail: options.trail && trailUniform(options.trail, time),
594
+ ...options.audio ? { audio: options.audio.audio ?? [0, 0, 0, 0], spectrum: options.audio.spectrum ?? SILENT, beat: options.audio.beat ?? -1 } : {},
595
+ ...options.scroll ? { scroll: options.scroll } : {}
449
596
  },
450
597
  this.textures
451
598
  );
@@ -492,7 +639,11 @@ var ShaderFrames = class {
492
639
  };
493
640
 
494
641
  export {
642
+ SCROLL_SMOOTHING,
643
+ SCROLL_SETTLE,
644
+ viewportProgress,
645
+ ScrollTracker,
495
646
  ShaderRenderer,
496
647
  ShaderFrames
497
648
  };
498
- //# sourceMappingURL=chunk-H6VDFBIO.js.map
649
+ //# sourceMappingURL=chunk-SMR2MJXB.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/shader/scroll.ts","../src/shader/renderer.ts","../src/shader/frames.ts"],"sourcesContent":["/**\n * Scrolling as shaders see it: `iScroll`, a `vec4` of\n * - x: the canvas's progress through the viewport, 0 when its top meets the\n * viewport's bottom, 1 when its bottom leaves the top;\n * - y: the page's scroll, as a share of how far it can scroll;\n * - z: scroll velocity in viewport heights per second (positive scrolling\n * down, smoothed);\n * - w: 1 while scrolling, easing back to 0 after.\n *\n * Where nothing scrolls it (thumbnails, recordings without a script) it is\n * all zero, and scroll effects play an idle demo.\n */\n\n/** Seconds the velocity takes to follow a change (most of the way). */\nexport const SCROLL_SMOOTHING = 0.12;\n\n/** Seconds `iScroll.w` takes to ease back to 0 once scrolling stops. */\nexport const SCROLL_SETTLE = 0.4;\n\nconst clamp01 = (x: number) => (x <= 0 ? 0 : x >= 1 ? 1 : x);\nconst smooth = (t: number) => (t <= 0 ? 0 : t >= 1 ? 1 : t * t * (3 - 2 * t));\n\n/**\n * An element's progress through the viewport: 0 when its top meets the\n * viewport's bottom, 1 when its bottom leaves the top, from its box (as\n * `getBoundingClientRect` gives it) and the viewport's height.\n */\nexport function viewportProgress(top: number, height: number, viewport: number): number {\n const travel = viewport + height;\n return travel > 0 ? clamp01((viewport - top) / travel) : 0;\n}\n\n/**\n * Follows one element's scrolling for `iScroll`: feed it where the element\n * is at each frame, with a steady clock in seconds, and read the uniform.\n * The same positions at the same times always give the same uniform.\n */\nexport class ScrollTracker {\n private progress = 0;\n private page = 0;\n private offset: number | undefined;\n private last: number | undefined;\n private velocity = 0;\n private movedAt = -Infinity;\n private seen = false;\n\n /**\n * Where things are at `time` seconds: the element's `progress` (see\n * {@link viewportProgress}), the page's scroll share, and the element's\n * top in viewport heights (from which the velocity comes; any steady\n * measure that grows as the page scrolls down will do).\n */\n sample(progress: number, page: number, offset: number, time: number) {\n const dt = this.last === undefined ? 0 : time - this.last;\n if (this.offset !== undefined && dt > 0) {\n const moved = offset - this.offset;\n const speed = moved / dt;\n this.velocity += (speed - this.velocity) * (1 - Math.exp(-dt / SCROLL_SMOOTHING));\n if (Math.abs(moved) > 1e-6) this.movedAt = time;\n }\n if (this.offset !== undefined && Math.abs(offset - this.offset) > 1e-6 && dt <= 0) this.movedAt = time;\n this.progress = clamp01(progress);\n this.page = clamp01(page);\n this.offset = offset;\n this.last = time;\n this.seen = true;\n }\n\n /** Whether scrolling has settled (nothing more to animate until it moves). */\n settled(time: number): boolean {\n return time - this.movedAt > SCROLL_SETTLE && Math.abs(this.velocity) < 2e-3;\n }\n\n /** `iScroll` at `time` (all zero before the first sample). */\n scroll(time: number): [number, number, number, number] {\n if (!this.seen) return [0, 0, 0, 0];\n const active = 1 - smooth((time - this.movedAt) / SCROLL_SETTLE);\n // A sliver above 0, so a measured page never reads as \"nothing scrolls it\".\n return [Math.max(this.progress, 1e-4), this.page, Math.abs(this.velocity) < 2e-3 ? 0 : this.velocity, active];\n }\n\n /** Forgets the velocity and activity (when the page jumps, say). */\n clear() {\n this.velocity = 0;\n this.movedAt = -Infinity;\n this.offset = undefined;\n this.last = undefined;\n }\n}\n","import { imageUrls, parseControls, type ControlDef, type ControlValue } from \"./controls.js\";\nimport {\n compileShader,\n createContext,\n drawFrame,\n resetGlCaches,\n type CompileError,\n type CompiledShader,\n} from \"./program.js\";\nimport { LiveAudio, type AudioFrame, type AudioSource } from \"./audio.js\";\nimport { PointerTracker } from \"./pointer.js\";\nimport { ScrollTracker, viewportProgress } from \"./scroll.js\";\nimport { TextureCache, type ImageLoadError, type LiveSource } from \"./textures.js\";\n\n/** Whether the latest source compiled, and if not, why. */\nexport interface CompileStatus {\n ok: boolean;\n errors: CompileError[];\n}\n\n/** What `onStats` reports: on play, pause, seek and restart, and twice a second while running. */\nexport interface RendererStats {\n /** `iTime`, in seconds. */\n time: number;\n /** Frames drawn per second over the last half second (0 while paused). */\n fps: number;\n playing: boolean;\n /** The drawing buffer's size in device pixels. */\n width: number;\n height: number;\n}\n\nexport interface ShaderRendererOptions {\n className?: string;\n /** Enable a transparent WebGL drawing buffer so shader alpha (`fragColor.a`) composites over the page (default false). */\n alpha?: boolean;\n /** Pause rendering when scrolled out of view via IntersectionObserver (default false). */\n pauseWhenHidden?: boolean;\n /** Start playing at once (default true). Set to false to start paused. */\n autoplay?: boolean;\n /** Wait to start playing until the canvas enters the viewport via IntersectionObserver (default false). When true, autoplay is deferred until first visible. */\n playOnEnter?: boolean;\n}\n\n/**\n * Runs one shader on a canvas: the editor's live preview. A source that does\n * not compile leaves the last working program on screen, so a typo never\n * blanks the preview; the errors are reported instead.\n *\n * The renderer owns its canvas (added to `host`, removed on dispose): a\n * canvas keeps its WebGL context for life, so reusing one after disposing\n * (a remount, hot reload) would get the released, lost context back.\n */\nexport class ShaderRenderer {\n readonly canvas: HTMLCanvasElement;\n private gl: WebGL2RenderingContext | null;\n private readonly alpha: boolean;\n private shader: CompiledShader | null = null;\n private textures: TextureCache | null = null;\n /** Live sources by image control, kept to set again on a restored context. */\n private live = new Map<string, LiveSource>();\n private controls: ControlDef[] = [];\n private values: Record<string, ControlValue> | undefined;\n private source = \"\";\n private raf = 0;\n private lastNow = 0;\n private frame = 0;\n private fpsFrames = 0;\n private fpsSince = 0;\n private ownsCanvas = false;\n private resizeObserver: ResizeObserver;\n private intersectionObserver?: IntersectionObserver;\n private wasPlayingBeforeHidden = false;\n private hasEnteredViewport = false;\n private mouse: [number, number, number, number] = [0, 0, 0, 0];\n /** `iPointer` and `iClicks`. */\n private pointer = new PointerTracker();\n /** `iAudio`, `iSpectrum` and `iBeat`, from `setAudioSource`. */\n private audio: LiveAudio | null = null;\n private audioFrame: AudioFrame | undefined;\n /** `iScroll`. */\n private scrollTracker = new ScrollTracker();\n private scrollOverride: number | null = null;\n private scrollMoved = true;\n /** The page clock's latest reading, in seconds, for `iScroll`'s easing. */\n private scrollNow = 0;\n /** `iScroll` as last drawn, so a paused preview redraws only when it changes. */\n private drawnScroll: readonly number[] = [0, 0, 0, 0];\n private disposed = false;\n /** The latest source's status. */\n status: CompileStatus = { ok: true, errors: [] };\n /** `iTime`, in seconds. Use `seek` to change it. */\n time = 0;\n /** Playback speed: 1 is real time. */\n speed = 1;\n /** Device pixels per CSS pixel; lower renders faster. Use `setPixelRatio` to change it. */\n pixelRatio = Math.min(window.devicePixelRatio || 1, 2);\n playing = true;\n fps = 0;\n onStats?: (stats: RendererStats) => void;\n /** Called with `true` when the GL context is lost and `false` once it is restored and recompiled. */\n onContextLost?: (lost: boolean) => void;\n /** Called when an image control's URL (or live video) cannot be loaded; the control shows black. */\n onImageError?: (error: ImageLoadError) => void;\n\n /**\n * Creates a canvas in `target` (with `className` or options), or attaches to an existing\n * `HTMLCanvasElement`, and starts the animation loop. Size the canvas with CSS.\n */\n constructor(target: HTMLElement | HTMLCanvasElement, options?: string | ShaderRendererOptions) {\n const opts: ShaderRendererOptions =\n typeof options === \"string\" ? { className: options } : (options ?? {});\n let canvas: HTMLCanvasElement;\n if (target instanceof HTMLCanvasElement || (\"tagName\" in target && target.tagName === \"CANVAS\")) {\n canvas = target as HTMLCanvasElement;\n if (opts.className) canvas.className = opts.className;\n } else {\n canvas = document.createElement(\"canvas\");\n if (opts.className) canvas.className = opts.className;\n target.appendChild(canvas);\n this.ownsCanvas = true;\n }\n this.canvas = canvas;\n this.alpha = Boolean(opts.alpha);\n this.gl = createContext(canvas, true, this.alpha);\n this.textures = this.gl && this.textureCache(this.gl);\n this.resizeObserver = new ResizeObserver(() => this.resize());\n this.resizeObserver.observe(canvas);\n\n const needsObserver =\n (opts.pauseWhenHidden || opts.playOnEnter) && typeof IntersectionObserver !== \"undefined\";\n\n if (needsObserver) {\n this.intersectionObserver = new IntersectionObserver(([entry]) => {\n if (!entry) return;\n if (entry.isIntersecting) {\n if (opts.playOnEnter && !this.hasEnteredViewport) {\n this.hasEnteredViewport = true;\n if (opts.autoplay ?? true) {\n this.play();\n }\n } else if (this.wasPlayingBeforeHidden) {\n this.wasPlayingBeforeHidden = false;\n this.play();\n }\n } else {\n if (this.playing && opts.pauseWhenHidden) {\n this.wasPlayingBeforeHidden = true;\n this.pause();\n }\n }\n });\n this.intersectionObserver.observe(canvas);\n }\n\n const shouldAutoplay = (opts.autoplay ?? true) && !opts.playOnEnter;\n this.playing = shouldAutoplay;\n\n canvas.addEventListener(\"webglcontextlost\", this.onLost);\n canvas.addEventListener(\"webglcontextrestored\", this.onRestored);\n canvas.addEventListener(\"pointerdown\", this.onPointerDown);\n canvas.addEventListener(\"pointermove\", this.onPointerMove);\n canvas.addEventListener(\"pointerenter\", this.onPointerEnter);\n canvas.addEventListener(\"pointerleave\", this.onPointerLeave);\n window.addEventListener(\"pointerup\", this.onPointerUp);\n // Any scroll that may move the canvas: the page's or a scrolling element's.\n window.addEventListener(\"scroll\", this.onScroll, { capture: true, passive: true });\n this.resize();\n this.raf = requestAnimationFrame(this.tick);\n }\n\n /** False when the browser has no WebGL2. */\n get supported(): boolean {\n return this.gl !== null;\n }\n\n /** Compiles a new source; returns its status. Keeps the previous program when it fails. */\n setSource(source: string): CompileStatus {\n this.source = source;\n const controls = parseControls(source);\n if (!this.gl) {\n this.status = {\n ok: false,\n errors: [{ line: 0, message: \"WebGL2 is not available in this browser.\" }],\n };\n return this.status;\n }\n const result = compileShader(this.gl, source, controls);\n if (result.ok) {\n if (this.shader) this.gl.deleteProgram(this.shader.program);\n this.shader = result.shader;\n this.controls = controls;\n this.status = { ok: true, errors: [] };\n // Where the page is now, so the first frame shows it.\n if (result.shader.builtins.iScroll) this.sampleScroll(performance.now() / 1000);\n } else {\n this.status = { ok: false, errors: result.errors };\n }\n this.syncTextures();\n if (!this.playing) this.render();\n return this.status;\n }\n\n /**\n * Sets control values (see `controlValue`); a paused preview redraws.\n * Images load in the background and show once they arrive: await\n * `ready()` before a `capture` that needs them.\n */\n setValues(values: Record<string, ControlValue> | undefined) {\n this.values = values;\n this.syncTextures();\n if (!this.playing) this.render();\n }\n\n /**\n * Shows a `<video>` (or a canvas) in the image control `name` (the\n * uniform's name, such as `uImage`) in place of its URL, frame by frame;\n * `null` goes back to the URL. The preview follows the video even while\n * the shader's own clock is paused, and the video plays, seeks and loops\n * on its own: the renderer never controls it. A video from another site\n * needs `crossOrigin = \"anonymous\"` and CORS, or `onImageError` hears why\n * it shows black. The value stays as it is, so documents, presets and\n * agents keep seeing the URL.\n */\n setLiveSource(name: string, source: LiveSource | null) {\n if (source) this.live.set(name, source);\n else this.live.delete(name);\n this.textures?.setLive(name, source);\n if (!this.playing) this.render();\n }\n\n /**\n * Feeds `iAudio`, `iSpectrum` and `iBeat` from a sound (`null` stops):\n * - an `<audio>` or `<video>` element: it keeps playing to the speakers,\n * now through Web Audio (an element can only be wired in once, so the\n * same node is reused by every renderer that listens to it);\n * - a `MediaStream`, such as a microphone from `getUserMedia`: listened\n * to, not played;\n * - any Web Audio node (an oscillator, a gain, a player's output), in its\n * own context.\n *\n * The `AudioContext` and `AnalyserNode` are made on first use (see\n * `sharedAudioContext`). Browsers keep a new context suspended until the\n * page has a click or key press (autoplay policy): call this from one, or\n * the context resumes on the first. While a source is set, a paused\n * preview still follows the sound. A media element from another site needs\n * `crossOrigin = \"anonymous\"` and CORS, or the browser feeds silence.\n */\n setAudioSource(source: AudioSource | null) {\n this.audio?.dispose();\n this.audio = source ? new LiveAudio(source) : null;\n this.audioFrame = undefined;\n if (!this.playing) this.render();\n }\n\n /**\n * Pins `iScroll` to a scroll position of your own (0–1, for x and y; the\n * velocity and \"scrolling\" follow its changes), for scrubbers and hosts\n * that scroll in their own way; `null` goes back to following the page.\n */\n setScrollProgress(progress: number | null) {\n this.scrollOverride = progress === null ? null : Math.min(1, Math.max(0, progress));\n this.scrollMoved = true;\n if (!this.playing) {\n this.sampleScroll(performance.now() / 1000);\n this.render();\n }\n }\n\n /**\n * Settles once the images the current controls show have loaded, and\n * every live video has a frame. Rejects with an {@link ImageLoadError}\n * when one cannot be (it shows black).\n */\n async ready(): Promise<void> {\n await Promise.all([\n this.textures?.ready(imageUrls(this.controls, this.values)),\n this.textures?.liveReady(),\n ]);\n }\n\n play() {\n this.playing = true;\n this.emit();\n }\n\n pause() {\n this.playing = false;\n this.emit();\n }\n\n /** Back to `iTime` 0 and frame 0. */\n restart() {\n this.time = 0;\n this.pointer.clear();\n this.frame = 0;\n this.render();\n this.emit();\n }\n\n /** Jumps to `time` seconds (not below 0) and draws it. */\n seek(time: number) {\n // Clicks are stamped with iTime: going back would replay them.\n if (time < this.time) this.pointer.clear();\n this.time = Math.max(0, time);\n this.render();\n this.emit();\n }\n\n setPixelRatio(ratio: number) {\n this.pixelRatio = ratio;\n this.resize();\n }\n\n /**\n * Captures the current frame (optionally at another time) as a data URL\n * (JPEG unless `type` says PNG), at most `maxEdge` pixels on the long side\n * (default 1024). Playback time is left as it was. Null when nothing has\n * compiled yet.\n */\n capture(\n options: { time?: number; maxEdge?: number; type?: \"image/jpeg\" | \"image/png\" } = {},\n ): string | null {\n if (!this.gl || !this.shader) return null;\n const saved = this.time;\n if (options.time !== undefined) this.time = Math.max(0, options.time);\n this.render();\n const { width, height } = this.canvas;\n const scale = Math.min(1, (options.maxEdge ?? 1024) / Math.max(width, height));\n const out = document.createElement(\"canvas\");\n out.width = Math.max(1, Math.round(width * scale));\n out.height = Math.max(1, Math.round(height * scale));\n out.getContext(\"2d\")!.drawImage(this.canvas, 0, 0, out.width, out.height);\n this.time = saved;\n if (options.time !== undefined) this.render();\n return out.toDataURL(options.type ?? \"image/jpeg\", 0.85);\n }\n\n /** Stops the loop, releases the context and removes the canvas. The renderer cannot be used again. */\n dispose() {\n this.disposed = true;\n cancelAnimationFrame(this.raf);\n this.resizeObserver.disconnect();\n this.intersectionObserver?.disconnect();\n this.canvas.removeEventListener(\"webglcontextlost\", this.onLost);\n this.canvas.removeEventListener(\"webglcontextrestored\", this.onRestored);\n this.canvas.removeEventListener(\"pointerdown\", this.onPointerDown);\n this.canvas.removeEventListener(\"pointermove\", this.onPointerMove);\n this.canvas.removeEventListener(\"pointerenter\", this.onPointerEnter);\n this.canvas.removeEventListener(\"pointerleave\", this.onPointerLeave);\n window.removeEventListener(\"pointerup\", this.onPointerUp);\n window.removeEventListener(\"scroll\", this.onScroll, { capture: true });\n this.audio?.dispose();\n this.audio = null;\n if (this.gl && this.shader) this.gl.deleteProgram(this.shader.program);\n this.textures?.dispose();\n this.textures = null;\n // Free the context now rather than waiting for GC (browsers cap live contexts).\n this.gl?.getExtension(\"WEBGL_lose_context\")?.loseContext();\n this.gl = null;\n if (this.ownsCanvas) this.canvas.remove();\n }\n\n private resize() {\n // The layout size, not the bounding box: a rotated or scaled canvas keeps its own proportions.\n const w = Math.max(1, Math.round(this.canvas.clientWidth * this.pixelRatio));\n const h = Math.max(1, Math.round(this.canvas.clientHeight * this.pixelRatio));\n if (this.canvas.width !== w || this.canvas.height !== h) {\n this.canvas.width = w;\n this.canvas.height = h;\n this.render();\n }\n }\n\n private render(delta = 0) {\n if (!this.gl || !this.shader || this.gl.isContextLost()) return;\n drawFrame(\n this.gl,\n this.shader,\n this.controls,\n this.values,\n {\n width: this.canvas.width,\n height: this.canvas.height,\n time: this.time,\n delta,\n frame: this.frame,\n mouse: this.mouse,\n pointer: this.pointer.pointer(this.time),\n clicks: this.pointer.clicksAt(this.time),\n trail: this.pointer.trailAt(this.time),\n ...(this.audioFrame ?? {}),\n scroll: (this.drawnScroll = this.scrollTracker.scroll(this.scrollNow)),\n },\n this.textures ?? undefined,\n );\n }\n\n private textureCache(gl: WebGL2RenderingContext): TextureCache {\n const cache = new TextureCache(gl, (_url, error) => {\n if (error) this.onImageError?.(error);\n else if (!this.playing) this.render();\n });\n for (const [name, source] of this.live) cache.setLive(name, source);\n return cache;\n }\n\n /** Starts loading the images now shown, and frees the ones no longer shown. */\n private syncTextures() {\n if (!this.textures) return;\n const urls = imageUrls(this.controls, this.values);\n this.textures.retain(urls);\n for (const url of urls) this.textures.request(url);\n }\n\n private tick = (now: number) => {\n if (this.disposed) return;\n this.raf = requestAnimationFrame(this.tick);\n const delta = this.lastNow ? Math.min((now - this.lastNow) / 1000, 0.1) : 0;\n this.lastNow = now;\n const seconds = now / 1000;\n // The sound and the scroll run on the page's clock, whether or not the shader's is paused.\n let follow = false;\n const b = this.shader?.builtins;\n if (this.audio && b && (b.iAudio || b.iSpectrum || b.iBeat)) {\n this.audioFrame = this.audio.read(seconds);\n follow = true;\n }\n this.scrollNow = seconds;\n if (b?.iScroll) {\n if (this.scrollMoved || this.scrollOverride === null || !this.scrollTracker.settled(seconds)) this.sampleScroll(seconds);\n const scroll = this.scrollTracker.scroll(seconds);\n if (scroll.some((v, i) => v !== this.drawnScroll[i])) follow = true;\n }\n if (this.playing) {\n this.time += delta * this.speed;\n this.frame += 1;\n this.render(delta * this.speed);\n this.fpsFrames += 1;\n } else if (this.textures?.liveChanged() || follow) {\n // A live video moved on (played, seeked or loaded), the sound changed or the page scrolled while the clock is paused.\n this.render();\n }\n if (now - this.fpsSince >= 500) {\n this.fps = this.playing\n ? Math.round((this.fpsFrames * 1000) / (now - this.fpsSince || 1))\n : 0;\n this.fpsFrames = 0;\n this.fpsSince = now;\n this.emit();\n }\n };\n\n private emit() {\n this.onStats?.({\n time: this.time,\n fps: this.fps,\n playing: this.playing,\n width: this.canvas.width,\n height: this.canvas.height,\n });\n }\n\n /** Where the canvas is in the viewport (or the pinned progress), into the scroll tracker. */\n private sampleScroll(seconds: number) {\n this.scrollMoved = false;\n this.scrollNow = seconds;\n if (this.scrollOverride !== null) {\n const p = this.scrollOverride;\n // As if the canvas were a viewport tall: progress 0 to 1 is two viewport heights of scrolling.\n this.scrollTracker.sample(p, p, p * 2, seconds);\n return;\n }\n const rect = this.canvas.getBoundingClientRect();\n const viewport = window.innerHeight || document.documentElement.clientHeight || 1;\n const page = document.scrollingElement ?? document.documentElement;\n const range = page.scrollHeight - page.clientHeight;\n this.scrollTracker.sample(viewportProgress(rect.top, rect.height, viewport), range > 0 ? page.scrollTop / range : 0, -rect.top / viewport, seconds);\n }\n\n private onScroll = () => {\n this.scrollMoved = true;\n };\n\n /** Pointer position in shader pixels (origin bottom-left, like gl_FragCoord). */\n private toPixels(e: PointerEvent): [number, number] {\n const rect = this.canvas.getBoundingClientRect();\n return [\n (e.clientX - rect.left) * (this.canvas.width / rect.width),\n (rect.bottom - e.clientY) * (this.canvas.height / rect.height),\n ];\n }\n\n private onPointerDown = (e: PointerEvent) => {\n const [x, y] = this.toPixels(e);\n this.mouse = [x, y, x, y];\n this.pointer.down(x, y, this.time);\n if (!this.playing) this.render();\n };\n\n private onPointerMove = (e: PointerEvent) => {\n const [x, y] = this.toPixels(e);\n this.pointer.move(x, y, this.time);\n if (this.mouse[2] <= 0 || e.buttons === 0) {\n if (!this.playing) this.render();\n return;\n }\n this.mouse = [x, y, this.mouse[2], this.mouse[3]];\n if (!this.playing) this.render();\n };\n\n private onPointerEnter = (e: PointerEvent) => {\n this.pointer.move(...this.toPixels(e), this.time);\n this.pointer.hover(true, this.time);\n };\n\n private onPointerLeave = () => {\n this.pointer.hover(false, this.time);\n };\n\n private onPointerUp = () => {\n this.pointer.up(this.time);\n if (this.mouse[2] > 0)\n this.mouse = [this.mouse[0], this.mouse[1], -this.mouse[2], -Math.abs(this.mouse[3])];\n };\n\n private onLost = (e: Event) => {\n e.preventDefault();\n this.shader = null;\n this.textures?.dispose();\n this.textures = null;\n this.onContextLost?.(true);\n };\n\n private onRestored = () => {\n resetGlCaches();\n this.gl = createContext(this.canvas, true, this.alpha);\n this.textures = this.gl && this.textureCache(this.gl);\n this.setSource(this.source);\n this.onContextLost?.(false);\n };\n}\n","import { imageUrls, parseControls, type ControlDef, type ControlValue } from \"./controls.js\";\nimport {\n compileShader,\n createContext,\n drawFrame,\n ShaderCompileError,\n type CompiledShader,\n} from \"./program.js\";\nimport { SPECTRUM_BINS, type AudioFrame } from \"./audio.js\";\nimport { clicksUniform, trailUniform, type ShaderClick } from \"./pointer.js\";\nimport { TextureCache, type LiveSource } from \"./textures.js\";\n\nexport interface ShaderFramesOptions {\n source: string;\n values?: Record<string, ControlValue>;\n width: number;\n height: number;\n /** Draw on this canvas instead of a new, detached one. It is resized to `width` × `height`. */\n canvas?: HTMLCanvasElement;\n /** Keep the shader's alpha (`fragColor.a`) in the frames, for transparent recordings (default false: opaque). */\n alpha?: boolean;\n}\n\nexport interface RenderOptions {\n /** `iFrame` (default: the number of frames rendered so far). */\n frame?: number;\n /** `iTimeDelta` in seconds (default: the time since the previous `render`, else 0). */\n delta?: number;\n /** `iMouse` (default: all zero, as on a page nobody has touched). */\n mouse?: [number, number, number, number];\n /** `iPointer`: x, y, how hovered (0–1), the press (seconds held, or minus seconds since release; default all zero). */\n pointer?: [number, number, number, number];\n /** Clicks so far, for `iClicks` (the latest `MAX_CLICKS` at or before the frame's time count). */\n clicks?: readonly ShaderClick[];\n /** Where the pointer has been, oldest first, for `iTrail` (the latest `MAX_TRAIL` at or before the frame's time count). */\n trail?: readonly ShaderClick[];\n /**\n * The sound, for `iAudio`, `iSpectrum` and `iBeat`: a frame from\n * `analyseAudioBuffer(buffer).at(t)` (frame-exact from an audio file), or\n * values of your own; what is left out is silence (`beat` -1). Without\n * it there is no audio at all (every audio uniform 0), and audio-reactive\n * effects play their idle demos.\n */\n audio?: Partial<AudioFrame>;\n /** `iScroll`: progress through the viewport, page scroll (0–1), velocity (viewport heights a second), scrolling (0–1). Default all zero: nothing scrolls it. */\n scroll?: readonly [number, number, number, number];\n}\n\nconst SILENT = new Float32Array(SPECTRUM_BINS);\n\n/**\n * Renders a shader at exact times you choose, off screen. There is no\n * animation loop and no clock: frame `n` of a recording at `fps` is\n * `render(start + n / fps)`, so the output is the same on any machine. It\n * suits recorders, video pipelines and anything else stepped by its own\n * clock. The constructor throws a {@link ShaderCompileError} when the source\n * does not compile.\n *\n * Image controls load in the background: `await frames.ready()` before the\n * first `render`, or the frames show black where the images go.\n *\n * A video in an image control (see `setLiveSource`) is drawn at whatever\n * frame it is on. To step it with the shader, seek it first: `await\n * seekVideo(video, t); frames.render(t)`.\n */\nexport class ShaderFrames {\n readonly canvas: HTMLCanvasElement;\n readonly width: number;\n readonly height: number;\n readonly controls: ControlDef[];\n private gl: WebGL2RenderingContext | null;\n private shader: CompiledShader;\n private textures: TextureCache;\n private values: Record<string, ControlValue> | undefined;\n private rendered = 0;\n private lastTime: number | null = null;\n private pixels: Uint8Array;\n\n constructor(options: ShaderFramesOptions) {\n const { source, values, width, height } = options;\n this.width = Math.max(1, Math.round(width));\n this.height = Math.max(1, Math.round(height));\n this.canvas = options.canvas ?? document.createElement(\"canvas\");\n this.canvas.width = this.width;\n this.canvas.height = this.height;\n this.values = values;\n const gl = createContext(this.canvas, true, Boolean(options.alpha));\n if (!gl) throw new Error(\"WebGL2 is not available in this browser.\");\n this.gl = gl;\n this.controls = parseControls(source);\n const compiled = compileShader(gl, source, this.controls);\n if (!compiled.ok) {\n this.dispose();\n throw new ShaderCompileError(compiled.errors);\n }\n this.shader = compiled.shader;\n this.textures = new TextureCache(gl);\n this.syncTextures();\n this.pixels = new Uint8Array(this.width * this.height * 4);\n }\n\n /** Changes control values for the frames that follow. */\n setValues(values: Record<string, ControlValue> | undefined) {\n this.values = values;\n this.syncTextures();\n }\n\n /**\n * Draws a `<video>` (or a canvas) in the image control `name` instead of\n * its URL, at the frame it is on when `render` runs; `null` goes back to\n * the URL. See `ShaderRenderer.setLiveSource`.\n */\n setLiveSource(name: string, source: LiveSource | null) {\n this.textures.setLive(name, source);\n }\n\n /**\n * Settles once every image the controls show has loaded, and every live\n * video has a frame. Rejects with an {@link ImageLoadError} when one\n * cannot be.\n */\n async ready(): Promise<void> {\n await Promise.all([this.textures.ready(imageUrls(this.controls, this.values)), this.textures.liveReady()]);\n }\n\n /** Draws the frame at `time` seconds onto `canvas`. */\n render(time: number, options: RenderOptions = {}) {\n const gl = this.context();\n const delta = options.delta ?? (this.lastTime === null ? 0 : time - this.lastTime);\n drawFrame(\n gl,\n this.shader,\n this.controls,\n this.values,\n {\n width: this.width,\n height: this.height,\n time,\n delta,\n frame: options.frame ?? this.rendered,\n mouse: options.mouse ?? [0, 0, 0, 0],\n pointer: options.pointer,\n clicks: options.clicks && clicksUniform(options.clicks, time),\n trail: options.trail && trailUniform(options.trail, time),\n ...(options.audio\n ? { audio: options.audio.audio ?? [0, 0, 0, 0], spectrum: options.audio.spectrum ?? SILENT, beat: options.audio.beat ?? -1 }\n : {}),\n ...(options.scroll ? { scroll: options.scroll } : {}),\n },\n this.textures,\n );\n this.rendered += 1;\n this.lastTime = time;\n }\n\n /**\n * The last rendered frame as RGBA bytes, rows top down (as in `ImageData`;\n * WebGL itself reads bottom up). Pass `out` to reuse a buffer of\n * `width * height * 4` bytes.\n */\n readPixels(out?: Uint8Array | Uint8ClampedArray): Uint8Array | Uint8ClampedArray {\n const gl = this.context();\n const { width, height } = this;\n const target = out ?? new Uint8Array(width * height * 4);\n if (target.length !== width * height * 4) {\n throw new RangeError(`readPixels needs a buffer of ${width * height * 4} bytes.`);\n }\n gl.readPixels(0, 0, width, height, gl.RGBA, gl.UNSIGNED_BYTE, this.pixels);\n const row = width * 4;\n for (let y = 0; y < height; y++) {\n target.set(this.pixels.subarray((height - 1 - y) * row, (height - y) * row), y * row);\n }\n return target;\n }\n\n /** Releases the GL context now rather than at garbage collection (browsers cap live contexts). */\n dispose() {\n if (!this.gl) return;\n if (this.shader) this.gl.deleteProgram(this.shader.program);\n this.textures?.dispose();\n this.gl.getExtension(\"WEBGL_lose_context\")?.loseContext();\n this.gl = null;\n }\n\n private syncTextures() {\n const urls = imageUrls(this.controls, this.values);\n this.textures.retain(urls);\n for (const url of urls) this.textures.request(url);\n }\n\n private context(): WebGL2RenderingContext {\n if (!this.gl) throw new Error(\"These frames have been disposed.\");\n if (this.gl.isContextLost()) throw new Error(\"The graphics context was lost.\");\n return this.gl;\n }\n}\n"],"mappings":"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","names":[]}
@@ -3,14 +3,14 @@ import {
3
3
  serialize,
4
4
  setAttr,
5
5
  svgFrameTree
6
- } from "./chunk-STKEKXCK.js";
6
+ } from "./chunk-AUH3PEFW.js";
7
7
  import {
8
8
  STEP_MS,
9
9
  SVG_PICTURE,
10
10
  stepStart,
11
11
  svgDuration,
12
12
  svgFrame
13
- } from "./chunk-PFGGPIMT.js";
13
+ } from "./chunk-DA4UUSDO.js";
14
14
  import {
15
15
  pathPointAt
16
16
  } from "./chunk-ORAFCHX3.js";
@@ -404,4 +404,4 @@ function scopeIds(svg, suffix) {
404
404
  export {
405
405
  svgAnimatedMarkup
406
406
  };
407
- //# sourceMappingURL=chunk-YBGRXVAE.js.map
407
+ //# sourceMappingURL=chunk-X3C2VFTM.js.map
@@ -9,7 +9,7 @@ import {
9
9
  } from "./chunk-5USSDEQL.js";
10
10
  import {
11
11
  ShaderFrames
12
- } from "./chunk-H6VDFBIO.js";
12
+ } from "./chunk-SMR2MJXB.js";
13
13
  import {
14
14
  HOVER_SECONDS,
15
15
  MAX_CLICKS,
@@ -23,7 +23,7 @@ import {
23
23
  drawFrame,
24
24
  resetGlCaches,
25
25
  wrapSource
26
- } from "./chunk-RDU63CDO.js";
26
+ } from "./chunk-HDYBY3PQ.js";
27
27
  import {
28
28
  controlValue,
29
29
  imageUrls,
@@ -300,6 +300,63 @@ async function recordGif(options) {
300
300
  // src/shader/standalone.ts
301
301
  var escapeHtml = (s) => s.replace(/[&<>"]/g, (c) => ({ "&": "&amp;", "<": "&lt;", ">": "&gt;", '"': "&quot;" })[c]);
302
302
  var scriptJson = (value) => JSON.stringify(value).replace(/</g, "\\u003c");
303
+ var AUDIO_SCRIPT = `
304
+ // iAudio, iSpectrum and iBeat, as Flair's AudioAnalysis works them out: dB to 0\u20131, 64 log bins, band
305
+ // averages, loudness from RMS, attack/release smoothing, and beats on the bass over its recent average.
306
+ let hear = null;
307
+ function listen(ctx, node) {
308
+ const an = ctx.createAnalyser();
309
+ an.fftSize = 2048; an.smoothingTimeConstant = 0; an.minDecibels = -100; an.maxDecibels = -30;
310
+ node.connect(an);
311
+ const n = an.frequencyBinCount, hz = ctx.sampleRate / an.fftSize, top = Math.min(16000, ctx.sampleRate / 2);
312
+ const freq = new Float32Array(n), wave = new Float32Array(an.fftSize), raw = new Float32Array(64), spec = new Float32Array(64), v = new Float32Array(n);
313
+ const bins = [...Array(64)].map((_, i) => [30 * (top / 30) ** (i / 64) / hz, 30 * (top / 30) ** ((i + 1) / 64) / hz]);
314
+ const bands = [[20, 250], [250, 2000], [2000, 16000]].map(([a, b]) => [Math.max(0, Math.ceil(a / hz)), Math.min(n - 1, Math.floor(Math.min(b, ctx.sampleRate / 2) / hz))]);
315
+ let vals = [0, 0, 0, 0], last = null, avg = 0, armed = true, beatAt = null;
316
+ hear = (t) => {
317
+ an.getFloatFrequencyData(freq); an.getFloatTimeDomainData(wave);
318
+ for (let k = 0; k < n; k++) v[k] = isFinite(freq[k]) ? Math.min(1, Math.max(0, (freq[k] + 100) / 70)) : 0;
319
+ bins.forEach(([k0, k1], b) => {
320
+ const from = Math.ceil(k0), to = Math.min(n - 1, Math.floor(k1));
321
+ let x = 0;
322
+ if (to >= from) for (let k = from; k <= to; k++) x = Math.max(x, v[k]);
323
+ else { const k = Math.min(n - 1.001, (k0 + k1) / 2), i = Math.floor(k); x = v[i] + (v[Math.min(n - 1, i + 1)] - v[i]) * (k - i); }
324
+ raw[b] = x;
325
+ });
326
+ const band = ([a, b]) => { let s = 0; for (let k = a; k <= b; k++) s += v[k]; return b >= a ? s / (b - a + 1) : 0; };
327
+ let rms = 0; for (const s of wave) rms += s * s; rms = Math.sqrt(rms / wave.length);
328
+ const level = rms > 0 ? Math.min(1, Math.max(0, 1 + 20 * Math.log10(rms) / 60)) : 0;
329
+ const bass = band(bands[0]), now = [level, bass, band(bands[1]), band(bands[2])];
330
+ const dt = last === null ? Infinity : Math.max(0, t - last), up = 1 - Math.exp(-dt / 0.03), down = 1 - Math.exp(-dt / 0.25);
331
+ const ease = (a, b) => a + (b - a) * (b > a ? up : down);
332
+ for (let b = 0; b < 64; b++) spec[b] = ease(spec[b], raw[b]);
333
+ vals = vals.map((x, i) => ease(x, now[i]));
334
+ const onset = bass - avg, th = 0.12 + 0.15 * avg;
335
+ if (last !== null && armed && onset > th && bass > 0.2 && (beatAt === null || t - beatAt >= 0.22)) { beatAt = t; armed = false; }
336
+ else if (onset < th * 0.5) armed = true;
337
+ avg = last === null ? bass : avg + (bass - avg) * (1 - Math.exp(-dt / 0.6));
338
+ last = t;
339
+ return { a: vals, s: spec, b: beatAt === null ? -1 : t - beatAt };
340
+ };
341
+ }
342
+ const audioCtx = () => (window.flairAudio ??= new (window.AudioContext || window.webkitAudioContext)());
343
+ const player = document.getElementById("a");
344
+ if (player) player.addEventListener("play", () => {
345
+ const ctx = audioCtx();
346
+ ctx.resume();
347
+ if (!hear) { const src = ctx.createMediaElementSource(player); src.connect(ctx.destination); listen(ctx, src); }
348
+ });
349
+ const mic = document.getElementById("m");
350
+ if (mic) mic.addEventListener("click", async () => {
351
+ const ctx = audioCtx();
352
+ await ctx.resume();
353
+ try {
354
+ const stream = await navigator.mediaDevices.getUserMedia({ audio: true });
355
+ listen(ctx, ctx.createMediaStreamSource(stream));
356
+ mic.textContent = "Listening";
357
+ mic.disabled = true;
358
+ } catch (e) { mic.textContent = "No microphone"; }
359
+ });`;
303
360
  function standaloneHtml(title, source, values, options = {}) {
304
361
  const controls = parseControls(source).map((c) => ({
305
362
  name: c.name,
@@ -308,6 +365,10 @@ function standaloneHtml(title, source, values, options = {}) {
308
365
  ...c.texture ? { texture: c.texture } : {}
309
366
  }));
310
367
  const credit = (options.credit ?? "Exported with Flair.").replace(/\n/g, " ");
368
+ const corner = "position:fixed;left:12px;bottom:12px;z-index:1";
369
+ const audio = options.audio === void 0 ? "" : options.audio === "mic" ? `<button id="m" style="${corner};font:14px system-ui,sans-serif;padding:6px 12px;border-radius:6px;border:0;background:#fff;color:#000;cursor:pointer">Use microphone</button>
370
+ ` : `<audio id="a" src="${escapeHtml(options.audio)}" controls loop crossorigin="anonymous" style="${corner}"></audio>
371
+ `;
311
372
  return `<!doctype html>
312
373
  <html lang="en">
313
374
  <head>
@@ -318,7 +379,7 @@ function standaloneHtml(title, source, values, options = {}) {
318
379
  </head>
319
380
  <body>
320
381
  <canvas id="c"></canvas>
321
- <script>
382
+ ${audio}<script>
322
383
  // ${credit.replace(/</g, "\\u003c")} Shadertoy-style fragment shader on WebGL2.
323
384
  const VERTEX = ${scriptJson(VERTEX_SOURCE)};
324
385
  const FRAGMENT = ${scriptJson(wrapSource(source))};
@@ -405,7 +466,7 @@ canvas.addEventListener("pointerleave", () => hover(false));
405
466
  const toTrail = () => { const t = seconds(); if (trail.length && t - trail[0][2] < ${TRAIL_SECONDS}) trail[0] = [at[0], at[1], trail[0][2], 1]; else trail = [[at[0], at[1], t, 1], ...trail].slice(0, ${MAX_TRAIL}); };
406
467
  canvas.addEventListener("pointermove", (e) => { at = px(e); toTrail(); });
407
468
  canvas.addEventListener("pointerdown", (e) => { at = px(e); pressedAt = seconds(); clicks.unshift([at[0], at[1], pressedAt, 1]); clicks = clicks.slice(0, ${MAX_CLICKS}); });
408
- addEventListener("pointerup", () => { if (pressedAt >= 0) releasedAt = seconds(); pressedAt = -1; });
469
+ addEventListener("pointerup", () => { if (pressedAt >= 0) releasedAt = seconds(); pressedAt = -1; });${options.audio === void 0 ? "" : AUDIO_SCRIPT}
409
470
  function draw(now) {
410
471
  const dpr = Math.min(devicePixelRatio || 1, 2);
411
472
  const w = Math.round(canvas.clientWidth * dpr), h = Math.round(canvas.clientHeight * dpr);
@@ -423,7 +484,9 @@ function draw(now) {
423
484
  gl.uniform4fv(u("iClicks"), c);
424
485
  const p = new Float32Array(${MAX_TRAIL * 4});
425
486
  trail.forEach((k, i) => p.set(k, i * 4));
426
- gl.uniform4fv(u("iTrail"), p);
487
+ gl.uniform4fv(u("iTrail"), p);${options.audio === void 0 ? "" : `
488
+ // With no sound yet every audio uniform stays 0, and audio effects play their idle demos.
489
+ if (hear) { const s = hear(now / 1000); gl.uniform4f(u("iAudio"), s.a[0], s.a[1], s.a[2], s.a[3]); gl.uniform4fv(u("iSpectrum"), s.s); gl.uniform1f(u("iBeat"), s.b); }`}
427
490
  gl.drawArrays(gl.TRIANGLES, 0, 3);
428
491
  last = now;
429
492
  requestAnimationFrame(draw);
@@ -1186,4 +1249,4 @@ export {
1186
1249
  validateScene,
1187
1250
  standaloneSceneHtml
1188
1251
  };
1189
- //# sourceMappingURL=chunk-CK6EV37I.js.map
1252
+ //# sourceMappingURL=chunk-ZULFHADW.js.map