@viji-dev/core 0.3.26 → 0.3.28

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.
@@ -1792,6 +1792,7 @@ class P5WorkerAdapter {
1792
1792
  this.offscreenCanvas = offscreenCanvas;
1793
1793
  this.setupFn = sceneCode.setup || null;
1794
1794
  this.renderFn = sceneCode.render;
1795
+ this.p5CanvasMode = sceneCode.canvasMode === "webgl" ? "webgl" : "2d";
1795
1796
  this.installMinimalShims();
1796
1797
  }
1797
1798
  p5Instance = null;
@@ -1804,6 +1805,7 @@ class P5WorkerAdapter {
1804
1805
  imageParameterCache = /* @__PURE__ */ new Map();
1805
1806
  // Track if P5.js's main canvas has been created
1806
1807
  mainCanvasCreated = false;
1808
+ p5CanvasMode;
1807
1809
  /**
1808
1810
  * Initialize P5 instance after P5.js library is loaded
1809
1811
  * This must be called after the P5 class is available
@@ -1816,7 +1818,15 @@ class P5WorkerAdapter {
1816
1818
  new this.p5Class((p) => {
1817
1819
  this.p5Instance = p;
1818
1820
  p.setup = () => {
1819
- p.createCanvas(this.offscreenCanvas.width, this.offscreenCanvas.height);
1821
+ if (this.p5CanvasMode === "webgl") {
1822
+ p.createCanvas(
1823
+ this.offscreenCanvas.width,
1824
+ this.offscreenCanvas.height,
1825
+ p.WEBGL
1826
+ );
1827
+ } else {
1828
+ p.createCanvas(this.offscreenCanvas.width, this.offscreenCanvas.height);
1829
+ }
1820
1830
  p.noLoop();
1821
1831
  this.p5InternalSetupComplete = true;
1822
1832
  resolve();
@@ -2052,8 +2062,8 @@ class P5WorkerAdapter {
2052
2062
  height: bitmap.height
2053
2063
  };
2054
2064
  }
2055
- if (Array.isArray(vijiAPI.streams)) {
2056
- for (const stream of vijiAPI.streams) {
2065
+ if (Array.isArray(vijiAPI.videoStreams)) {
2066
+ for (const stream of vijiAPI.videoStreams) {
2057
2067
  if (stream?.currentFrame instanceof OffscreenCanvas) {
2058
2068
  const canvas = stream.currentFrame;
2059
2069
  stream.currentFrame = {
@@ -2379,12 +2389,14 @@ class ShaderWorkerAdapter {
2379
2389
  this.gl = gl;
2380
2390
  }
2381
2391
  }
2382
- static MAX_STREAMS = 8;
2392
+ static MAX_VIDEO_STREAMS = 8;
2383
2393
  // Maximum number of compositor input streams
2384
2394
  static MAX_EXTERNAL_DEVICES = 8;
2385
2395
  // Maximum number of external devices
2386
2396
  static MAX_DEVICE_VIDEOS = 8;
2387
2397
  // Maximum number of device video streams
2398
+ static MAX_AUDIO_STREAMS = 8;
2399
+ // Maximum number of additional audio streams
2388
2400
  gl;
2389
2401
  program = null;
2390
2402
  uniformLocations = /* @__PURE__ */ new Map();
@@ -2404,7 +2416,7 @@ class ShaderWorkerAdapter {
2404
2416
  segmentationTexture = null;
2405
2417
  keyboardTexture = null;
2406
2418
  // Multi-stream textures
2407
- streamTextures = [];
2419
+ videoStreamTextures = [];
2408
2420
  // Device video textures
2409
2421
  deviceTextures = new Array(ShaderWorkerAdapter.MAX_DEVICE_VIDEOS).fill(null);
2410
2422
  // Accumulator state (CPU-side phase accumulators for smooth parameter-driven animation)
@@ -2692,31 +2704,31 @@ uniform vec2 u_videoResolution; // Video frame width and height in pixels
2692
2704
  uniform float u_videoFrameRate; // Video frame rate in frames per second
2693
2705
 
2694
2706
  // Multi-Stream Compositor Support (using individual uniforms due to WebGL 1.0 limitations)
2695
- uniform int u_streamCount; // Number of available compositor input streams (0-8)
2696
- uniform sampler2D u_stream0; // Stream 0 texture
2697
- uniform sampler2D u_stream1; // Stream 1 texture
2698
- uniform sampler2D u_stream2; // Stream 2 texture
2699
- uniform sampler2D u_stream3; // Stream 3 texture
2700
- uniform sampler2D u_stream4; // Stream 4 texture
2701
- uniform sampler2D u_stream5; // Stream 5 texture
2702
- uniform sampler2D u_stream6; // Stream 6 texture
2703
- uniform sampler2D u_stream7; // Stream 7 texture
2704
- uniform vec2 u_stream0Resolution; // Stream 0 resolution
2705
- uniform vec2 u_stream1Resolution; // Stream 1 resolution
2706
- uniform vec2 u_stream2Resolution; // Stream 2 resolution
2707
- uniform vec2 u_stream3Resolution; // Stream 3 resolution
2708
- uniform vec2 u_stream4Resolution; // Stream 4 resolution
2709
- uniform vec2 u_stream5Resolution; // Stream 5 resolution
2710
- uniform vec2 u_stream6Resolution; // Stream 6 resolution
2711
- uniform vec2 u_stream7Resolution; // Stream 7 resolution
2712
- uniform bool u_stream0Connected; // Stream 0 connection status
2713
- uniform bool u_stream1Connected; // Stream 1 connection status
2714
- uniform bool u_stream2Connected; // Stream 2 connection status
2715
- uniform bool u_stream3Connected; // Stream 3 connection status
2716
- uniform bool u_stream4Connected; // Stream 4 connection status
2717
- uniform bool u_stream5Connected; // Stream 5 connection status
2718
- uniform bool u_stream6Connected; // Stream 6 connection status
2719
- uniform bool u_stream7Connected; // Stream 7 connection status
2707
+ uniform int u_videoStreamCount; // Number of available compositor input streams (0-8)
2708
+ uniform sampler2D u_videoStream0; // Video stream 0 texture
2709
+ uniform sampler2D u_videoStream1; // Video stream 1 texture
2710
+ uniform sampler2D u_videoStream2; // Video stream 2 texture
2711
+ uniform sampler2D u_videoStream3; // Video stream 3 texture
2712
+ uniform sampler2D u_videoStream4; // Video stream 4 texture
2713
+ uniform sampler2D u_videoStream5; // Video stream 5 texture
2714
+ uniform sampler2D u_videoStream6; // Video stream 6 texture
2715
+ uniform sampler2D u_videoStream7; // Video stream 7 texture
2716
+ uniform vec2 u_videoStream0Resolution; // Video stream 0 resolution
2717
+ uniform vec2 u_videoStream1Resolution; // Video stream 1 resolution
2718
+ uniform vec2 u_videoStream2Resolution; // Video stream 2 resolution
2719
+ uniform vec2 u_videoStream3Resolution; // Video stream 3 resolution
2720
+ uniform vec2 u_videoStream4Resolution; // Video stream 4 resolution
2721
+ uniform vec2 u_videoStream5Resolution; // Video stream 5 resolution
2722
+ uniform vec2 u_videoStream6Resolution; // Video stream 6 resolution
2723
+ uniform vec2 u_videoStream7Resolution; // Video stream 7 resolution
2724
+ uniform bool u_videoStream0Connected; // Video stream 0 connection status
2725
+ uniform bool u_videoStream1Connected; // Video stream 1 connection status
2726
+ uniform bool u_videoStream2Connected; // Video stream 2 connection status
2727
+ uniform bool u_videoStream3Connected; // Video stream 3 connection status
2728
+ uniform bool u_videoStream4Connected; // Video stream 4 connection status
2729
+ uniform bool u_videoStream5Connected; // Video stream 5 connection status
2730
+ uniform bool u_videoStream6Connected; // Video stream 6 connection status
2731
+ uniform bool u_videoStream7Connected; // Video stream 7 connection status
2720
2732
 
2721
2733
  // Device Video Support (device cameras)
2722
2734
  uniform int u_deviceCount; // Number of device videos (0-8)
@@ -2745,6 +2757,81 @@ uniform bool u_device5Connected; // Device 5 connection status
2745
2757
  uniform bool u_device6Connected; // Device 6 connection status
2746
2758
  uniform bool u_device7Connected; // Device 7 connection status
2747
2759
 
2760
+ // Additional Audio Streams (lightweight analysis, no beat/FFT)
2761
+ uniform int u_audioStreamCount; // Number of additional audio streams (0-8)
2762
+ uniform bool u_audioStream0Connected; // Stream 0 connection status
2763
+ uniform float u_audioStream0Volume; // Stream 0 volume (0-1)
2764
+ uniform float u_audioStream0Low; // Stream 0 low band (0-1)
2765
+ uniform float u_audioStream0LowMid; // Stream 0 low-mid band (0-1)
2766
+ uniform float u_audioStream0Mid; // Stream 0 mid band (0-1)
2767
+ uniform float u_audioStream0HighMid; // Stream 0 high-mid band (0-1)
2768
+ uniform float u_audioStream0High; // Stream 0 high band (0-1)
2769
+ uniform float u_audioStream0Brightness; // Stream 0 spectral brightness (0-1)
2770
+ uniform float u_audioStream0Flatness; // Stream 0 spectral flatness (0-1)
2771
+ uniform bool u_audioStream1Connected;
2772
+ uniform float u_audioStream1Volume;
2773
+ uniform float u_audioStream1Low;
2774
+ uniform float u_audioStream1LowMid;
2775
+ uniform float u_audioStream1Mid;
2776
+ uniform float u_audioStream1HighMid;
2777
+ uniform float u_audioStream1High;
2778
+ uniform float u_audioStream1Brightness;
2779
+ uniform float u_audioStream1Flatness;
2780
+ uniform bool u_audioStream2Connected;
2781
+ uniform float u_audioStream2Volume;
2782
+ uniform float u_audioStream2Low;
2783
+ uniform float u_audioStream2LowMid;
2784
+ uniform float u_audioStream2Mid;
2785
+ uniform float u_audioStream2HighMid;
2786
+ uniform float u_audioStream2High;
2787
+ uniform float u_audioStream2Brightness;
2788
+ uniform float u_audioStream2Flatness;
2789
+ uniform bool u_audioStream3Connected;
2790
+ uniform float u_audioStream3Volume;
2791
+ uniform float u_audioStream3Low;
2792
+ uniform float u_audioStream3LowMid;
2793
+ uniform float u_audioStream3Mid;
2794
+ uniform float u_audioStream3HighMid;
2795
+ uniform float u_audioStream3High;
2796
+ uniform float u_audioStream3Brightness;
2797
+ uniform float u_audioStream3Flatness;
2798
+ uniform bool u_audioStream4Connected;
2799
+ uniform float u_audioStream4Volume;
2800
+ uniform float u_audioStream4Low;
2801
+ uniform float u_audioStream4LowMid;
2802
+ uniform float u_audioStream4Mid;
2803
+ uniform float u_audioStream4HighMid;
2804
+ uniform float u_audioStream4High;
2805
+ uniform float u_audioStream4Brightness;
2806
+ uniform float u_audioStream4Flatness;
2807
+ uniform bool u_audioStream5Connected;
2808
+ uniform float u_audioStream5Volume;
2809
+ uniform float u_audioStream5Low;
2810
+ uniform float u_audioStream5LowMid;
2811
+ uniform float u_audioStream5Mid;
2812
+ uniform float u_audioStream5HighMid;
2813
+ uniform float u_audioStream5High;
2814
+ uniform float u_audioStream5Brightness;
2815
+ uniform float u_audioStream5Flatness;
2816
+ uniform bool u_audioStream6Connected;
2817
+ uniform float u_audioStream6Volume;
2818
+ uniform float u_audioStream6Low;
2819
+ uniform float u_audioStream6LowMid;
2820
+ uniform float u_audioStream6Mid;
2821
+ uniform float u_audioStream6HighMid;
2822
+ uniform float u_audioStream6High;
2823
+ uniform float u_audioStream6Brightness;
2824
+ uniform float u_audioStream6Flatness;
2825
+ uniform bool u_audioStream7Connected;
2826
+ uniform float u_audioStream7Volume;
2827
+ uniform float u_audioStream7Low;
2828
+ uniform float u_audioStream7LowMid;
2829
+ uniform float u_audioStream7Mid;
2830
+ uniform float u_audioStream7HighMid;
2831
+ uniform float u_audioStream7High;
2832
+ uniform float u_audioStream7Brightness;
2833
+ uniform float u_audioStream7Flatness;
2834
+
2748
2835
  // CV - Face Detection & Expressions
2749
2836
  uniform int u_faceCount; // Number of detected faces (0-1)
2750
2837
  uniform vec4 u_face0Bounds; // First face bounding box (x, y, width, height) normalized 0-1
@@ -2973,7 +3060,7 @@ ${error}`);
2973
3060
  if (info) {
2974
3061
  const location = gl.getUniformLocation(this.program, info.name);
2975
3062
  this.uniformLocations.set(info.name, location);
2976
- if (info.name.includes("stream") || info.name.includes("u_stream")) {
3063
+ if (info.name.includes("Stream") || info.name.includes("stream")) {
2977
3064
  streamUniforms.push(info.name);
2978
3065
  }
2979
3066
  }
@@ -2993,8 +3080,11 @@ ${error}`);
2993
3080
  this.textureUnits.set("u_video", this.nextTextureUnit++);
2994
3081
  this.textureUnits.set("u_segmentationMask", this.nextTextureUnit++);
2995
3082
  this.textureUnits.set("u_keyboard", this.nextTextureUnit++);
2996
- for (let i = 0; i < ShaderWorkerAdapter.MAX_STREAMS; i++) {
2997
- this.textureUnits.set(`u_stream${i}`, this.nextTextureUnit++);
3083
+ for (let i = 0; i < ShaderWorkerAdapter.MAX_VIDEO_STREAMS; i++) {
3084
+ this.textureUnits.set(`u_videoStream${i}`, this.nextTextureUnit++);
3085
+ }
3086
+ for (let i = 0; i < ShaderWorkerAdapter.MAX_DEVICE_VIDEOS; i++) {
3087
+ this.textureUnits.set(`u_device${i}`, this.nextTextureUnit++);
2998
3088
  }
2999
3089
  if (this.backbufferEnabled) {
3000
3090
  this.textureUnits.set("backbuffer", this.nextTextureUnit++);
@@ -3165,18 +3255,18 @@ ${error}`);
3165
3255
  this.setUniform("u_videoResolution", "vec2", [0, 0]);
3166
3256
  this.setUniform("u_videoFrameRate", "float", 0);
3167
3257
  }
3168
- const streams = viji.streams || [];
3169
- const streamCount = Math.min(streams.length, ShaderWorkerAdapter.MAX_STREAMS);
3170
- this.setUniform("u_streamCount", "int", streamCount);
3171
- for (let i = 0; i < ShaderWorkerAdapter.MAX_STREAMS; i++) {
3172
- const connectedUniform = `u_stream${i}Connected`;
3173
- const resolutionUniform = `u_stream${i}Resolution`;
3174
- if (i < streamCount && streams[i]?.isConnected && streams[i]?.currentFrame) {
3175
- this.updateStreamTexture(i, streams[i].currentFrame);
3258
+ const videoStreams = viji.videoStreams || [];
3259
+ const videoStreamCount = Math.min(videoStreams.length, ShaderWorkerAdapter.MAX_VIDEO_STREAMS);
3260
+ this.setUniform("u_videoStreamCount", "int", videoStreamCount);
3261
+ for (let i = 0; i < ShaderWorkerAdapter.MAX_VIDEO_STREAMS; i++) {
3262
+ const connectedUniform = `u_videoStream${i}Connected`;
3263
+ const resolutionUniform = `u_videoStream${i}Resolution`;
3264
+ if (i < videoStreamCount && videoStreams[i]?.isConnected && videoStreams[i]?.currentFrame) {
3265
+ this.updateVideoStreamTexture(i, videoStreams[i].currentFrame);
3176
3266
  this.setUniform(
3177
3267
  resolutionUniform,
3178
3268
  "vec2",
3179
- [streams[i].frameWidth, streams[i].frameHeight]
3269
+ [videoStreams[i].frameWidth, videoStreams[i].frameHeight]
3180
3270
  );
3181
3271
  this.setUniform(connectedUniform, "bool", true);
3182
3272
  } else {
@@ -3203,6 +3293,34 @@ ${error}`);
3203
3293
  this.setUniform(connectedUniform, "bool", false);
3204
3294
  }
3205
3295
  }
