@viji-dev/core 0.3.26 → 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.
@@ -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,8 @@ ${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++);
2998
3085
  }
2999
3086
  if (this.backbufferEnabled) {
3000
3087
  this.textureUnits.set("backbuffer", this.nextTextureUnit++);
@@ -3165,18 +3252,18 @@ ${error}`);
3165
3252
  this.setUniform("u_videoResolution", "vec2", [0, 0]);
3166
3253
  this.setUniform("u_videoFrameRate", "float", 0);
3167
3254
  }
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);
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);
3176
3263
  this.setUniform(
3177
3264
  resolutionUniform,
3178
3265
  "vec2",
3179
- [streams[i].frameWidth, streams[i].frameHeight]
3266
+ [videoStreams[i].frameWidth, videoStreams[i].frameHeight]
3180
3267
  );
3181
3268
  this.setUniform(connectedUniform, "bool", true);
3182
3269
  } else {
@@ -3203,6 +3290,34 @@ ${error}`);
3203
3290
  this.setUniform(connectedUniform, "bool", false);
3204
3291
  }
3205
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
+ }
3206
3321
  const faces = video.faces || [];
3207
3322
  this.setUniform("u_faceCount", "int", faces.length);
3208
3323
  if (faces.length > 0) {
@@ -3732,18 +3847,18 @@ ${error}`);
3732
3847
  }
3733
3848
  }
3734
3849
  /**
3735
- * Update compositor stream texture at specified index
3850
+ * Update compositor video stream texture at specified index
3736
3851
  * Supports both OffscreenCanvas and ImageBitmap for zero-copy pipeline
3737
3852
  */
3738
- updateStreamTexture(index, streamFrame) {
3853
+ updateVideoStreamTexture(index, streamFrame) {
3739
3854
  const gl = this.gl;
3740
- const uniformName = `u_stream${index}`;
3855
+ const uniformName = `u_videoStream${index}`;
3741
3856
  const unit = this.textureUnits.get(uniformName);
3742
- if (!this.streamTextures[index]) {
3743
- this.streamTextures[index] = gl.createTexture();
3857
+ if (!this.videoStreamTextures[index]) {
3858
+ this.videoStreamTextures[index] = gl.createTexture();
3744
3859
  }
3745
3860
  gl.activeTexture(gl.TEXTURE0 + unit);
3746
- gl.bindTexture(gl.TEXTURE_2D, this.streamTextures[index]);
3861
+ gl.bindTexture(gl.TEXTURE_2D, this.videoStreamTextures[index]);
3747
3862
  const shouldFlip = streamFrame instanceof OffscreenCanvas;
3748
3863
  if (shouldFlip) {
3749
3864
  gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, true);
@@ -3934,10 +4049,10 @@ ${error}`);
3934
4049
  if (this.audioWaveformTexture) gl.deleteTexture(this.audioWaveformTexture);
3935
4050
  if (this.videoTexture) gl.deleteTexture(this.videoTexture);
3936
4051
  if (this.segmentationTexture) gl.deleteTexture(this.segmentationTexture);
