@viji-dev/core 0.8.16 → 0.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.ts CHANGED
@@ -307,7 +307,9 @@ export declare class AudioSystem {
307
307
  private debugLog;
308
308
  private fftSize;
309
309
  private audioStateBeat;
310
- private sendAnalysisResults;
310
+ private analysisSinks;
311
+ private latestChannelResults;
312
+ private channelMeta;
311
313
  constructor(sendAnalysisResultsCallback?: (data: any) => void);
312
314
  /**
313
315
  * Get the current audio analysis state (for host-side usage)
@@ -420,6 +422,23 @@ export declare class AudioSystem {
420
422
  * Tags with streamIndex and conditionally includes beat data (main channel only).
421
423
  */
422
424
  private sendChannelResults;
425
+ /**
426
+ * Register a result-delivery sink (one per attached core's worker) and
427
+ * replay current state to it in FIFO order: the latest main-channel
428
+ * analysis, then per non-main channel its `audio-stream-setup` identity
429
+ * followed by the latest cached analysis. A mid-stream attach is thereby
430
+ * indistinguishable from a fresh connect on the receiving worker.
431
+ */
432
+ addSink(id: string, send: (message: {
433
+ type: string;
434
+ data: any;
435
+ }) => void): void;
436
+ /**
437
+ * Remove a previously registered result-delivery sink.
438
+ */
439
+ removeSink(id: string): void;
440
+ private buildStreamSetupMessage;
441
+ private emitStreamSetup;
423
442
  /**
424
443
  * Reset audio values to defaults (main channel + beat state)
425
444
  */
@@ -436,17 +455,25 @@ export declare class AudioSystem {
436
455
  resetAudioState(): Promise<void>;
437
456
  /**
438
457
  * Add an additional audio stream (lightweight analysis, no beat detection).
439
- * @param streamIndex Global stream index (from VijiCore: AUDIO_ADDITIONAL_BASE + n or AUDIO_DEVICE_BASE + n)
458
+ * When `meta` is provided, the stream identity is recorded and an
459
+ * `audio-stream-setup` message is emitted to every sink (and replayed to
460
+ * sinks attached later).
461
+ * @param streamIndex Global stream index (AUDIO_ADDITIONAL_BASE + n or AUDIO_DEVICE_BASE + n)
440
462
  * @param stream The MediaStream to analyze
463
+ * @param meta Stream identity for the worker-side two-phase setup protocol
441
464
  */
442
- addStream(streamIndex: number, stream: MediaStream): Promise<void>;
465
+ addStream(streamIndex: number, stream: MediaStream, meta?: {
466
+ streamType: 'additional' | 'device';
467
+ deviceId?: string;
468
+ }): Promise<void>;
443
469
  /**
444
470
  * Remove an additional audio stream.
445
471
  */
446
472
  removeStream(streamIndex: number): void;
447
473
  /**
448
- * Tear down all additional channels and rebuild with corrected indices.
449
- * Mirrors video's reinitializeAdditionalCoordinators pattern.
474
+ * Tear down all additional channels in the base range and rebuild with
475
+ * corrected indices, re-emitting `audio-stream-setup` for the re-indexed
476
+ * streams. Mirrors video's reinitializeAdditionalCoordinators pattern.
450
477
  */
451
478
  reinitializeAdditionalChannels(streams: MediaStream[], baseIndex: number): Promise<void>;
452
479
  /**
@@ -2665,7 +2692,7 @@ declare type TrackingState = 'TRACKING' | 'LOCKED' | 'BREAKDOWN' | 'LOST';
2665
2692
  /** Returned by `on*` listener registration calls; invoke to unsubscribe. */
2666
2693
  export declare type Unsubscribe = () => void;
2667
2694
 
2668
- export declare const VERSION = "0.8.15";
2695
+ export declare const VERSION = "0.9.0";
2669
2696
 
2670
2697
  /**
2671
2698
  * Real-time video API: drawable frame, dimensions, and the source-side
@@ -3024,7 +3051,6 @@ export declare class VijiCore {
3024
3051
  private workerManager;
3025
3052
  private interactionManager;
3026
3053
  private deviceSensorManager;
3027
- private audioSystem;
3028
3054
  private isInitialized;
3029
3055
  private isDestroyed;
3030
3056
  private isInitializing;
@@ -3038,23 +3064,10 @@ export declare class VijiCore {
3038
3064
  private debugLog;
3039
3065
  private config;
3040
3066
  private isHeadless;
3041
- private currentAudioStream;
3042
- private static readonly ADDITIONAL_STREAM_BASE;
3043
- private static readonly DEVICE_VIDEO_BASE;
3044
3067
  private static readonly DIRECT_FRAME_BASE;
3045
- private static readonly AUDIO_ADDITIONAL_BASE;
3046
- private static readonly AUDIO_DEVICE_BASE;
3047
- private static readonly MAX_ADDITIONAL_AUDIO_STREAMS;
3048
- private static readonly MAX_ADDITIONAL_VIDEO_STREAMS;
3049
- private videoStream;
3050
- private videoStreams;
3051
- private audioStreams;
3052
- private deviceAudioStreamIndices;
3053
- private mainVideoCoordinator;
3054
- private additionalCoordinators;
3068
+ private engineGroup;
3069
+ private groupMember;
3055
3070
  private directFrameSlots;
3056
- private deviceVideoCoordinators;
3057
- private deviceVideoStreamIndices;
3058
3071
  private latestFrameBuffer;
3059
3072
  private autoCaptureEnabled;
3060
3073
  private eventSourceCore;
@@ -3367,27 +3380,33 @@ export declare class VijiCore {
3367
3380
  code: string;
3368
3381
  }) => void): void;
3369
3382
  /**
3370
- * Snapshot the full audio analysis + onset state for cross-instance
3371
- * transfer. Use for **same-process scene-switch** continuity (a fresh
3372
- * `VijiCore` can resume detection where the previous one left off).
3383
+ * Snapshot the full audio analysis + onset state for **cross-device**
3384
+ * transfer (e.g. host → second host, multi-machine sync). Pair with
3385
+ * `importFullState` on the receiver, applying an NTP-derived `clockOffset`.
3386
+ *
3387
+ * NOT needed for same-process transitions: construct the successor core
3388
+ * with `{ mediaSource: oldCore }` instead — the shared audio engine (and
3389
+ * its live BPM lock / onset patterns) survives the swap with no
3390
+ * export/import step and no warm-up gap.
3373
3391
  *
3374
- * For **cross-device** transfer, prefer `audio.onset.exportSessionState`
3392
+ * For controller → host onset sync, prefer `audio.onset.exportSessionState`
3375
3393
  * — the controller's audio analysis state doesn't apply to the host's
3376
3394
  * audio source and would corrupt detection.
3377
3395
  *
3378
3396
  * Wall-clock fields are in this instance's `performance.now()` clock space.
3379
- * Receiver applies `clockOffset` on import (use `0` for same-process).
3380
3397
  *
3381
- * **Staleness caveat**: the audio block is robust to scene-load gaps under
3382
- * ~1 second. Longer gaps (>5s, e.g. cold cache + slow network) may cause
3398
+ * **Staleness caveat**: the audio block is robust to transfer gaps under
3399
+ * ~1 second. Longer gaps (>5s, e.g. slow network) may cause
3383
3400
  * stale-timestamp drift in `gridLockMemoryTime` / `warmupStartTimeMs`;
3384
3401
  * callers should treat that as a degraded path and skip the audio block.
3385
3402
  */
3386
3403
  exportFullState(): SerializedCoreState;
3387
3404
  /**
3388
- * Replace the audio analysis + onset state from a serialized payload.
3389
- * `clockOffset` is added to all sender-clocked fields (`0` for same-process,
3390
- * NTP-derived for cross-device). Mutation is synchronous.
3405
+ * Replace the audio analysis + onset state from a serialized payload
3406
+ * (**cross-device** transfer — for same-process transitions use
3407
+ * `mediaSource` instead, see {@link exportFullState}).
3408
+ * `clockOffset` is added to all sender-clocked fields (NTP-derived;
3409
+ * `0` only when clocks are genuinely shared). Mutation is synchronous.
3391
3410
  *
3392
3411
  * Field-level events (`onModeChange`, `onMuteChange`) fire when imported
3393
3412
  * values differ from current — consumer-visible state stays consistent
@@ -3495,57 +3514,76 @@ export declare class VijiCore {
3495
3514
  */
3496
3515
  setAudioStream(audioStream: MediaStream | null): Promise<void>;
3497
3516
  /**
3498
- * Sets the main video stream (CV enabled)
3517
+ * Sets the main video stream (CV enabled).
3518
+ *
3519
+ * Engine-level write: visible to ALL cores sharing this core's media
3520
+ * engines — each attached core's worker gets its own fresh capture for the
3521
+ * new stream. Capability change notifications fan out to all of them.
3499
3522
  */
3500
3523
  setVideoStream(stream: MediaStream | null): Promise<void>;
3501
3524
  /**
3502
- * Gets the main video stream
3525
+ * Gets the main video stream (engine-scoped — identical for all cores
3526
+ * sharing this core's media engines)
3503
3527
  */
3504
3528
  getVideoStream(): MediaStream | null;
3505
3529
  /**
3506
3530
  * Adds an additional video stream (no CV). Returns its index in viji.videoStreams[].
3531
+ *
3532
+ * Engine-level write: the stream appears in `viji.videoStreams[]` of EVERY
3533
+ * core sharing this core's media engines.
3507
3534
  */
3508
3535
  addVideoStream(stream: MediaStream): Promise<number>;
3509
3536
  /**
3510
- * Removes an additional video stream by index.
3537
+ * Removes an additional video stream by index. Stops the stream's tracks.
3538
+ *
3539
+ * Engine-level write: removal (and the resulting re-indexing) is visible to
3540
+ * ALL cores sharing this core's media engines.
3511
3541
  */
3512
3542
  removeVideoStream(index: number): Promise<void>;
3513
3543
  /**
3514
- * Gets the number of additional video streams.
3544
+ * Gets the number of additional video streams (engine-scoped).
3515
3545
  */
3516
3546
  getVideoStreamCount(): number;
3517
3547
  /**
3518
3548
  * Add an additional audio stream for lightweight analysis (no beat detection).
3549
+ *
3550
+ * Engine-level write: the stream appears in `viji.audioStreams[]` of EVERY
3551
+ * core sharing this core's media engines.
3519
3552
  * @param stream MediaStream with audio tracks
3520
3553
  * @returns Index of the new stream (0-based within additional streams)
3521
3554
  * @throws VijiCoreError if limit reached (max 8)
3522
3555
  */
3523
3556
  addAudioStream(stream: MediaStream): Promise<number>;
3524
3557
  /**
3525
- * Remove an additional audio stream by index.
3526
- * Triggers re-indexing of remaining streams.
3558
+ * Remove an additional audio stream by index. Stops the stream's tracks
3559
+ * and triggers re-indexing of remaining streams.
3560
+ *
3561
+ * Engine-level write: removal is visible to ALL cores sharing this core's
3562
+ * media engines.
3527
3563
  */
3528
3564
  removeAudioStream(index: number): Promise<void>;
3529
3565
  /**
3530
- * Gets the number of additional audio streams.
3566
+ * Gets the number of additional audio streams (engine-scoped).
3531
3567
  */
3532
3568
  getAudioStreamCount(): number;
3533
3569
  /**
3534
3570
  * Set audio stream from an external device.
3571
+ *
3572
+ * Engine-level write: visible to ALL cores sharing this core's media
3573
+ * engines. Device registration (`addExternalDevice`) stays per-core.
3535
3574
  * @param deviceId Device identifier (must be registered via addExternalDevice)
3536
3575
  * @param stream MediaStream with audio tracks from the device
3537
3576
  */
3538
3577
  setDeviceAudio(deviceId: string, stream: MediaStream): Promise<void>;
3539
3578
  /**
3540
3579
  * Clear audio stream from a device.
3580
+ *
3581
+ * Engine-level write: visible to ALL cores sharing this core's media engines.
3541
3582
  */
3542
3583
  clearDeviceAudio(deviceId: string): Promise<void>;
3543
3584
  /**
3544
- * Reinitializes all additional coordinators after array mutation.
3545
- */
3546
- private reinitializeAdditionalCoordinators;
3547
- /**
3548
- * Gets the current audio stream
3585
+ * Gets the current audio stream (engine-scoped — identical for all cores
3586
+ * sharing this core's media engines)
3549
3587
  */
3550
3588
  getAudioStream(): MediaStream | null;
3551
3589
  /**
@@ -3556,6 +3594,16 @@ export declare class VijiCore {
3556
3594
  * Gets the current interaction enabled state
3557
3595
  */
3558
3596
  getInteractionEnabled(): boolean;
3597
+ /**
3598
+ * Lazy audio-engine access. Read-only getters use `audioEngine` (never
3599
+ * materializes — headless previews construct nothing); every other
3600
+ * `core.audio.*` call uses `ensureAudioEngine()` (materialize-on-use:
3601
+ * mutators, subscriptions, imports AND exports — a listener registered or
3602
+ * a tap received before any stream exists must hit a live engine, see the
3603
+ * constructor-started idle ticker in AudioSystem).
3604
+ */
3605
+ private get audioEngine();
3606
+ private ensureAudioEngine;
3559
3607
  readonly audio: {
3560
3608
  /**
3561
3609
  * Beat control namespace
@@ -3637,6 +3685,11 @@ export declare class VijiCore {
3637
3685
  *
3638
3686
  * Idempotent transitions (already in target mode) do not fire.
3639
3687
  *
3688
+ * Engine-level: the listener registry lives on the shared audio engine,
3689
+ * so subscribing through several cores that share media engines targets
3690
+ * ONE registry — subscribe once per app. The same function reference is
3691
+ * deduped by identity; distinct closures doing the same work are not.
3692
+ *
3640
3693
  * @returns Unsubscribe function. Call to remove the listener.
3641
3694
  */
3642
3695
  onModeChange: (listener: (ev: OnsetModeChangeEvent) => void) => Unsubscribe;
@@ -3656,6 +3709,9 @@ export declare class VijiCore {
3656
3709
  * when fields differ; this event does not because session-end is a
3657
3710
  * different category — about session lifecycle, not field state.
3658
3711
  *
3712
+ * Engine-level: one registry per shared audio engine — subscribe once
3713
+ * per app (see {@link onModeChange}).
3714
+ *
3659
3715
  * @returns Unsubscribe function. Call to remove the listener.
3660
3716
  */
3661
3717
  onSessionEnd: (listener: (ev: OnsetSessionEndEvent) => void) => Unsubscribe;
@@ -3671,6 +3727,9 @@ export declare class VijiCore {
3671
3727
  * Idempotent calls (e.g. `setMuted(true)` on an already-muted instrument)
3672
3728
  * do not fire.
3673
3729
  *
3730
+ * Engine-level: one registry per shared audio engine — subscribe once
3731
+ * per app (see {@link onModeChange}).
3732
+ *
3674
3733
  * @returns Unsubscribe function. Call to remove the listener.
3675
3734
  */
3676
3735
  onMuteChange: (listener: (ev: OnsetMuteChangeEvent) => void) => Unsubscribe;
@@ -3717,7 +3776,11 @@ export declare class VijiCore {
3717
3776
  importSessionState: (state: SerializedOnsetState, clockOffset: number) => void;
3718
3777
  };
3719
3778
  /**
3720
- * Advanced audio controls
3779
+ * Advanced audio controls.
3780
+ *
3781
+ * Engine-level: these settings live on the shared audio engine and are
3782
+ * visible to ALL cores sharing this core's media engines (same rule as
3783
+ * the stream mutators).
3721
3784
  */
3722
3785
  advanced: {
3723
3786
  /**
@@ -3939,12 +4002,20 @@ export declare class VijiCore {
3939
4002
  /**
3940
4003
  * Set video stream for an external device
3941
4004
  * Device camera will appear in viji.devices[].video
4005
+ *
4006
+ * Engine-level write: every core sharing this core's media engines gets
4007
+ * its own capture of the device stream. Device registration
4008
+ * (`addExternalDevice`) and sensor state stay per-core, so each attached
4009
+ * core's host app registers the device on that core — the worker
4010
+ * self-heals whichever of stream/registration arrives last.
3942
4011
  * @param deviceId - Device identifier
3943
4012
  * @param stream - MediaStream from device camera
3944
4013
  */
3945
4014
  setDeviceVideo(deviceId: string, stream: MediaStream): Promise<void>;
3946
4015
  /**
3947
4016
  * Clear video stream from device
4017
+ *
4018
+ * Engine-level write: visible to ALL cores sharing this core's media engines.
3948
4019
  * @param deviceId - Device identifier
3949
4020
  */
3950
4021
  clearDeviceVideo(deviceId: string): Promise<void>;
@@ -4004,8 +4075,27 @@ export declare interface VijiCoreConfig {
4004
4075
  videoStreams?: MediaStream[];
4005
4076
  /** Additional audio input streams (lightweight analysis, no beat detection) */
4006
4077
  audioStreams?: MediaStream[];
4007
- /** Disable input processing (for preview instances) */
4008
- noInputs?: boolean;
4078
+ /**
4079
+ * Share another core's media analysis engines instead of creating private
4080
+ * ones. The new core attaches to the referenced core's engine group at
4081
+ * construction time and detaches on `destroy()`; the engines (audio
4082
+ * analysis, video capture) survive as long as ANY attached core is alive,
4083
+ * so analysis state (BPM lock, onset patterns, stream connections) carries
4084
+ * across core transitions with no export/import step.
4085
+ *
4086
+ * Mutually exclusive with `audioStream`, `videoStream`, `audioStreams`,
4087
+ * and `videoStreams` — the engine group already owns the streams.
4088
+ *
4089
+ * Stream mutators (`setAudioStream`, `setVideoStream`, `addAudioStream`,
4090
+ * `setDeviceAudio`, …) called on ANY attached core write to the shared
4091
+ * engines and are visible to ALL attached cores.
4092
+ *
4093
+ * The referenced core must not be destroyed and must not have a fenced
4094
+ * benchmark in flight (`BENCHMARK_NOT_ALLOWED`). The reference is resolved
4095
+ * to the engine group at construction; it is not retained afterwards and
4096
+ * is omitted from the `configuration` getter.
4097
+ */
4098
+ mediaSource?: VijiCore;
4009
4099
  /** Enable user interaction events (DOM event capture, disabled in headless by default) */
4010
4100
  allowUserInteraction?: boolean;
4011
4101
  /** Enable device sensor APIs (motion, orientation) */
package/dist/index.js CHANGED
@@ -1,4 +1,4 @@
1
- import { A as s, C as o, I as E, V as i, a as A, b as C } from "./index-f5EKzN3f.js";
1
+ import { A as s, C as o, I as E, V as i, a as A, b as C } from "./index-CAx7Pxyl.js";
2
2
  export {
3
3
  s as AudioSystem,
4
4
  o as CV_FEATURE_KEYS,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@viji-dev/core",
3
- "version": "0.8.16",
3
+ "version": "0.9.0",
4
4
  "description": "Universal execution engine for Viji Creative scenes",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -49,6 +49,7 @@
49
49
  ],
50
50
  "scripts": {
51
51
  "prebuild": "node scripts/prebuild-clean.mjs && node scripts/sync-version.mjs",
52
+ "version": "npm run build && git add -A",
52
53
  "build": "vite build && node scripts/copy-tasks-assets.mjs && node scripts/build-artist-types.mjs && node scripts/build-docs-api.mjs",
53
54
  "build:docs": "node scripts/build-docs-api.mjs",
54
55
  "lint:ai-prompts-drift": "node scripts/lint-ai-prompts-drift.mjs",