@mudah-cli/vgpu 0.7.0 → 0.9.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 ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Mudah 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 all
13
+ 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 THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,44 @@
1
+ # @mudah-cli/vgpu
2
+
3
+ Run [vgpu](https://vgpu.sh/) WGSL effects inside a Mudah app and blit the pixels to the terminal
4
+ (Kitty graphics, with a half-block fallback).
5
+
6
+ **Optional.** The `@mudah-cli/mudah` umbrella does not depend on this package — install it only
7
+ if you want GPU shaders in the terminal.
8
+
9
+ ## Install
10
+
11
+ ```sh
12
+ npm install @mudah-cli/vgpu
13
+ ```
14
+
15
+ On a machine without a GPU, install the software renderer once:
16
+
17
+ ```sh
18
+ npx vgpu install-software-renderer
19
+ ```
20
+
21
+ ## Usage
22
+
23
+ ```ts
24
+ import { ShaderSession } from '@mudah-cli/vgpu';
25
+
26
+ const session = await ShaderSession.create({
27
+ shader: /* fragment-only WGSL */,
28
+ width: 320,
29
+ height: 180,
30
+ });
31
+
32
+ await session.frame({ columns: 80, rows: 24 }); // draw + blit
33
+ session.dispose();
34
+ ```
35
+
36
+ Also ships a built-in shader catalog (`SHADER_CATALOG`), WGSL `import` resolution, shader
37
+ hot-reload (`watchShader`), an audio-reactive bridge (`bindAudioReactive`), a parameter-slider
38
+ overlay, and framebuffer capture to PNG.
39
+
40
+ ## Links
41
+
42
+ - [Full documentation](https://github.com/thesimonharms/mudah#readme)
43
+ - [shader-lab example](https://github.com/thesimonharms/mudah/tree/main/examples/shader-lab)
44
+ - [License: MIT](LICENSE)
@@ -0,0 +1,6 @@
1
+ /** Built-in fragment snippets. Keys are lowercase catalog ids. */
2
+ export declare const SHADER_CATALOG: Record<string, string>;
3
+ /** Catalog ids in a stable order. */
4
+ export declare function listShaders(): string[];
5
+ /** Look up a catalog snippet. Names are case-insensitive. */
6
+ export declare function getShader(name: string): string;
@@ -0,0 +1,87 @@
1
+ const PARAMS = `
2
+ struct Params {
3
+ time: f32,
4
+ energy: f32,
5
+ bass: f32,
6
+ mid: f32,
7
+ high: f32,
8
+ width: f32,
9
+ height: f32,
10
+ }
11
+ @group(0) @binding(0) var<uniform> params: Params;
12
+ `;
13
+ /** Built-in fragment snippets. Keys are lowercase catalog ids. */
14
+ export const SHADER_CATALOG = {
15
+ plasma: `${PARAMS}
16
+ @fragment fn fs_main(@location(0) uv: vec2f) -> @location(0) vec4f {
17
+ let t = params.time;
18
+ let e = params.energy;
19
+ let n = sin(uv.x * 8.0 + t) + sin(uv.y * 6.0 - t * 0.7) + sin((uv.x + uv.y) * 4.0 + t * 1.3);
20
+ let v = 0.5 + 0.5 * sin(n + e);
21
+ return vec4f(v, 0.35 + 0.45 * sin(n + 2.1), 0.75 + 0.2 * e, 1.0);
22
+ }
23
+ `,
24
+ metaballs: `${PARAMS}
25
+ @fragment fn fs_main(@location(0) uv: vec2f) -> @location(0) vec4f {
26
+ let t = params.time;
27
+ let a = vec2f(0.35 + 0.2 * sin(t), 0.45 + 0.15 * cos(t * 0.8));
28
+ let b = vec2f(0.65 + 0.18 * cos(t * 1.1), 0.55 + 0.2 * sin(t * 0.9));
29
+ let c = vec2f(0.5 + 0.22 * sin(t * 0.6), 0.35 + 0.18 * cos(t * 1.4));
30
+ let field = 0.12 / max(dot(uv - a, uv - a), 0.001)
31
+ + 0.10 / max(dot(uv - b, uv - b), 0.001)
32
+ + 0.08 / max(dot(uv - c, uv - c), 0.001);
33
+ let m = smoothstep(0.85, 1.15, field + params.energy * 0.25);
34
+ return vec4f(m * 0.2, m * 0.75, m * 0.95, 1.0);
35
+ }
36
+ `,
37
+ fire: `${PARAMS}
38
+ @fragment fn fs_main(@location(0) uv: vec2f) -> @location(0) vec4f {
39
+ let t = params.time;
40
+ let flicker = sin(uv.x * 18.0 + t * 6.0) * sin(uv.y * 11.0 - t * 4.0);
41
+ let rise = 1.0 - uv.y;
42
+ let heat = rise * rise * (0.7 + 0.3 * flicker + params.energy * 0.4);
43
+ let col = vec3f(heat, heat * 0.45, heat * 0.08);
44
+ return vec4f(col, 1.0);
45
+ }
46
+ `,
47
+ voronoi: `${PARAMS}
48
+ @fragment fn fs_main(@location(0) uv: vec2f) -> @location(0) vec4f {
49
+ let t = params.time * 0.15;
50
+ var cell = floor(uv * 6.0);
51
+ var local = fract(uv * 6.0);
52
+ var md = 8.0;
53
+ for (var j = -1; j <= 1; j++) {
54
+ for (var i = -1; i <= 1; i++) {
55
+ let neighbor = vec2f(f32(i), f32(j));
56
+ let seed = cell + neighbor;
57
+ let p = neighbor + 0.5 + 0.5 * sin(vec2f(seed.x * 3.1 + t, seed.y * 2.7 - t));
58
+ md = min(md, length(local - p));
59
+ }
60
+ }
61
+ let edge = smoothstep(0.0, 0.18, md);
62
+ let e = params.energy;
63
+ return vec4f(edge * 0.15 + e * 0.2, edge * 0.55, 0.85 - edge * 0.3, 1.0);
64
+ }
65
+ `,
66
+ };
67
+ const ALIASES = {
68
+ plasma: 'plasma',
69
+ metaballs: 'metaballs',
70
+ metaball: 'metaballs',
71
+ fire: 'fire',
72
+ voronoi: 'voronoi',
73
+ };
74
+ /** Catalog ids in a stable order. */
75
+ export function listShaders() {
76
+ return Object.keys(SHADER_CATALOG);
77
+ }
78
+ /** Look up a catalog snippet. Names are case-insensitive. */
79
+ export function getShader(name) {
80
+ const key = ALIASES[name.toLowerCase()];
81
+ const source = key === undefined ? undefined : SHADER_CATALOG[key];
82
+ if (source === undefined) {
83
+ throw new Error(`[vgpu] Unknown shader "${name}". Available: ${listShaders().join(', ')}`);
84
+ }
85
+ return source;
86
+ }
87
+ //# sourceMappingURL=catalog.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"catalog.js","sourceRoot":"","sources":["../src/catalog.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,GAAG;;;;;;;;;;;CAWd,CAAC;AAEF,kEAAkE;AAClE,MAAM,CAAC,MAAM,cAAc,GAA2B;IACpD,MAAM,EAAE,GAAG,MAAM;;;;;;;;CAQlB;IACC,SAAS,EAAE,GAAG,MAAM;;;;;;;;;;;;CAYrB;IACC,IAAI,EAAE,GAAG,MAAM;;;;;;;;;CAShB;IACC,OAAO,EAAE,GAAG,MAAM;;;;;;;;;;;;;;;;;;CAkBnB;CACA,CAAC;AAEF,MAAM,OAAO,GAA2B;IACtC,MAAM,EAAE,QAAQ;IAChB,SAAS,EAAE,WAAW;IACtB,QAAQ,EAAE,WAAW;IACrB,IAAI,EAAE,MAAM;IACZ,OAAO,EAAE,SAAS;CACnB,CAAC;AAEF,qCAAqC;AACrC,MAAM,UAAU,WAAW;IACzB,OAAO,MAAM,CAAC,IAAI,CAAC,cAAc,CAAC,CAAC;AACrC,CAAC;AAED,6DAA6D;AAC7D,MAAM,UAAU,SAAS,CAAC,IAAY;IACpC,MAAM,GAAG,GAAG,OAAO,CAAC,IAAI,CAAC,WAAW,EAAE,CAAC,CAAC;IACxC,MAAM,MAAM,GAAG,GAAG,KAAK,SAAS,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,cAAc,CAAC,GAAG,CAAC,CAAC;IACnE,IAAI,MAAM,KAAK,SAAS,EAAE,CAAC;QACzB,MAAM,IAAI,KAAK,CAAC,0BAA0B,IAAI,iBAAiB,WAAW,EAAE,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;IAC7F,CAAC;IACD,OAAO,MAAM,CAAC;AAChB,CAAC"}
@@ -0,0 +1,11 @@
1
+ export interface ResolveWgslOptions {
2
+ /** File reader for included paths (default `readFileSync`). */
3
+ readFile?: (path: string) => string;
4
+ /** Directory that relative includes resolve against. Default `process.cwd()`. */
5
+ baseDir?: string;
6
+ }
7
+ /**
8
+ * Expand `#include "foo.wgsl"` and `// @import foo` (or `@import foo`)
9
+ * directives. Detects include cycles.
10
+ */
11
+ export declare function resolveWgsl(source: string, options?: ResolveWgslOptions): string;
@@ -0,0 +1,51 @@
1
+ import { readFileSync } from 'node:fs';
2
+ import { dirname, join } from 'node:path';
3
+ const INCLUDE = /^[ \t]*(?:#include\s+["'<]([^"'>]+)["'>]|\/\/\s*@import\s+["']?([^\s"']+)["']?|@import\s+["']?([^\s"']+)["']?)\s*$/;
4
+ /**
5
+ * Expand `#include "foo.wgsl"` and `// @import foo` (or `@import foo`)
6
+ * directives. Detects include cycles.
7
+ */
8
+ export function resolveWgsl(source, options = {}) {
9
+ const readFile = options.readFile ?? defaultRead;
10
+ const baseDir = options.baseDir ?? process.cwd();
11
+ return expand(source, baseDir, [], readFile, '<source>');
12
+ }
13
+ function defaultRead(path) {
14
+ return readFileSync(path, 'utf8');
15
+ }
16
+ function parseInclude(line) {
17
+ const match = INCLUDE.exec(line);
18
+ if (match === null)
19
+ return undefined;
20
+ return match[1] ?? match[2] ?? match[3];
21
+ }
22
+ function withExtension(spec) {
23
+ return spec.endsWith('.wgsl') ? spec : `${spec}.wgsl`;
24
+ }
25
+ function expand(source, baseDir, stack, readFile, current) {
26
+ if (stack.includes(current)) {
27
+ throw new Error(`[vgpu] Circular WGSL include: ${[...stack, current].join(' -> ')}`);
28
+ }
29
+ const nextStack = [...stack, current];
30
+ const lines = source.split(/\r?\n/);
31
+ const out = [];
32
+ for (const line of lines) {
33
+ const spec = parseInclude(line);
34
+ if (spec === undefined) {
35
+ out.push(line);
36
+ continue;
37
+ }
38
+ const resolved = join(baseDir, withExtension(spec));
39
+ let body;
40
+ try {
41
+ body = readFile(resolved);
42
+ }
43
+ catch (error) {
44
+ const reason = error instanceof Error ? error.message : String(error);
45
+ throw new Error(`[vgpu] Cannot read WGSL include "${resolved}" (${reason}).`);
46
+ }
47
+ out.push(expand(body, dirname(resolved), nextStack, readFile, resolved));
48
+ }
49
+ return out.join('\n');
50
+ }
51
+ //# sourceMappingURL=include.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"include.js","sourceRoot":"","sources":["../src/include.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AACvC,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAS1C,MAAM,OAAO,GACX,oHAAoH,CAAC;AAEvH;;;GAGG;AACH,MAAM,UAAU,WAAW,CAAC,MAAc,EAAE,OAAO,GAAuB,EAAE;IAC1E,MAAM,QAAQ,GAAG,OAAO,CAAC,QAAQ,IAAI,WAAW,CAAC;IACjD,MAAM,OAAO,GAAG,OAAO,CAAC,OAAO,IAAI,OAAO,CAAC,GAAG,EAAE,CAAC;IACjD,OAAO,MAAM,CAAC,MAAM,EAAE,OAAO,EAAE,EAAE,EAAE,QAAQ,EAAE,UAAU,CAAC,CAAC;AAC3D,CAAC;AAED,SAAS,WAAW,CAAC,IAAY;IAC/B,OAAO,YAAY,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC;AACpC,CAAC;AAED,SAAS,YAAY,CAAC,IAAY;IAChC,MAAM,KAAK,GAAG,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IACjC,IAAI,KAAK,KAAK,IAAI;QAAE,OAAO,SAAS,CAAC;IACrC,OAAO,KAAK,CAAC,CAAC,CAAC,IAAI,KAAK,CAAC,CAAC,CAAC,IAAI,KAAK,CAAC,CAAC,CAAC,CAAC;AAC1C,CAAC;AAED,SAAS,aAAa,CAAC,IAAY;IACjC,OAAO,IAAI,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,GAAG,IAAI,OAAO,CAAC;AACxD,CAAC;AAED,SAAS,MAAM,CACb,MAAc,EACd,OAAe,EACf,KAAwB,EACxB,QAAkC,EAClC,OAAe;IAEf,IAAI,KAAK,CAAC,QAAQ,CAAC,OAAO,CAAC,EAAE,CAAC;QAC5B,MAAM,IAAI,KAAK,CAAC,iCAAiC,CAAC,GAAG,KAAK,EAAE,OAAO,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC;IACvF,CAAC;IACD,MAAM,SAAS,GAAG,CAAC,GAAG,KAAK,EAAE,OAAO,CAAC,CAAC;IACtC,MAAM,KAAK,GAAG,MAAM,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC;IACpC,MAAM,GAAG,GAAa,EAAE,CAAC;IACzB,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;QACzB,MAAM,IAAI,GAAG,YAAY,CAAC,IAAI,CAAC,CAAC;QAChC,IAAI,IAAI,KAAK,SAAS,EAAE,CAAC;YACvB,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;YACf,SAAS;QACX,CAAC;QACD,MAAM,QAAQ,GAAG,IAAI,CAAC,OAAO,EAAE,aAAa,CAAC,IAAI,CAAC,CAAC,CAAC;QACpD,IAAI,IAAY,CAAC;QACjB,IAAI,CAAC;YACH,IAAI,GAAG,QAAQ,CAAC,QAAQ,CAAC,CAAC;QAC5B,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,MAAM,MAAM,GAAG,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;YACtE,MAAM,IAAI,KAAK,CAAC,oCAAoC,QAAQ,MAAM,MAAM,IAAI,CAAC,CAAC;QAChF,CAAC;QACD,GAAG,CAAC,IAAI,CAAC,MAAM,CAAC,IAAI,EAAE,OAAO,CAAC,QAAQ,CAAC,EAAE,SAAS,EAAE,QAAQ,EAAE,QAAQ,CAAC,CAAC,CAAC;IAC3E,CAAC;IACD,OAAO,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;AACxB,CAAC"}
package/dist/index.d.ts CHANGED
@@ -1,2 +1,9 @@
1
+ export { getShader, listShaders, SHADER_CATALOG } from './catalog.js';
2
+ export { type ResolveWgslOptions, resolveWgsl } from './include.js';
3
+ export { type ParticleComputeOptions, type ParticleComputeResult, runParticleCompute, } from './particles.js';
4
+ export { capturePng } from './png.js';
1
5
  export { FramePresenter, type FramePresenterOptions, type PresentMode, } from './presenter.js';
2
- export { ShaderSession, type GpuAdapterKind, type ShaderSessionOptions, } from './session.js';
6
+ export { type AudioReactiveBinding, type AudioReactiveSource, type BindAudioReactiveOptions, bindAudioReactive, type ShaderUniformTarget, } from './reactive.js';
7
+ export { type GpuAdapterKind, ShaderSession, type ShaderSessionOptions, } from './session.js';
8
+ export { type ShaderSliderParam, ShaderSliders, type ShaderSlidersOptions, } from './sliders.js';
9
+ export { parseWatchFlag, type ShaderWatchFn, type WatchShaderOptions, watchShader, } from './watch.js';
package/dist/index.js CHANGED
@@ -1,3 +1,10 @@
1
+ export { getShader, listShaders, SHADER_CATALOG } from './catalog.js';
2
+ export { resolveWgsl } from './include.js';
3
+ export { runParticleCompute, } from './particles.js';
4
+ export { capturePng } from './png.js';
1
5
  export { FramePresenter, } from './presenter.js';
6
+ export { bindAudioReactive, } from './reactive.js';
2
7
  export { ShaderSession, } from './session.js';
8
+ export { ShaderSliders, } from './sliders.js';
9
+ export { parseWatchFlag, watchShader, } from './watch.js';
3
10
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,cAAc,GAGf,MAAM,gBAAgB,CAAC;AACxB,OAAO,EACL,aAAa,GAGd,MAAM,cAAc,CAAC"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,WAAW,EAAE,cAAc,EAAE,MAAM,cAAc,CAAC;AACtE,OAAO,EAA2B,WAAW,EAAE,MAAM,cAAc,CAAC;AACpE,OAAO,EAGL,kBAAkB,GACnB,MAAM,gBAAgB,CAAC;AACxB,OAAO,EAAE,UAAU,EAAE,MAAM,UAAU,CAAC;AACtC,OAAO,EACL,cAAc,GAGf,MAAM,gBAAgB,CAAC;AACxB,OAAO,EAIL,iBAAiB,GAElB,MAAM,eAAe,CAAC;AACvB,OAAO,EAEL,aAAa,GAEd,MAAM,cAAc,CAAC;AACtB,OAAO,EAEL,aAAa,GAEd,MAAM,cAAc,CAAC;AACtB,OAAO,EACL,cAAc,EAGd,WAAW,GACZ,MAAM,YAAY,CAAC"}
@@ -0,0 +1,21 @@
1
+ export interface ParticleComputeOptions {
2
+ /** Number of particles (vec2 positions). */
3
+ count: number;
4
+ /** Timestep in seconds. */
5
+ dt: number;
6
+ /** Interleaved x,y. Length `count * 2`. Allocated when omitted. */
7
+ positions?: Float32Array;
8
+ /** Interleaved vx,vy. Length `count * 2`. Allocated when omitted. */
9
+ velocities?: Float32Array;
10
+ /** Downward acceleration applied to vy. Default 0. */
11
+ gravity?: number;
12
+ }
13
+ export interface ParticleComputeResult {
14
+ positions: Float32Array;
15
+ velocities: Float32Array;
16
+ }
17
+ /**
18
+ * Software compute pass for particle physics (semi-implicit Euler).
19
+ * `ShaderSession` has no GPU compute path, so this runs on the CPU.
20
+ */
21
+ export declare function runParticleCompute(options: ParticleComputeOptions): ParticleComputeResult;
@@ -0,0 +1,43 @@
1
+ /**
2
+ * Software compute pass for particle physics (semi-implicit Euler).
3
+ * `ShaderSession` has no GPU compute path, so this runs on the CPU.
4
+ */
5
+ export function runParticleCompute(options) {
6
+ const count = Math.max(0, Math.floor(options.count));
7
+ const dt = options.dt;
8
+ const gravity = options.gravity ?? 0;
9
+ const positions = ensureVec2(options.positions, count, (i) => seedPosition(i, count));
10
+ const velocities = ensureVec2(options.velocities, count, () => [0, 0]);
11
+ for (let i = 0; i < count; i++) {
12
+ const ix = i * 2;
13
+ const iy = ix + 1;
14
+ const vy = (velocities[iy] ?? 0) + gravity * dt;
15
+ velocities[iy] = vy;
16
+ positions[ix] = (positions[ix] ?? 0) + (velocities[ix] ?? 0) * dt;
17
+ positions[iy] = (positions[iy] ?? 0) + vy * dt;
18
+ }
19
+ return { positions, velocities };
20
+ }
21
+ function ensureVec2(input, count, fill) {
22
+ const needed = count * 2;
23
+ if (input !== undefined && input.length >= needed)
24
+ return input;
25
+ const out = new Float32Array(needed);
26
+ if (input !== undefined)
27
+ out.set(input.subarray(0, Math.min(input.length, needed)));
28
+ for (let i = 0; i < count; i++) {
29
+ if (input !== undefined && i * 2 + 1 < input.length)
30
+ continue;
31
+ const [x, y] = fill(i);
32
+ out[i * 2] = x;
33
+ out[i * 2 + 1] = y;
34
+ }
35
+ return out;
36
+ }
37
+ function seedPosition(index, count) {
38
+ if (count <= 0)
39
+ return [0, 0];
40
+ const angle = (index / count) * Math.PI * 2;
41
+ return [0.5 + 0.25 * Math.cos(angle), 0.5 + 0.25 * Math.sin(angle)];
42
+ }
43
+ //# sourceMappingURL=particles.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"particles.js","sourceRoot":"","sources":["../src/particles.ts"],"names":[],"mappings":"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"}
package/dist/png.d.ts ADDED
@@ -0,0 +1,5 @@
1
+ /**
2
+ * Encode tightly packed RGBA8 pixels as an uncompressed-filter PNG
3
+ * (IHDR + zlib IDAT + IEND). Filter byte 0 on every scanline.
4
+ */
5
+ export declare function capturePng(rgba: Uint8Array, width: number, height: number): Uint8Array;
package/dist/png.js ADDED
@@ -0,0 +1,82 @@
1
+ import { deflateSync } from 'node:zlib';
2
+ const PNG_SIG = Uint8Array.of(0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a);
3
+ const CRC_TABLE = makeCrcTable();
4
+ function makeCrcTable() {
5
+ const table = new Uint32Array(256);
6
+ for (let n = 0; n < 256; n++) {
7
+ let c = n;
8
+ for (let k = 0; k < 8; k++) {
9
+ c = (c & 1) === 1 ? 0xedb88320 ^ (c >>> 1) : c >>> 1;
10
+ }
11
+ table[n] = c >>> 0;
12
+ }
13
+ return table;
14
+ }
15
+ function crc32(bytes) {
16
+ let c = 0xffffffff;
17
+ for (let i = 0; i < bytes.length; i++) {
18
+ c = (CRC_TABLE[(c ^ (bytes[i] ?? 0)) & 0xff] ?? 0) ^ (c >>> 8);
19
+ }
20
+ return (c ^ 0xffffffff) >>> 0;
21
+ }
22
+ function u32(value) {
23
+ const out = new Uint8Array(4);
24
+ const view = new DataView(out.buffer);
25
+ view.setUint32(0, value >>> 0, false);
26
+ return out;
27
+ }
28
+ function concat(parts) {
29
+ let length = 0;
30
+ for (const part of parts)
31
+ length += part.byteLength;
32
+ const out = new Uint8Array(length);
33
+ let offset = 0;
34
+ for (const part of parts) {
35
+ out.set(part, offset);
36
+ offset += part.byteLength;
37
+ }
38
+ return out;
39
+ }
40
+ function chunk(type, data) {
41
+ const tag = new Uint8Array(4);
42
+ for (let i = 0; i < 4; i++)
43
+ tag[i] = type.charCodeAt(i);
44
+ const body = concat([tag, data]);
45
+ return concat([u32(data.byteLength), body, u32(crc32(body))]);
46
+ }
47
+ /**
48
+ * Encode tightly packed RGBA8 pixels as an uncompressed-filter PNG
49
+ * (IHDR + zlib IDAT + IEND). Filter byte 0 on every scanline.
50
+ */
51
+ export function capturePng(rgba, width, height) {
52
+ if (width < 1 || height < 1) {
53
+ throw new Error(`[vgpu] capturePng needs a positive size, got ${width}x${height}.`);
54
+ }
55
+ const expected = width * height * 4;
56
+ if (rgba.byteLength < expected) {
57
+ throw new Error(`[vgpu] RGBA buffer is ${rgba.byteLength} bytes, need ${expected}.`);
58
+ }
59
+ const ihdr = new Uint8Array(13);
60
+ const ihdrView = new DataView(ihdr.buffer);
61
+ ihdrView.setUint32(0, width, false);
62
+ ihdrView.setUint32(4, height, false);
63
+ ihdr[8] = 8;
64
+ ihdr[9] = 6;
65
+ ihdr[10] = 0;
66
+ ihdr[11] = 0;
67
+ ihdr[12] = 0;
68
+ const stride = width * 4;
69
+ const raw = new Uint8Array(height * (1 + stride));
70
+ for (let y = 0; y < height; y++) {
71
+ const dest = y * (1 + stride);
72
+ raw[dest] = 0;
73
+ raw.set(rgba.subarray(y * stride, y * stride + stride), dest + 1);
74
+ }
75
+ return concat([
76
+ PNG_SIG,
77
+ chunk('IHDR', ihdr),
78
+ chunk('IDAT', deflateSync(raw)),
79
+ chunk('IEND', new Uint8Array(0)),
80
+ ]);
81
+ }
82
+ //# sourceMappingURL=png.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"png.js","sourceRoot":"","sources":["../src/png.ts"],"names":[],"mappings":"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"}
package/dist/presenter.js CHANGED
@@ -1,4 +1,4 @@
1
- import { KittyGraphics, detectCapabilities, encodeHalfBlocks, } from '@mudah-cli/terminal';
1
+ import { detectCapabilities, encodeHalfBlocks, KittyGraphics, } from '@mudah-cli/terminal';
2
2
  /**
3
3
  * Blit an RGBA/RGB framebuffer to the terminal. Prefers the Kitty graphics
4
4
  * protocol; falls back to unicode half-blocks on terminals that cannot
@@ -13,8 +13,7 @@ export class FramePresenter {
13
13
  this.writer = options.stdout ?? process.stdout;
14
14
  const caps = options.capabilities ?? detectCapabilities();
15
15
  const requested = options.mode ?? 'auto';
16
- this.mode =
17
- requested === 'auto' ? (caps.kittyGraphics ? 'kitty' : 'half') : requested;
16
+ this.mode = requested === 'auto' ? (caps.kittyGraphics ? 'kitty' : 'half') : requested;
18
17
  this.format = options.format ?? 'rgba';
19
18
  this.gfx = new KittyGraphics(this.writer, { id: options.id ?? 1 });
20
19
  }
@@ -1 +1 @@
1
- {"version":3,"file":"presenter.js","sourceRoot":"","sources":["../src/presenter.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,aAAa,EACb,kBAAkB,EAClB,gBAAgB,GAIjB,MAAM,qBAAqB,CAAC;AAc7B;;;;GAIG;AACH,MAAM,OAAO,cAAc;IACR,MAAM,CAAY;IAC1B,IAAI,CAA+B;IAC3B,MAAM,CAAc;IACpB,GAAG,CAAgB;IAEpC,YAAY,OAAO,GAA0B,EAAE;QAC7C,IAAI,CAAC,MAAM,GAAG,OAAO,CAAC,MAAM,IAAI,OAAO,CAAC,MAAM,CAAC;QAC/C,MAAM,IAAI,GAAG,OAAO,CAAC,YAAY,IAAI,kBAAkB,EAAE,CAAC;QAC1D,MAAM,SAAS,GAAG,OAAO,CAAC,IAAI,IAAI,MAAM,CAAC;QACzC,IAAI,CAAC,IAAI;YACP,SAAS,KAAK,MAAM,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,aAAa,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC;QAC7E,IAAI,CAAC,MAAM,GAAG,OAAO,CAAC,MAAM,IAAI,MAAM,CAAC;QACvC,IAAI,CAAC,GAAG,GAAG,IAAI,aAAa,CAAC,IAAI,CAAC,MAAM,EAAE,EAAE,EAAE,EAAE,OAAO,CAAC,EAAE,IAAI,CAAC,EAAE,CAAC,CAAC;IACrE,CAAC;IAED;;;OAGG;IACH,OAAO,CACL,MAAkB,EAClB,KAAa,EACb,MAAc,EACd,MAAM,GAAwC,EAAE;QAEhD,IAAI,IAAI,CAAC,IAAI,KAAK,OAAO,EAAE,CAAC;YAC1B,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE;gBACnC,MAAM,EAAE,IAAI,CAAC,MAAM;gBACnB,OAAO,EAAE,MAAM,CAAC,OAAO;gBACvB,IAAI,EAAE,MAAM,CAAC,IAAI;aAClB,CAAC,CAAC;YACH,OAAO;QACT,CAAC;QACD,MAAM,KAAK,GAAG,gBAAgB,CAAC,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,IAAI,CAAC,MAAM,CAAC,CAAC;QACnE,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC;QACpC,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;IAC1B,CAAC;IAED,KAAK;QACH,IAAI,IAAI,CAAC,IAAI,KAAK,OAAO;YAAE,IAAI,CAAC,GAAG,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;IACnD,CAAC;CACF"}
1
+ {"version":3,"file":"presenter.js","sourceRoot":"","sources":["../src/presenter.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,kBAAkB,EAClB,gBAAgB,EAChB,aAAa,GAId,MAAM,qBAAqB,CAAC;AAc7B;;;;GAIG;AACH,MAAM,OAAO,cAAc;IACR,MAAM,CAAY;IAC1B,IAAI,CAA+B;IAC3B,MAAM,CAAc;IACpB,GAAG,CAAgB;IAEpC,YAAY,OAAO,GAA0B,EAAE;QAC7C,IAAI,CAAC,MAAM,GAAG,OAAO,CAAC,MAAM,IAAI,OAAO,CAAC,MAAM,CAAC;QAC/C,MAAM,IAAI,GAAG,OAAO,CAAC,YAAY,IAAI,kBAAkB,EAAE,CAAC;QAC1D,MAAM,SAAS,GAAG,OAAO,CAAC,IAAI,IAAI,MAAM,CAAC;QACzC,IAAI,CAAC,IAAI,GAAG,SAAS,KAAK,MAAM,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,aAAa,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC;QACvF,IAAI,CAAC,MAAM,GAAG,OAAO,CAAC,MAAM,IAAI,MAAM,CAAC;QACvC,IAAI,CAAC,GAAG,GAAG,IAAI,aAAa,CAAC,IAAI,CAAC,MAAM,EAAE,EAAE,EAAE,EAAE,OAAO,CAAC,EAAE,IAAI,CAAC,EAAE,CAAC,CAAC;IACrE,CAAC;IAED;;;OAGG;IACH,OAAO,CACL,MAAkB,EAClB,KAAa,EACb,MAAc,EACd,MAAM,GAAwC,EAAE;QAEhD,IAAI,IAAI,CAAC,IAAI,KAAK,OAAO,EAAE,CAAC;YAC1B,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE;gBACnC,MAAM,EAAE,IAAI,CAAC,MAAM;gBACnB,OAAO,EAAE,MAAM,CAAC,OAAO;gBACvB,IAAI,EAAE,MAAM,CAAC,IAAI;aAClB,CAAC,CAAC;YACH,OAAO;QACT,CAAC;QACD,MAAM,KAAK,GAAG,gBAAgB,CAAC,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,IAAI,CAAC,MAAM,CAAC,CAAC;QACnE,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC;QACpC,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;IAC1B,CAAC;IAED,KAAK;QACH,IAAI,IAAI,CAAC,IAAI,KAAK,OAAO;YAAE,IAAI,CAAC,GAAG,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;IACnD,CAAC;CACF"}
@@ -0,0 +1,22 @@
1
+ export interface AudioReactiveSource {
2
+ bands(): number[];
3
+ energy(): number;
4
+ }
5
+ export interface ShaderUniformTarget {
6
+ setUniforms?(values: Record<string, unknown>): unknown;
7
+ set?(values: Record<string, unknown>): unknown;
8
+ }
9
+ export interface BindAudioReactiveOptions {
10
+ /** Write uniforms flat, nested under `params`, or both. Default `both`. */
11
+ binding?: 'flat' | 'params' | 'both';
12
+ }
13
+ export interface AudioReactiveBinding {
14
+ /** Read the source and write uniforms now. */
15
+ sync(): Record<string, number>;
16
+ dispose(): void;
17
+ }
18
+ /**
19
+ * Drive shader uniforms `energy`, `bass`, `mid`, `high` from an audio
20
+ * source (typically `@mudah-cli/audio` `createReactiveBridge()`).
21
+ */
22
+ export declare function bindAudioReactive(session: ShaderUniformTarget, source: AudioReactiveSource, options?: BindAudioReactiveOptions): AudioReactiveBinding;
@@ -0,0 +1,49 @@
1
+ /**
2
+ * Drive shader uniforms `energy`, `bass`, `mid`, `high` from an audio
3
+ * source (typically `@mudah-cli/audio` `createReactiveBridge()`).
4
+ */
5
+ export function bindAudioReactive(session, source, options = {}) {
6
+ const binding = options.binding ?? 'both';
7
+ let disposed = false;
8
+ const sync = () => {
9
+ const energy = source.energy();
10
+ const bands = source.bands();
11
+ const bass = pickBand(bands, 0, 0, 0.25);
12
+ const mid = pickBand(bands, 1, 0.25, 0.7);
13
+ const high = pickBand(bands, 2, 0.7, 1);
14
+ const values = { energy, bass, mid, high };
15
+ if (!disposed)
16
+ writeUniforms(session, values, binding);
17
+ return values;
18
+ };
19
+ return {
20
+ sync,
21
+ dispose() {
22
+ disposed = true;
23
+ },
24
+ };
25
+ }
26
+ function pickBand(bands, index, startFrac, endFrac) {
27
+ if (bands.length === 0)
28
+ return 0;
29
+ if (bands.length === 3)
30
+ return bands[index] ?? 0;
31
+ const start = Math.floor(startFrac * bands.length);
32
+ const end = Math.max(start + 1, Math.ceil(endFrac * bands.length));
33
+ let sum = 0;
34
+ let n = 0;
35
+ for (let i = start; i < end && i < bands.length; i++) {
36
+ sum += bands[i] ?? 0;
37
+ n += 1;
38
+ }
39
+ return n === 0 ? 0 : sum / n;
40
+ }
41
+ function writeUniforms(session, values, binding) {
42
+ const payload = binding === 'flat' ? values : binding === 'params' ? { params: values } : { ...values, params: values };
43
+ if (session.setUniforms !== undefined) {
44
+ session.setUniforms(payload);
45
+ return;
46
+ }
47
+ session.set?.(payload);
48
+ }
49
+ //# sourceMappingURL=reactive.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"reactive.js","sourceRoot":"","sources":["../src/reactive.ts"],"names":[],"mappings":"AAqBA;;;GAGG;AACH,MAAM,UAAU,iBAAiB,CAC/B,OAA4B,EAC5B,MAA2B,EAC3B,OAAO,GAA6B,EAAE;IAEtC,MAAM,OAAO,GAAG,OAAO,CAAC,OAAO,IAAI,MAAM,CAAC;IAC1C,IAAI,QAAQ,GAAG,KAAK,CAAC;IAErB,MAAM,IAAI,GAAG,GAA2B,EAAE;QACxC,MAAM,MAAM,GAAG,MAAM,CAAC,MAAM,EAAE,CAAC;QAC/B,MAAM,KAAK,GAAG,MAAM,CAAC,KAAK,EAAE,CAAC;QAC7B,MAAM,IAAI,GAAG,QAAQ,CAAC,KAAK,EAAE,CAAC,EAAE,CAAC,EAAE,IAAI,CAAC,CAAC;QACzC,MAAM,GAAG,GAAG,QAAQ,CAAC,KAAK,EAAE,CAAC,EAAE,IAAI,EAAE,GAAG,CAAC,CAAC;QAC1C,MAAM,IAAI,GAAG,QAAQ,CAAC,KAAK,EAAE,CAAC,EAAE,GAAG,EAAE,CAAC,CAAC,CAAC;QACxC,MAAM,MAAM,GAAG,EAAE,MAAM,EAAE,IAAI,EAAE,GAAG,EAAE,IAAI,EAAE,CAAC;QAC3C,IAAI,CAAC,QAAQ;YAAE,aAAa,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,CAAC,CAAC;QACvD,OAAO,MAAM,CAAC;IAChB,CAAC,CAAC;IAEF,OAAO;QACL,IAAI;QACJ,OAAO;YACL,QAAQ,GAAG,IAAI,CAAC;QAClB,CAAC;KACF,CAAC;AACJ,CAAC;AAED,SAAS,QAAQ,CAAC,KAAe,EAAE,KAAa,EAAE,SAAiB,EAAE,OAAe;IAClF,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,CAAC,CAAC;IACjC,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,KAAK,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;IACjD,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC,SAAS,GAAG,KAAK,CAAC,MAAM,CAAC,CAAC;IACnD,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,CAAC,KAAK,GAAG,CAAC,EAAE,IAAI,CAAC,IAAI,CAAC,OAAO,GAAG,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC;IACnE,IAAI,GAAG,GAAG,CAAC,CAAC;IACZ,IAAI,CAAC,GAAG,CAAC,CAAC;IACV,KAAK,IAAI,CAAC,GAAG,KAAK,EAAE,CAAC,GAAG,GAAG,IAAI,CAAC,GAAG,KAAK,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;QACrD,GAAG,IAAI,KAAK,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC;QACrB,CAAC,IAAI,CAAC,CAAC;IACT,CAAC;IACD,OAAO,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,GAAG,CAAC,CAAC;AAC/B,CAAC;AAED,SAAS,aAAa,CACpB,OAA4B,EAC5B,MAA8B,EAC9B,OAAmC;IAEnC,MAAM,OAAO,GACX,OAAO,KAAK,MAAM,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,OAAO,KAAK,QAAQ,CAAC,CAAC,CAAC,EAAE,MAAM,EAAE,MAAM,EAAE,CAAC,CAAC,CAAC,EAAE,GAAG,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,CAAC;IAC1G,IAAI,OAAO,CAAC,WAAW,KAAK,SAAS,EAAE,CAAC;QACtC,OAAO,CAAC,WAAW,CAAC,OAAO,CAAC,CAAC;QAC7B,OAAO;IACT,CAAC;IACD,OAAO,CAAC,GAAG,EAAE,CAAC,OAAO,CAAC,CAAC;AACzB,CAAC"}
package/dist/session.d.ts CHANGED
@@ -27,11 +27,19 @@ export declare class ShaderSession {
27
27
  private effect;
28
28
  private readonly target;
29
29
  private lastFrame;
30
+ private lastUniforms;
30
31
  private constructor();
31
32
  static create(options: ShaderSessionOptions): Promise<ShaderSession>;
32
33
  /** Replace the fragment shader without tearing down the GPU device. */
33
34
  useShader(source: string, set?: Record<string, unknown>, label?: string): this;
34
35
  set(values: Record<string, unknown>): this;
36
+ /**
37
+ * Merge a uniform record for audio-reactive / slider helpers.
38
+ * Best-effort GPU write: missing WGSL bindings are kept on the session.
39
+ */
40
+ setUniforms(values: Record<string, unknown>): this;
41
+ /** Last values passed to `set` / `setUniforms` / `useShader`. */
42
+ uniforms(): Record<string, unknown>;
35
43
  /** Draw one frame and return tightly packed RGBA bytes. */
36
44
  render(): Promise<Uint8Array>;
37
45
  /** Draw, then blit to the terminal. */
@@ -44,5 +52,7 @@ export declare class ShaderSession {
44
52
  columns?: number;
45
53
  rows?: number;
46
54
  }): void;
55
+ /** Encode the last (or a freshly rendered) RGBA frame as PNG. */
56
+ capturePng(): Promise<Uint8Array>;
47
57
  dispose(): void;
48
58
  }
package/dist/session.js CHANGED
@@ -1,3 +1,4 @@
1
+ import { capturePng } from './png.js';
1
2
  import { FramePresenter } from './presenter.js';
2
3
  async function loadVgpu(adapter) {
3
4
  try {
@@ -25,6 +26,7 @@ export class ShaderSession {
25
26
  effect;
26
27
  target;
27
28
  lastFrame;
29
+ lastUniforms = {};
28
30
  constructor(vgpu, gpu, effect, target, presenter, width, height) {
29
31
  this.vgpu = vgpu;
30
32
  this.gpu = gpu;
@@ -50,7 +52,10 @@ export class ShaderSession {
50
52
  stdout: options.stdout,
51
53
  mode: options.present,
52
54
  });
53
- return new ShaderSession(vgpu, gpu, fx, colorTarget, presenter, width, height);
55
+ const session = new ShaderSession(vgpu, gpu, fx, colorTarget, presenter, width, height);
56
+ if (options.set !== undefined)
57
+ session.lastUniforms = { ...options.set };
58
+ return session;
54
59
  }
55
60
  /** Replace the fragment shader without tearing down the GPU device. */
56
61
  useShader(source, set, label) {
@@ -58,12 +63,38 @@ export class ShaderSession {
58
63
  label: label ?? 'mudah-vgpu',
59
64
  set,
60
65
  });
66
+ if (set !== undefined)
67
+ this.lastUniforms = { ...this.lastUniforms, ...set };
61
68
  return this;
62
69
  }
63
70
  set(values) {
71
+ this.lastUniforms = { ...this.lastUniforms, ...values };
64
72
  this.effect.set(values);
65
73
  return this;
66
74
  }
75
+ /**
76
+ * Merge a uniform record for audio-reactive / slider helpers.
77
+ * Best-effort GPU write: missing WGSL bindings are kept on the session.
78
+ */
79
+ setUniforms(values) {
80
+ this.lastUniforms = { ...this.lastUniforms, ...flattenUniforms(values) };
81
+ try {
82
+ this.effect.set(values);
83
+ }
84
+ catch {
85
+ try {
86
+ this.effect.set({ params: this.lastUniforms });
87
+ }
88
+ catch {
89
+ // Shader has no matching bindings; values stay on `uniforms()`.
90
+ }
91
+ }
92
+ return this;
93
+ }
94
+ /** Last values passed to `set` / `setUniforms` / `useShader`. */
95
+ uniforms() {
96
+ return { ...this.lastUniforms };
97
+ }
67
98
  /** Draw one frame and return tightly packed RGBA bytes. */
68
99
  async render() {
69
100
  const errors = [];
@@ -95,9 +126,23 @@ export class ShaderSession {
95
126
  return;
96
127
  this.presenter.present(this.lastFrame, this.width, this.height, layout);
97
128
  }
129
+ /** Encode the last (or a freshly rendered) RGBA frame as PNG. */
130
+ async capturePng() {
131
+ const pixels = this.lastFrame ?? (await this.render());
132
+ return capturePng(pixels, this.width, this.height);
133
+ }
98
134
  dispose() {
99
135
  this.presenter.clear();
100
136
  this.gpu.dispose();
101
137
  }
102
138
  }
139
+ function flattenUniforms(values) {
140
+ const out = { ...values };
141
+ const params = values['params'];
142
+ if (params !== undefined && typeof params === 'object' && params !== null && !Array.isArray(params)) {
143
+ Object.assign(out, params);
144
+ delete out['params'];
145
+ }
146
+ return out;
147
+ }
103
148
  //# sourceMappingURL=session.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"session.js","sourceRoot":"","sources":["../src/session.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"session.js","sourceRoot":"","sources":["../src/session.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,23 @@
1
+ export interface ShaderSliderParam {
2
+ name: string;
3
+ min: number;
4
+ max: number;
5
+ value: number;
6
+ }
7
+ export interface ShaderSlidersOptions {
8
+ /** Bar width in cells. Default 8. */
9
+ width?: number;
10
+ }
11
+ /**
12
+ * Data model for shader parameter sliders. `renderLines()` is a TUI overlay
13
+ * fragment — enough to paint next to a framebuffer without a full Program.
14
+ */
15
+ export declare class ShaderSliders {
16
+ readonly params: ShaderSliderParam[];
17
+ private readonly width;
18
+ constructor(params: readonly ShaderSliderParam[], options?: ShaderSlidersOptions);
19
+ set(name: string, value: number): void;
20
+ snapshot(): Record<string, number>;
21
+ /** One line per param, e.g. `energy [████░░] 0.62`. */
22
+ renderLines(): string[];
23
+ }
@@ -0,0 +1,51 @@
1
+ function clamp(value, min, max) {
2
+ return Math.min(max, Math.max(min, value));
3
+ }
4
+ /**
5
+ * Data model for shader parameter sliders. `renderLines()` is a TUI overlay
6
+ * fragment — enough to paint next to a framebuffer without a full Program.
7
+ */
8
+ export class ShaderSliders {
9
+ params;
10
+ width;
11
+ constructor(params, options = {}) {
12
+ this.params = params.map((param) => ({
13
+ name: param.name,
14
+ min: param.min,
15
+ max: param.max,
16
+ value: clamp(param.value, param.min, param.max),
17
+ }));
18
+ this.width = options.width ?? 8;
19
+ }
20
+ set(name, value) {
21
+ const param = this.params.find((entry) => entry.name === name);
22
+ if (param === undefined)
23
+ throw new Error(`[vgpu] Unknown slider "${name}".`);
24
+ param.value = clamp(value, param.min, param.max);
25
+ }
26
+ snapshot() {
27
+ const out = {};
28
+ for (const param of this.params)
29
+ out[param.name] = param.value;
30
+ return out;
31
+ }
32
+ /** One line per param, e.g. `energy [████░░] 0.62`. */
33
+ renderLines() {
34
+ const nameWidth = Math.max(0, ...this.params.map((param) => param.name.length));
35
+ return this.params.map((param) => {
36
+ const span = param.max - param.min;
37
+ const t = span === 0 ? 1 : (param.value - param.min) / span;
38
+ const filled = Math.round(this.width * clamp(t, 0, 1));
39
+ const bar = `${'█'.repeat(filled)}${'░'.repeat(this.width - filled)}`;
40
+ const name = param.name.padEnd(nameWidth);
41
+ return `${name} [${bar}] ${formatValue(param.value)}`;
42
+ });
43
+ }
44
+ }
45
+ function formatValue(value) {
46
+ const abs = Math.abs(value);
47
+ if (abs !== 0 && (abs < 0.01 || abs >= 100))
48
+ return value.toFixed(2);
49
+ return value.toFixed(2);
50
+ }
51
+ //# sourceMappingURL=sliders.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sliders.js","sourceRoot":"","sources":["../src/sliders.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,23 @@
1
+ /**
2
+ * Minimal `fs.watch` surface. Tests inject a fake so they never touch the
3
+ * real filesystem or need a live file.
4
+ */
5
+ export type ShaderWatchFn = (path: string, listener: (eventType: string, filename: string | Buffer | null) => void) => {
6
+ close(): void;
7
+ };
8
+ export interface WatchShaderOptions {
9
+ /** Aborting this signal disposes the watcher. */
10
+ signal?: AbortSignal;
11
+ /** Watch implementation (default `fs.watch`). */
12
+ watch?: ShaderWatchFn;
13
+ /** File reader invoked when the watcher fires (default `readFileSync`). */
14
+ readFile?: (path: string) => string;
15
+ }
16
+ /**
17
+ * Watch a WGSL file and invoke `onChange` with the new source on every
18
+ * filesystem event. Returns a disposer. `signal` abort is equivalent to
19
+ * calling the disposer.
20
+ */
21
+ export declare function watchShader(path: string, onChange: (source: string) => void, options?: WatchShaderOptions): () => void;
22
+ /** True when `argv` contains `--watch` or `-w`. */
23
+ export declare function parseWatchFlag(argv?: readonly string[]): boolean;
package/dist/watch.js ADDED
@@ -0,0 +1,53 @@
1
+ import { watch as fsWatch, readFileSync } from 'node:fs';
2
+ /**
3
+ * Watch a WGSL file and invoke `onChange` with the new source on every
4
+ * filesystem event. Returns a disposer. `signal` abort is equivalent to
5
+ * calling the disposer.
6
+ */
7
+ export function watchShader(path, onChange, options = {}) {
8
+ const watchFn = options.watch ?? defaultWatch;
9
+ const read = options.readFile ?? defaultRead;
10
+ let watcher;
11
+ const dispose = () => {
12
+ if (watcher === undefined)
13
+ return;
14
+ try {
15
+ watcher.close();
16
+ }
17
+ catch {
18
+ // already closed
19
+ }
20
+ watcher = undefined;
21
+ };
22
+ if (options.signal?.aborted)
23
+ return dispose;
24
+ const fire = () => {
25
+ try {
26
+ onChange(read(path));
27
+ }
28
+ catch {
29
+ // Missing file or the reader rejected the path.
30
+ }
31
+ };
32
+ try {
33
+ watcher = watchFn(path, () => {
34
+ fire();
35
+ });
36
+ }
37
+ catch {
38
+ return dispose;
39
+ }
40
+ options.signal?.addEventListener('abort', dispose, { once: true });
41
+ return dispose;
42
+ }
43
+ function defaultWatch(path, listener) {
44
+ return fsWatch(path, listener);
45
+ }
46
+ function defaultRead(path) {
47
+ return readFileSync(path, 'utf8');
48
+ }
49
+ /** True when `argv` contains `--watch` or `-w`. */
50
+ export function parseWatchFlag(argv = process.argv) {
51
+ return argv.includes('--watch') || argv.includes('-w');
52
+ }
53
+ //# sourceMappingURL=watch.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"watch.js","sourceRoot":"","sources":["../src/watch.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,IAAI,OAAO,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AAoBzD;;;;GAIG;AACH,MAAM,UAAU,WAAW,CACzB,IAAY,EACZ,QAAkC,EAClC,OAAO,GAAuB,EAAE;IAEhC,MAAM,OAAO,GAAG,OAAO,CAAC,KAAK,IAAI,YAAY,CAAC;IAC9C,MAAM,IAAI,GAAG,OAAO,CAAC,QAAQ,IAAI,WAAW,CAAC;IAC7C,IAAI,OAAsC,CAAC;IAE3C,MAAM,OAAO,GAAG,GAAS,EAAE;QACzB,IAAI,OAAO,KAAK,SAAS;YAAE,OAAO;QAClC,IAAI,CAAC;YACH,OAAO,CAAC,KAAK,EAAE,CAAC;QAClB,CAAC;QAAC,MAAM,CAAC;YACP,iBAAiB;QACnB,CAAC;QACD,OAAO,GAAG,SAAS,CAAC;IACtB,CAAC,CAAC;IAEF,IAAI,OAAO,CAAC,MAAM,EAAE,OAAO;QAAE,OAAO,OAAO,CAAC;IAE5C,MAAM,IAAI,GAAG,GAAS,EAAE;QACtB,IAAI,CAAC;YACH,QAAQ,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC;QACvB,CAAC;QAAC,MAAM,CAAC;YACP,gDAAgD;QAClD,CAAC;IACH,CAAC,CAAC;IAEF,IAAI,CAAC;QACH,OAAO,GAAG,OAAO,CAAC,IAAI,EAAE,GAAG,EAAE;YAC3B,IAAI,EAAE,CAAC;QACT,CAAC,CAAC,CAAC;IACL,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,OAAO,CAAC;IACjB,CAAC;IAED,OAAO,CAAC,MAAM,EAAE,gBAAgB,CAAC,OAAO,EAAE,OAAO,EAAE,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC,CAAC;IACnE,OAAO,OAAO,CAAC;AACjB,CAAC;AAED,SAAS,YAAY,CACnB,IAAY,EACZ,QAAuE;IAEvE,OAAO,OAAO,CAAC,IAAI,EAAE,QAAQ,CAAC,CAAC;AACjC,CAAC;AAED,SAAS,WAAW,CAAC,IAAY;IAC/B,OAAO,YAAY,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC;AACpC,CAAC;AAED,mDAAmD;AACnD,MAAM,UAAU,cAAc,CAAC,IAAI,GAAsB,OAAO,CAAC,IAAI;IACnE,OAAO,IAAI,CAAC,QAAQ,CAAC,SAAS,CAAC,IAAI,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC;AACzD,CAAC"}
package/package.json CHANGED
@@ -1,9 +1,24 @@
1
1
  {
2
2
  "name": "@mudah-cli/vgpu",
3
- "version": "0.7.0",
3
+ "version": "0.9.0",
4
4
  "description": "Run vgpu WGSL effects inside a Mudah app and blit the pixels to the terminal (Kitty graphics, with a half-block fallback).",
5
5
  "type": "module",
6
6
  "license": "MIT",
7
+ "author": "Mudah Contributors",
8
+ "homepage": "https://github.com/thesimonharms/mudah#readme",
9
+ "repository": {
10
+ "type": "git",
11
+ "url": "git+https://github.com/thesimonharms/mudah.git",
12
+ "directory": "packages/vgpu"
13
+ },
14
+ "keywords": [
15
+ "terminal",
16
+ "gpu",
17
+ "wgsl",
18
+ "shader",
19
+ "kitty",
20
+ "typescript"
21
+ ],
7
22
  "engines": {
8
23
  "node": ">=26"
9
24
  },
@@ -18,7 +33,7 @@
18
33
  ],
19
34
  "sideEffects": false,
20
35
  "dependencies": {
21
- "@mudah-cli/terminal": "^0.7.0",
36
+ "@mudah-cli/terminal": "^0.9.0",
22
37
  "vgpu": "^0.3.1"
23
38
  },
24
39
  "publishConfig": {