3296
+ const audioStreams = viji.audioStreams || [];
3297
+ const audioStreamCount = Math.min(audioStreams.length, ShaderWorkerAdapter.MAX_AUDIO_STREAMS);
3298
+ this.setUniform("u_audioStreamCount", "int", audioStreamCount);
3299
+ for (let i = 0; i < ShaderWorkerAdapter.MAX_AUDIO_STREAMS; i++) {
3300
+ const prefix = `u_audioStream${i}`;
3301
+ if (i < audioStreamCount && audioStreams[i]?.isConnected) {
3302
+ const s = audioStreams[i];
3303
+ this.setUniform(`${prefix}Connected`, "bool", true);
3304
+ this.setUniform(`${prefix}Volume`, "float", s.volume?.current || 0);
3305
+ this.setUniform(`${prefix}Low`, "float", s.bands?.low || 0);
3306
+ this.setUniform(`${prefix}LowMid`, "float", s.bands?.lowMid || 0);
3307
+ this.setUniform(`${prefix}Mid`, "float", s.bands?.mid || 0);
3308
+ this.setUniform(`${prefix}HighMid`, "float", s.bands?.highMid || 0);
3309
+ this.setUniform(`${prefix}High`, "float", s.bands?.high || 0);
3310
+ this.setUniform(`${prefix}Brightness`, "float", s.spectral?.brightness || 0);
3311
+ this.setUniform(`${prefix}Flatness`, "float", s.spectral?.flatness || 0);
3312
+ } else {
3313
+ this.setUniform(`${prefix}Connected`, "bool", false);
3314
+ this.setUniform(`${prefix}Volume`, "float", 0);
3315
+ this.setUniform(`${prefix}Low`, "float", 0);
3316
+ this.setUniform(`${prefix}LowMid`, "float", 0);
3317
+ this.setUniform(`${prefix}Mid`, "float", 0);
3318
+ this.setUniform(`${prefix}HighMid`, "float", 0);
3319
+ this.setUniform(`${prefix}High`, "float", 0);
3320
+ this.setUniform(`${prefix}Brightness`, "float", 0);
3321
+ this.setUniform(`${prefix}Flatness`, "float", 0);
3322
+ }
3323
+ }
3206
3324
  const faces = video.faces || [];