3937
- for (const texture of this.streamTextures) {
4052
+ for (const texture of this.videoStreamTextures) {
3938
4053
  if (texture) gl.deleteTexture(texture);
3939
4054
  }
3940
- this.streamTextures = [];
4055
+ this.videoStreamTextures = [];
3941
4056
  for (const texture of this.deviceTextures) {
3942
4057
  if (texture) gl.deleteTexture(texture);
3943
4058
  }
@@ -4027,6 +4142,11 @@ class VijiWorkerRuntime {
4027
4142
  };
4028
4143
  // Map deviceId → streamIndex for O(1) device video lookup
4029
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();
4030
4150
  // Video state is now managed by the worker-side VideoSystem
4031
4151
  // Artist API object
4032
4152
  viji = {
@@ -4124,7 +4244,9 @@ class VijiWorkerRuntime {
4124
4244
  }
4125
4245
  },
4126
4246
  // Additional video streams (index 1+, no CV)
4127
- streams: [],
4247
+ videoStreams: [],
4248
+ // Additional audio streams (lightweight analysis, no beat detection)
4249
+ audioStreams: [],
4128
4250
  // Interaction APIs will be added during construction
4129
4251
  mouse: {},
4130
4252
  keyboard: {},
@@ -4195,7 +4317,7 @@ class VijiWorkerRuntime {
4195
4317
  * Initialize P5.js mode
4196
4318
  * Sets up P5 rendering with P5WorkerAdapter
4197
4319
  */
4198
- async initP5Mode(setup, render) {
4320
+ async initP5Mode(setup, render, options) {
4199
4321
  try {
4200
4322
  this.rendererType = "p5";
4201
4323
  this.debugLog("🎨 Initializing P5.js mode...");
@@ -4204,7 +4326,8 @@ class VijiWorkerRuntime {
4204
4326
  this.viji,
4205
4327
  {
4206
4328
  setup,
4207
- render
4329
+ render,
4330
+ canvasMode: options?.canvasMode === "webgl" ? "webgl" : "2d"
4208
4331
  }
4209
4332
  );
4210
4333
  await this.p5Adapter.init();
@@ -4296,20 +4419,47 @@ class VijiWorkerRuntime {
4296
4419
  if (this.videoSystems[0]) {
4297
4420
  Object.assign(this.viji.video, this.videoSystems[0].getVideoAPI());
4298
4421
  }
4299
- this.updateVijiStreams();
4422
+ this.updateVijiVideoStreams();
4300
4423
  }
4301
4424
  /**
4302
- * Updates viji.streams from videoSystems array.
4425
+ * Updates viji.videoStreams from videoSystems array.
4303
4426
  * Collects 'additional' MediaStreams first, then 'directFrame' injected frames.
4304
4427
  * Excludes 'main' and 'device' streams (those go to viji.video and viji.devices[].video).
4305
4428
  */
4306
- updateVijiStreams() {
4429
+ updateVijiVideoStreams() {
4307
4430
  const additional = this.videoSystems.filter((vs) => vs && vs.getStreamType() === "additional").map((vs) => vs.getVideoAPI());
4308
4431
  const directFrames = this.videoSystems.filter((vs) => vs && vs.getStreamType() === "directFrame").map((vs) => vs.getVideoAPI());
4309
4432
  const freshStreams = [...additional, ...directFrames];
4310
- this.viji.streams.length = freshStreams.length;
4433
+ this.viji.videoStreams.length = freshStreams.length;
4311
4434
  for (let i = 0; i < freshStreams.length; i++) {
4312
- 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
+ };
4313
4463
  }
4314
4464
  }
4315
4465
  // Send all parameters (from helper functions) to host
@@ -4353,6 +4503,9 @@ class VijiWorkerRuntime {
4353
4503
  case "audio-analysis-update":
4354
4504
  this.handleAudioAnalysisUpdate(message);
4355
4505
  break;
4506
+ case "audio-stream-setup":
4507
+ this.handleAudioStreamSetup(message);
4508
+ break;
4356
4509
  case "video-canvas-setup":
4357
4510
  this.handleVideoCanvasSetup(message);
4358
4511
  break;
@@ -4522,6 +4675,14 @@ class VijiWorkerRuntime {
4522
4675
  }
4523
4676
  handleAudioAnalysisUpdate(message) {
4524
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) {
4525
4686
  const audio = this.viji.audio;
4526
4687
  audio.isConnected = d.isConnected;
4527
4688
  audio.volume.current = d.volume.current;
@@ -4537,30 +4698,78 @@ class VijiWorkerRuntime {
4537
4698
  audio.bands.midSmoothed = d.bands.midSmoothed;
4538
4699
  audio.bands.highMidSmoothed = d.bands.highMidSmoothed;
4539
4700
  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;
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
+ }
4559
4722
  audio.spectral.brightness = d.spectral.brightness;
4560
4723
  audio.spectral.flatness = d.spectral.flatness;
4561
4724
  this.audioFrequencyData = new Uint8Array(d.frequencyData);
4562
4725
  this.audioWaveformData = d.waveformData ? new Float32Array(d.waveformData) : new Float32Array(0);
4563
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
+ }
4564
4773
  /**
4565
4774
  * Reset frame-scoped audio events (triggers + events array).
4566
4775
  * Called after each render, mirroring interactionSystem.frameStart().
@@ -4613,6 +4822,9 @@ class VijiWorkerRuntime {
4613
4822
  audio.spectral.flatness = 0;
4614
4823
  this.audioFrequencyData = new Uint8Array(0);
4615
4824
  this.audioWaveformData = new Float32Array(0);
4825
+ this.audioStreamStates.clear();
4826
+ this.deviceAudioMap.clear();
4827
+ this.viji.audioStreams.length = 0;
4616
4828
  }
4617
4829
  handleVideoCanvasSetup(message) {
4618
4830
  const { streamIndex, streamType, deviceId } = message.data;
@@ -4633,7 +4845,7 @@ class VijiWorkerRuntime {
4633
4845
  Object.assign(this.viji.video, videoSystem.getVideoAPI());
4634
4846
  break;
4635
4847
  case "additional":
4636
- this.updateVijiStreams();
4848
+ this.updateVijiVideoStreams();
4637
4849
  break;
4638
4850
  case "device":
4639
4851
  if (deviceId) {
@@ -4724,7 +4936,7 @@ class VijiWorkerRuntime {
4724
4936
  this.videoSystems[index].setDebugMode(this.debugMode);
4725
4937
  this.videoSystems[index].initializeForDirectFrames(this.rendererType);
4726
4938
  }
4727
- this.updateVijiStreams();
4939
+ this.updateVijiVideoStreams();
4728
4940
  this.debugLog(`[Compositor] Prepared ${directFrameCount} direct frame slot(s)`);
4729
4941
  }
4730
4942
  handleVideoFrameDirect(message) {
@@ -4852,7 +5064,8 @@ class VijiWorkerRuntime {
4852
5064
  renderFrame() {
4853
5065
  if (!this.isRunning) return;
4854
5066
  const currentTime = performance.now();
4855
- this.updateVijiStreams();
5067
+ this.updateVijiVideoStreams();
5068
+ this.updateVijiAudioStreams();
4856
5069
  this.viji.fps = this.frameRateMode === "full" ? this.screenRefreshRate : this.screenRefreshRate / 2;
4857
5070
  let shouldRender = true;
4858
5071
  if (this.frameRateMode === "half") {
@@ -4952,17 +5165,29 @@ class VijiWorkerRuntime {
4952
5165
  this.viji.device = this.deviceState.device;
4953
5166
  const updatedDevices = this.deviceState.devices.map((deviceData) => {
4954
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;
4955
5180
  if (existingDevice) {
4956
5181
  existingDevice.name = deviceData.name;
4957
5182
  existingDevice.motion = deviceData.motion;
4958
5183
  existingDevice.orientation = deviceData.orientation;
5184
+ if (audioAPI) existingDevice.audio = audioAPI;
4959
5185
  return existingDevice;
4960
5186
  } else {
4961
- const streamIndex = this.deviceVideoMap.get(deviceData.id);
4962
- const videoSystem = streamIndex !== void 0 ? this.videoSystems[streamIndex] : void 0;
4963
5187
  return {
4964
5188
  ...deviceData,
4965
- video: videoSystem ? videoSystem.getVideoAPI() : null
5189
+ video: videoSystem ? videoSystem.getVideoAPI() : null,
5190
+ audio: audioAPI
4966
5191
  };
4967
5192
  }
4968
5193
  });
@@ -4991,6 +5216,21 @@ class SceneAnalyzer {
4991
5216
  }
4992
5217
  return "native";
4993
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
+ }
4994
5234
  }
4995
5235
  var ContextualKeyword;
4996
5236
  (function(ContextualKeyword2) {
@@ -26312,6 +26552,7 @@ async function setSceneCode(sceneCode) {
26312
26552
  await runtime.initShaderMode(sceneCode);
26313
26553
  runtime.sendAllParametersToHost();
26314
26554
  } else if (rendererType === "p5") {
26555
+ const p5CanvasMode = SceneAnalyzer.detectP5CanvasMode(sceneCode);
26315
26556
  const jsCode = prepareSceneCode(sceneCode);
26316
26557
  const functionBody = jsCode + '\nreturn { setup: typeof setup !== "undefined" ? setup : null, render: typeof render !== "undefined" ? render : null };';
26317
26558
  const sceneFunction = new Function("viji", "p5", functionBody);
@@ -26319,7 +26560,7 @@ async function setSceneCode(sceneCode) {
26319
26560
  if (!render) {
26320
26561
  throw new Error("P5 mode requires a render(viji, p5) function");
26321
26562
  }
26322
- await runtime.initP5Mode(setup, render);
26563
+ await runtime.initP5Mode(setup, render, { canvasMode: p5CanvasMode });
26323
26564
  runtime.sendAllParametersToHost();
26324
26565
  } else {
26325
26566
  const jsCode = prepareSceneCode(sceneCode);
@@ -26345,4 +26586,4 @@ async function setSceneCode(sceneCode) {
26345
26586
  }
26346
26587
  }
26347
26588
  self.setSceneCode = setSceneCode;
26348
- //# sourceMappingURL=viji.worker-PAf0oIec.js.map
26589
+ //# sourceMappingURL=viji.worker-jTmB7qoQ.js.map