@viji-dev/core 0.3.25 → 0.3.27

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.
@@ -1185,21 +1185,21 @@ class CVSystem {
1185
1185
  setTimeout(() => reject(new Error("CV processing timeout")), 500);
1186
1186
  });
1187
1187
  const results = await Promise.race([processPromise, timeoutPromise]);
1188
- if (results.faces && (this.activeFeatures.has("faceDetection") || this.activeFeatures.has("faceMesh") || this.activeFeatures.has("emotionDetection"))) {
1188
+ if ("faces" in results && (this.activeFeatures.has("faceDetection") || this.activeFeatures.has("faceMesh") || this.activeFeatures.has("emotionDetection"))) {
1189
1189
  this.results.faces = results.faces;
1190
1190
  this.debugLog(`📥 Received ${results.faces.length} face results`);
1191
1191
  }
1192
- if (results.hands && this.activeFeatures.has("handTracking")) {
1192
+ if ("hands" in results && this.activeFeatures.has("handTracking")) {
1193
1193
  this.results.hands = results.hands;
1194
1194
  this.debugLog(`📥 Received ${results.hands.length} hand results`);
1195
1195
  }
1196
- if (results.pose && this.activeFeatures.has("poseDetection")) {
1196
+ if ("pose" in results && this.activeFeatures.has("poseDetection")) {
1197
1197
  this.results.pose = results.pose;
1198
- this.debugLog(`📥 Received pose results with ${results.pose.landmarks.length} landmarks`);
1198
+ this.debugLog(`📥 Received pose results: ${results.pose ? results.pose.landmarks.length + " landmarks" : "none"}`);
1199
1199
  }
1200
- if (results.segmentation && this.activeFeatures.has("bodySegmentation")) {
1200
+ if ("segmentation" in results && this.activeFeatures.has("bodySegmentation")) {
1201
1201
  this.results.segmentation = results.segmentation;
1202
- this.debugLog(`📥 Received segmentation results ${results.segmentation.width}x${results.segmentation.height}`);
1202
+ this.debugLog(`📥 Received segmentation: ${results.segmentation ? results.segmentation.width + "x" + results.segmentation.height : "none"}`);
1203
1203
  }
1204
1204
  const processingTime = performance.now() - this.processingStartTime;
1205
1205
  this.processingTimes.push(processingTime);
@@ -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)
@@ -2642,11 +2654,11 @@ uniform float u_audioPeak; // Peak amplitude (0-1)
2642
2654
  uniform float u_audioVolumeSmoothed; // Smoothed volume for animation (200ms decay)
2643
2655
 
2644
2656
  // Audio - Frequency Bands (instant)
2645
- uniform float u_audioLow; // Low 20-150 Hz (0-1)
2646
- uniform float u_audioLowMid; // Low-mid 150-400 Hz (0-1)
2647
- uniform float u_audioMid; // Mid 400-2500 Hz (0-1)
2648
- uniform float u_audioHighMid; // High-mid 2500-8000 Hz (0-1)
2649
- uniform float u_audioHigh; // High 8000-20000 Hz (0-1)
2657
+ uniform float u_audioLow; // Low 20-120 Hz (0-1)
2658
+ uniform float u_audioLowMid; // Low-mid 120-400 Hz (0-1)
2659
+ uniform float u_audioMid; // Mid 400-1600 Hz (0-1)
2660
+ uniform float u_audioHighMid; // High-mid 1600-6000 Hz (0-1)
2661
+ uniform float u_audioHigh; // High 6000-16000 Hz (0-1)
2650
2662
 
2651
2663
  // Audio - Frequency Bands (smoothed, 150ms decay)
2652
2664
  uniform float u_audioLowSmoothed; // Smoothed low band (0-1)
@@ -2683,7 +2695,7 @@ uniform float u_audioBrightness; // Spectral brightness / centroid (0-1)
2683
2695
  uniform float u_audioFlatness; // Spectral flatness / noisiness (0-1)
2684
2696
 
2685
2697
  // Audio - Textures
2686
- uniform sampler2D u_audioFFT; // FFT frequency spectrum (512 bins, 0-255)
2698
+ uniform sampler2D u_audioFFT; // FFT frequency spectrum (1024 bins, 0-255)
2687
2699
  uniform sampler2D u_audioWaveform; // Time-domain waveform (fftSize samples, -1 to 1)
2688
2700
 
2689
2701
  // Video
@@ -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
@@ -2823,18 +2910,41 @@ uniform float u_face0TongueOut;
2823
2910
  uniform int u_handCount; // Number of detected hands (0-2)
2824
2911
  uniform vec3 u_leftHandPalm; // Left hand palm position (x, y, z)
2825
2912
  uniform vec3 u_rightHandPalm; // Right hand palm position (x, y, z)
2913
+ uniform float u_leftHandConfidence; // Left hand detection confidence (0-1)
2914
+ uniform float u_rightHandConfidence; // Right hand detection confidence (0-1)
2915
+ uniform vec4 u_leftHandBounds; // Left hand bounding box (x, y, width, height) normalized 0-1
2916
+ uniform vec4 u_rightHandBounds; // Right hand bounding box (x, y, width, height) normalized 0-1
2826
2917
  uniform float u_leftHandFist; // Left hand fist gesture confidence (0-1)
2827
2918
  uniform float u_leftHandOpen; // Left hand open palm gesture confidence (0-1)
2919
+ uniform float u_leftHandPeace; // Left hand peace/victory gesture confidence (0-1)
2920
+ uniform float u_leftHandThumbsUp; // Left hand thumbs up gesture confidence (0-1)
2921
+ uniform float u_leftHandThumbsDown; // Left hand thumbs down gesture confidence (0-1)
2922
+ uniform float u_leftHandPointing; // Left hand pointing gesture confidence (0-1)
2923
+ uniform float u_leftHandILoveYou; // Left hand I-love-you gesture confidence (0-1)
2828
2924
  uniform float u_rightHandFist; // Right hand fist gesture confidence (0-1)
2829
2925
  uniform float u_rightHandOpen; // Right hand open palm gesture confidence (0-1)
2926
+ uniform float u_rightHandPeace; // Right hand peace/victory gesture confidence (0-1)
2927
+ uniform float u_rightHandThumbsUp; // Right hand thumbs up gesture confidence (0-1)
2928
+ uniform float u_rightHandThumbsDown; // Right hand thumbs down gesture confidence (0-1)
2929
+ uniform float u_rightHandPointing; // Right hand pointing gesture confidence (0-1)
2930
+ uniform float u_rightHandILoveYou; // Right hand I-love-you gesture confidence (0-1)
2830
2931
 
2831
2932
  // CV - Pose Detection
2832
2933
  uniform bool u_poseDetected; // True if a pose is currently detected
2833
- uniform vec2 u_nosePosition; // Nose landmark position in pixels
2834
- uniform vec2 u_leftWristPosition; // Left wrist landmark position in pixels
2835
- uniform vec2 u_rightWristPosition; // Right wrist landmark position in pixels
2836
- uniform vec2 u_leftAnklePosition; // Left ankle landmark position in pixels
2837
- uniform vec2 u_rightAnklePosition; // Right ankle landmark position in pixels
2934
+ uniform float u_poseConfidence; // Pose detection confidence (0-1)
2935
+ uniform vec2 u_nosePosition; // Nose landmark position (normalized 0-1)
2936
+ uniform vec2 u_leftShoulderPosition; // Left shoulder position (normalized 0-1)
2937
+ uniform vec2 u_rightShoulderPosition; // Right shoulder position (normalized 0-1)
2938
+ uniform vec2 u_leftElbowPosition; // Left elbow position (normalized 0-1)
2939
+ uniform vec2 u_rightElbowPosition; // Right elbow position (normalized 0-1)
2940
+ uniform vec2 u_leftWristPosition; // Left wrist position (normalized 0-1)
2941
+ uniform vec2 u_rightWristPosition; // Right wrist position (normalized 0-1)
2942
+ uniform vec2 u_leftHipPosition; // Left hip position (normalized 0-1)
2943
+ uniform vec2 u_rightHipPosition; // Right hip position (normalized 0-1)
2944
+ uniform vec2 u_leftKneePosition; // Left knee position (normalized 0-1)
2945
+ uniform vec2 u_rightKneePosition; // Right knee position (normalized 0-1)
2946
+ uniform vec2 u_leftAnklePosition; // Left ankle position (normalized 0-1)
2947
+ uniform vec2 u_rightAnklePosition; // Right ankle position (normalized 0-1)
2838
2948
 
2839
2949
  // CV - Segmentation
2840
2950
  uniform sampler2D u_segmentationMask; // Body segmentation mask texture (0=background, 1=person)
@@ -2950,7 +3060,7 @@ ${error}`);
2950
3060
  if (info) {
2951
3061
  const location = gl.getUniformLocation(this.program, info.name);
2952
3062
  this.uniformLocations.set(info.name, location);
2953
- if (info.name.includes("stream") || info.name.includes("u_stream")) {
3063
+ if (info.name.includes("Stream") || info.name.includes("stream")) {
2954
3064
  streamUniforms.push(info.name);
2955
3065
  }
2956
3066
  }
@@ -2970,8 +3080,8 @@ ${error}`);
2970
3080
  this.textureUnits.set("u_video", this.nextTextureUnit++);
2971
3081
  this.textureUnits.set("u_segmentationMask", this.nextTextureUnit++);
2972
3082
  this.textureUnits.set("u_keyboard", this.nextTextureUnit++);
2973
- for (let i = 0; i < ShaderWorkerAdapter.MAX_STREAMS; i++) {
2974
- 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++);
2975
3085
  }
2976
3086
  if (this.backbufferEnabled) {
2977
3087
  this.textureUnits.set("backbuffer", this.nextTextureUnit++);
@@ -3142,18 +3252,18 @@ ${error}`);
3142
3252
  this.setUniform("u_videoResolution", "vec2", [0, 0]);
3143
3253
  this.setUniform("u_videoFrameRate", "float", 0);
3144
3254
  }
3145
- const streams = viji.streams || [];
3146
- const streamCount = Math.min(streams.length, ShaderWorkerAdapter.MAX_STREAMS);
3147
- this.setUniform("u_streamCount", "int", streamCount);
3148
- for (let i = 0; i < ShaderWorkerAdapter.MAX_STREAMS; i++) {
3149
- const connectedUniform = `u_stream${i}Connected`;
3150
- const resolutionUniform = `u_stream${i}Resolution`;
3151
- if (i < streamCount && streams[i]?.isConnected && streams[i]?.currentFrame) {
3152
- this.updateStreamTexture(i, streams[i].currentFrame);
3255
+ const videoStreams = viji.videoStreams || [];
3256
+ const videoStreamCount = Math.min(videoStreams.length, ShaderWorkerAdapter.MAX_VIDEO_STREAMS);
3257
+ this.setUniform("u_videoStreamCount", "int", videoStreamCount);
3258
+ for (let i = 0; i < ShaderWorkerAdapter.MAX_VIDEO_STREAMS; i++) {
3259
+ const connectedUniform = `u_videoStream${i}Connected`;
3260
+ const resolutionUniform = `u_videoStream${i}Resolution`;
3261
+ if (i < videoStreamCount && videoStreams[i]?.isConnected && videoStreams[i]?.currentFrame) {
3262
+ this.updateVideoStreamTexture(i, videoStreams[i].currentFrame);
3153
3263
  this.setUniform(
3154
3264
  resolutionUniform,
3155
3265
  "vec2",
3156
- [streams[i].frameWidth, streams[i].frameHeight]
3266
+ [videoStreams[i].frameWidth, videoStreams[i].frameHeight]
3157
3267
  );
3158
3268
  this.setUniform(connectedUniform, "bool", true);
3159
3269
  } else {
@@ -3180,6 +3290,34 @@ ${error}`);
3180
3290
  this.setUniform(connectedUniform, "bool", false);
3181
3291
  }