3207
3325
  this.setUniform("u_faceCount", "int", faces.length);
3208
3326
  if (faces.length > 0) {
@@ -3732,18 +3850,18 @@ ${error}`);
3732
3850
  }
3733
3851
  }
3734
3852
  /**
3735
- * Update compositor stream texture at specified index
3853
+ * Update compositor video stream texture at specified index
3736
3854
  * Supports both OffscreenCanvas and ImageBitmap for zero-copy pipeline
3737
3855
  */
3738
- updateStreamTexture(index, streamFrame) {
3856
+ updateVideoStreamTexture(index, streamFrame) {
3739
3857
  const gl = this.gl;
3740
- const uniformName = `u_stream${index}`;
3858
+ const uniformName = `u_videoStream${index}`;
3741
3859
  const unit = this.textureUnits.get(uniformName);
3742
- if (!this.streamTextures[index]) {
3743
- this.streamTextures[index] = gl.createTexture();
3860
+ if (!this.videoStreamTextures[index]) {
3861
+ this.videoStreamTextures[index] = gl.createTexture();
3744
3862
  }
3745
3863
  gl.activeTexture(gl.TEXTURE0 + unit);
3746
- gl.bindTexture(gl.TEXTURE_2D, this.streamTextures[index]);
3864
+ gl.bindTexture(gl.TEXTURE_2D, this.videoStreamTextures[index]);
3747
3865
  const shouldFlip = streamFrame instanceof OffscreenCanvas;
3748
3866
  if (shouldFlip) {
3749
3867
  gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, true);
@@ -3781,7 +3899,8 @@ ${error}`);
3781
3899
  }
3782
3900
  const texture = this.deviceTextures[index];
3783
3901
  if (!texture) return;
3784
- const textureUnit = 8 + index;
3902
+ const textureUnit = this.textureUnits.get(`u_device${index}`);
3903
+ if (textureUnit === void 0) return;
3785
3904
  this.gl.activeTexture(this.gl.TEXTURE0 + textureUnit);
3786
3905
  this.gl.bindTexture(this.gl.TEXTURE_2D, texture);
3787
3906
  const shouldFlip = frame instanceof OffscreenCanvas;
@@ -3803,6 +3922,11 @@ ${error}`);
3803
3922
  this.gl.texParameteri(this.gl.TEXTURE_2D, this.gl.TEXTURE_MAG_FILTER, this.gl.LINEAR);
3804
3923
  this.gl.texParameteri(this.gl.TEXTURE_2D, this.gl.TEXTURE_WRAP_S, this.gl.CLAMP_TO_EDGE);
3805
3924
  this.gl.texParameteri(this.gl.TEXTURE_2D, this.gl.TEXTURE_WRAP_T, this.gl.CLAMP_TO_EDGE);
3925
+ const uniformName = `u_device${index}`;
3926
+ const location = this.uniformLocations.get(uniformName);
3927
+ if (location) {
3928
+ this.gl.uniform1i(location, textureUnit);
3929
+ }
3806
3930
  if (frame instanceof ImageBitmap) {
3807
3931
  frame.close();
3808
3932
  }
@@ -3934,10 +4058,10 @@ ${error}`);
3934
4058
  if (this.audioWaveformTexture) gl.deleteTexture(this.audioWaveformTexture);
3935
4059
  if (this.videoTexture) gl.deleteTexture(this.videoTexture);
3936
4060
  if (this.segmentationTexture) gl.deleteTexture(this.segmentationTexture);
