@viji-dev/core 0.2.20 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.ts CHANGED
@@ -1,27 +1,643 @@
1
- export declare interface AnalysisConfiguration {
2
- fftSize?: number;
3
- smoothing?: number;
4
- frequencyBands?: FrequencyBand[];
5
- beatDetection?: boolean;
6
- onsetDetection?: boolean;
1
+ /* Excluded from this release type: AnalysisConfiguration */
2
+
3
+ declare interface AudioAnalysisState {
4
+ isConnected: boolean;
5
+ currentBPM: number;
6
+ confidence: number;
7
+ mode: 'auto' | 'manual';
8
+ tapCount: number;
9
+ isLocked: boolean;
10
+ sensitivity: number;
7
11
  }
8
12
 
9
13
  export declare interface AudioAPI {
10
14
  isConnected: boolean;
11
15
  volume: {
12
- rms: number;
16
+ current: number;
13
17
  peak: number;
18
+ smoothed: number;
19
+ };
20
+ bands: {
21
+ low: number;
22
+ lowMid: number;
23
+ mid: number;
24
+ highMid: number;
25
+ high: number;
26
+ lowSmoothed: number;
27
+ lowMidSmoothed: number;
28
+ midSmoothed: number;
29
+ highMidSmoothed: number;
30
+ highSmoothed: number;
14
31
  };
15
- beat?: {
16
- isKick: boolean;
32
+ beat: {
33
+ kick: number;
34
+ snare: number;
35
+ hat: number;
36
+ any: number;
37
+ kickSmoothed: number;
38
+ snareSmoothed: number;
39
+ anySmoothed: number;
40
+ triggers: {
41
+ any: boolean;
42
+ kick: boolean;
43
+ snare: boolean;
44
+ hat: boolean;
45
+ };
46
+ events: Array<{
47
+ type: 'kick' | 'snare' | 'hat';
48
+ time: number;
49
+ strength: number;
50
+ }>;
51
+ bpm: number;
52
+ phase: number;
53
+ bar: number;
17
54
  confidence: number;
55
+ isLocked: boolean;
56
+ };
57
+ spectral: {
58
+ brightness: number;
59
+ flatness: number;
60
+ flux: number;
18
61
  };
19
- bands: Record<string, number>;
20
62
  getFrequencyData: () => Uint8Array;
63
+ getWaveform?: (samples?: number) => Float32Array;
64
+ getFFT?: (bins?: number, scale?: 'linear' | 'log') => Float32Array;
65
+ }
66
+
67
+ /**
68
+ * Manages audio analysis and processing for the host side.
69
+ * Handles audio stream processing and provides real-time audio analysis.
70
+ *
71
+ * This system provides:
72
+ * - Real-time audio analysis (RMS, peak, frequency bands)
73
+ * - Beat detection with automatic BPM tracking
74
+ * - Onset detection for audio-reactive triggers
75
+ * - Smooth energy curves with envelope followers
76
+ * - Tap tempo and manual BPM control
77
+ * - Auto-gain normalization
78
+ * - Spectral feature extraction
79
+ * - AudioContext management and cleanup
80
+ * - Raw FFT data access for custom analysis
81
+ * - Analysis results sent to worker for artist API
82
+ */
83
+ export declare class AudioSystem {
84
+ private audioContext;
85
+ private analyser;
86
+ private mediaStreamSource;
87
+ private currentStream;
88
+ private analysisMode;
89
+ private workletNode;
90
+ private workletReady;
91
+ private currentSampleRate;
92
+ private pcmRing;
93
+ private pcmWriteIndex;
94
+ private pcmFilled;
95
+ private pcmHistorySeconds;
96
+ private lastTempoExtraction;
97
+ private tempoExtractionIntervalMs;
98
+ private analysisBackend;
99
+ private bandNoiseFloor;
100
+ /** Tracks the last non-zero BPM chosen for output to avoid dropping to 0 */
101
+ private lastNonZeroBpm;
102
+ /** Tracks which source provided the current BPM (pll | tempo | carry | default) */
103
+ private lastBpmSource;
104
+ private aubioWarningLogged;
105
+ private useEssentiaTempo;
106
+ private workletFrameCount;
107
+ private forceAnalyser;
108
+ private statusLogTimer;
109
+ private isAudioLab;
110
+ private analysisTicks;
111
+ private lastLoopWarn;
112
+ private verboseLabLogs;
113
+ private lastPhaseLogTime;
114
+ private onsetLogBuffer;
115
+ private debugMode;
116
+ private diagnosticLogger;
117
+ private onsetDetection;
118
+ private essentiaOnsetDetection;
119
+ private tempoInduction;
120
+ private pll;
121
+ private stateManager;
122
+ private lastKickDetected;
123
+ private volumeAutoGain;
124
+ private bandAutoGain;
125
+ /** Raw band snapshot before auto-gain (for debug) */
126
+ private rawBandsPreGain;
127
+ /** Last dt used in analysis loop (ms) */
128
+ private lastDtMs;
129
+ /** Flag to disable auto-gain for debugging */
130
+ private debugDisableAutoGain;
131
+ /** Flag to track if we've logged detection method (to avoid spam) */
132
+ private readonly bandNames;
133
+ private essentiaBandHistories;
134
+ private readonly essentiaHistoryWindowMs;
135
+ private beatMode;
136
+ private manualBPM;
137
+ private tapHistory;
138
+ private tapTimeout;
139
+ private envelopeFollowers;
140
+ private sensitivity;
141
+ private beatDetectionEnabled;
142
+ private onsetDetectionEnabled;
143
+ private autoGainEnabled;
144
+ /**
145
+ * Enable or disable comprehensive debug logging for all layers
146
+ * Enables enhanced logging in: MultiOnsetDetection, BeatStateManager
147
+ */
148
+ setDebugMode(enabled: boolean): void;
149
+ /**
150
+ * Reset Essentia band onset histories (used to feed tempo induction when using the Essentia backend)
151
+ */
152
+ private resetEssentiaBandHistories;
153
+ /**
154
+ * Update Essentia band onset histories and trim old entries
155
+ */
156
+ private updateEssentiaBandHistories;
157
+ private getEssentiaBandHistory;
158
+ private getEssentiaAllHistories;
159
+ private logVerbose;
160
+ /**
161
+ * Start/stop periodic status logging. If maxSamples provided, stops after that many ticks.
162
+ */
163
+ private toggleStatusLogger;
164
+ /**
165
+ * Handle frames pushed from AudioWorklet
166
+ */
167
+ private handleWorkletFrame;
168
+ /**
169
+ * Unified analysis pipeline (worklet and analyser paths)
170
+ */
171
+ private analyzeFrame;
172
+ /**
173
+ * Compute FFT and derived arrays
174
+ */
175
+ private computeFFT;
176
+ /**
177
+ * Calculate RMS/peak from float PCM frame (-1..1)
178
+ */
179
+ private calculateVolumeMetrics;
180
+ /**
181
+ * Calculate perceptual/log-ish bands (pre-gain), normalized by max magnitude
182
+ */
183
+ private calculateFrequencyBandsFromMagnitude;
184
+ /**
185
+ * Spectral features from magnitude spectrum
186
+ */
187
+ private calculateSpectralFeaturesFromMagnitude;
188
+ /**
189
+ * Append PCM frame to ring buffer for periodic Essentia tempo extraction
190
+ */
191
+ private appendPcmFrame;
192
+ /**
193
+ * Periodically estimate tempo using Essentia's RhythmExtractor2013 (offline chunk)
194
+ */
195
+ private runEssentiaTempoEstimate;
196
+ /**
197
+ * Run onset + beat detection pipeline and return BeatState
198
+ */
199
+ private runBeatPipeline;
200
+ /**
201
+ * Debug logging helper
202
+ */
203
+ private debugLog;
204
+ private fftSize;
205
+ private smoothingTimeConstant;
206
+ private analysisInterval;
207
+ private analysisIntervalMs;
208
+ private frequencyData;
209
+ private timeDomainData;
210
+ private fftEngine;
211
+ private fftInput;
212
+ private fftOutput;
213
+ private fftMagnitude;
214
+ private fftMagnitudeDb;
215
+ private fftPhase;
216
+ private hannWindow;
217
+ private audioState;
218
+ private analysisLoopId;
219
+ private isAnalysisRunning;
220
+ private lastAnalysisTimestamp;
221
+ private sendAnalysisResults;
222
+ private prevSpectralCentroid;
223
+ constructor(sendAnalysisResultsCallback?: (data: any) => void);
224
+ /**
225
+ * Prepare FFT buffers and windowing for the selected fftSize
226
+ */
227
+ private refreshFFTResources;
228
+ /**
229
+ * Get the current audio analysis state (for host-side usage)
230
+ */
231
+ getAudioState(): {
232
+ isConnected: boolean;
233
+ volume: {
234
+ current: number;
235
+ peak: number;
236
+ smoothed: number;
237
+ };
238
+ bands: {
239
+ low: number;
240
+ lowMid: number;
241
+ mid: number;
242
+ highMid: number;
243
+ high: number;
244
+ lowSmoothed: number;
245
+ lowMidSmoothed: number;
246
+ midSmoothed: number;
247
+ highMidSmoothed: number;
248
+ highSmoothed: number;
249
+ };
250
+ beat: {
251
+ kick: number;
252
+ snare: number;
253
+ hat: number;
254
+ any: number;
255
+ kickSmoothed: number;
256
+ snareSmoothed: number;
257
+ anySmoothed: number;
258
+ events: BeatEvent[];
259
+ bpm: number;
260
+ phase: number;
261
+ bar: number;
262
+ confidence: number;
263
+ isLocked: boolean;
264
+ };
265
+ spectral: {
266
+ brightness: number;
267
+ flatness: number;
268
+ flux: number;
269
+ };
270
+ };
271
+ /**
272
+ * Initialize Essentia.js (async WASM loading)
273
+ */
274
+ private initializeEssentia;
275
+ /**
276
+ * Wait for Essentia.js to initialize (for testing/benchmarking)
277
+ * Returns immediately if already initialized or if initialization failed
278
+ */
279
+ waitForEssentia(timeoutMs?: number): Promise<boolean>;
280
+ /**
281
+ * Handle audio stream update (called from VijiCore)
282
+ */
283
+ handleAudioStreamUpdate(data: {
284
+ audioStream?: MediaStream | null;
285
+ analysisConfig?: AnalysisConfiguration;
286
+ timestamp: number;
287
+ }): void;
288
+ /**
289
+ * Set the audio stream for analysis
290
+ */
291
+ private setAudioStream;
292
+ /**
293
+ * Disconnect current audio stream and clean up resources
294
+ */
295
+ private disconnectAudioStream;
296
+ /**
297
+ * Update analysis configuration
298
+ */
299
+ private updateAnalysisConfig;
300
+ /**
301
+ * Initialize audio worklet for high-quality capture (complex STFT path)
302
+ */
303
+ private setupAudioWorklet;
304
+ /**
305
+ * Start the audio analysis loop at high speed (8ms intervals for transient capture)
306
+ */
307
+ private startAnalysisLoop;
308
+ /**
309
+ * Stop the audio analysis loop
310
+ */
311
+ private stopAnalysisLoop;
312
+ /**
313
+ * Pause audio analysis (for tests or temporary suspension)
314
+ * The setInterval continues but performAnalysis() exits early
315
+ */
316
+ pauseAnalysis(): void;
317
+ /**
318
+ * Resume audio analysis after pause
319
+ */
320
+ resumeAnalysis(): void;
321
+ /**
322
+ * Perform audio analysis (called every frame)
323
+ * Uses industry-grade 4-layer architecture:
324
+ * Layer 1: MultiOnsetDetection (per-band onset detection)
325
+ * Layer 2: TempoInduction (dual-method BPM detection)
326
+ * Layer 3: PhaseLockedLoop (stable phase tracking)
327
+ * Layer 4: BeatStateManager (state machine + confidence)
328
+ */
329
+ private performAnalysis;
330
+ private applyAutoGain;
331
+ /**
332
+ * Update smooth versions of frequency bands
333
+ */
334
+ private updateSmoothBands;
335
+ /**
336
+ * Send analysis results to worker
337
+ */
338
+ private sendAnalysisResultsToWorker;
339
+ /**
340
+ * Reset audio values to defaults
341
+ */
342
+ private resetAudioValues;
343
+ /**
344
+ * Reset all audio state (called when destroying)
345
+ */
346
+ resetAudioState(): void;
347
+ /**
348
+ * Get current analysis configuration
349
+ */
350
+ getAnalysisConfig(): {
351
+ fftSize: number;
352
+ smoothing: number;
353
+ };
354
+ /**
355
+ * Set analysis backend preference
356
+ */
357
+ setAnalysisBackend(backend: 'auto' | 'essentia' | 'custom' | 'aubio'): void;
358
+ getAnalysisBackend(): 'auto' | 'essentia' | 'custom' | 'aubio';
359
+ /**
360
+ * Force analyser path (skip worklet) for debugging
361
+ */
362
+ setForceAnalyser(enabled: boolean): void;
363
+ isForceAnalyser(): boolean;
364
+ /**
365
+ * Enable/disable Essentia tempo extraction (disabled by default due to WASM exception config)
366
+ */
367
+ setEssentiaTempoEnabled(enabled: boolean): void;
368
+ isEssentiaTempoEnabled(): boolean;
369
+ /**
370
+ * Set global sensitivity multiplier
371
+ * @param value - Sensitivity (0.5-2.0, default 1.0)
372
+ */
373
+ setSensitivity(value: number): void;
374
+ /**
375
+ * Get current sensitivity
376
+ */
377
+ getSensitivity(): number;
378
+ /**
379
+ * Tap tempo - record tap for manual BPM
380
+ */
381
+ tapTempo(): void;
382
+ /**
383
+ * Calculate BPM from tap history
384
+ */
385
+ private calculateBPMFromTaps;
386
+ /**
387
+ * Start auto-clear timeout for tap tempo
388
+ */
389
+ private startTapClearTimeout;
390
+ /**
391
+ * Clear tap tempo history
392
+ */
393
+ clearTaps(): void;
394
+ /**
395
+ * Get tap count
396
+ */
397
+ getTapCount(): number;
398
+ /**
399
+ * Set beat sync mode
400
+ */
401
+ setBeatMode(mode: 'auto' | 'manual'): void;
402
+ /**
403
+ * Get beat sync mode
404
+ */
405
+ getBeatMode(): 'auto' | 'manual';
406
+ /**
407
+ * Set manual BPM
408
+ */
409
+ setManualBPM(bpm: number): void;
410
+ /**
411
+ * Get current BPM (manual or auto-detected)
412
+ */
413
+ getCurrentBPM(): number;
414
+ /**
415
+ * Nudge beat phase
416
+ */
417
+ nudgeBeatPhase(amount: number): void;
418
+ /**
419
+ * Reset beat phase to next beat
420
+ */
421
+ resetBeatPhase(): void;
422
+ /**
423
+ * Set FFT size
424
+ */
425
+ setFFTSize(size: 2048 | 4096 | 8192): void;
426
+ /**
427
+ * Set smoothing time constant
428
+ */
429
+ setSmoothing(value: number): void;
430
+ /**
431
+ * Enable/disable auto-gain
432
+ */
433
+ setAutoGain(enabled: boolean): void;
434
+ /**
435
+ * Enable/disable beat detection
436
+ */
437
+ setBeatDetection(enabled: boolean): void;
438
+ /**
439
+ * Enable/disable onset detection
440
+ */
441
+ setOnsetDetection(enabled: boolean): void;
442
+ /**
443
+ * Get complete audio analysis state
444
+ */
445
+ getState(): {
446
+ isConnected: boolean;
447
+ currentBPM: number;
448
+ confidence: number;
449
+ mode: 'auto' | 'manual';
450
+ tapCount: number;
451
+ isLocked: boolean;
452
+ sensitivity: number;
453
+ trackingState: string;
454
+ };
455
+ /**
456
+ * Get current per-frame audio analysis data (for UI display)
457
+ * Returns a snapshot of the latest audio analysis values
458
+ */
459
+ getCurrentAudioData(): {
460
+ isConnected: boolean;
461
+ volume: {
462
+ current: number;
463
+ peak: number;
464
+ smoothed: number;
465
+ };
466
+ bands: {
467
+ low: number;
468
+ lowMid: number;
469
+ mid: number;
470
+ highMid: number;
471
+ high: number;
472
+ lowSmoothed: number;
473
+ lowMidSmoothed: number;
474
+ midSmoothed: number;
475
+ highMidSmoothed: number;
476
+ highSmoothed: number;
477
+ };
478
+ beat: {
479
+ kick: number;
480
+ snare: number;
481
+ hat: number;
482
+ any: number;
483
+ kickSmoothed: number;
484
+ snareSmoothed: number;
485
+ anySmoothed: number;
486
+ events: BeatEvent[];
487
+ bpm: number;
488
+ phase: number;
489
+ bar: number;
490
+ confidence: number;
491
+ isLocked: boolean;
492
+ };
493
+ spectral: {
494
+ brightness: number;
495
+ flatness: number;
496
+ flux: number;
497
+ };
498
+ };
499
+ /**
500
+ * Get enhanced debug snapshot from all layers
501
+ */
502
+ getEnhancedDebugSnapshot(): {
503
+ onset: {
504
+ currentBPM: number;
505
+ debugMode: boolean;
506
+ totalOnsets: number;
507
+ currentCluster: {
508
+ primaryBands: string[];
509
+ ageMssinceStart: number;
510
+ } | null;
511
+ bandStates: Record<string, {
512
+ lastOnsetTime: number;
513
+ onsetCount: number;
514
+ cutoff: number;
515
+ minEnergy: number;
516
+ }>;
517
+ };
518
+ tempo: {
519
+ bpm: number;
520
+ confidence: number;
521
+ };
522
+ pll: PLLState;
523
+ state: {
524
+ trackingState: TrackingState;
525
+ isLocked: boolean;
526
+ };
527
+ };
528
+ /**
529
+ * Get comprehensive debug info for testing/debugging
530
+ * Exposes internal state of all analysis layers
531
+ */
532
+ getDebugInfo(): {
533
+ onset: Record<BandName, {
534
+ energy: number;
535
+ prevEnergy: number;
536
+ prevEnergy2: number;
537
+ minEnergy: number;
538
+ flux: number;
539
+ cutoff: number;
540
+ minThreshold: number;
541
+ minFlux: number;
542
+ wouldTrigger: boolean;
543
+ reason: string;
544
+ debounceMs: number;
545
+ lastOnsetAgo: number;
546
+ baselineAlpha?: number;
547
+ dtMs?: number;
548
+ fluxPrevOnly?: number;
549
+ fluxBaseline?: number;
550
+ initialized?: boolean;
551
+ lastFluxStored?: number;
552
+ fluxCalcNow?: number;
553
+ }>;
554
+ tempo: {
555
+ currentBPM: number;
556
+ confidence: number;
557
+ method: string;
558
+ anchorBand: string | null;
559
+ methodAgreement: number;
560
+ bpmHistory: number[];
561
+ envelopeLength: number;
562
+ lastAutocorrBPM: number;
563
+ lastIOIBPM: number;
564
+ };
565
+ pll: {
566
+ bpm: number;
567
+ phase: number;
568
+ bar: number;
569
+ driftRate: number;
570
+ state: PLLState;
571
+ };
572
+ state: {
573
+ trackingState: TrackingState;
574
+ isLocked: boolean;
575
+ };
576
+ levels: {
577
+ low: number;
578
+ lowMid: number;
579
+ mid: number;
580
+ highMid: number;
581
+ high: number;
582
+ volume: number;
583
+ };
584
+ rawBands: {
585
+ low: number;
586
+ lowMid: number;
587
+ mid: number;
588
+ highMid: number;
589
+ high: number;
590
+ };
591
+ bandGains: {
592
+ low: number;
593
+ lowMid: number;
594
+ mid: number;
595
+ highMid: number;
596
+ high: number;
597
+ };
598
+ volumeGain: number;
599
+ dtMs: number;
600
+ smoothingTimeConstant: number;
601
+ analysisIntervalMs: number;
602
+ events: BeatEvent[];
603
+ };
604
+ }
605
+
606
+ /**
607
+ * Multi-band onset detection with hybrid peak-decay + spectral flux approach
608
+ *
609
+ * Industry-grade onset detection that:
610
+ * - Uses 5 frequency bands for precise instrument detection
611
+ * - Combines peak-decay threshold (proven reliable) with spectral flux (transient detection)
612
+ * - Maintains per-band onset history for tempo pattern analysis
613
+ * - Is frame-rate independent (dt-aware)
614
+ * - Prevents double-triggering with adaptive cooldown
615
+ */
616
+ /**
617
+ * Band names for the 5-band analysis
618
+ */
619
+ declare type BandName = 'low' | 'lowMid' | 'mid' | 'highMid' | 'high';
620
+
621
+ /**
622
+ * Beat event (discrete, timestamped)
623
+ */
624
+ declare interface BeatEvent {
625
+ type: 'kick' | 'snare' | 'hat';
626
+ time: number;
627
+ strength: number;
628
+ confidence?: number;
629
+ phase?: number;
630
+ bar?: number;
631
+ isLayered?: boolean;
632
+ isPredicted?: boolean;
633
+ bpm?: number;
634
+ bands?: string[];
21
635
  }
22
636
 
23
637
  export declare interface CaptureFrameOptions {
24
- /** MIME type for output, e.g., 'image/png', 'image/jpeg', 'image/webp' */
638
+ /** Output format: 'blob' for encoded image, 'bitmap' for GPU-friendly ImageBitmap */
639
+ format?: 'blob' | 'bitmap';
640
+ /** MIME type for blob output (ignored for bitmap), e.g., 'image/png', 'image/jpeg', 'image/webp' */
25
641
  type?: string;
26
642
  /**
27
643
  * Target resolution.
@@ -65,6 +681,67 @@ declare type CVFeature = 'faceDetection' | 'faceMesh' | 'handTracking' | 'poseDe
65
681
 
66
682
  declare type CVFrameRateMode = 'full' | 'half' | 'quarter' | 'eighth';
67
683
 
684
+ /**
685
+ * DeviceMotionEvent acceleration data
686
+ * Matches native DeviceMotionEvent.acceleration structure
687
+ */
688
+ declare interface DeviceMotionData {
689
+ /** Acceleration without gravity (m/s²) */
690
+ acceleration: {
691
+ x: number | null;
692
+ y: number | null;
693
+ z: number | null;
694
+ } | null;
695
+ /** Acceleration including gravity (m/s²) */
696
+ accelerationIncludingGravity: {
697
+ x: number | null;
698
+ y: number | null;
699
+ z: number | null;
700
+ } | null;
701
+ /** Rotation rate (degrees/second) */
702
+ rotationRate: {
703
+ alpha: number | null;
704
+ beta: number | null;
705
+ gamma: number | null;
706
+ } | null;
707
+ /** Interval between updates (milliseconds) */
708
+ interval: number;
709
+ }
710
+
711
+ /**
712
+ * DeviceOrientationEvent data
713
+ * Matches native DeviceOrientationEvent structure
714
+ */
715
+ declare interface DeviceOrientationData {
716
+ /** Rotation around Z-axis (0-360 degrees, compass heading) */
717
+ alpha: number | null;
718
+ /** Rotation around X-axis (-180 to 180 degrees, front-to-back tilt) */
719
+ beta: number | null;
720
+ /** Rotation around Y-axis (-90 to 90 degrees, left-to-right tilt) */
721
+ gamma: number | null;
722
+ /** True if using magnetometer (compass) for absolute orientation */
723
+ absolute: boolean;
724
+ }
725
+
726
+ /**
727
+ * Complete device sensor state (internal device - no id/name)
728
+ */
729
+ declare interface DeviceSensorState {
730
+ motion: DeviceMotionData | null;
731
+ orientation: DeviceOrientationData | null;
732
+ geolocation: GeolocationData | null;
733
+ }
734
+
735
+ /**
736
+ * External device state (includes id and name)
737
+ */
738
+ declare interface DeviceState extends DeviceSensorState {
739
+ /** Unique device identifier */
740
+ id: string;
741
+ /** User-friendly device name */
742
+ name: string;
743
+ }
744
+
68
745
  export declare interface FaceData {
69
746
  id: number;
70
747
  bounds: {
@@ -109,6 +786,27 @@ export declare interface FrequencyBand {
109
786
  max: number;
110
787
  }
111
788
 
789
+ /**
790
+ * Geolocation API data
791
+ * Matches native GeolocationPosition.coords structure
792
+ */
793
+ declare interface GeolocationData {
794
+ /** Latitude in decimal degrees */
795
+ latitude: number | null;
796
+ /** Longitude in decimal degrees */
797
+ longitude: number | null;
798
+ /** Altitude in meters above sea level */
799
+ altitude: number | null;
800
+ /** Accuracy of latitude/longitude in meters */
801
+ accuracy: number | null;
802
+ /** Accuracy of altitude in meters */
803
+ altitudeAccuracy: number | null;
804
+ /** Direction of travel (0-360 degrees, 0=North, 90=East) */
805
+ heading: number | null;
806
+ /** Speed in meters per second */
807
+ speed: number | null;
808
+ }
809
+
112
810
  export declare interface HandData {
113
811
  id: number;
114
812
  handedness: 'left' | 'right';
@@ -210,6 +908,16 @@ export declare interface KeyboardAPI {
210
908
  meta: boolean;
211
909
  }
212
910
 
911
+ export declare interface KeyboardEventData {
912
+ type: 'keydown' | 'keyup';
913
+ key: string;
914
+ code: string;
915
+ shiftKey: boolean;
916
+ ctrlKey: boolean;
917
+ altKey: boolean;
918
+ metaKey: boolean;
919
+ }
920
+
213
921
  export declare interface MouseAPI {
214
922
  x: number;
215
923
  y: number;
@@ -232,6 +940,17 @@ export declare interface MouseAPI {
232
940
  wasMoved: boolean;
233
941
  }
234
942
 
943
+ export declare interface MouseEventData {
944
+ x: number;
945
+ y: number;
946
+ buttons: number;
947
+ deltaX?: number;
948
+ deltaY?: number;
949
+ wheelDeltaX?: number;
950
+ wheelDeltaY?: number;
951
+ isInCanvas?: boolean;
952
+ }
953
+
235
954
  declare interface NumberConfig {
236
955
  min?: number;
237
956
  max?: number;
@@ -312,6 +1031,35 @@ export declare interface PerformanceStats {
312
1031
  };
313
1032
  }
314
1033
 
1034
+ /**
1035
+ * Phase-Locked Loop for stable beat phase tracking
1036
+ *
1037
+ * Industry-grade phase tracking that:
1038
+ * - Maintains expected beat times based on current BPM
1039
+ * - Compares actual onsets to expected times (phase error)
1040
+ * - Corrects phase gradually to avoid jumps
1041
+ * - Handles breakdowns by extrapolating phase
1042
+ * - Detects tempo drift in DJ sets
1043
+ * - Provides stable bar position (0-3 for 4/4 time)
1044
+ */
1045
+ /**
1046
+ * PLL state for external access
1047
+ */
1048
+ declare interface PLLState {
1049
+ /** Current phase (0-1, where 0 = beat) */
1050
+ phase: number;
1051
+ /** Current bar position (0-3 for 4/4 time) */
1052
+ bar: number;
1053
+ /** Current BPM being tracked */
1054
+ bpm: number;
1055
+ /** Time since last confirmed beat (ms) */
1056
+ timeSinceLastBeat: number;
1057
+ /** Whether tempo is drifting (DJ transition) */
1058
+ isDrifting: boolean;
1059
+ /** Drift rate (BPM per second, positive = speeding up) */
1060
+ driftRate: number;
1061
+ }
1062
+
315
1063
  declare interface PoseData {
316
1064
  confidence: number;
317
1065
  landmarks: {
@@ -439,6 +1187,20 @@ export declare interface TouchAPI {
439
1187
  gestures: TouchGestureAPI;
440
1188
  }
441
1189
 
1190
+ export declare interface TouchEventData {
1191
+ type: 'touchstart' | 'touchmove' | 'touchend' | 'touchcancel';
1192
+ touches: Array<{
1193
+ identifier: number;
1194
+ clientX: number;
1195
+ clientY: number;
1196
+ pressure: number;
1197
+ radiusX: number;
1198
+ radiusY: number;
1199
+ rotationAngle: number;
1200
+ force: number;
1201
+ }>;
1202
+ }
1203
+
442
1204
  declare interface TouchGestureAPI {
443
1205
  isPinching: boolean;
444
1206
  isRotating: boolean;
@@ -481,6 +1243,12 @@ declare interface TouchPoint {
481
1243
  isEnding: boolean;
482
1244
  }
483
1245
 
1246
+ /**
1247
+ * State machine states
1248
+ * NOTE: LEARNING state removed - system now uses continuous rolling window adaptation
1249
+ */
1250
+ declare type TrackingState = 'TRACKING' | 'LOCKED' | 'BREAKDOWN' | 'LOST';
1251
+
484
1252
  export declare const VERSION = "0.2.20";
485
1253
 
486
1254
  export declare interface VideoAPI {
@@ -518,9 +1286,12 @@ export declare interface VijiAPI {
518
1286
  fps: number;
519
1287
  audio: AudioAPI;
520
1288
  video: VideoAPI;
1289
+ streams: VideoAPI[];
521
1290
  mouse: MouseAPI;
522
1291
  keyboard: KeyboardAPI;
523
1292
  touches: TouchAPI;
1293
+ device: DeviceSensorState;
1294
+ devices: DeviceState[];
524
1295
  slider: (defaultValue: number, config: SliderConfig) => SliderParameter;
525
1296
  color: (defaultValue: string, config: ColorConfig) => ColorParameter;
526
1297
  toggle: (defaultValue: boolean, config: ToggleConfig) => ToggleParameter;
@@ -541,8 +1312,8 @@ export declare class VijiCore {
541
1312
  private iframeManager;
542
1313
  private workerManager;
543
1314
  private interactionManager;
1315
+ private deviceSensorManager;
544
1316
  private audioSystem;
545
- private videoCoordinator;
546
1317
  private isInitialized;
547
1318
  private isDestroyed;
548
1319
  private isInitializing;
@@ -554,8 +1325,19 @@ export declare class VijiCore {
554
1325
  */
555
1326
  private debugLog;
556
1327
  private config;
1328
+ private isHeadless;
557
1329
  private currentAudioStream;
558
- private currentVideoStream;
1330
+ private videoStream;
1331
+ private videoStreams;
1332
+ private mainVideoCoordinator;
1333
+ private additionalCoordinators;
1334
+ private directFrameSlots;
1335
+ private latestFrameBuffer;
1336
+ private autoCaptureEnabled;
1337
+ private eventSourceCore;
1338
+ private frameSourceCores;
1339
+ private eventSourceCleanup;
1340
+ private resizeSyncEnabled;
559
1341
  private currentInteractionEnabled;
560
1342
  private parameterGroups;
561
1343
  private parameterValues;
@@ -567,10 +1349,33 @@ export declare class VijiCore {
567
1349
  private stats;
568
1350
  constructor(config: VijiCoreConfig);
569
1351
  /**
570
- * Capture current scene frame as an image Blob.
571
- * Resolution can be a scale (0-1+) or explicit width/height with center-crop.
1352
+ * Capture current scene frame
1353
+ * @param options.format 'blob' = encoded image, 'bitmap' = GPU-friendly ImageBitmap
572
1354
  */
1355
+ captureFrame(options: {
1356
+ format: 'bitmap';
1357
+ } & Partial<CaptureFrameOptions>): Promise<ImageBitmap>;
573
1358
  captureFrame(options?: CaptureFrameOptions): Promise<Blob>;
1359
+ /**
1360
+ * Update frame buffer (internal, called by worker message)
1361
+ */
1362
+ private updateFrameBuffer;
1363
+ /**
1364
+ * Get latest frame (transfers ownership, zero-copy)
1365
+ */
1366
+ getLatestFrame(): ImageBitmap | null;
1367
+ /**
1368
+ * Get latest frames from all linked sources
1369
+ */
1370
+ getLatestFramesFromSources(): (ImageBitmap | null)[];
1371
+ /**
1372
+ * Enable auto-capture with optional format (internal, called by linkFrameSources)
1373
+ */
1374
+ private enableAutoCapture;
1375
+ /**
1376
+ * Disable auto-capture (internal)
1377
+ */
1378
+ private disableAutoCapture;
574
1379
  /**
575
1380
  * Enable or disable debug logging
576
1381
  */
@@ -579,6 +1384,11 @@ export declare class VijiCore {
579
1384
  * Get current debug mode status
580
1385
  */
581
1386
  getDebugMode(): boolean;
1387
+ /**
1388
+ * Select audio analysis backend
1389
+ */
1390
+ setAudioAnalysisBackend(backend: 'auto' | 'essentia' | 'custom' | 'aubio'): void;
1391
+ getAudioAnalysisBackend(): 'auto' | 'essentia' | 'custom' | 'aubio';
582
1392
  /**
583
1393
  * Initializes the core components in sequence
584
1394
  */
@@ -591,6 +1401,14 @@ export declare class VijiCore {
591
1401
  * Sets up the interaction system for Phase 7
592
1402
  */
593
1403
  private setupInteractionSystem;
1404
+ /**
1405
+ * Sets up device sensor system for internal device sensors
1406
+ */
1407
+ private setupDeviceSensors;
1408
+ /**
1409
+ * Syncs device sensor state to worker
1410
+ */
1411
+ private syncDeviceStateToWorker;
594
1412
  /**
595
1413
  * Sets up communication between components
596
1414
  */
@@ -639,6 +1457,39 @@ export declare class VijiCore {
639
1457
  * Get current core capabilities (what's currently active)
640
1458
  */
641
1459
  getCapabilities(): CoreCapabilities;
1460
+ /**
1461
+ * Inject mouse event (for headless cores or event forwarding)
1462
+ */
1463
+ injectMouseEvent(data: MouseEventData): void;
1464
+ /**
1465
+ * Inject keyboard event
1466
+ */
1467
+ injectKeyboardEvent(data: KeyboardEventData): void;
1468
+ /**
1469
+ * Inject touch event
1470
+ */
1471
+ injectTouchEvent(data: TouchEventData): void;
1472
+ /**
1473
+ * Inject frames directly (compositor pipeline)
1474
+ */
1475
+ injectFrames(bitmaps: ImageBitmap[]): void;
1476
+ /**
1477
+ * Link this core to receive events from a source core
1478
+ * @param syncResolution Smart default: true for headless, false for visible
1479
+ */
1480
+ linkEventSource(sourceCore: VijiCore, syncResolution?: boolean): void;
1481
+ /**
1482
+ * Unlink from event source
1483
+ */
1484
+ unlinkEventSource(): void;
1485
+ /**
1486
+ * Link source cores that this core will pull frames from
1487
+ */
1488
+ linkFrameSources(...sourceCores: VijiCore[]): void;
1489
+ /**
1490
+ * Unlink all frame sources
1491
+ */
1492
+ unlinkFrameSources(): void;
642
1493
  /**
643
1494
  * Get parameter groups filtered by active capabilities
644
1495
  */
@@ -725,17 +1576,49 @@ export declare class VijiCore {
725
1576
  */
726
1577
  setAudioStream(audioStream: MediaStream | null): Promise<void>;
727
1578
  /**
728
- * Sets the video stream for processing
1579
+ * Sets the main video stream (CV enabled)
729
1580
  */
730
- setVideoStream(videoStream: MediaStream | null): Promise<void>;
1581
+ setVideoStream(stream: MediaStream | null): Promise<void>;
731
1582
  /**
732
- * Gets the current audio stream
1583
+ * Gets the main video stream
733
1584
  */
734
- getAudioStream(): MediaStream | null;
1585
+ getVideoStream(): MediaStream | null;
735
1586
  /**
736
- * Gets the current video stream
1587
+ * Sets all additional video streams (no CV)
737
1588
  */
738
- getVideoStream(): MediaStream | null;
1589
+ setVideoStreams(streams: MediaStream[]): Promise<void>;
1590
+ /**
1591
+ * Gets all additional video streams
1592
+ */
1593
+ getVideoStreams(): MediaStream[];
1594
+ /**
1595
+ * Gets video stream at specific index
1596
+ */
1597
+ getVideoStreamAt(index: number): MediaStream | null;
1598
+ /**
1599
+ * Adds a video stream
1600
+ */
1601
+ addVideoStream(stream: MediaStream): Promise<number>;
1602
+ /**
1603
+ * Removes video stream at index
1604
+ */
1605
+ removeVideoStreamAt(index: number): Promise<void>;
1606
+ /**
1607
+ * Removes video stream by reference
1608
+ */
1609
+ removeVideoStream(stream: MediaStream): Promise<boolean>;
1610
+ /**
1611
+ * Updates video stream at index
1612
+ */
1613
+ setVideoStreamAt(index: number, stream: MediaStream): Promise<void>;
1614
+ /**
1615
+ * Reinitializes all additional coordinators
1616
+ */
1617
+ private reinitializeAdditionalCoordinators;
1618
+ /**
1619
+ * Gets the current audio stream
1620
+ */
1621
+ getAudioStream(): MediaStream | null;
739
1622
  /**
740
1623
  * Enables or disables user interactions (mouse, keyboard, touch) at runtime
741
1624
  */
@@ -744,17 +1627,183 @@ export declare class VijiCore {
744
1627
  * Gets the current interaction enabled state
745
1628
  */
746
1629
  getInteractionEnabled(): boolean;
1630
+ readonly audio: {
1631
+ /**
1632
+ * Set global audio sensitivity (0.5-2.0, default 1.0)
1633
+ */
1634
+ setSensitivity: (value: number) => void;
1635
+ /**
1636
+ * Get current sensitivity
1637
+ */
1638
+ getSensitivity: () => number;
1639
+ /**
1640
+ * Beat control namespace
1641
+ */
1642
+ beat: {
1643
+ /**
1644
+ * Tap tempo - record tap for manual BPM
1645
+ */
1646
+ tap: () => void;
1647
+ /**
1648
+ * Clear tap tempo history
1649
+ */
1650
+ clearTaps: () => void;
1651
+ /**
1652
+ * Get tap count
1653
+ */
1654
+ getTapCount: () => number;
1655
+ /**
1656
+ * Set beat sync mode
1657
+ */
1658
+ setMode: (mode: "auto" | "manual") => void;
1659
+ /**
1660
+ * Get beat sync mode
1661
+ */
1662
+ getMode: () => "auto" | "manual";
1663
+ /**
1664
+ * Set manual BPM
1665
+ */
1666
+ setBPM: (bpm: number) => void;
1667
+ /**
1668
+ * Get current BPM (manual or auto-detected)
1669
+ */
1670
+ getBPM: () => number;
1671
+ /**
1672
+ * Nudge beat phase
1673
+ */
1674
+ nudge: (amount: number) => void;
1675
+ /**
1676
+ * Reset beat phase to next beat
1677
+ */
1678
+ resetPhase: () => void;
1679
+ };
1680
+ /**
1681
+ * Advanced audio controls
1682
+ */
1683
+ advanced: {
1684
+ /**
1685
+ * Set FFT size (2048, 4096, or 8192)
1686
+ */
1687
+ setFFTSize: (size: 2048 | 4096 | 8192) => void;
1688
+ /**
1689
+ * Set smoothing time constant (0-1)
1690
+ */
1691
+ setSmoothing: (value: number) => void;
1692
+ /**
1693
+ * Enable/disable auto-gain
1694
+ */
1695
+ setAutoGain: (enabled: boolean) => void;
1696
+ /**
1697
+ * Enable/disable beat detection
1698
+ */
1699
+ setBeatDetection: (enabled: boolean) => void;
1700
+ /**
1701
+ * Enable/disable onset detection
1702
+ */
1703
+ setOnsetDetection: (enabled: boolean) => void;
1704
+ };
1705
+ /**
1706
+ * Get audio analysis state (metadata)
1707
+ */
1708
+ getState: () => AudioAnalysisState;
1709
+ /**
1710
+ * Get current per-frame audio analysis data
1711
+ * Returns volume, bands, beat energies, triggers, and spectral features
1712
+ * Useful for UI displays and visualizations
1713
+ */
1714
+ getCurrentAudioData: () => {
1715
+ isConnected: boolean;
1716
+ volume: {
1717
+ current: number;
1718
+ peak: number;
1719
+ smoothed: number;
1720
+ };
1721
+ bands: {
1722
+ low: number;
1723
+ lowMid: number;
1724
+ mid: number;
1725
+ highMid: number;
1726
+ high: number;
1727
+ lowSmoothed: number;
1728
+ lowMidSmoothed: number;
1729
+ midSmoothed: number;
1730
+ highMidSmoothed: number;
1731
+ highSmoothed: number;
1732
+ };
1733
+ beat: {
1734
+ kick: number;
1735
+ snare: number;
1736
+ hat: number;
1737
+ any: number;
1738
+ kickSmoothed: number;
1739
+ snareSmoothed: number;
1740
+ anySmoothed: number;
1741
+ events: BeatEvent[];
1742
+ bpm: number;
1743
+ phase: number;
1744
+ bar: number;
1745
+ confidence: number;
1746
+ isLocked: boolean;
1747
+ };
1748
+ spectral: {
1749
+ brightness: number;
1750
+ flatness: number;
1751
+ flux: number;
1752
+ };
1753
+ } | {
1754
+ isConnected: false;
1755
+ volume: {
1756
+ current: number;
1757
+ peak: number;
1758
+ smoothed: number;
1759
+ };
1760
+ bands: {
1761
+ low: number;
1762
+ lowMid: number;
1763
+ mid: number;
1764
+ highMid: number;
1765
+ high: number;
1766
+ lowSmoothed: number;
1767
+ lowMidSmoothed: number;
1768
+ midSmoothed: number;
1769
+ highMidSmoothed: number;
1770
+ highSmoothed: number;
1771
+ };
1772
+ beat: {
1773
+ kick: number;
1774
+ snare: number;
1775
+ hat: number;
1776
+ any: number;
1777
+ kickSmoothed: number;
1778
+ snareSmoothed: number;
1779
+ anySmoothed: number;
1780
+ triggers: {
1781
+ any: boolean;
1782
+ kick: boolean;
1783
+ snare: boolean;
1784
+ hat: boolean;
1785
+ };
1786
+ bpm: number;
1787
+ phase: number;
1788
+ bar: number;
1789
+ confidence: number;
1790
+ isLocked: false;
1791
+ };
1792
+ spectral: {
1793
+ brightness: number;
1794
+ flatness: number;
1795
+ flux: number;
1796
+ };
1797
+ };
1798
+ };
747
1799
  /**
748
- * Updates audio analysis configuration
749
- */
750
- setAudioAnalysisConfig(config: {
751
- fftSize?: number;
752
- smoothing?: number;
753
- }): Promise<void>;
754
- /**
755
- * Updates the canvas resolution by scale
1800
+ * Updates the canvas resolution (scale or explicit dimensions)
1801
+ * Works in both headless and normal modes
756
1802
  */
757
- setResolution(scale: number): void;
1803
+ setResolution(resolution: number | {
1804
+ width: number;
1805
+ height: number;
1806
+ }): void;
758
1807
  /**
759
1808
  * Detects the screen refresh rate
760
1809
  */
@@ -767,6 +1816,42 @@ export declare class VijiCore {
767
1816
  * Gets current performance statistics
768
1817
  */
769
1818
  getStats(): PerformanceStats;
1819
+ /**
1820
+ * Register a new external device
1821
+ * @param device Device metadata (id and name)
1822
+ * @returns Device index in the devices array
1823
+ */
1824
+ addExternalDevice(device: {
1825
+ id: string;
1826
+ name: string;
1827
+ }): number;
1828
+ /**
1829
+ * Update sensor data for an external device
1830
+ * @param update Device sensor update (must include id)
1831
+ * @returns true if updated, false if device not found (not registered or removed)
1832
+ */
1833
+ updateExternalDevice(update: {
1834
+ id: string;
1835
+ name?: string;
1836
+ motion?: DeviceMotionData | null;
1837
+ orientation?: DeviceOrientationData | null;
1838
+ geolocation?: GeolocationData | null;
1839
+ }): boolean;
1840
+ /**
1841
+ * Remove/ban an external device
1842
+ * Further updates for this device will be ignored
1843
+ * @param deviceId Device identifier to remove
1844
+ */
1845
+ removeExternalDevice(deviceId: string): void;
1846
+ /**
1847
+ * Get list of connected external devices (for host UI)
1848
+ * @returns Array of device metadata with id, name, and array index
1849
+ */
1850
+ getConnectedDevices(): Array<{
1851
+ id: string;
1852
+ name: string;
1853
+ index: number;
1854
+ }>;
770
1855
  /**
771
1856
  * Checks if the core is ready for use
772
1857
  */
@@ -794,26 +1879,33 @@ export declare class VijiCore {
794
1879
  }
795
1880
 
796
1881
  export declare interface VijiCoreConfig {
797
- /** Container element where the scene will be rendered */
798
- hostContainer: HTMLElement;
1882
+ /** Container element where the scene will be rendered (optional for headless mode) */
1883
+ hostContainer?: HTMLElement;
799
1884
  /** Artist scene code to execute */
800
1885
  sceneCode: string;
801
1886
  /** Frame rate mode - 'full' for every animation frame, 'half' for every second frame */
802
1887
  frameRateMode?: FrameRateMode;
803
- /** Enable automatic performance optimization */
804
- autoOptimize?: boolean;
1888
+ /** Resolution: scale (number) or explicit dimensions (object). Required for headless, must be object form */
1889
+ resolution?: number | {
1890
+ width: number;
1891
+ height: number;
1892
+ };
805
1893
  /** Audio input stream */
806
1894
  audioStream?: MediaStream;
807
- /** Video input stream */
1895
+ /** Video input stream (main stream, CV enabled) */
808
1896
  videoStream?: MediaStream;
1897
+ /** Additional video input streams (no CV processing) */
1898
+ videoStreams?: MediaStream[];
809
1899
  /** Audio analysis configuration */
810
1900
  analysisConfig?: AnalysisConfiguration;
811
1901
  /** Initial parameter values */
812
1902
  parameters?: ParameterGroup[];
813
1903
  /** Disable input processing (for preview instances) */
814
1904
  noInputs?: boolean;
815
- /** Enable user interaction events */
1905
+ /** Enable user interaction events (DOM event capture, disabled in headless by default) */
816
1906
  allowUserInteraction?: boolean;
1907
+ /** Enable device sensor APIs (motion, orientation, geolocation) */
1908
+ allowDeviceInteraction?: boolean;
817
1909
  }
818
1910
 
819
1911
  export declare class VijiCoreError extends Error {