3182
3292
  }
3293
+ const audioStreams = viji.audioStreams || [];
3294
+ const audioStreamCount = Math.min(audioStreams.length, ShaderWorkerAdapter.MAX_AUDIO_STREAMS);
3295
+ this.setUniform("u_audioStreamCount", "int", audioStreamCount);
3296
+ for (let i = 0; i < ShaderWorkerAdapter.MAX_AUDIO_STREAMS; i++) {
3297
+ const prefix = `u_audioStream${i}`;
3298
+ if (i < audioStreamCount && audioStreams[i]?.isConnected) {
3299
+ const s = audioStreams[i];
3300
+ this.setUniform(`${prefix}Connected`, "bool", true);
3301
+ this.setUniform(`${prefix}Volume`, "float", s.volume?.current || 0);
3302
+ this.setUniform(`${prefix}Low`, "float", s.bands?.low || 0);
3303
+ this.setUniform(`${prefix}LowMid`, "float", s.bands?.lowMid || 0);
3304
+ this.setUniform(`${prefix}Mid`, "float", s.bands?.mid || 0);
3305
+ this.setUniform(`${prefix}HighMid`, "float", s.bands?.highMid || 0);
3306
+ this.setUniform(`${prefix}High`, "float", s.bands?.high || 0);
3307
+ this.setUniform(`${prefix}Brightness`, "float", s.spectral?.brightness || 0);
3308
+ this.setUniform(`${prefix}Flatness`, "float", s.spectral?.flatness || 0);
3309
+ } else {
3310
+ this.setUniform(`${prefix}Connected`, "bool", false);
3311
+ this.setUniform(`${prefix}Volume`, "float", 0);
3312
+ this.setUniform(`${prefix}Low`, "float", 0);
3313
+ this.setUniform(`${prefix}LowMid`, "float", 0);
3314
+ this.setUniform(`${prefix}Mid`, "float", 0);
3315
+ this.setUniform(`${prefix}HighMid`, "float", 0);
3316
+ this.setUniform(`${prefix}High`, "float", 0);
3317
+ this.setUniform(`${prefix}Brightness`, "float", 0);
3318
+ this.setUniform(`${prefix}Flatness`, "float", 0);
3319
+ }
3320
+ }
3183
3321
  const faces = video.faces || [];