3937
- for (const texture of this.streamTextures) {
4061
+ for (const texture of this.videoStreamTextures) {
3938
4062
  if (texture) gl.deleteTexture(texture);
3939
4063
  }
3940
- this.streamTextures = [];
4064
+ this.videoStreamTextures = [];
3941
4065
  for (const texture of this.deviceTextures) {
3942
4066
  if (texture) gl.deleteTexture(texture);
3943
4067
  }
@@ -3953,6 +4077,394 @@ ${error}`);
3953
4077
  if (this.quadBuffer) gl.deleteBuffer(this.quadBuffer);
3954
4078
  }
3955
4079
  }
4080
+ const _PK = {
4081
+ kty: "EC",
4082
+ crv: "P-256",
4083
+ x: "6bkPbVZXx-c08blVs6qIwm0gfSRLS2nnf2Tsz19ggtM",
4084
+ y: "lhi486ASKQkS8TGbVee-FpdAu7qKaO3gad_4ScX8qJI"
4085
+ };
4086
+ const GRACE_SECONDS = 60;
4087
+ let _key = null;
4088
+ let _restricted = false;
4089
+ let _cache = null;
4090
+ let _expiry = 0;
4091
+ function _b64url(s) {
4092
+ const padded = s.replace(/-/g, "+").replace(/_/g, "/");
4093
+ const bin = atob(padded);
4094
+ const bytes = new Uint8Array(bin.length);
4095
+ for (let i = 0; i < bin.length; i++) bytes[i] = bin.charCodeAt(i);
4096
+ return bytes;
4097
+ }
4098
+ async function init$1() {
4099
+ if (typeof crypto === "undefined" || !crypto.subtle) {
4100
+ _restricted = true;
4101
+ console.warn("[Viji] _SC: secure context required — restricted mode active");
4102
+ return;
4103
+ }
4104
+ try {
4105
+ _key = await crypto.subtle.importKey(
4106
+ "jwk",
4107
+ _PK,
4108
+ { name: "ECDSA", namedCurve: "P-256" },
4109
+ false,
4110
+ ["verify"]
4111
+ );
4112
+ } catch {
4113
+ _restricted = true;
4114
+ console.warn("[Viji] _SC: key import failed — restricted mode active");
4115
+ }
4116
+ }
4117
+ async function update(token) {
4118
+ if (_restricted || !_key) {
4119
+ _cache = null;
4120
+ _expiry = 0;
4121
+ return;
4122
+ }
4123
+ try {
4124
+ const parts = token.split(".");
4125
+ if (parts.length !== 3) throw new Error("malformed");
4126
+ const signingInput = new TextEncoder().encode(parts[0] + "." + parts[1]);
4127
+ const signature = _b64url(parts[2]);
4128
+ const valid = await crypto.subtle.verify(
4129
+ { name: "ECDSA", hash: "SHA-256" },
4130
+ _key,
4131
+ signature.buffer,
4132
+ signingInput.buffer
4133
+ );
4134
+ if (!valid) throw new Error("invalid signature");
4135
+ const payload = JSON.parse(new TextDecoder().decode(_b64url(parts[1])));
4136
+ _cache = payload.c ?? {};
4137
+ _expiry = payload.e ?? 0;
4138
+ } catch {
4139
+ _cache = null;
4140
+ _expiry = 0;
4141
+ }
4142
+ }
4143
+ function get$1(key, defaultValue) {
4144
+ if (_restricted || !_cache) return defaultValue;
4145
+ const now = Math.floor(Date.now() / 1e3);
4146
+ if (_expiry > 0 && _expiry + GRACE_SECONDS < now) {
4147
+ _cache = null;
4148
+ _expiry = 0;
4149
+ return defaultValue;
4150
+ }
4151
+ return typeof _cache[key] === "number" ? _cache[key] : defaultValue;
4152
+ }
4153
+ const FULL_W = 44, FULL_H = 74;
4154
+ const MINI_W = 44, MINI_H = 23;
4155
+ const STROKE_COLOR = "#F1F1F1";
4156
+ let _bufferFull = null;
4157
+ let _bufferMini = null;
4158
+ let _glTexFull = null;
4159
+ let _glTexMini = null;
4160
+ let _glProgram = null;
4161
+ let _glVbo = null;
4162
+ let _glVao = null;
4163
+ let _glInitFor = null;
4164
+ let _hint = 0;
4165
+ const VERT_SRC = `
4166
+ attribute vec2 a_pos;
4167
+ attribute vec2 a_uv;
4168
+ varying vec2 v_uv;
4169
+ void main() {
4170
+ v_uv = a_uv;
4171
+ gl_Position = vec4(a_pos, 0.0, 1.0);
4172
+ }`;
4173
+ const FRAG_SRC = `
4174
+ precision mediump float;
4175
+ uniform sampler2D u_tex;
4176
+ uniform float u_alpha;
4177
+ varying vec2 v_uv;
4178
+ void main() {
4179
+ gl_FragColor = texture2D(u_tex, v_uv) * u_alpha;
4180
+ }`;
4181
+ function renderFullLogo(ctx) {
4182
+ ctx.strokeStyle = STROKE_COLOR;
4183
+ ctx.lineCap = "round";
4184
+ ctx.lineWidth = 2.8;
4185
+ ctx.beginPath();
4186
+ ctx.moveTo(21.9991, 5);
4187
+ ctx.lineTo(5, 21.999);
4188
+ ctx.stroke();
4189
+ ctx.beginPath();
4190
+ ctx.moveTo(38.9981, 21.998);
4191
+ ctx.lineTo(21.999, 38.9971);
4192
+ ctx.stroke();
4193
+ ctx.beginPath();
4194
+ ctx.moveTo(38.9984, 5);
4195
+ ctx.lineTo(5, 38.9983);
4196
+ ctx.stroke();
4197
+ ctx.lineWidth = 3.5;
4198
+ ctx.beginPath();
4199
+ ctx.moveTo(5.00052, 5.00155);
4200
+ ctx.lineTo(5.00098, 5);
4201
+ ctx.stroke();
4202
+ ctx.beginPath();
4203
+ ctx.moveTo(38.9986, 38.9996);
4204
+ ctx.lineTo(38.999, 38.998);
4205
+ ctx.stroke();
4206
+ ctx.lineWidth = 2.8;
4207
+ ctx.lineJoin = "round";
4208
+ ctx.beginPath();
4209
+ ctx.moveTo(5.875, 53);
4210
+ ctx.lineTo(10.1417, 60);
4211
+ ctx.lineTo(13.875, 53);
4212
+ ctx.stroke();
4213
+ ctx.beginPath();
4214
+ ctx.moveTo(21, 53);
4215
+ ctx.lineTo(21, 60);
4216
+ ctx.stroke();
4217
+ ctx.beginPath();
4218
+ ctx.moveTo(38.125, 53);
4219
+ ctx.lineTo(38.125, 60);
4220
+ ctx.stroke();
4221
+ ctx.beginPath();
4222
+ ctx.moveTo(30, 53);
4223
+ ctx.lineTo(30, 62.1502);
4224
+ ctx.bezierCurveTo(30, 64.2764, 28.2764, 66, 26.1502, 66);
4225
+ ctx.lineTo(26, 66);
4226
+ ctx.stroke();
4227
+ }
4228
+ function renderMiniLogo(ctx) {
4229
+ ctx.strokeStyle = STROKE_COLOR;
4230
+ ctx.lineCap = "round";
4231
+ ctx.lineJoin = "round";
4232
+ ctx.lineWidth = 2.8;
4233
+ ctx.beginPath();
4234
+ ctx.moveTo(5.875, 5);
4235
+ ctx.lineTo(10.1417, 12);
4236
+ ctx.lineTo(13.875, 5);
4237
+ ctx.stroke();
4238
+ ctx.beginPath();
4239
+ ctx.moveTo(21, 5);
4240
+ ctx.lineTo(21, 12);
4241
+ ctx.stroke();
4242
+ ctx.beginPath();
4243
+ ctx.moveTo(38.125, 5);
4244
+ ctx.lineTo(38.125, 12);
4245
+ ctx.stroke();
4246
+ ctx.beginPath();
4247
+ ctx.moveTo(30, 5);
4248
+ ctx.lineTo(30, 14.1502);
4249
+ ctx.bezierCurveTo(30, 16.2764, 28.2764, 18, 26.1502, 18);
4250
+ ctx.lineTo(26, 18);
4251
+ ctx.stroke();
4252
+ }
4253
+ function init(_canvas) {
4254
+ try {
4255
+ _bufferFull = new OffscreenCanvas(FULL_W, FULL_H);
4256
+ const ctxFull = _bufferFull.getContext("2d");
4257
+ if (ctxFull) renderFullLogo(ctxFull);
4258
+ _bufferMini = new OffscreenCanvas(MINI_W, MINI_H);
4259
+ const ctxMini = _bufferMini.getContext("2d");
4260
+ if (ctxMini) renderMiniLogo(ctxMini);
4261
+ } catch (e) {
4262
+ console.warn("[Viji] overlay: init failed", e);
4263
+ }
4264
+ }
4265
+ function setRenderHint(h) {
4266
+ _hint = h;
4267
+ }
4268
+ function draw(canvas, ctx, gl, brandingMode, displayScale = 1) {
4269
+ if (brandingMode >= 2) return;
4270
+ if (!_bufferFull && !_bufferMini) return;
4271
+ const isMini = _hint === 1;
4272
+ const s = displayScale || 1;
4273
+ if (ctx) {
4274
+ draw2D(ctx, isMini, canvas.width, s);
4275
+ } else if (gl) {
4276
+ drawGL(gl, canvas, isMini, s);
4277
+ }
4278
+ }
4279
+ const GL_VERTEX_ARRAY_BINDING = 34229;
4280
+ function uploadTexture(gl, source) {
4281
+ if (!source) return null;
4282
+ const prevTexBinding = gl.getParameter(gl.TEXTURE_BINDING_2D);
4283
+ const prevFlipY = gl.getParameter(gl.UNPACK_FLIP_Y_WEBGL);
4284
+ const prevPremultiply = gl.getParameter(gl.UNPACK_PREMULTIPLY_ALPHA_WEBGL);
4285
+ gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, 1);
4286
+ gl.pixelStorei(gl.UNPACK_PREMULTIPLY_ALPHA_WEBGL, 0);
4287
+ const tex = gl.createTexture();
4288
+ gl.bindTexture(gl.TEXTURE_2D, tex);
4289
+ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
4290
+ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);
4291
+ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
4292
+ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
4293
+ gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, source);
4294
+ gl.bindTexture(gl.TEXTURE_2D, prevTexBinding);
4295
+ gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, prevFlipY ? 1 : 0);
4296
+ gl.pixelStorei(gl.UNPACK_PREMULTIPLY_ALPHA_WEBGL, prevPremultiply ? 1 : 0);
4297
+ return tex;
4298
+ }
4299
+ function initGL(gl) {
4300
+ if (_glInitFor === gl) return;
4301
+ _glInitFor = gl;
4302
+ _glTexFull = uploadTexture(gl, _bufferFull);
4303
+ _glTexMini = uploadTexture(gl, _bufferMini);
4304
+ const vs = gl.createShader(gl.VERTEX_SHADER);
4305
+ gl.shaderSource(vs, VERT_SRC);
4306
+ gl.compileShader(vs);
4307
+ const fs = gl.createShader(gl.FRAGMENT_SHADER);
4308
+ gl.shaderSource(fs, FRAG_SRC);
4309
+ gl.compileShader(fs);
4310
+ _glProgram = gl.createProgram();
4311
+ gl.attachShader(_glProgram, vs);
4312
+ gl.attachShader(_glProgram, fs);
4313
+ gl.linkProgram(_glProgram);
4314
+ gl.deleteShader(vs);
4315
+ gl.deleteShader(fs);
4316
+ if (!gl.getProgramParameter(_glProgram, gl.LINK_STATUS)) {
4317
+ console.warn("[Viji] overlay: shader link failed");
4318
+ _glProgram = null;
4319
+ return;
4320
+ }
4321
+ _glVbo = gl.createBuffer();
4322
+ const gl2 = gl;
4323
+ if (typeof gl2.createVertexArray === "function") {
4324
+ const prevVAO = gl.getParameter(GL_VERTEX_ARRAY_BINDING);
4325
+ const prevBuf = gl.getParameter(gl.ARRAY_BUFFER_BINDING);
4326
+ _glVao = gl2.createVertexArray();
4327
+ gl2.bindVertexArray(_glVao);
4328
+ gl.bindBuffer(gl.ARRAY_BUFFER, _glVbo);
4329
+ const aPos = gl.getAttribLocation(_glProgram, "a_pos");
4330
+ const aUv = gl.getAttribLocation(_glProgram, "a_uv");
4331
+ gl.enableVertexAttribArray(aPos);
4332
+ gl.enableVertexAttribArray(aUv);
4333
+ gl.vertexAttribPointer(aPos, 2, gl.FLOAT, false, 16, 0);
4334
+ gl.vertexAttribPointer(aUv, 2, gl.FLOAT, false, 16, 8);
4335
+ gl2.bindVertexArray(prevVAO);
4336
+ gl.bindBuffer(gl.ARRAY_BUFFER, prevBuf);
4337
+ }
4338
+ }
4339
+ function draw2D(ctx, mini, canvasW, s) {
4340
+ const buffer = mini ? _bufferMini : _bufferFull;
4341
+ if (!buffer) return;
4342
+ const drawW = (mini ? MINI_W : FULL_W) * s;
4343
+ const drawH = (mini ? MINI_H : FULL_H) * s;
4344
+ const x = mini ? Math.round((canvasW - drawW) / 2) : Math.round(48 * s);
4345
+ const y = Math.round((mini ? 52 : 48) * s);
4346
+ ctx.save();
4347
+ ctx.globalAlpha = 0.85;
4348
+ ctx.drawImage(buffer, x, y, drawW, drawH);
4349
+ ctx.restore();
4350
+ }
4351
+ function drawGL(gl, canvas, mini, s) {
4352
+ initGL(gl);
4353
+ if (!_glProgram || !_glVbo) return;
4354
+ const tex = mini ? _glTexMini : _glTexFull;
4355
+ if (!tex) return;
4356
+ const logoW = (mini ? MINI_W : FULL_W) * s;
4357
+ const logoH = (mini ? MINI_H : FULL_H) * s;
4358
+ const cw = canvas.width;
4359
+ const ch = canvas.height;
4360
+ const px = mini ? Math.round((cw - logoW) / 2) : Math.round(48 * s);
4361
+ const py = Math.round((mini ? 52 : 48) * s);
4362
+ const x0 = px / cw * 2 - 1;
4363
+ const y0 = 1 - (py + logoH) / ch * 2;
4364
+ const x1 = (px + logoW) / cw * 2 - 1;
4365
+ const y1 = 1 - py / ch * 2;
4366
+ const gl2 = gl;
4367
+ const hasVAO = _glVao && typeof gl2.bindVertexArray === "function";
4368
+ const prevProgram = gl.getParameter(gl.CURRENT_PROGRAM);
4369
+ const prevActiveTexture = gl.getParameter(gl.ACTIVE_TEXTURE);
4370
+ gl.activeTexture(gl.TEXTURE0);
4371
+ const prevTex0 = gl.getParameter(gl.TEXTURE_BINDING_2D);
4372
+ const prevArrayBuffer = gl.getParameter(gl.ARRAY_BUFFER_BINDING);
4373
+ const prevFramebuffer = gl.getParameter(gl.FRAMEBUFFER_BINDING);
4374
+ const prevViewport = gl.getParameter(gl.VIEWPORT);
4375
+ const prevBlend = gl.isEnabled(gl.BLEND);
4376
+ const prevBlendSrcRGB = gl.getParameter(gl.BLEND_SRC_RGB);
4377
+ const prevBlendDstRGB = gl.getParameter(gl.BLEND_DST_RGB);
4378
+ const prevBlendSrcAlpha = gl.getParameter(gl.BLEND_SRC_ALPHA);
4379
+ const prevBlendDstAlpha = gl.getParameter(gl.BLEND_DST_ALPHA);
4380
+ const prevBlendEqRGB = gl.getParameter(gl.BLEND_EQUATION_RGB);
4381
+ const prevBlendEqAlpha = gl.getParameter(gl.BLEND_EQUATION_ALPHA);
4382
+ const prevDepthTest = gl.isEnabled(gl.DEPTH_TEST);
4383
+ const prevScissorTest = gl.isEnabled(gl.SCISSOR_TEST);
4384
+ const prevStencilTest = gl.isEnabled(gl.STENCIL_TEST);
4385
+ const prevCullFace = gl.isEnabled(gl.CULL_FACE);
4386
+ const prevColorMask = gl.getParameter(gl.COLOR_WRITEMASK);
4387
+ const prevUnpackFlipY = gl.getParameter(gl.UNPACK_FLIP_Y_WEBGL);
4388
+ const prevVAO = hasVAO ? gl.getParameter(GL_VERTEX_ARRAY_BINDING) : null;
4389
+ gl.bindFramebuffer(gl.FRAMEBUFFER, null);
4390
+ gl.viewport(0, 0, cw, ch);
4391
+ gl.useProgram(_glProgram);
4392
+ gl.enable(gl.BLEND);
4393
+ gl.blendEquation(gl.FUNC_ADD);
4394
+ gl.blendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA);
4395
+ gl.disable(gl.DEPTH_TEST);
4396
+ gl.disable(gl.SCISSOR_TEST);
4397
+ gl.disable(gl.STENCIL_TEST);
4398
+ gl.disable(gl.CULL_FACE);
4399
+ gl.colorMask(true, true, true, true);
4400
+ gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, 0);
4401
+ gl.bindTexture(gl.TEXTURE_2D, tex);
4402
+ gl.uniform1i(gl.getUniformLocation(_glProgram, "u_tex"), 0);
4403
+ gl.uniform1f(gl.getUniformLocation(_glProgram, "u_alpha"), 0.85);
4404
+ const verts = new Float32Array([
4405
+ x0,
4406
+ y0,
4407
+ 0,
4408
+ 0,
4409
+ x1,
4410
+ y0,
4411
+ 1,
4412
+ 0,
4413
+ x0,
4414
+ y1,
4415
+ 0,
4416
+ 1,
4417
+ x0,
4418
+ y1,
4419
+ 0,
4420
+ 1,
4421
+ x1,
4422
+ y0,
4423
+ 1,
4424
+ 0,
4425
+ x1,
4426
+ y1,
4427
+ 1,
4428
+ 1
4429
+ ]);
4430
+ if (hasVAO) {
4431
+ gl2.bindVertexArray(_glVao);
4432
+ gl.bindBuffer(gl.ARRAY_BUFFER, _glVbo);
4433
+ gl.bufferData(gl.ARRAY_BUFFER, verts, gl.DYNAMIC_DRAW);
4434
+ } else {
4435
+ gl.bindBuffer(gl.ARRAY_BUFFER, _glVbo);
4436
+ gl.bufferData(gl.ARRAY_BUFFER, verts, gl.DYNAMIC_DRAW);
4437
+ const aPos = gl.getAttribLocation(_glProgram, "a_pos");
4438
+ const aUv = gl.getAttribLocation(_glProgram, "a_uv");
4439
+ gl.enableVertexAttribArray(aPos);
4440
+ gl.enableVertexAttribArray(aUv);
4441
+ gl.vertexAttribPointer(aPos, 2, gl.FLOAT, false, 16, 0);
4442
+ gl.vertexAttribPointer(aUv, 2, gl.FLOAT, false, 16, 8);
4443
+ }
4444
+ gl.drawArrays(gl.TRIANGLES, 0, 6);
4445
+ if (hasVAO) gl2.bindVertexArray(prevVAO);
4446
+ gl.bindFramebuffer(gl.FRAMEBUFFER, prevFramebuffer);
4447
+ gl.viewport(prevViewport[0], prevViewport[1], prevViewport[2], prevViewport[3]);
4448
+ gl.bindBuffer(gl.ARRAY_BUFFER, prevArrayBuffer);
4449
+ gl.colorMask(prevColorMask[0], prevColorMask[1], prevColorMask[2], prevColorMask[3]);
4450
+ gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, prevUnpackFlipY ? 1 : 0);
4451
+ if (prevScissorTest) gl.enable(gl.SCISSOR_TEST);
4452
+ else gl.disable(gl.SCISSOR_TEST);
4453
+ if (prevStencilTest) gl.enable(gl.STENCIL_TEST);
4454
+ else gl.disable(gl.STENCIL_TEST);
4455
+ if (prevCullFace) gl.enable(gl.CULL_FACE);
4456
+ else gl.disable(gl.CULL_FACE);
4457
+ if (prevDepthTest) gl.enable(gl.DEPTH_TEST);
4458
+ else gl.disable(gl.DEPTH_TEST);
4459
+ if (prevBlend) gl.enable(gl.BLEND);
4460
+ else gl.disable(gl.BLEND);
4461
+ gl.blendFuncSeparate(prevBlendSrcRGB, prevBlendDstRGB, prevBlendSrcAlpha, prevBlendDstAlpha);
4462
+ gl.blendEquationSeparate(prevBlendEqRGB, prevBlendEqAlpha);
4463
+ gl.activeTexture(gl.TEXTURE0);
4464
+ gl.bindTexture(gl.TEXTURE_2D, prevTex0);
4465
+ gl.activeTexture(prevActiveTexture);
4466
+ gl.useProgram(prevProgram);
4467
+ }
3956
4468
  class VijiWorkerRuntime {
3957
4469
  canvas = null;
3958
4470
  ctx = null;
@@ -3972,6 +4484,8 @@ class VijiWorkerRuntime {
3972
4484
  // Shader adapter for shader mode
3973
4485
  shaderAdapter = null;
3974
4486
  rendererType = "native";
4487
+ _isHeadless = false;
4488
+ _displayScale = 1;
3975
4489
  // Pending capture requests (queue to handle multiple simultaneous requests)
3976
4490
  pendingCaptures = [];
3977
4491
  /**
@@ -4027,6 +4541,11 @@ class VijiWorkerRuntime {
4027
4541
  };
4028
4542
  // Map deviceId → streamIndex for O(1) device video lookup
4029
4543
  deviceVideoMap = /* @__PURE__ */ new Map();
4544
+ // Audio stream state management (mirroring video stream pattern)
4545
+ static AUDIO_ADDITIONAL_BASE = 1;
4546
+ static AUDIO_DEVICE_BASE = 100;
4547
+ audioStreamStates = /* @__PURE__ */ new Map();
4548
+ deviceAudioMap = /* @__PURE__ */ new Map();
4030
4549
  // Video state is now managed by the worker-side VideoSystem
4031
4550
  // Artist API object
4032
4551
  viji = {
@@ -4124,7 +4643,9 @@ class VijiWorkerRuntime {
4124
4643
  }
4125
4644
  },
4126
4645
  // Additional video streams (index 1+, no CV)
4127
- streams: [],
4646
+ videoStreams: [],
4647
+ // Additional audio streams (lightweight analysis, no beat detection)
4648
+ audioStreams: [],
4128
4649
  // Interaction APIs will be added during construction
4129
4650
  mouse: {},
4130
4651
  keyboard: {},
@@ -4195,7 +4716,7 @@ class VijiWorkerRuntime {
4195
4716
  * Initialize P5.js mode
4196
4717
  * Sets up P5 rendering with P5WorkerAdapter
4197
4718
  */
4198
- async initP5Mode(setup, render) {
4719
+ async initP5Mode(setup, render, options) {
4199
4720
  try {
4200
4721
  this.rendererType = "p5";
4201
4722
  this.debugLog("🎨 Initializing P5.js mode...");
@@ -4204,7 +4725,8 @@ class VijiWorkerRuntime {
4204
4725
  this.viji,
4205
4726
  {
4206
4727
  setup,
4207
- render
4728
+ render,
4729
+ canvasMode: options?.canvasMode === "webgl" ? "webgl" : "2d"
4208
4730
  }
4209
4731
  );
4210
4732
  await this.p5Adapter.init();
@@ -4296,20 +4818,47 @@ class VijiWorkerRuntime {
4296
4818
  if (this.videoSystems[0]) {
4297
4819
  Object.assign(this.viji.video, this.videoSystems[0].getVideoAPI());
4298
4820
  }
4299
- this.updateVijiStreams();
4821
+ this.updateVijiVideoStreams();
4300
4822
  }
4301
4823
  /**
4302
- * Updates viji.streams from videoSystems array.
4824
+ * Updates viji.videoStreams from videoSystems array.
4303
4825
  * Collects 'additional' MediaStreams first, then 'directFrame' injected frames.
4304
4826
  * Excludes 'main' and 'device' streams (those go to viji.video and viji.devices[].video).
4305
4827
  */
4306
- updateVijiStreams() {
4828
+ updateVijiVideoStreams() {
4307
4829
  const additional = this.videoSystems.filter((vs) => vs && vs.getStreamType() === "additional").map((vs) => vs.getVideoAPI());
4308
4830
  const directFrames = this.videoSystems.filter((vs) => vs && vs.getStreamType() === "directFrame").map((vs) => vs.getVideoAPI());
4309
4831
  const freshStreams = [...additional, ...directFrames];
4310
- this.viji.streams.length = freshStreams.length;
4832
+ this.viji.videoStreams.length = freshStreams.length;
4311
4833
  for (let i = 0; i < freshStreams.length; i++) {
4312
- this.viji.streams[i] = freshStreams[i];
4834
+ this.viji.videoStreams[i] = freshStreams[i];
4835
+ }
4836
+ }
4837
+ /**
4838
+ * Rebuilds viji.audioStreams from audioStreamStates.
4839
+ * Collects entries in the additional range (1-99), maps to AudioStreamAPI objects.
4840
+ */
4841
+ updateVijiAudioStreams() {
4842
+ const entries = [];
4843
+ for (const [idx, state2] of this.audioStreamStates) {
4844
+ if (idx >= VijiWorkerRuntime.AUDIO_ADDITIONAL_BASE && idx < VijiWorkerRuntime.AUDIO_DEVICE_BASE) {
4845
+ entries.push({ index: idx, ...state2 });
4846
+ }
4847
+ }
4848
+ entries.sort((a, b) => a.index - b.index);
4849
+ this.viji.audioStreams.length = entries.length;
4850
+ for (let i = 0; i < entries.length; i++) {
4851
+ const s = entries[i];
4852
+ const freqData = s.frequencyData;
4853
+ const waveData = s.waveformData;
4854
+ this.viji.audioStreams[i] = {
4855
+ isConnected: s.isConnected,
4856
+ volume: s.volume,
4857
+ bands: s.bands,
4858
+ spectral: s.spectral,
4859
+ getFrequencyData: () => freqData,
4860
+ getWaveform: () => waveData
4861
+ };
4313
4862
  }
4314
4863
  }
4315
4864
  // Send all parameters (from helper functions) to host
@@ -4353,6 +4902,9 @@ class VijiWorkerRuntime {
4353
4902
  case "audio-analysis-update":
4354
4903
  this.handleAudioAnalysisUpdate(message);
4355
4904
  break;
4905
+ case "audio-stream-setup":
4906
+ this.handleAudioStreamSetup(message);
4907
+ break;
4356
4908
  case "video-canvas-setup":
4357
4909
  this.handleVideoCanvasSetup(message);
4358
4910
  break;
@@ -4398,6 +4950,12 @@ class VijiWorkerRuntime {
4398
4950
  case "capture-frame":
4399
4951
  this.handleCaptureFrame(message);
4400
4952
  break;
4953
+ case "sc-update":
4954
+ update(message.data.t);
4955
+ break;
4956
+ case "rh-update":
4957
+ setRenderHint(message.data.h);
4958
+ break;
4401
4959
  }
4402
4960
  };
4403
4961
  }
@@ -4405,8 +4963,25 @@ class VijiWorkerRuntime {
4405
4963
  try {
4406
4964
  this.canvas = message.data.canvas;
4407
4965
  this.viji.canvas = this.canvas;
4966
+ this._isHeadless = !!message.data.isHeadless;
4967
+ const origGetContext = this.canvas.getContext.bind(this.canvas);
4968
+ this.canvas.getContext = (type, attrs) => {
4969
+ const result = origGetContext(type, attrs);
4970
+ if (result) {
4971
+ if (type === "2d") {
4972
+ this.ctx = result;
4973
+ this.viji.ctx = this.ctx;
4974
+ } else if (type === "webgl" || type === "webgl2") {
4975
+ this.gl = result;
4976
+ this.viji.gl = this.gl;
4977
+ }
4978
+ }
4979
+ return result;
4980
+ };
4408
4981
  this.viji.width = this.canvas.width;
4409
4982
  this.viji.height = this.canvas.height;
4983
+ init$1();
4984
+ init(this.canvas);
4410
4985
  this.startRenderLoop();
4411
4986
  this.postMessage("init-response", {
4412
4987
  id: message.id
@@ -4491,6 +5066,7 @@ class VijiWorkerRuntime {
4491
5066
  }
4492
5067
  this.viji.width = Math.round(message.data.effectiveWidth);
4493
5068
  this.viji.height = Math.round(message.data.effectiveHeight);
5069
+ this._displayScale = message.data.displayScale ?? 1;
4494
5070
  if (this.gl) {
4495
5071
  this.gl.viewport(0, 0, this.viji.width, this.viji.height);
4496
5072
  }
@@ -4510,8 +5086,8 @@ class VijiWorkerRuntime {
4510
5086
  }
4511
5087
  handleParameterBatchUpdate(message) {
4512
5088
  if (message.data && message.data.updates) {
4513
- for (const update of message.data.updates) {
4514
- this.parameterSystem.updateParameterValue(update.name, update.value);
5089
+ for (const update2 of message.data.updates) {
5090
+ this.parameterSystem.updateParameterValue(update2.name, update2.value);
4515
5091
  }
4516
5092
  this.parameterSystem.markInitialValuesSynced();
4517
5093
  this.debugLog("Parameter system initialized successfully");
@@ -4522,6 +5098,14 @@ class VijiWorkerRuntime {
4522
5098
  }
4523
5099
  handleAudioAnalysisUpdate(message) {
4524
5100
  const d = message.data;
5101
+ const streamIndex = d.streamIndex ?? 0;
5102
+ if (streamIndex === 0) {
5103
+ this.handleMainAudioUpdate(d);
5104
+ } else {
5105
+ this.handleAdditionalAudioUpdate(streamIndex, d);
5106
+ }
5107
+ }
5108
+ handleMainAudioUpdate(d) {
4525
5109
  const audio = this.viji.audio;
4526
5110
  audio.isConnected = d.isConnected;
4527
5111
  audio.volume.current = d.volume.current;
@@ -4537,30 +5121,78 @@ class VijiWorkerRuntime {
4537
5121
  audio.bands.midSmoothed = d.bands.midSmoothed;
4538
5122
  audio.bands.highMidSmoothed = d.bands.highMidSmoothed;
4539
5123
  audio.bands.highSmoothed = d.bands.highSmoothed;
4540
- audio.beat.kick = d.beat.kick;
4541
- audio.beat.snare = d.beat.snare;
4542
- audio.beat.hat = d.beat.hat;
4543
- audio.beat.any = d.beat.any;
4544
- audio.beat.kickSmoothed = d.beat.kickSmoothed;
4545
- audio.beat.snareSmoothed = d.beat.snareSmoothed;
4546
- audio.beat.hatSmoothed = d.beat.hatSmoothed;
4547
- audio.beat.anySmoothed = d.beat.anySmoothed;
4548
- const events = d.beat.events || [];
4549
- if (events.length > 0) {
4550
- audio.beat.triggers.kick = audio.beat.triggers.kick || events.some((e) => e.type === "kick");
4551
- audio.beat.triggers.snare = audio.beat.triggers.snare || events.some((e) => e.type === "snare");
4552
- audio.beat.triggers.hat = audio.beat.triggers.hat || events.some((e) => e.type === "hat");
4553
- audio.beat.triggers.any = true;
4554
- audio.beat.events.push(...events);
4555
- }
4556
- audio.beat.bpm = d.beat.bpm;
4557
- audio.beat.confidence = d.beat.confidence;
4558
- audio.beat.isLocked = d.beat.isLocked;
5124
+ if (d.beat) {
5125
+ audio.beat.kick = d.beat.kick;
5126
+ audio.beat.snare = d.beat.snare;
5127
+ audio.beat.hat = d.beat.hat;
5128
+ audio.beat.any = d.beat.any;
5129
+ audio.beat.kickSmoothed = d.beat.kickSmoothed;
5130
+ audio.beat.snareSmoothed = d.beat.snareSmoothed;
5131
+ audio.beat.hatSmoothed = d.beat.hatSmoothed;
5132
+ audio.beat.anySmoothed = d.beat.anySmoothed;
5133
+ const events = d.beat.events || [];
5134
+ if (events.length > 0) {
5135
+ audio.beat.triggers.kick = audio.beat.triggers.kick || events.some((e) => e.type === "kick");
5136
+ audio.beat.triggers.snare = audio.beat.triggers.snare || events.some((e) => e.type === "snare");
5137
+ audio.beat.triggers.hat = audio.beat.triggers.hat || events.some((e) => e.type === "hat");
5138
+ audio.beat.triggers.any = true;
5139
+ audio.beat.events.push(...events);
5140
+ }
5141
+ audio.beat.bpm = d.beat.bpm;
5142
+ audio.beat.confidence = d.beat.confidence;
5143
+ audio.beat.isLocked = d.beat.isLocked;
5144
+ }
4559
5145
  audio.spectral.brightness = d.spectral.brightness;
4560
5146
  audio.spectral.flatness = d.spectral.flatness;
4561
5147
  this.audioFrequencyData = new Uint8Array(d.frequencyData);
4562
5148
  this.audioWaveformData = d.waveformData ? new Float32Array(d.waveformData) : new Float32Array(0);
4563
5149
  }
5150
+ handleAdditionalAudioUpdate(streamIndex, d) {
5151
+ const frequencyData = new Uint8Array(d.frequencyData);
5152
+ const waveformData = d.waveformData ? new Float32Array(d.waveformData) : new Float32Array(0);
5153
+ this.audioStreamStates.set(streamIndex, {
5154
+ isConnected: d.isConnected,
5155
+ volume: { ...d.volume },
5156
+ bands: { ...d.bands },
5157
+ spectral: { ...d.spectral },
5158
+ frequencyData,
5159
+ waveformData
5160
+ });
5161
+ this.updateVijiAudioStreams();
5162
+ }
5163
+ /**
5164
+ * Handles audio-stream-setup: registers stream type and device mapping.
5165
+ * Mirrors handleVideoCanvasSetup — two-phase protocol where setup establishes
5166
+ * the stream identity, and subsequent audio-analysis-update messages carry data.
5167
+ */
5168
+ handleAudioStreamSetup(message) {
5169
+ const { streamIndex, streamType, deviceId } = message.data;
5170
+ switch (streamType) {
5171
+ case "additional":
5172
+ this.updateVijiAudioStreams();
5173
+ break;
5174
+ case "device":
5175
+ if (deviceId) {
5176
+ this.deviceAudioMap.set(deviceId, streamIndex);
5177
+ const device = this.viji.devices.find((d) => d.id === deviceId);
5178
+ if (device) {
5179
+ const audioState = this.audioStreamStates.get(streamIndex);
5180
+ if (audioState) {
5181
+ device.audio = {
5182
+ isConnected: audioState.isConnected,
5183
+ volume: audioState.volume,
5184
+ bands: audioState.bands,
5185
+ spectral: audioState.spectral,
5186
+ getFrequencyData: () => audioState.frequencyData,
5187
+ getWaveform: () => audioState.waveformData
5188
+ };
5189
+ }
5190
+ }
5191
+ }
5192
+ break;
5193
+ }
5194
+ this.debugLog(`Audio stream setup at index ${streamIndex}, type: ${streamType}${deviceId ? `, deviceId: ${deviceId}` : ""}`);
5195
+ }
4564
5196
  /**
4565
5197
  * Reset frame-scoped audio events (triggers + events array).
4566
5198
  * Called after each render, mirroring interactionSystem.frameStart().
@@ -4613,6 +5245,9 @@ class VijiWorkerRuntime {
4613
5245
  audio.spectral.flatness = 0;
4614
5246
  this.audioFrequencyData = new Uint8Array(0);
4615
5247
  this.audioWaveformData = new Float32Array(0);
5248
+ this.audioStreamStates.clear();
5249
+ this.deviceAudioMap.clear();
5250
+ this.viji.audioStreams.length = 0;
4616
5251
  }
4617
5252
  handleVideoCanvasSetup(message) {
4618
5253
  const { streamIndex, streamType, deviceId } = message.data;
@@ -4633,7 +5268,7 @@ class VijiWorkerRuntime {
4633
5268
  Object.assign(this.viji.video, videoSystem.getVideoAPI());
4634
5269
  break;
4635
5270
  case "additional":
4636
- this.updateVijiStreams();
5271
+ this.updateVijiVideoStreams();
4637
5272
  break;
4638
5273
  case "device":
4639
5274
  if (deviceId) {
@@ -4724,7 +5359,7 @@ class VijiWorkerRuntime {
4724
5359
  this.videoSystems[index].setDebugMode(this.debugMode);
4725
5360
  this.videoSystems[index].initializeForDirectFrames(this.rendererType);
4726
5361
  }
4727
- this.updateVijiStreams();
5362
+ this.updateVijiVideoStreams();
4728
5363
  this.debugLog(`[Compositor] Prepared ${directFrameCount} direct frame slot(s)`);
4729
5364
  }
4730
5365
  handleVideoFrameDirect(message) {
@@ -4852,7 +5487,8 @@ class VijiWorkerRuntime {
4852
5487
  renderFrame() {
4853
5488
  if (!this.isRunning) return;
4854
5489
  const currentTime = performance.now();
4855
- this.updateVijiStreams();
5490
+ this.updateVijiVideoStreams();
5491
+ this.updateVijiAudioStreams();
4856
5492
  this.viji.fps = this.frameRateMode === "full" ? this.screenRefreshRate : this.screenRefreshRate / 2;
4857
5493
  let shouldRender = true;
4858
5494
  if (this.frameRateMode === "half") {
@@ -4920,6 +5556,10 @@ class VijiWorkerRuntime {
4920
5556
  console.warn("[AutoCapture] Failed:", error);
4921
5557
  }
4922
5558
  }
5559
+ if (!this._isHeadless && this.canvas) {
5560
+ const brandingMode = get$1("b", 0);
5561
+ draw(this.canvas, this.ctx, this.gl, brandingMode, this._displayScale);
5562
+ }
4923
5563
  }
4924
5564
  this.reportPerformanceStats(currentTime);
4925
5565
  this.interactionSystem.frameStart();
@@ -4952,17 +5592,29 @@ class VijiWorkerRuntime {
4952
5592
  this.viji.device = this.deviceState.device;
4953
5593
  const updatedDevices = this.deviceState.devices.map((deviceData) => {
4954
5594
  const existingDevice = this.viji.devices.find((d) => d.id === deviceData.id);
5595
+ const videoStreamIndex = this.deviceVideoMap.get(deviceData.id);
5596
+ const videoSystem = videoStreamIndex !== void 0 ? this.videoSystems[videoStreamIndex] : void 0;
5597
+ const audioStreamIndex = this.deviceAudioMap.get(deviceData.id);
5598
+ const audioState = audioStreamIndex !== void 0 ? this.audioStreamStates.get(audioStreamIndex) : void 0;
5599
+ const audioAPI = audioState ? {
5600
+ isConnected: audioState.isConnected,
5601
+ volume: audioState.volume,
5602
+ bands: audioState.bands,
5603
+ spectral: audioState.spectral,
5604
+ getFrequencyData: () => audioState.frequencyData,
5605
+ getWaveform: () => audioState.waveformData
5606
+ } : null;
4955
5607
  if (existingDevice) {
4956
5608
  existingDevice.name = deviceData.name;
4957
5609
  existingDevice.motion = deviceData.motion;
4958
5610
  existingDevice.orientation = deviceData.orientation;
5611
+ if (audioAPI) existingDevice.audio = audioAPI;
4959
5612
  return existingDevice;
4960
5613
  } else {
4961
- const streamIndex = this.deviceVideoMap.get(deviceData.id);
4962
- const videoSystem = streamIndex !== void 0 ? this.videoSystems[streamIndex] : void 0;
4963
5614
  return {
4964
5615
  ...deviceData,
4965
- video: videoSystem ? videoSystem.getVideoAPI() : null
5616
+ video: videoSystem ? videoSystem.getVideoAPI() : null,
5617
+ audio: audioAPI
4966
5618
  };
4967
5619
  }
4968
5620
  });
@@ -4991,6 +5643,21 @@ class SceneAnalyzer {
4991
5643
  }
4992
5644
  return "native";
4993
5645
  }
5646
+ /**
5647
+ * P5 main-canvas mode from the renderer directive (only meaningful when
5648
+ * {@link detectRendererType} is `'p5'`).
5649
+ *
5650
+ * - `// @renderer p5` → 2D (default)
5651
+ * - `// @renderer p5 webgl` → WEBGL
5652
+ */
5653
+ static detectP5CanvasMode(sceneCode) {
5654
+ if (/\/\/\s*@renderer\s+p5\s+webgl\b|\/\*\s*@renderer\s+p5\s+webgl\s*\*\//i.test(
5655
+ sceneCode
5656
+ )) {
5657
+ return "webgl";
5658
+ }
5659
+ return "2d";
5660
+ }
4994
5661
  }
