@tensordoc/prism 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +175 -0
- package/dist/backends/milkdrop.d.ts +28 -0
- package/dist/backends/shadertoy.d.ts +22 -0
- package/dist/image-feed.d.ts +32 -0
- package/dist/image-overlay.d.ts +84 -0
- package/dist/index.d.ts +9 -0
- package/dist/player.d.ts +150 -0
- package/dist/prism.cjs +37 -0
- package/dist/prism.cjs.map +1 -0
- package/dist/prism.mjs +1271 -0
- package/dist/prism.mjs.map +1 -0
- package/dist/registry.d.ts +18 -0
- package/dist/runtime.d.ts +25 -0
- package/dist/synth.d.ts +47 -0
- package/dist/types.d.ts +28 -0
- package/package.json +58 -0
- package/src/backends/milkdrop.ts +190 -0
- package/src/backends/shadertoy.ts +290 -0
- package/src/image-feed.ts +185 -0
- package/src/image-overlay.ts +302 -0
- package/src/index.ts +27 -0
- package/src/peer-types.d.ts +18 -0
- package/src/player.ts +395 -0
- package/src/registry.generated.json +2022 -0
- package/src/registry.ts +70 -0
- package/src/runtime.ts +100 -0
- package/src/synth.ts +251 -0
- package/src/types.ts +98 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Scott Penberthy and Prism contributors
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in
|
|
13
|
+
all copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
21
|
+
THE SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
# @tensordoc/prism
|
|
2
|
+
|
|
3
|
+
Two-line embed for audio-reactive visualizations. Drop a `<div>`,
|
|
4
|
+
call `new PrismPlayer({ container })`, get a Milkdrop preset or
|
|
5
|
+
Shadertoy fragment shader playing in the browser — reacting to
|
|
6
|
+
whatever audio you connect.
|
|
7
|
+
|
|
8
|
+
```bash
|
|
9
|
+
npm install @tensordoc/prism
|
|
10
|
+
```
|
|
11
|
+
|
|
12
|
+
```html
|
|
13
|
+
<div id="viz" style="width:100vw;height:100vh"></div>
|
|
14
|
+
<script type="module">
|
|
15
|
+
import { PrismPlayer } from "@tensordoc/prism";
|
|
16
|
+
new PrismPlayer({ container: "viz" });
|
|
17
|
+
</script>
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
That's it. The visualization runs against a built-in synthetic signal
|
|
21
|
+
until you connect real audio.
|
|
22
|
+
|
|
23
|
+
## Live demo
|
|
24
|
+
|
|
25
|
+
[**prism.scott.ai**](https://prism.scott.ai) — the deployed site uses this
|
|
26
|
+
exact package. The "Two-line embed" preview at
|
|
27
|
+
[prism.scott.ai/examples/embed.html](https://prism.scott.ai/examples/embed.html)
|
|
28
|
+
is the smallest possible HTML page using it.
|
|
29
|
+
|
|
30
|
+
## What you get
|
|
31
|
+
|
|
32
|
+
**Two rendering backends** that swap automatically based on the
|
|
33
|
+
graph you load:
|
|
34
|
+
|
|
35
|
+
- **Milkdrop** via [butterchurn](https://github.com/jberg/butterchurn) —
|
|
36
|
+
~80 named presets ship in the bundle
|
|
37
|
+
- **Shadertoy** — any GLSL fragment shader following Shadertoy's
|
|
38
|
+
`mainImage()` convention; loaded from a URL
|
|
39
|
+
|
|
40
|
+
**Six audio sources** the player accepts:
|
|
41
|
+
|
|
42
|
+
```js
|
|
43
|
+
new PrismPlayer({
|
|
44
|
+
container: "viz",
|
|
45
|
+
audio: "mic", // getUserMedia
|
|
46
|
+
audio: "tab", // getDisplayMedia
|
|
47
|
+
audio: someAudioNode, // your Web Audio graph
|
|
48
|
+
audio: someMediaStream, // anything with audio tracks
|
|
49
|
+
// audio: undefined (default) → built-in synthetic signal
|
|
50
|
+
});
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
**Six image sources** for shader inputs (`iChannel1`):
|
|
54
|
+
|
|
55
|
+
```js
|
|
56
|
+
new PrismPlayer({
|
|
57
|
+
container: "viz",
|
|
58
|
+
image: "https://example.com/a.jpg", // single URL
|
|
59
|
+
image: ["a.jpg", "b.jpg", "c.jpg"], // built-in crossfading slideshow
|
|
60
|
+
image: "webcam", // getUserMedia({video: true})
|
|
61
|
+
image: "tab", // getDisplayMedia({video: true})
|
|
62
|
+
image: someVideoElement, // <video>
|
|
63
|
+
image: someCanvas, // your own renderer
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
// Tune slideshow timing
|
|
67
|
+
new PrismPlayer({ container, image: ["a.jpg","b.jpg"], holdSeconds: 6 });
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
**Methods** for runtime control:
|
|
71
|
+
|
|
72
|
+
```js
|
|
73
|
+
player.load(graphOrShortId); // swap visualization
|
|
74
|
+
player.connectAudio("mic"); // change audio source
|
|
75
|
+
player.disconnectAudio(); // revert to synthetic signal
|
|
76
|
+
player.connectImage("webcam"); // change image feed
|
|
77
|
+
player.disconnectImage(); // release webcam track, etc.
|
|
78
|
+
player.destroy(); // clean up everything
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
**Readonly handles** for power users (the underlying primitives are
|
|
82
|
+
exposed; you can call them directly):
|
|
83
|
+
|
|
84
|
+
```js
|
|
85
|
+
player.audioCtx // AudioContext
|
|
86
|
+
player.activeBackend // "milkdrop" | "shadertoy"
|
|
87
|
+
player.milkdrop // butterchurn handle
|
|
88
|
+
player.shadertoy // shader runtime
|
|
89
|
+
player.synth // synthetic signal driver
|
|
90
|
+
player.runtime // graph executor
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
## Share-by-URL with short IDs
|
|
94
|
+
|
|
95
|
+
Every curated catalog entry has a permanent **6-character base62
|
|
96
|
+
share token**. Same idea as YouTube video IDs:
|
|
97
|
+
|
|
98
|
+
```js
|
|
99
|
+
new PrismPlayer({ container: "viz", graph: "7Hq3pK" });
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
The lookup happens against the bundled registry — no network call. The
|
|
103
|
+
companion site exposes `prism.scott.ai/?g=<id>` as a one-click shareable URL.
|
|
104
|
+
|
|
105
|
+
```js
|
|
106
|
+
import { lookup, shortIdToGraph } from "@tensordoc/prism";
|
|
107
|
+
lookup("7Hq3pK"); // { name, source_type, source_url, ... }
|
|
108
|
+
shortIdToGraph("7Hq3pK"); // ready-to-use PrismGraph
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
## Constructor options (full)
|
|
112
|
+
|
|
113
|
+
```ts
|
|
114
|
+
new PrismPlayer({
|
|
115
|
+
container: HTMLElement | string, // required — DOM id or element
|
|
116
|
+
graph?: PrismGraph | string, // short_id or full graph
|
|
117
|
+
audio?: "mic" | "tab" | MediaStream | AudioNode,
|
|
118
|
+
audioCtx?: AudioContext, // bring your own
|
|
119
|
+
image?: ImageSource, // see "image sources" above
|
|
120
|
+
holdSeconds?: number, // slideshow timing, default 6
|
|
121
|
+
defaultImage?: string, // static fallback URL
|
|
122
|
+
initialPresetName?: string, // cold-open Milkdrop preset
|
|
123
|
+
onReady?: () => void, // first-frame callback
|
|
124
|
+
onError?: (err: Error) => void,
|
|
125
|
+
});
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
## Optional: draggable picture-in-picture overlay
|
|
129
|
+
|
|
130
|
+
If you want a Picture-in-Picture viewer for the slideshow, ship with
|
|
131
|
+
`ImageOverlay` — a separate export that gives you a draggable,
|
|
132
|
+
resizable, collapsible card. The player stays headless; you compose:
|
|
133
|
+
|
|
134
|
+
```js
|
|
135
|
+
import { ImageOverlay } from "@tensordoc/prism";
|
|
136
|
+
const overlay = new ImageOverlay({ mount: document.body });
|
|
137
|
+
// overlay.element is a DOM div you can position or style;
|
|
138
|
+
// overlay.rect is the current rectangle, updated on drag/resize
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
## Use from a CDN (no install)
|
|
142
|
+
|
|
143
|
+
```html
|
|
144
|
+
<script type="module">
|
|
145
|
+
import { PrismPlayer } from "https://esm.sh/@tensordoc/prism";
|
|
146
|
+
new PrismPlayer({ container: "viz" });
|
|
147
|
+
</script>
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
## Audio context note
|
|
151
|
+
|
|
152
|
+
Browsers require a user gesture before audio starts. The player
|
|
153
|
+
creates an AudioContext but leaves it suspended; resume it on the
|
|
154
|
+
first interaction:
|
|
155
|
+
|
|
156
|
+
```js
|
|
157
|
+
const resume = () => player.audioCtx.resume();
|
|
158
|
+
window.addEventListener("pointerdown", resume, { once: true });
|
|
159
|
+
window.addEventListener("keydown", resume, { once: true });
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
## Bundle size
|
|
163
|
+
|
|
164
|
+
ESM ~25 KB gzipped (excluding butterchurn). Butterchurn itself ships
|
|
165
|
+
in the bundle as a regular dependency (~500 KB). If you don't need
|
|
166
|
+
Milkdrop and only want shader visualizations, you can tree-shake
|
|
167
|
+
butterchurn out by importing only what you use:
|
|
168
|
+
|
|
169
|
+
```js
|
|
170
|
+
import { createShadertoyBackground } from "@tensordoc/prism";
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
## License
|
|
174
|
+
|
|
175
|
+
[MIT](./LICENSE)
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
export interface MilkdropBg {
|
|
2
|
+
/** Pretty name of the currently-loaded preset (live; updates on rotation). */
|
|
3
|
+
readonly presetName: string;
|
|
4
|
+
/** Raw preset key (matches catalog `preset_id`). Stable for share / API use. */
|
|
5
|
+
readonly currentPresetId: string;
|
|
6
|
+
/** Swap the audio source — call when real tab-audio comes in. */
|
|
7
|
+
connectAudio: (node: AudioNode) => void;
|
|
8
|
+
/** Load a new random preset (with a blend transition). Returns the
|
|
9
|
+
* pretty name of the newly-loaded preset. */
|
|
10
|
+
loadRandom: (blendSeconds?: number) => string;
|
|
11
|
+
/** Load a specific preset by its raw key (matches catalog `preset_id`).
|
|
12
|
+
* Returns the pretty name on success; `null` if no preset with that key
|
|
13
|
+
* exists in the bundled library. */
|
|
14
|
+
loadByName: (name: string, blendSeconds?: number) => string | null;
|
|
15
|
+
/** Load a .milk preset from a URL: fetch text → convertPreset → loadPreset.
|
|
16
|
+
* Used for the 526 favorites and future contributor uploads which live
|
|
17
|
+
* at public/presets/milkdrop/<slug>.milk. Returns the pretty name on
|
|
18
|
+
* success; throws on fetch / parse error so callers can surface it. */
|
|
19
|
+
loadFromUrl: (url: string, blendSeconds?: number) => Promise<string>;
|
|
20
|
+
destroy: () => void;
|
|
21
|
+
}
|
|
22
|
+
export interface MilkdropBgOptions {
|
|
23
|
+
/** Preset key to load on cold open. Falls back to a random pick if the
|
|
24
|
+
* name doesn't exist in the bundle. Lets the landing pin a curated
|
|
25
|
+
* "atelier" default instead of getting random_$$$ Royal Mashup. */
|
|
26
|
+
initialPresetName?: string;
|
|
27
|
+
}
|
|
28
|
+
export declare function createMilkdropBackground(audioCtx: AudioContext, canvas: HTMLCanvasElement, silentSource: AudioNode, onReady?: () => void, options?: MilkdropBgOptions): MilkdropBg;
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
export interface ShadertoyBg {
|
|
2
|
+
/** Pretty name from the source URL, useful for SKILL readout. */
|
|
3
|
+
readonly presetName: string;
|
|
4
|
+
/** URL of the currently-loaded shader. */
|
|
5
|
+
readonly currentUrl: string | null;
|
|
6
|
+
/** Connect a Web Audio source — Shadertoy's iChannel0 sees its FFT. */
|
|
7
|
+
connectAudio: (node: AudioNode) => void;
|
|
8
|
+
/** Fetch + compile + render a new shader. Resolves once first frame
|
|
9
|
+
* paints. Throws on compile error. */
|
|
10
|
+
loadFromUrl: (url: string) => Promise<string>;
|
|
11
|
+
/** Bind an image URL as iChannel1. Resolves once decoded + uploaded.
|
|
12
|
+
* Pass null to clear (resets to the 1x1 placeholder). */
|
|
13
|
+
bindImage: (url: string | null) => Promise<void>;
|
|
14
|
+
/** Pipe a live source (e.g. a slideshow's canvas) into iChannel1 — its
|
|
15
|
+
* contents are uploaded to the GPU every frame, so when the slideshow
|
|
16
|
+
* advances to the next image, the shader sees it immediately. Pass
|
|
17
|
+
* null to disable + revert to whatever was last bound via bindImage. */
|
|
18
|
+
setLiveSource: (source: HTMLCanvasElement | HTMLVideoElement | null) => void;
|
|
19
|
+
/** Pause the render loop + free GL resources. */
|
|
20
|
+
destroy: () => void;
|
|
21
|
+
}
|
|
22
|
+
export declare function createShadertoyBackground(audioCtx: AudioContext, canvas: HTMLCanvasElement, silentSource: AudioNode): ShadertoyBg;
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
export interface SlideshowOptions {
|
|
2
|
+
/** How long (seconds) each image is held on-screen before the
|
|
3
|
+
* crossfade to the next begins. Defaults to 6. */
|
|
4
|
+
holdSeconds?: number;
|
|
5
|
+
}
|
|
6
|
+
export declare class HeadlessSlideshow {
|
|
7
|
+
/** The output canvas — bind this to the shader via setLiveSource. */
|
|
8
|
+
readonly canvas: HTMLCanvasElement;
|
|
9
|
+
private readonly ctx;
|
|
10
|
+
private readonly urls;
|
|
11
|
+
private readonly holdMs;
|
|
12
|
+
private readonly images;
|
|
13
|
+
private currentIndex;
|
|
14
|
+
private nextIndex;
|
|
15
|
+
/** Wall-clock time the current crossfade started, or null if we're
|
|
16
|
+
* in the steady-hold portion of the cycle. */
|
|
17
|
+
private crossfadeStartMs;
|
|
18
|
+
private holdTimer;
|
|
19
|
+
private rafHandle;
|
|
20
|
+
private destroyed;
|
|
21
|
+
constructor(urls: string[], opts?: SlideshowOptions);
|
|
22
|
+
destroy(): void;
|
|
23
|
+
private preload;
|
|
24
|
+
private start;
|
|
25
|
+
private scheduleNext;
|
|
26
|
+
private advance;
|
|
27
|
+
private tickCrossfade;
|
|
28
|
+
/** Draw the current image fully opaque. */
|
|
29
|
+
private drawFrame;
|
|
30
|
+
/** Draw the current image, then the next one at alpha=t on top. */
|
|
31
|
+
private drawCrossfade;
|
|
32
|
+
}
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
export interface Rect {
|
|
2
|
+
x: number;
|
|
3
|
+
y: number;
|
|
4
|
+
w: number;
|
|
5
|
+
h: number;
|
|
6
|
+
}
|
|
7
|
+
export interface OverlayState {
|
|
8
|
+
rect: Rect;
|
|
9
|
+
collapsed: boolean;
|
|
10
|
+
}
|
|
11
|
+
export interface ImageOverlayOptions {
|
|
12
|
+
/** Element to mount the card div in. Defaults to document.body. */
|
|
13
|
+
mount?: HTMLElement;
|
|
14
|
+
/** Initial card rect in CSS pixels. Defaults to a centered card sized
|
|
15
|
+
* to ~58% of viewport width. */
|
|
16
|
+
initialRect?: Rect;
|
|
17
|
+
/** BEM class prefix for the card and its parts. Defaults to
|
|
18
|
+
* "prism-overlay" — produces classes `.prism-overlay`,
|
|
19
|
+
* `.prism-overlay__handle`, `.prism-overlay__collapse`. */
|
|
20
|
+
className?: string;
|
|
21
|
+
/** Thumbnail (collapsed) width in CSS pixels. Defaults to 200. */
|
|
22
|
+
thumbWidth?: number;
|
|
23
|
+
/** Min visible margin in pixels — drag is clamped so this much of
|
|
24
|
+
* the card always stays in the viewport. Defaults to 60. */
|
|
25
|
+
minVisible?: number;
|
|
26
|
+
/** Min top edge (pixels from viewport top), e.g. to clear a status
|
|
27
|
+
* bar. Defaults to 36. */
|
|
28
|
+
topMargin?: number;
|
|
29
|
+
/** Minimum card dimensions while resizing. Defaults to 80×45 (16:9). */
|
|
30
|
+
minWidth?: number;
|
|
31
|
+
minHeight?: number;
|
|
32
|
+
/** Called whenever the rect or collapsed state changes (drag, resize,
|
|
33
|
+
* collapse, expand, window resize re-anchor). */
|
|
34
|
+
onChange?: (state: OverlayState) => void;
|
|
35
|
+
}
|
|
36
|
+
export declare class ImageOverlay {
|
|
37
|
+
readonly element: HTMLElement;
|
|
38
|
+
private readonly mount;
|
|
39
|
+
private readonly className;
|
|
40
|
+
private readonly thumbW;
|
|
41
|
+
private readonly thumbH;
|
|
42
|
+
private readonly thumbMargin;
|
|
43
|
+
private readonly statusBarH;
|
|
44
|
+
private readonly minVisible;
|
|
45
|
+
private readonly minWidth;
|
|
46
|
+
private readonly minHeight;
|
|
47
|
+
private readonly onChange?;
|
|
48
|
+
private _rect;
|
|
49
|
+
private _collapsed;
|
|
50
|
+
private uncollapsedRect;
|
|
51
|
+
private drag;
|
|
52
|
+
private destroyed;
|
|
53
|
+
constructor(opts?: ImageOverlayOptions);
|
|
54
|
+
/** Current card rect (live; do not mutate — call setRect instead). */
|
|
55
|
+
get rect(): Rect;
|
|
56
|
+
isCollapsed(): boolean;
|
|
57
|
+
/** Programmatically set the rect. Silent: does NOT call onChange.
|
|
58
|
+
* Use this to push externally-managed state into the overlay
|
|
59
|
+
* without echoing back into your own change handler. The internal
|
|
60
|
+
* drag/resize handlers emit onChange themselves — that's the only
|
|
61
|
+
* source of change notifications. */
|
|
62
|
+
setRect(next: Rect): void;
|
|
63
|
+
/** Programmatically collapse. Silent — see setRect. */
|
|
64
|
+
collapse(): void;
|
|
65
|
+
/** Programmatically expand. Silent — see setRect. */
|
|
66
|
+
expand(): void;
|
|
67
|
+
show(): void;
|
|
68
|
+
hide(): void;
|
|
69
|
+
/** Whether the card is currently visible (DOM attribute is the
|
|
70
|
+
* source of truth, so this stays accurate if anyone toggles it
|
|
71
|
+
* externally for testing). */
|
|
72
|
+
isVisible(): boolean;
|
|
73
|
+
destroy(): void;
|
|
74
|
+
private buildDom;
|
|
75
|
+
private applyRectToDom;
|
|
76
|
+
private emit;
|
|
77
|
+
private defaultRect;
|
|
78
|
+
private thumbRect;
|
|
79
|
+
private clampRect;
|
|
80
|
+
private readonly onWindowResize;
|
|
81
|
+
private readonly onPointerDown;
|
|
82
|
+
private readonly onPointerMove;
|
|
83
|
+
private readonly onPointerEnd;
|
|
84
|
+
}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export { PrismPlayer, type PrismPlayerOptions, type GraphInput, type ImageSource, } from './player';
|
|
2
|
+
export { HeadlessSlideshow, type SlideshowOptions } from './image-feed';
|
|
3
|
+
export { ImageOverlay, type ImageOverlayOptions, type OverlayState, type Rect, } from './image-overlay';
|
|
4
|
+
export { GraphRuntime, type ApplyResult, type RuntimeContext } from './runtime';
|
|
5
|
+
export { SyntheticSignal } from './synth';
|
|
6
|
+
export { createMilkdropBackground, type MilkdropBg, type MilkdropBgOptions, } from './backends/milkdrop';
|
|
7
|
+
export { createShadertoyBackground, type ShadertoyBg, } from './backends/shadertoy';
|
|
8
|
+
export { lookup, shortIds, shortIdToGraph, type RegistryEntry } from './registry';
|
|
9
|
+
export * from './types';
|
package/dist/player.d.ts
ADDED
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
import { MilkdropBg } from './backends/milkdrop';
|
|
2
|
+
import { ShadertoyBg } from './backends/shadertoy';
|
|
3
|
+
import { GraphRuntime, ApplyResult } from './runtime';
|
|
4
|
+
import { SyntheticSignal } from './synth';
|
|
5
|
+
import { PrismGraph } from './types';
|
|
6
|
+
/** Anything `load()` and the `graph` option accept. A string is treated
|
|
7
|
+
* as a catalog short_id (6-char base62) and resolved against the
|
|
8
|
+
* bundled registry. */
|
|
9
|
+
export type GraphInput = PrismGraph | string;
|
|
10
|
+
/** Anything the `image` option / `connectImage()` accepts. Strings
|
|
11
|
+
* beyond the "webcam" / "tab" sentinels are treated as static URLs. */
|
|
12
|
+
export type ImageSource = "webcam" | "tab" | string | string[] | HTMLCanvasElement | HTMLVideoElement | MediaStream;
|
|
13
|
+
export interface PrismPlayerOptions {
|
|
14
|
+
/** Where to mount the visualization canvases. Pass either an element
|
|
15
|
+
* (preferred from frameworks — React refs, Vue template refs, etc.)
|
|
16
|
+
* or a DOM id string for hand-written HTML embeds. The player
|
|
17
|
+
* creates its own canvases inside; it does not touch any children
|
|
18
|
+
* that already exist there. */
|
|
19
|
+
container: HTMLElement | string;
|
|
20
|
+
/** Optional initial graph. Pass a PrismGraph object directly, or a
|
|
21
|
+
* 6-char short_id string (e.g. "7Hq3pK") to look up an entry from
|
|
22
|
+
* the bundled catalog. Without it, cold-opens on a curated milkdrop
|
|
23
|
+
* preset; you can call load() later. */
|
|
24
|
+
graph?: GraphInput;
|
|
25
|
+
/** Audio source. An AudioNode is connected directly; "mic" and "tab"
|
|
26
|
+
* request the matching media stream via getUserMedia /
|
|
27
|
+
* getDisplayMedia and connect that. Undefined → the built-in
|
|
28
|
+
* SyntheticSignal drives both backends. */
|
|
29
|
+
audio?: "mic" | "tab" | MediaStream | AudioNode;
|
|
30
|
+
/** Bring your own AudioContext (useful when the embedder already has
|
|
31
|
+
* one; web pages can only have a small number of them). */
|
|
32
|
+
audioCtx?: AudioContext;
|
|
33
|
+
/** iChannel1 fallback used by shader backends until something more
|
|
34
|
+
* specific is bound by the graph or setLiveSource(). */
|
|
35
|
+
defaultImage?: string;
|
|
36
|
+
/** Image feed for shader backends (bound to iChannel1). Accepts:
|
|
37
|
+
* - A single URL → static image
|
|
38
|
+
* - An array of URLs → built-in crossfading slideshow
|
|
39
|
+
* - "webcam" / "tab" → getUserMedia / getDisplayMedia
|
|
40
|
+
* - a MediaStream / HTMLVideoElement → live video
|
|
41
|
+
* - an HTMLCanvasElement → live canvas (e.g. your own
|
|
42
|
+
* renderer; the player just
|
|
43
|
+
* re-uploads its contents every
|
|
44
|
+
* frame)
|
|
45
|
+
* Use this for "I want pictures showing through the shader" without
|
|
46
|
+
* thinking about iChannel1 or setLiveSource. */
|
|
47
|
+
image?: ImageSource;
|
|
48
|
+
/** Seconds each image is held on-screen before the crossfade to the
|
|
49
|
+
* next begins. Only used when `image` is a URL array. Defaults to 6. */
|
|
50
|
+
holdSeconds?: number;
|
|
51
|
+
/** Milkdrop preset to load on cold-open. Falls back to a random preset
|
|
52
|
+
* from the bundled library if the name isn't found. */
|
|
53
|
+
initialPresetName?: string;
|
|
54
|
+
/** Called once the first frame has painted. */
|
|
55
|
+
onReady?: () => void;
|
|
56
|
+
/** Async failures (audio capture rejection, graph load errors that
|
|
57
|
+
* cannot be surfaced from a synchronous call) land here. */
|
|
58
|
+
onError?: (err: Error) => void;
|
|
59
|
+
}
|
|
60
|
+
export declare class PrismPlayer {
|
|
61
|
+
/** AudioContext driving both backends + the default synthetic signal.
|
|
62
|
+
* Created on construction unless one was passed in `opts.audioCtx`. */
|
|
63
|
+
readonly audioCtx: AudioContext;
|
|
64
|
+
/** Default audio driver: a silent "pink-noise-ish" pad that keeps the
|
|
65
|
+
* visualization animated when no real audio is connected. Kept alive
|
|
66
|
+
* for the lifetime of the player — call `player.synth.stop()` if you
|
|
67
|
+
* want to free it after switching to a real audio source. */
|
|
68
|
+
readonly synth: SyntheticSignal;
|
|
69
|
+
/** Milkdrop (butterchurn) backend handle. Cold-opens on a random
|
|
70
|
+
* preset (or `initialPresetName` if you passed one). */
|
|
71
|
+
readonly milkdrop: MilkdropBg;
|
|
72
|
+
/** Shadertoy backend handle. Idle until a graph with `lf.shadertoy` is
|
|
73
|
+
* loaded (or you call `shadertoy.loadFromUrl` directly). */
|
|
74
|
+
readonly shadertoy: ShadertoyBg;
|
|
75
|
+
/** Graph executor — dispatches to the right backend based on the
|
|
76
|
+
* graph's light-field generator node. */
|
|
77
|
+
readonly runtime: GraphRuntime;
|
|
78
|
+
/** Tracks which backend's canvas is currently visible. */
|
|
79
|
+
activeBackend: "milkdrop" | "shadertoy";
|
|
80
|
+
private readonly milkdropCanvas;
|
|
81
|
+
private readonly shadertoyCanvas;
|
|
82
|
+
private readonly ownsAudioCtx;
|
|
83
|
+
/** Default hold per image when `image` is a URL list; set in ctor. */
|
|
84
|
+
private readonly defaultHoldSeconds;
|
|
85
|
+
/** Owned media resources (MediaStreams we started, <video> elements
|
|
86
|
+
* we created internally, headless slideshow). Cleaned up by
|
|
87
|
+
* connectImage() before re-binding and by destroy() on teardown. */
|
|
88
|
+
private currentSlideshow;
|
|
89
|
+
private currentVideo;
|
|
90
|
+
private currentStream;
|
|
91
|
+
/** Audio MediaStream the player started itself ("mic" / "tab"). Held
|
|
92
|
+
* so disconnectAudio() can stop the tracks. Null if the caller
|
|
93
|
+
* passed in their own stream/node (we never stop tracks we don't own). */
|
|
94
|
+
private currentAudioOwnedStream;
|
|
95
|
+
constructor(opts: PrismPlayerOptions);
|
|
96
|
+
/** Swap to a new graph. Accepts either a full PrismGraph object or a
|
|
97
|
+
* 6-char short_id string from the bundled registry. Returns the
|
|
98
|
+
* ApplyResult the runtime emits so the caller can react to backend
|
|
99
|
+
* switches / errors. An unknown short_id returns
|
|
100
|
+
* `{ ok: false, error: "unknown short_id ..." }` without touching
|
|
101
|
+
* the running visualization. */
|
|
102
|
+
load(graph: GraphInput, blendSeconds?: number): ApplyResult;
|
|
103
|
+
/** Connect a new audio source. Accepts:
|
|
104
|
+
* - "mic" — getUserMedia({ audio: true })
|
|
105
|
+
* - "tab" — getDisplayMedia({ audio: true })
|
|
106
|
+
* - MediaStream — wrapped in a MediaStreamAudioSourceNode
|
|
107
|
+
* - AudioNode — connected directly
|
|
108
|
+
* Routes the source to both backends so whichever one is active sees
|
|
109
|
+
* reactivity. Tracks any MediaStream the player started itself so
|
|
110
|
+
* disconnectAudio() can stop the tracks (mic indicator off, etc.). */
|
|
111
|
+
connectAudio(source: "mic" | "tab" | MediaStream | AudioNode): Promise<AudioNode>;
|
|
112
|
+
/** Disconnect the current audio source and revert both backends to
|
|
113
|
+
* the built-in SyntheticSignal. Stops any MediaStream tracks the
|
|
114
|
+
* player started itself (mic / tab capture indicator goes off).
|
|
115
|
+
* Streams or AudioNodes the caller passed in are left untouched —
|
|
116
|
+
* the caller owns their lifetime. Idempotent. */
|
|
117
|
+
disconnectAudio(): void;
|
|
118
|
+
/** Pipe a live source (e.g. a slideshow's canvas) into iChannel1 — its
|
|
119
|
+
* contents are re-uploaded each frame. Pass null to disable.
|
|
120
|
+
* Most embedders should prefer connectImage(), which handles
|
|
121
|
+
* webcam/tab/slideshow plumbing for you. */
|
|
122
|
+
setLiveSource(source: HTMLCanvasElement | HTMLVideoElement | null): void;
|
|
123
|
+
/** Connect an image source for the shader's iChannel1. Symmetric to
|
|
124
|
+
* connectAudio. Replaces any prior image feed; old MediaStream
|
|
125
|
+
* tracks are stopped and internal slideshow timers are cleared.
|
|
126
|
+
* See ImageSource for the accepted shapes. */
|
|
127
|
+
connectImage(source: ImageSource): Promise<void>;
|
|
128
|
+
/** Stop the current image feed and unbind from the shader. Releases
|
|
129
|
+
* resources the player owned: slideshow timer cleared, MediaStream
|
|
130
|
+
* tracks stopped (webcam light off), internal <video> detached.
|
|
131
|
+
* Canvas/video elements the caller passed in are left untouched.
|
|
132
|
+
* After this, the shader reverts to whatever was last bound via
|
|
133
|
+
* `defaultImage` or `shadertoy.bindImage()` (or the 1×1 placeholder).
|
|
134
|
+
* Idempotent. */
|
|
135
|
+
disconnectImage(): void;
|
|
136
|
+
/** Toggle which backend's canvas is visible. Called by GraphRuntime;
|
|
137
|
+
* callers can flip it manually too (rare). */
|
|
138
|
+
setActiveBackend(which: "milkdrop" | "shadertoy"): void;
|
|
139
|
+
/** Stop animation, free GL resources, detach canvases, close the
|
|
140
|
+
* AudioContext (only if the player created it itself). */
|
|
141
|
+
destroy(): void;
|
|
142
|
+
/** Wrap a MediaStream in an autoplaying <video> and bind that as the
|
|
143
|
+
* live source. The video element is held internally; teardown is
|
|
144
|
+
* handled by teardownImageFeed(). */
|
|
145
|
+
private bindStream;
|
|
146
|
+
/** Stop the current image feed (slideshow timer, MediaStream tracks,
|
|
147
|
+
* internal <video>) and unbind from the shader. Idempotent. */
|
|
148
|
+
private teardownImageFeed;
|
|
149
|
+
private resolveAudioNode;
|
|
150
|
+
}
|