3184
3322
  this.setUniform("u_faceCount", "int", faces.length);
3185
3323
  if (faces.length > 0) {
@@ -3268,40 +3406,88 @@ ${error}`);
3268
3406
  const leftHand = hands.find((h) => h.handedness === "left");
3269
3407
  const rightHand = hands.find((h) => h.handedness === "right");
3270
3408
  if (leftHand) {
3271
- this.setUniform("u_leftHandPalm", "vec3", [leftHand.palm.x, leftHand.palm.y, leftHand.palm.z]);
3272
- this.setUniform("u_leftHandFist", "float", leftHand.gestures?.fist || 0);
3273
- this.setUniform("u_leftHandOpen", "float", leftHand.gestures?.openPalm || 0);
3409
+ const palm = leftHand.palm || leftHand.landmarks?.[9];
3410
+ const g = leftHand.gestures || {};
3411
+ const b = leftHand.bounds;
3412
+ this.setUniform("u_leftHandPalm", "vec3", [palm?.x || 0, palm?.y || 0, palm?.z || 0]);
3413
+ this.setUniform("u_leftHandConfidence", "float", leftHand.confidence || 0);
3414
+ this.setUniform("u_leftHandBounds", "vec4", [b?.x || 0, b?.y || 0, b?.width || 0, b?.height || 0]);
3415
+ this.setUniform("u_leftHandFist", "float", g.fist || 0);
3416
+ this.setUniform("u_leftHandOpen", "float", g.openPalm || 0);
3417
+ this.setUniform("u_leftHandPeace", "float", g.peace || 0);
3418
+ this.setUniform("u_leftHandThumbsUp", "float", g.thumbsUp || 0);
3419
+ this.setUniform("u_leftHandThumbsDown", "float", g.thumbsDown || 0);
3420
+ this.setUniform("u_leftHandPointing", "float", g.pointing || 0);
3421
+ this.setUniform("u_leftHandILoveYou", "float", g.iLoveYou || 0);
3274
3422
  } else {
3275
3423
  this.setUniform("u_leftHandPalm", "vec3", [0, 0, 0]);
3424
+ this.setUniform("u_leftHandConfidence", "float", 0);
3425
+ this.setUniform("u_leftHandBounds", "vec4", [0, 0, 0, 0]);
3276
3426
  this.setUniform("u_leftHandFist", "float", 0);
3277
3427
  this.setUniform("u_leftHandOpen", "float", 0);
3428
+ this.setUniform("u_leftHandPeace", "float", 0);
3429
+ this.setUniform("u_leftHandThumbsUp", "float", 0);
3430
+ this.setUniform("u_leftHandThumbsDown", "float", 0);
3431
+ this.setUniform("u_leftHandPointing", "float", 0);
3432
+ this.setUniform("u_leftHandILoveYou", "float", 0);
3278
3433
  }
3279
3434
  if (rightHand) {
3280
- this.setUniform("u_rightHandPalm", "vec3", [rightHand.palm.x, rightHand.palm.y, rightHand.palm.z]);
3281
- this.setUniform("u_rightHandFist", "float", rightHand.gestures?.fist || 0);
3282
- this.setUniform("u_rightHandOpen", "float", rightHand.gestures?.openPalm || 0);
3435
+ const palm = rightHand.palm || rightHand.landmarks?.[9];
3436
+ const g = rightHand.gestures || {};
3437
+ const b = rightHand.bounds;
3438
+ this.setUniform("u_rightHandPalm", "vec3", [palm?.x || 0, palm?.y || 0, palm?.z || 0]);
3439
+ this.setUniform("u_rightHandConfidence", "float", rightHand.confidence || 0);
3440
+ this.setUniform("u_rightHandBounds", "vec4", [b?.x || 0, b?.y || 0, b?.width || 0, b?.height || 0]);
3441
+ this.setUniform("u_rightHandFist", "float", g.fist || 0);
3442
+ this.setUniform("u_rightHandOpen", "float", g.openPalm || 0);
3443
+ this.setUniform("u_rightHandPeace", "float", g.peace || 0);
3444
+ this.setUniform("u_rightHandThumbsUp", "float", g.thumbsUp || 0);
3445
+ this.setUniform("u_rightHandThumbsDown", "float", g.thumbsDown || 0);
3446
+ this.setUniform("u_rightHandPointing", "float", g.pointing || 0);
3447
+ this.setUniform("u_rightHandILoveYou", "float", g.iLoveYou || 0);
3283
3448
  } else {
3284
3449
  this.setUniform("u_rightHandPalm", "vec3", [0, 0, 0]);
3450
+ this.setUniform("u_rightHandConfidence", "float", 0);
3451
+ this.setUniform("u_rightHandBounds", "vec4", [0, 0, 0, 0]);
3285
3452
  this.setUniform("u_rightHandFist", "float", 0);
3286
3453
  this.setUniform("u_rightHandOpen", "float", 0);
3454
+ this.setUniform("u_rightHandPeace", "float", 0);
3455
+ this.setUniform("u_rightHandThumbsUp", "float", 0);
3456
+ this.setUniform("u_rightHandThumbsDown", "float", 0);
3457
+ this.setUniform("u_rightHandPointing", "float", 0);
3458
+ this.setUniform("u_rightHandILoveYou", "float", 0);
3287
3459
  }
3288
3460
  const pose = video.pose;
3289
3461
  this.setUniform("u_poseDetected", "bool", pose !== null);
3290
- if (pose) {
3291
- const nose = pose.landmarks[0];
3292
- const leftWrist = pose.landmarks[15];
3293
- const rightWrist = pose.landmarks[16];
3294
- const leftAnkle = pose.landmarks[27];
3295
- const rightAnkle = pose.landmarks[28];
3296
- this.setUniform("u_nosePosition", "vec2", [nose?.x || 0, nose?.y || 0]);
3297
- this.setUniform("u_leftWristPosition", "vec2", [leftWrist?.x || 0, leftWrist?.y || 0]);
3298
- this.setUniform("u_rightWristPosition", "vec2", [rightWrist?.x || 0, rightWrist?.y || 0]);
3299
- this.setUniform("u_leftAnklePosition", "vec2", [leftAnkle?.x || 0, leftAnkle?.y || 0]);
3300
- this.setUniform("u_rightAnklePosition", "vec2", [rightAnkle?.x || 0, rightAnkle?.y || 0]);
3462
+ if (pose && pose.landmarks) {
3463
+ const lm = pose.landmarks;
3464
+ this.setUniform("u_poseConfidence", "float", pose.confidence || 0);
3465
+ this.setUniform("u_nosePosition", "vec2", [lm[0]?.x || 0, lm[0]?.y || 0]);
3466
+ this.setUniform("u_leftShoulderPosition", "vec2", [lm[11]?.x || 0, lm[11]?.y || 0]);
3467
+ this.setUniform("u_rightShoulderPosition", "vec2", [lm[12]?.x || 0, lm[12]?.y || 0]);
3468
+ this.setUniform("u_leftElbowPosition", "vec2", [lm[13]?.x || 0, lm[13]?.y || 0]);
3469
+ this.setUniform("u_rightElbowPosition", "vec2", [lm[14]?.x || 0, lm[14]?.y || 0]);
3470
+ this.setUniform("u_leftWristPosition", "vec2", [lm[15]?.x || 0, lm[15]?.y || 0]);
3471
+ this.setUniform("u_rightWristPosition", "vec2", [lm[16]?.x || 0, lm[16]?.y || 0]);
3472
+ this.setUniform("u_leftHipPosition", "vec2", [lm[23]?.x || 0, lm[23]?.y || 0]);
3473
+ this.setUniform("u_rightHipPosition", "vec2", [lm[24]?.x || 0, lm[24]?.y || 0]);
3474
+ this.setUniform("u_leftKneePosition", "vec2", [lm[25]?.x || 0, lm[25]?.y || 0]);
3475
+ this.setUniform("u_rightKneePosition", "vec2", [lm[26]?.x || 0, lm[26]?.y || 0]);
3476
+ this.setUniform("u_leftAnklePosition", "vec2", [lm[27]?.x || 0, lm[27]?.y || 0]);
3477
+ this.setUniform("u_rightAnklePosition", "vec2", [lm[28]?.x || 0, lm[28]?.y || 0]);
3301
3478
  } else {
3479
+ this.setUniform("u_poseConfidence", "float", 0);
3302
3480
  this.setUniform("u_nosePosition", "vec2", [0, 0]);
3481
+ this.setUniform("u_leftShoulderPosition", "vec2", [0, 0]);
3482
+ this.setUniform("u_rightShoulderPosition", "vec2", [0, 0]);
3483
+ this.setUniform("u_leftElbowPosition", "vec2", [0, 0]);
3484
+ this.setUniform("u_rightElbowPosition", "vec2", [0, 0]);
3303
3485
  this.setUniform("u_leftWristPosition", "vec2", [0, 0]);
3304
3486
  this.setUniform("u_rightWristPosition", "vec2", [0, 0]);
3487
+ this.setUniform("u_leftHipPosition", "vec2", [0, 0]);
3488
+ this.setUniform("u_rightHipPosition", "vec2", [0, 0]);
3489
+ this.setUniform("u_leftKneePosition", "vec2", [0, 0]);
3490
+ this.setUniform("u_rightKneePosition", "vec2", [0, 0]);
3305
3491
  this.setUniform("u_leftAnklePosition", "vec2", [0, 0]);
3306
3492
  this.setUniform("u_rightAnklePosition", "vec2", [0, 0]);
3307
3493
  }
@@ -3661,18 +3847,18 @@ ${error}`);
3661
3847
  }