4995
5662
  var ContextualKeyword;
4996
5663
  (function(ContextualKeyword2) {
@@ -26312,6 +26979,7 @@ async function setSceneCode(sceneCode) {
26312
26979
  await runtime.initShaderMode(sceneCode);
26313
26980
  runtime.sendAllParametersToHost();
26314
26981
  } else if (rendererType === "p5") {
26982
+ const p5CanvasMode = SceneAnalyzer.detectP5CanvasMode(sceneCode);
26315
26983
  const jsCode = prepareSceneCode(sceneCode);
26316
26984
  const functionBody = jsCode + '\nreturn { setup: typeof setup !== "undefined" ? setup : null, render: typeof render !== "undefined" ? render : null };';
26317
26985
  const sceneFunction = new Function("viji", "p5", functionBody);
@@ -26319,7 +26987,7 @@ async function setSceneCode(sceneCode) {
26319
26987
  if (!render) {
26320
26988
  throw new Error("P5 mode requires a render(viji, p5) function");
26321
26989
  }
26322
- await runtime.initP5Mode(setup, render);
26990
+ await runtime.initP5Mode(setup, render, { canvasMode: p5CanvasMode });
26323
26991
  runtime.sendAllParametersToHost();
26324
26992
  } else {
26325
26993
  const jsCode = prepareSceneCode(sceneCode);
@@ -26345,4 +27013,4 @@ async function setSceneCode(sceneCode) {
26345
27013
  }
26346
27014
  }
26347
27015
  self.setSceneCode = setSceneCode;
26348
- //# sourceMappingURL=viji.worker-PAf0oIec.js.map
27016
+ //# sourceMappingURL=viji.worker-CdHkRwxI.js.map