3662
3848
  }
3663
3849
  /**
3664
- * Update compositor stream texture at specified index
3850
+ * Update compositor video stream texture at specified index
3665
3851
  * Supports both OffscreenCanvas and ImageBitmap for zero-copy pipeline
3666
3852
  */
3667
- updateStreamTexture(index, streamFrame) {
3853
+ updateVideoStreamTexture(index, streamFrame) {
3668
3854
  const gl = this.gl;
3669
- const uniformName = `u_stream${index}`;
3855
+ const uniformName = `u_videoStream${index}`;
3670
3856
  const unit = this.textureUnits.get(uniformName);
3671
- if (!this.streamTextures[index]) {
3672
- this.streamTextures[index] = gl.createTexture();
3857
+ if (!this.videoStreamTextures[index]) {
3858
+ this.videoStreamTextures[index] = gl.createTexture();
3673
3859
  }
3674
3860
  gl.activeTexture(gl.TEXTURE0 + unit);
3675
- gl.bindTexture(gl.TEXTURE_2D, this.streamTextures[index]);
3861
+ gl.bindTexture(gl.TEXTURE_2D, this.videoStreamTextures[index]);
3676
3862
  const shouldFlip = streamFrame instanceof OffscreenCanvas;
3677
3863
  if (shouldFlip) {
3678
3864
  gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, true);
@@ -3863,10 +4049,10 @@ ${error}`);
3863
4049
  if (this.audioWaveformTexture) gl.deleteTexture(this.audioWaveformTexture);
3864
4050
  if (this.videoTexture) gl.deleteTexture(this.videoTexture);
3865
4051
  if (this.segmentationTexture) gl.deleteTexture(this.segmentationTexture);
3866
- for (const texture of this.streamTextures) {
4052
+ for (const texture of this.videoStreamTextures) {
3867
4053
  if (texture) gl.deleteTexture(texture);
3868
4054
  }
3869
- this.streamTextures = [];
4055
+ this.videoStreamTextures = [];
3870
4056
  for (const texture of this.deviceTextures) {
3871
4057
  if (texture) gl.deleteTexture(texture);
3872
4058
  }
@@ -3943,43 +4129,9 @@ class VijiWorkerRuntime {
3943
4129
  autoCaptureEnabled = false;
3944
4130
  autoCaptureFormat = { flipY: true };
3945
4131
  // Default: flip for WebGL compatibility
3946
- // Audio state (Phase 5) - receives analysis results from host
3947
- audioState = {
3948
- isConnected: false,
3949
- volume: { current: 0, peak: 0, smoothed: 0 },
3950
- bands: {
3951
- low: 0,
3952
- lowMid: 0,
3953
- mid: 0,
3954
- highMid: 0,
3955
- high: 0,
3956
- lowSmoothed: 0,
3957
- lowMidSmoothed: 0,
3958
- midSmoothed: 0,
3959
- highMidSmoothed: 0,
3960
- highSmoothed: 0
3961
- },
3962
- beat: {
3963
- kick: 0,
3964
- snare: 0,
3965
- hat: 0,
3966
- any: 0,
3967
- kickSmoothed: 0,
3968
- snareSmoothed: 0,
3969
- hatSmoothed: 0,
3970
- anySmoothed: 0,
3971
- triggers: { any: false, kick: false, snare: false, hat: false },
3972
- bpm: 120,
3973
- confidence: 0,
3974
- isLocked: false
3975
- },
3976
- spectral: {
3977
- brightness: 0,
3978
- flatness: 0
3979
- },
3980
- frequencyData: new Uint8Array(0),
3981
- waveformData: new Float32Array(0)
3982
- };
4132
+ // Audio raw data buffers (referenced by closures in viji.audio)
4133
+ audioFrequencyData = new Uint8Array(0);
4134
+ audioWaveformData = new Float32Array(0);
3983
4135
  // Device sensor state (internal device + external devices)
3984
4136
  deviceState = {
3985
4137
  device: {
@@ -3990,6 +4142,11 @@ class VijiWorkerRuntime {
3990
4142
  };
3991
4143
  // Map deviceId → streamIndex for O(1) device video lookup
3992
4144
  deviceVideoMap = /* @__PURE__ */ new Map();
4145
+ // Audio stream state management (mirroring video stream pattern)
4146
+ static AUDIO_ADDITIONAL_BASE = 1;
4147
+ static AUDIO_DEVICE_BASE = 100;
4148
+ audioStreamStates = /* @__PURE__ */ new Map();
4149
+ deviceAudioMap = /* @__PURE__ */ new Map();
3993
4150
  // Video state is now managed by the worker-side VideoSystem
3994
4151
  // Artist API object
3995
4152
  viji = {
@@ -4004,8 +4161,41 @@ class VijiWorkerRuntime {
4004
4161
  deltaTime: 0,
4005
4162
  frameCount: 0,
4006
4163
  fps: 60,
4007
- // Audio API (Phase 5) - will be set in constructor
4008
- audio: {},
4164
+ // Audio API (Phase 5) stable reference, mutated in place
4165
+ audio: {
4166
+ isConnected: false,
4167
+ volume: { current: 0, peak: 0, smoothed: 0 },
4168
+ bands: {
4169
+ low: 0,
4170
+ lowMid: 0,
4171
+ mid: 0,
4172
+ highMid: 0,
4173
+ high: 0,
4174
+ lowSmoothed: 0,
4175
+ lowMidSmoothed: 0,
4176
+ midSmoothed: 0,
4177
+ highMidSmoothed: 0,
4178
+ highSmoothed: 0
4179
+ },
4180
+ beat: {
4181
+ kick: 0,
4182
+ snare: 0,
4183
+ hat: 0,
4184
+ any: 0,
4185
+ kickSmoothed: 0,
4186
+ snareSmoothed: 0,
4187
+ hatSmoothed: 0,
4188
+ anySmoothed: 0,
4189
+ triggers: { any: false, kick: false, snare: false, hat: false },
4190
+ events: [],
4191
+ bpm: 120,
4192
+ confidence: 0,
4193
+ isLocked: false
4194
+ },
4195
+ spectral: { brightness: 0, flatness: 0 },
4196
+ getFrequencyData: () => new Uint8Array(0),
4197
+ getWaveform: () => new Float32Array(0)
4198
+ },
4009
4199
  // Main video stream (index 0, CV enabled)
4010
4200
  video: {
4011
4201
  isConnected: false,
@@ -4054,7 +4244,9 @@ class VijiWorkerRuntime {
4054
4244
  }
4055
4245
  },
4056
4246
  // Additional video streams (index 1+, no CV)
4057
- streams: [],
4247
+ videoStreams: [],
4248
+ // Additional audio streams (lightweight analysis, no beat detection)
4249
+ audioStreams: [],
4058
4250
  // Interaction APIs will be added during construction
4059
4251
  mouse: {},
4060
4252
  keyboard: {},
@@ -4117,18 +4309,15 @@ class VijiWorkerRuntime {
4117
4309
  });
4118
4310
  this.interactionSystem = new InteractionSystem();
4119
4311
  Object.assign(this.viji, this.interactionSystem.getInteractionAPIs());
4120
- this.viji.audio = {
4121
- ...this.audioState,
4122
- getFrequencyData: () => this.audioState.frequencyData,
4123
- getWaveform: () => this.audioState.waveformData
4124
- };
4312
+ this.viji.audio.getFrequencyData = () => this.audioFrequencyData;
4313
+ this.viji.audio.getWaveform = () => this.audioWaveformData;
4125
4314
  this.setupMessageHandling();
4126
4315
  }
4127
4316
  /**
4128
4317
  * Initialize P5.js mode
4129
4318
  * Sets up P5 rendering with P5WorkerAdapter
4130
4319
  */
4131
- async initP5Mode(setup, render) {
4320
+ async initP5Mode(setup, render, options) {
4132
4321
  try {
4133
4322
  this.rendererType = "p5";
4134
4323
  this.debugLog("🎨 Initializing P5.js mode...");
@@ -4137,7 +4326,8 @@ class VijiWorkerRuntime {
4137
4326
  this.viji,
4138
4327
  {
4139
4328
  setup,
4140
- render
4329
+ render,
4330
+ canvasMode: options?.canvasMode === "webgl" ? "webgl" : "2d"
4141
4331
  }
4142
4332
  );
4143
4333
  await this.p5Adapter.init();
@@ -4224,65 +4414,52 @@ class VijiWorkerRuntime {
4224
4414
  resetParameterState() {
4225
4415
  this.parameterSystem.resetParameterState();
4226
4416
  this.interactionSystem.resetInteractionState();
4227
- this.audioState = {
4228
- isConnected: false,
4229
- volume: { current: 0, peak: 0, smoothed: 0 },
4230
- bands: {
4231
- low: 0,
4232
- lowMid: 0,
4233
- mid: 0,
4234
- highMid: 0,
4235
- high: 0,
4236
- lowSmoothed: 0,
4237
- lowMidSmoothed: 0,
4238
- midSmoothed: 0,
4239
- highMidSmoothed: 0,
4240
- highSmoothed: 0
4241
- },
4242
- beat: {
4243
- kick: 0,
4244
- snare: 0,
4245
- hat: 0,
4246
- any: 0,
4247
- kickSmoothed: 0,
4248
- snareSmoothed: 0,
4249
- hatSmoothed: 0,
4250
- anySmoothed: 0,
4251
- triggers: { any: false, kick: false, snare: false, hat: false },
4252
- bpm: 120,
4253
- confidence: 0,
4254
- isLocked: false
4255
- },
4256
- spectral: {
4257
- brightness: 0,
4258
- flatness: 0
4259
- },
4260
- frequencyData: new Uint8Array(0),
4261
- waveformData: new Float32Array(0)
4262
- };
4263
- this.viji.audio = {
4264
- ...this.audioState,
4265
- getFrequencyData: () => this.audioState.frequencyData,
4266
- getWaveform: () => this.audioState.waveformData
4267
- };
4417
+ this.resetAudioState();
4268
4418
  this.videoSystems.forEach((vs) => vs?.resetVideoState());
4269
4419
  if (this.videoSystems[0]) {
4270
4420
  Object.assign(this.viji.video, this.videoSystems[0].getVideoAPI());
4271
4421
  }
4272
- this.updateVijiStreams();
4422
+ this.updateVijiVideoStreams();
4273
4423
  }
4274
4424
  /**
4275
- * Updates viji.streams from videoSystems array.
4425
+ * Updates viji.videoStreams from videoSystems array.
4276
4426
  * Collects 'additional' MediaStreams first, then 'directFrame' injected frames.
4277
4427
  * Excludes 'main' and 'device' streams (those go to viji.video and viji.devices[].video).
4278
4428
  */
4279
- updateVijiStreams() {
4429
+ updateVijiVideoStreams() {
4280
4430
  const additional = this.videoSystems.filter((vs) => vs && vs.getStreamType() === "additional").map((vs) => vs.getVideoAPI());
4281
4431
  const directFrames = this.videoSystems.filter((vs) => vs && vs.getStreamType() === "directFrame").map((vs) => vs.getVideoAPI());
4282
4432
  const freshStreams = [...additional, ...directFrames];
4283
- this.viji.streams.length = freshStreams.length;
4433
+ this.viji.videoStreams.length = freshStreams.length;
4284
4434
  for (let i = 0; i < freshStreams.length; i++) {
4285
- this.viji.streams[i] = freshStreams[i];
4435
+ this.viji.videoStreams[i] = freshStreams[i];
4436
+ }
4437
+ }
4438
+ /**
4439
+ * Rebuilds viji.audioStreams from audioStreamStates.
4440
+ * Collects entries in the additional range (1-99), maps to AudioStreamAPI objects.
4441
+ */
4442
+ updateVijiAudioStreams() {
4443
+ const entries = [];
4444
+ for (const [idx, state2] of this.audioStreamStates) {
4445
+ if (idx >= VijiWorkerRuntime.AUDIO_ADDITIONAL_BASE && idx < VijiWorkerRuntime.AUDIO_DEVICE_BASE) {
4446
+ entries.push({ index: idx, ...state2 });
4447
+ }
4448
+ }
4449
+ entries.sort((a, b) => a.index - b.index);
4450
+ this.viji.audioStreams.length = entries.length;
4451
+ for (let i = 0; i < entries.length; i++) {
4452
+ const s = entries[i];
4453
+ const freqData = s.frequencyData;
4454
+ const waveData = s.waveformData;
4455
+ this.viji.audioStreams[i] = {
4456
+ isConnected: s.isConnected,
4457
+ volume: s.volume,
4458
+ bands: s.bands,
4459
+ spectral: s.spectral,
4460
+ getFrequencyData: () => freqData,
4461
+ getWaveform: () => waveData
4462
+ };
4286
4463
  }
4287
4464
  }
4288
4465
  // Send all parameters (from helper functions) to host
@@ -4326,6 +4503,9 @@ class VijiWorkerRuntime {
4326
4503
  case "audio-analysis-update":
4327
4504
  this.handleAudioAnalysisUpdate(message);
4328
4505
  break;
4506
+ case "audio-stream-setup":
4507
+ this.handleAudioStreamSetup(message);
4508
+ break;
4329
4509
  case "video-canvas-setup":
4330
4510
  this.handleVideoCanvasSetup(message);
4331
4511
  break;
@@ -4494,30 +4674,157 @@ class VijiWorkerRuntime {
4494
4674
  this.debugLog("Stream update:", message.data);
4495
4675
  }
4496
4676
  handleAudioAnalysisUpdate(message) {
4497
- const events = message.data.beat.events || [];
4498
- const triggers = {
4499
- kick: events.some((e) => e.type === "kick"),
4500
- snare: events.some((e) => e.type === "snare"),
4501
- hat: events.some((e) => e.type === "hat"),
4502
- any: events.length > 0
4503
- };
4504
- this.audioState = {
4505
- isConnected: message.data.isConnected,
4506
- volume: message.data.volume,
4507
- bands: message.data.bands,
4508
- beat: {
4509
- ...message.data.beat,
4510
- triggers
4511
- },
4512
- spectral: message.data.spectral,
4513
- frequencyData: new Uint8Array(message.data.frequencyData),
4514
- waveformData: message.data.waveformData ? new Float32Array(message.data.waveformData) : new Float32Array(0)
4515
- };
4516
- this.viji.audio = {
4517
- ...this.audioState,
4518
- getFrequencyData: () => this.audioState.frequencyData,
4519
- getWaveform: () => this.audioState.waveformData
4520
- };
4677
+ const d = message.data;
4678
+ const streamIndex = d.streamIndex ?? 0;
4679
+ if (streamIndex === 0) {
4680
+ this.handleMainAudioUpdate(d);
4681
+ } else {
4682
+ this.handleAdditionalAudioUpdate(streamIndex, d);
4683
+ }
4684
+ }
4685
+ handleMainAudioUpdate(d) {
4686
+ const audio = this.viji.audio;
4687
+ audio.isConnected = d.isConnected;
4688
+ audio.volume.current = d.volume.current;
4689
+ audio.volume.peak = d.volume.peak;
4690
+ audio.volume.smoothed = d.volume.smoothed;
4691
+ audio.bands.low = d.bands.low;
4692
+ audio.bands.lowMid = d.bands.lowMid;
4693
+ audio.bands.mid = d.bands.mid;
4694
+ audio.bands.highMid = d.bands.highMid;
4695
+ audio.bands.high = d.bands.high;
4696
+ audio.bands.lowSmoothed = d.bands.lowSmoothed;
4697
+ audio.bands.lowMidSmoothed = d.bands.lowMidSmoothed;
4698
+ audio.bands.midSmoothed = d.bands.midSmoothed;
4699
+ audio.bands.highMidSmoothed = d.bands.highMidSmoothed;
4700
+ audio.bands.highSmoothed = d.bands.highSmoothed;
4701
+ if (d.beat) {
4702
+ audio.beat.kick = d.beat.kick;
4703
+ audio.beat.snare = d.beat.snare;
4704
+ audio.beat.hat = d.beat.hat;
4705
+ audio.beat.any = d.beat.any;
4706
+ audio.beat.kickSmoothed = d.beat.kickSmoothed;
4707
+ audio.beat.snareSmoothed = d.beat.snareSmoothed;
4708
+ audio.beat.hatSmoothed = d.beat.hatSmoothed;
4709
+ audio.beat.anySmoothed = d.beat.anySmoothed;
4710
+ const events = d.beat.events || [];
4711
+ if (events.length > 0) {
4712
+ audio.beat.triggers.kick = audio.beat.triggers.kick || events.some((e) => e.type === "kick");
4713
+ audio.beat.triggers.snare = audio.beat.triggers.snare || events.some((e) => e.type === "snare");
4714
+ audio.beat.triggers.hat = audio.beat.triggers.hat || events.some((e) => e.type === "hat");
4715
+ audio.beat.triggers.any = true;
4716
+ audio.beat.events.push(...events);
4717
+ }
4718
+ audio.beat.bpm = d.beat.bpm;
4719
+ audio.beat.confidence = d.beat.confidence;
4720
+ audio.beat.isLocked = d.beat.isLocked;
4721
+ }
4722
+ audio.spectral.brightness = d.spectral.brightness;
4723
+ audio.spectral.flatness = d.spectral.flatness;
4724
+ this.audioFrequencyData = new Uint8Array(d.frequencyData);
4725
+ this.audioWaveformData = d.waveformData ? new Float32Array(d.waveformData) : new Float32Array(0);
4726
+ }
4727
+ handleAdditionalAudioUpdate(streamIndex, d) {
4728
+ const frequencyData = new Uint8Array(d.frequencyData);
4729
+ const waveformData = d.waveformData ? new Float32Array(d.waveformData) : new Float32Array(0);
4730
+ this.audioStreamStates.set(streamIndex, {
4731
+ isConnected: d.isConnected,
4732
+ volume: { ...d.volume },
4733
+ bands: { ...d.bands },
4734
+ spectral: { ...d.spectral },
4735
+ frequencyData,
4736
+ waveformData
4737
+ });
4738
+ this.updateVijiAudioStreams();
4739
+ }
4740
+ /**
4741
+ * Handles audio-stream-setup: registers stream type and device mapping.
4742
+ * Mirrors handleVideoCanvasSetup — two-phase protocol where setup establishes
4743
+ * the stream identity, and subsequent audio-analysis-update messages carry data.
4744
+ */
4745
+ handleAudioStreamSetup(message) {
4746
+ const { streamIndex, streamType, deviceId } = message.data;
4747
+ switch (streamType) {
4748
+ case "additional":
4749
+ this.updateVijiAudioStreams();
4750
+ break;
4751
+ case "device":
4752
+ if (deviceId) {
4753
+ this.deviceAudioMap.set(deviceId, streamIndex);
4754
+ const device = this.viji.devices.find((d) => d.id === deviceId);
4755
+ if (device) {
4756
+ const audioState = this.audioStreamStates.get(streamIndex);
4757
+ if (audioState) {
4758
+ device.audio = {
4759
+ isConnected: audioState.isConnected,
4760
+ volume: audioState.volume,
4761
+ bands: audioState.bands,
4762
+ spectral: audioState.spectral,
4763
+ getFrequencyData: () => audioState.frequencyData,
4764
+ getWaveform: () => audioState.waveformData
4765
+ };
4766
+ }
4767
+ }
4768
+ }
4769
+ break;
4770
+ }
4771
+ this.debugLog(`Audio stream setup at index ${streamIndex}, type: ${streamType}${deviceId ? `, deviceId: ${deviceId}` : ""}`);
4772
+ }
4773
+ /**
4774
+ * Reset frame-scoped audio events (triggers + events array).
4775
+ * Called after each render, mirroring interactionSystem.frameStart().
4776
+ */
4777
+ resetAudioFrameEvents() {
4778
+ const t = this.viji.audio.beat.triggers;
4779
+ t.kick = false;
4780
+ t.snare = false;
4781
+ t.hat = false;
4782
+ t.any = false;
4783
+ this.viji.audio.beat.events.length = 0;
4784
+ }
4785
+ /**
4786
+ * Full audio state reset (scene reload / disconnect).
4787
+ * Zeroes everything without replacing the stable viji.audio reference.
4788
+ */
4789
+ resetAudioState() {
4790
+ const audio = this.viji.audio;
4791
+ audio.isConnected = false;
4792
+ audio.volume.current = 0;
4793
+ audio.volume.peak = 0;
4794
+ audio.volume.smoothed = 0;
4795
+ audio.bands.low = 0;
4796
+ audio.bands.lowMid = 0;
4797
+ audio.bands.mid = 0;
4798
+ audio.bands.highMid = 0;
4799
+ audio.bands.high = 0;
4800
+ audio.bands.lowSmoothed = 0;
4801
+ audio.bands.lowMidSmoothed = 0;
4802
+ audio.bands.midSmoothed = 0;
4803
+ audio.bands.highMidSmoothed = 0;
4804
+ audio.bands.highSmoothed = 0;
4805
+ audio.beat.kick = 0;
4806
+ audio.beat.snare = 0;
4807
+ audio.beat.hat = 0;
4808
+ audio.beat.any = 0;
4809
+ audio.beat.kickSmoothed = 0;
4810
+ audio.beat.snareSmoothed = 0;
4811
+ audio.beat.hatSmoothed = 0;
4812
+ audio.beat.anySmoothed = 0;
4813
+ audio.beat.triggers.kick = false;
4814
+ audio.beat.triggers.snare = false;
4815
+ audio.beat.triggers.hat = false;
4816
+ audio.beat.triggers.any = false;
4817
+ audio.beat.events.length = 0;
4818
+ audio.beat.bpm = 120;
4819
+ audio.beat.confidence = 0;
4820
+ audio.beat.isLocked = false;
4821
+ audio.spectral.brightness = 0;
4822
+ audio.spectral.flatness = 0;
4823
+ this.audioFrequencyData = new Uint8Array(0);
4824
+ this.audioWaveformData = new Float32Array(0);
4825
+ this.audioStreamStates.clear();
4826
+ this.deviceAudioMap.clear();
4827
+ this.viji.audioStreams.length = 0;
4521
4828
  }
4522
4829
  handleVideoCanvasSetup(message) {
4523
4830
  const { streamIndex, streamType, deviceId } = message.data;
@@ -4538,7 +4845,7 @@ class VijiWorkerRuntime {
4538
4845
  Object.assign(this.viji.video, videoSystem.getVideoAPI());
4539
4846
  break;
4540
4847
  case "additional":
4541
- this.updateVijiStreams();
4848
+ this.updateVijiVideoStreams();
4542
4849
  break;
4543
4850
  case "device":
4544
4851
  if (deviceId) {
@@ -4629,7 +4936,7 @@ class VijiWorkerRuntime {
4629
4936
  this.videoSystems[index].setDebugMode(this.debugMode);
4630
4937
  this.videoSystems[index].initializeForDirectFrames(this.rendererType);
4631
4938
  }
4632
- this.updateVijiStreams();
4939
+ this.updateVijiVideoStreams();
4633
4940
  this.debugLog(`[Compositor] Prepared ${directFrameCount} direct frame slot(s)`);
4634
4941
  }
4635
4942
  handleVideoFrameDirect(message) {
@@ -4757,7 +5064,8 @@ class VijiWorkerRuntime {
4757
5064
  renderFrame() {
4758
5065
  if (!this.isRunning) return;
4759
5066
  const currentTime = performance.now();
4760
- this.updateVijiStreams();
5067
+ this.updateVijiVideoStreams();
5068
+ this.updateVijiAudioStreams();
4761
5069
  this.viji.fps = this.frameRateMode === "full" ? this.screenRefreshRate : this.screenRefreshRate / 2;
4762
5070
  let shouldRender = true;
4763
5071
  if (this.frameRateMode === "half") {
@@ -4828,6 +5136,7 @@ class VijiWorkerRuntime {
4828
5136
  }
4829
5137
  this.reportPerformanceStats(currentTime);
4830
5138
  this.interactionSystem.frameStart();
5139
+ this.resetAudioFrameEvents();
4831
5140
  requestAnimationFrame(() => this.renderFrame());
4832
5141
  }
4833
5142
  postMessage(type, data) {
@@ -4856,17 +5165,29 @@ class VijiWorkerRuntime {
4856
5165
  this.viji.device = this.deviceState.device;
4857
5166
  const updatedDevices = this.deviceState.devices.map((deviceData) => {
4858
5167
  const existingDevice = this.viji.devices.find((d) => d.id === deviceData.id);
5168
+ const videoStreamIndex = this.deviceVideoMap.get(deviceData.id);
5169
+ const videoSystem = videoStreamIndex !== void 0 ? this.videoSystems[videoStreamIndex] : void 0;
5170
+ const audioStreamIndex = this.deviceAudioMap.get(deviceData.id);
5171
+ const audioState = audioStreamIndex !== void 0 ? this.audioStreamStates.get(audioStreamIndex) : void 0;
5172
+ const audioAPI = audioState ? {
5173
+ isConnected: audioState.isConnected,
5174
+ volume: audioState.volume,
5175
+ bands: audioState.bands,
5176
+ spectral: audioState.spectral,
5177
+ getFrequencyData: () => audioState.frequencyData,
5178
+ getWaveform: () => audioState.waveformData
5179
+ } : null;
4859
5180
  if (existingDevice) {
4860
5181
  existingDevice.name = deviceData.name;
4861
5182
  existingDevice.motion = deviceData.motion;
4862
5183
  existingDevice.orientation = deviceData.orientation;
5184
+ if (audioAPI) existingDevice.audio = audioAPI;
4863
5185
  return existingDevice;
4864
5186
  } else {
4865
- const streamIndex = this.deviceVideoMap.get(deviceData.id);
4866
- const videoSystem = streamIndex !== void 0 ? this.videoSystems[streamIndex] : void 0;
4867
5187
  return {
4868
5188
  ...deviceData,
4869
- video: videoSystem ? videoSystem.getVideoAPI() : null
5189
+ video: videoSystem ? videoSystem.getVideoAPI() : null,
5190
+ audio: audioAPI
4870
5191
  };
4871
5192
  }
4872
5193
  });
@@ -4895,6 +5216,21 @@ class SceneAnalyzer {
4895
5216
  }
4896
5217
  return "native";
4897
5218
  }
5219
+ /**
5220
+ * P5 main-canvas mode from the renderer directive (only meaningful when
5221
+ * {@link detectRendererType} is `'p5'`).
5222
+ *
5223
+ * - `// @renderer p5` → 2D (default)
5224
+ * - `// @renderer p5 webgl` → WEBGL
5225
+ */
5226
+ static detectP5CanvasMode(sceneCode) {
5227
+ if (/\/\/\s*@renderer\s+p5\s+webgl\b|\/\*\s*@renderer\s+p5\s+webgl\s*\*\//i.test(
5228
+ sceneCode
5229
+ )) {
5230
+ return "webgl";
5231
+ }
5232
+ return "2d";
5233
+ }
4898
5234
  }
4899
5235
  var ContextualKeyword;
4900
5236
  (function(ContextualKeyword2) {
@@ -26216,6 +26552,7 @@ async function setSceneCode(sceneCode) {
26216
26552
  await runtime.initShaderMode(sceneCode);
26217
26553
  runtime.sendAllParametersToHost();
26218
26554
  } else if (rendererType === "p5") {
26555
+ const p5CanvasMode = SceneAnalyzer.detectP5CanvasMode(sceneCode);
26219
26556
  const jsCode = prepareSceneCode(sceneCode);
26220
26557
  const functionBody = jsCode + '\nreturn { setup: typeof setup !== "undefined" ? setup : null, render: typeof render !== "undefined" ? render : null };';
26221
26558
  const sceneFunction = new Function("viji", "p5", functionBody);
@@ -26223,7 +26560,7 @@ async function setSceneCode(sceneCode) {
26223
26560
  if (!render) {
26224
26561
  throw new Error("P5 mode requires a render(viji, p5) function");
26225
26562
  }
26226
- await runtime.initP5Mode(setup, render);
26563
+ await runtime.initP5Mode(setup, render, { canvasMode: p5CanvasMode });
26227
26564
  runtime.sendAllParametersToHost();
26228
26565
  } else {
26229
26566
  const jsCode = prepareSceneCode(sceneCode);
@@ -26249,4 +26586,4 @@ async function setSceneCode(sceneCode) {
26249
26586
  }
26250
26587
  }
26251
26588
  self.setSceneCode = setSceneCode;
26252
- //# sourceMappingURL=viji.worker-Zg128woJ.js.map
26589
+ //# sourceMappingURL=viji.worker-jTmB7qoQ.js.map