@viji-dev/core 0.5.4 → 0.5.6
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/artist-dts-p5.js +1 -1
- package/dist/artist-dts.js +1 -1
- package/dist/artist-global-p5.d.ts +1 -1
- package/dist/artist-global.d.ts +1 -1
- package/dist/docs-api.js +2 -2
- package/dist/{essentia-wasm.web-aU6UPupF.js → essentia-wasm.web-B2bIxnGE.js} +2 -2
- package/dist/{essentia-wasm.web-aU6UPupF.js.map → essentia-wasm.web-B2bIxnGE.js.map} +1 -1
- package/dist/{index-_PbbZgmh.js → index-Yg6_UX8C.js} +78 -27
- package/dist/{index-_PbbZgmh.js.map → index-Yg6_UX8C.js.map} +1 -1
- package/dist/index.d.ts +32 -12
- package/dist/index.js +1 -1
- package/package.json +1 -1
|
@@ -1897,7 +1897,7 @@ class EssentiaOnsetDetection {
|
|
|
1897
1897
|
this.initPromise = (async () => {
|
|
1898
1898
|
try {
|
|
1899
1899
|
const essentiaModule = await import("./essentia.js-core.es-DnrJE0uR.js");
|
|
1900
|
-
const wasmModule = await import("./essentia-wasm.web-
|
|
1900
|
+
const wasmModule = await import("./essentia-wasm.web-B2bIxnGE.js").then((n) => n.e);
|
|
1901
1901
|
const EssentiaClass = essentiaModule.Essentia || essentiaModule.default?.Essentia || essentiaModule.default;
|
|
1902
1902
|
let WASMModule = wasmModule.default || wasmModule.EssentiaWASM || wasmModule.default?.EssentiaWASM;
|
|
1903
1903
|
if (!WASMModule) {
|
|
@@ -6181,15 +6181,24 @@ class OnsetTapManager {
|
|
|
6181
6181
|
muteChangeListeners = /* @__PURE__ */ new Set();
|
|
6182
6182
|
/**
|
|
6183
6183
|
* Record a tap on `instrument`. Drives the visual envelope, advances
|
|
6184
|
-
* `lastTapTime`, and (by default) feeds
|
|
6184
|
+
* `lastTapTime`, flips mode to reflect activity, and (by default) feeds
|
|
6185
|
+
* the pattern-recognition pipeline.
|
|
6185
6186
|
*
|
|
6186
|
-
* `options.skipRecognition: true`
|
|
6187
|
-
*
|
|
6188
|
-
*
|
|
6189
|
-
*
|
|
6190
|
-
*
|
|
6191
|
-
*
|
|
6192
|
-
*
|
|
6187
|
+
* `options.skipRecognition: true` opts the tap out of the **recognition
|
|
6188
|
+
* pipeline only** — `tapIOIs` accumulation, `tryRecognizePattern`,
|
|
6189
|
+
* `applyPattern`, and `handlePatternTap` are skipped. Mode still flips
|
|
6190
|
+
* `'auto' → 'tapping'` on the first tap of a session (already-`'tapping'`
|
|
6191
|
+
* stays; already-`'pattern'` stays). This is load-bearing: `processFrame`'s
|
|
6192
|
+
* drain of `pendingTapEvents` and its audio-event filter both gate on
|
|
6193
|
+
* mode being non-`'auto'`, and downstream consumers (`onModeChange`
|
|
6194
|
+
* listeners, `broadcastOnsetState`) read mode as the signal that a tap
|
|
6195
|
+
* session is in progress. Suppressing the mode flip would silently break
|
|
6196
|
+
* all of them.
|
|
6197
|
+
*
|
|
6198
|
+
* Used for host-side relay of forwarded tap messages where another
|
|
6199
|
+
* instance owns the authoritative recognition (e.g. host receiving
|
|
6200
|
+
* controller taps over WebRTC). The `MIN_TAP_INTERVAL_MS` debounce still
|
|
6201
|
+
* applies regardless of `skipRecognition`.
|
|
6193
6202
|
*/
|
|
6194
6203
|
tap(instrument, options) {
|
|
6195
6204
|
const s = this.state[instrument];
|
|
@@ -6218,7 +6227,12 @@ class OnsetTapManager {
|
|
|
6218
6227
|
s.pendingTapEvents.push(now);
|
|
6219
6228
|
s.sessionActive = true;
|
|
6220
6229
|
this.scheduleSessionTimers(instrument);
|
|
6221
|
-
if (skipRecognition)
|
|
6230
|
+
if (skipRecognition) {
|
|
6231
|
+
if (s.mode === "auto") {
|
|
6232
|
+
this.setMode(instrument, "tapping");
|
|
6233
|
+
}
|
|
6234
|
+
return;
|
|
6235
|
+
}
|
|
6222
6236
|
if (s.mode === "auto") {
|
|
6223
6237
|
this.setMode(instrument, "tapping");
|
|
6224
6238
|
if (ioi > 0) {
|
|
@@ -7408,6 +7422,7 @@ class AudioChannel {
|
|
|
7408
7422
|
if (this.workletNode) {
|
|
7409
7423
|
try {
|
|
7410
7424
|
this.workletNode.port.onmessage = null;
|
|
7425
|
+
this.workletNode.port.close();
|
|
7411
7426
|
this.workletNode.disconnect();
|
|
7412
7427
|
} catch (_) {
|
|
7413
7428
|
}
|
|
@@ -7457,6 +7472,14 @@ class AudioSystem {
|
|
|
7457
7472
|
onsetLogBuffer = [];
|
|
7458
7473
|
// Debug logging control
|
|
7459
7474
|
debugMode = false;
|
|
7475
|
+
/**
|
|
7476
|
+
* One-way lifecycle flag flipped at the top of `resetAudioState()`.
|
|
7477
|
+
* Gates re-entry into `ensureAudioContext()` and the public stream-mutation
|
|
7478
|
+
* entry points so an in-flight async caller (e.g. a worker RPC firing
|
|
7479
|
+
* mid-teardown) cannot resurrect a fresh `AudioContext` after destroy
|
|
7480
|
+
* has already nulled the field. AudioSystem is single-use after reset.
|
|
7481
|
+
*/
|
|
7482
|
+
destroyed = false;
|
|
7460
7483
|
// Diagnostic logger
|
|
7461
7484
|
diagnosticLogger = new DiagnosticLogger();
|
|
7462
7485
|
// Industry-grade audio analysis modules (4-layer architecture)
|
|
@@ -8042,6 +8065,7 @@ class AudioSystem {
|
|
|
8042
8065
|
* Handle audio stream update for main stream (called from VijiCore)
|
|
8043
8066
|
*/
|
|
8044
8067
|
handleAudioStreamUpdate(data) {
|
|
8068
|
+
if (this.destroyed) return;
|
|
8045
8069
|
try {
|
|
8046
8070
|
if (data.audioStream) {
|
|
8047
8071
|
this.setAudioStream(data.audioStream);
|
|
@@ -8058,6 +8082,9 @@ class AudioSystem {
|
|
|
8058
8082
|
* Ensure the shared AudioContext is created and resumed.
|
|
8059
8083
|
*/
|
|
8060
8084
|
async ensureAudioContext() {
|
|
8085
|
+
if (this.destroyed) {
|
|
8086
|
+
throw new Error("AudioSystem is destroyed");
|
|
8087
|
+
}
|
|
8061
8088
|
if (!this.audioContext) {
|
|
8062
8089
|
this.audioContext = new AudioContext();
|
|
8063
8090
|
}
|
|
@@ -8109,6 +8136,7 @@ class AudioSystem {
|
|
|
8109
8136
|
if (ch.workletNode) {
|
|
8110
8137
|
try {
|
|
8111
8138
|
ch.workletNode.port.onmessage = null;
|
|
8139
|
+
ch.workletNode.port.close();
|
|
8112
8140
|
ch.workletNode.disconnect();
|
|
8113
8141
|
} catch {
|
|
8114
8142
|
}
|
|
@@ -8159,6 +8187,7 @@ class AudioSystem {
|
|
|
8159
8187
|
* Set the main audio stream for analysis (preserves original public API surface).
|
|
8160
8188
|
*/
|
|
8161
8189
|
async setAudioStream(audioStream) {
|
|
8190
|
+
if (this.destroyed) return;
|
|
8162
8191
|
this.disconnectMainStream();
|
|
8163
8192
|
this.resetEssentiaBandHistories();
|
|
8164
8193
|
await this.connectChannel(this.mainChannel, audioStream, true);
|
|
@@ -8430,8 +8459,14 @@ class AudioSystem {
|
|
|
8430
8459
|
/**
|
|
8431
8460
|
* Reset all audio state (called when destroying).
|
|
8432
8461
|
* Disconnects all channels, closes AudioContext, resets all modules.
|
|
8462
|
+
*
|
|
8463
|
+
* Returns a Promise that resolves once `AudioContext.close()` has fully
|
|
8464
|
+
* transitioned the context to `closed`. Callers tearing the system down
|
|
8465
|
+
* (e.g. `VijiCore.destroy()`) MUST await this; otherwise Blink keeps the
|
|
8466
|
+
* context alive in `Pending activities` and leaks ~4 MB per cycle.
|
|
8433
8467
|
*/
|
|
8434
|
-
resetAudioState() {
|
|
8468
|
+
async resetAudioState() {
|
|
8469
|
+
this.destroyed = true;
|
|
8435
8470
|
this.stopAnalysisLoop();
|
|
8436
8471
|
this.stopStalenessTimer();
|
|
8437
8472
|
this.stopIdleTicker();
|
|
@@ -8444,9 +8479,14 @@ class AudioSystem {
|
|
|
8444
8479
|
this.mainChannel.audioState.isConnected = false;
|
|
8445
8480
|
this.mainChannel.isAnalysisRunning = false;
|
|
8446
8481
|
this.mainChannel.currentStream = null;
|
|
8447
|
-
|
|
8448
|
-
|
|
8449
|
-
|
|
8482
|
+
const ctxToClose = this.audioContext;
|
|
8483
|
+
this.audioContext = null;
|
|
8484
|
+
if (ctxToClose && ctxToClose.state !== "closed") {
|
|
8485
|
+
try {
|
|
8486
|
+
await ctxToClose.close();
|
|
8487
|
+
} catch (e) {
|
|
8488
|
+
this.debugLog(`AudioContext.close() rejected: ${e?.message ?? e}`);
|
|
8489
|
+
}
|
|
8450
8490
|
}
|
|
8451
8491
|
this.workletRegistered = false;
|
|
8452
8492
|
this.onsetDetection.reset();
|
|
@@ -8468,6 +8508,7 @@ class AudioSystem {
|
|
|
8468
8508
|
* @param stream The MediaStream to analyze
|
|
8469
8509
|
*/
|
|
8470
8510
|
async addStream(streamIndex, stream) {
|
|
8511
|
+
if (this.destroyed) return;
|
|
8471
8512
|
if (this.additionalChannels.has(streamIndex)) {
|
|
8472
8513
|
this.removeStream(streamIndex);
|
|
8473
8514
|
}
|
|
@@ -8497,6 +8538,7 @@ class AudioSystem {
|
|
|
8497
8538
|
* Mirrors video's reinitializeAdditionalCoordinators pattern.
|
|
8498
8539
|
*/
|
|
8499
8540
|
async reinitializeAdditionalChannels(streams, baseIndex) {
|
|
8541
|
+
if (this.destroyed) return;
|
|
8500
8542
|
const toRemove = [];
|
|
8501
8543
|
for (const [idx, ch] of this.additionalChannels) {
|
|
8502
8544
|
if (idx >= baseIndex && idx < baseIndex + 100) {
|
|
@@ -8525,9 +8567,12 @@ class AudioSystem {
|
|
|
8525
8567
|
// ═══════════════════════════════════════════════════════════
|
|
8526
8568
|
/**
|
|
8527
8569
|
* Record a tap for the specified instrument onset.
|
|
8528
|
-
* `options.skipRecognition`
|
|
8529
|
-
*
|
|
8530
|
-
*
|
|
8570
|
+
* `options.skipRecognition` opts the tap out of the recognition pipeline
|
|
8571
|
+
* only (no IOI accumulation, no `tryRecognizePattern`, no `applyPattern`).
|
|
8572
|
+
* Mode still flips `'auto' → 'tapping'` so the visual envelope and
|
|
8573
|
+
* audio-event filter run; only the pattern-detection pipeline is bypassed.
|
|
8574
|
+
* Used by host-side relay of forwarded tap messages where another core
|
|
8575
|
+
* owns the authoritative recognition.
|
|
8531
8576
|
*/
|
|
8532
8577
|
tapOnset(instrument, options) {
|
|
8533
8578
|
this.onsetTapManager.tap(instrument, options);
|
|
@@ -8611,6 +8656,7 @@ class AudioSystem {
|
|
|
8611
8656
|
// do when audio is active.
|
|
8612
8657
|
// ─────────────────────────────────────────────────────────────────────────
|
|
8613
8658
|
startIdleTicker() {
|
|
8659
|
+
if (this.destroyed) return;
|
|
8614
8660
|
if (this.idleTickerHandle !== null) return;
|
|
8615
8661
|
if (typeof requestAnimationFrame === "undefined") return;
|
|
8616
8662
|
this.idleTickerLastTime = performance.now();
|
|
@@ -8625,6 +8671,7 @@ class AudioSystem {
|
|
|
8625
8671
|
}
|
|
8626
8672
|
tickIdle(now) {
|
|
8627
8673
|
this.idleTickerHandle = null;
|
|
8674
|
+
if (this.destroyed) return;
|
|
8628
8675
|
if (this.mainChannel.audioState.isConnected) return;
|
|
8629
8676
|
const dtMs = Math.min(100, now - this.idleTickerLastTime);
|
|
8630
8677
|
this.idleTickerLastTime = now;
|
|
@@ -11139,13 +11186,17 @@ class VijiCore {
|
|
|
11139
11186
|
* First tap switches the instrument from auto to tapping mode.
|
|
11140
11187
|
* If a repeating pattern is recognized, it continues after tapping stops.
|
|
11141
11188
|
*
|
|
11142
|
-
* `options.skipRecognition: true`
|
|
11143
|
-
*
|
|
11144
|
-
* no
|
|
11145
|
-
*
|
|
11146
|
-
*
|
|
11147
|
-
*
|
|
11148
|
-
*
|
|
11189
|
+
* `options.skipRecognition: true` opts the tap out of the **recognition
|
|
11190
|
+
* pipeline only** — no `tapIOIs` accumulation, no `tryRecognizePattern`,
|
|
11191
|
+
* no `applyPattern`, no `handlePatternTap`. Mode still flips
|
|
11192
|
+
* `'auto' → 'tapping'` on the first tap of a session (and back to
|
|
11193
|
+
* `'auto'` on the 5s timeout). The mode flip is load-bearing for
|
|
11194
|
+
* `processFrame`'s drain of `pendingTapEvents`, the audio-event filter
|
|
11195
|
+
* that prevents music+tap doubling, and `onModeChange` consumers. Use
|
|
11196
|
+
* `skipRecognition` when relaying tap messages from another instance
|
|
11197
|
+
* that owns the authoritative recognition (e.g. host receiving
|
|
11198
|
+
* controller taps over WebRTC); the receiving core's pattern is then
|
|
11199
|
+
* driven only by `importSessionState` from the authoritative sender.
|
|
11149
11200
|
*
|
|
11150
11201
|
* The `MIN_TAP_INTERVAL_MS` debounce still applies regardless of the
|
|
11151
11202
|
* `skipRecognition` flag.
|
|
@@ -11609,7 +11660,7 @@ class VijiCore {
|
|
|
11609
11660
|
}
|
|
11610
11661
|
this.deviceAudioStreamIndices.clear();
|
|
11611
11662
|
if (this.audioSystem) {
|
|
11612
|
-
this.audioSystem.resetAudioState();
|
|
11663
|
+
await this.audioSystem.resetAudioState();
|
|
11613
11664
|
this.audioSystem = null;
|
|
11614
11665
|
}
|
|
11615
11666
|
this.currentAudioStream = null;
|
|
@@ -11702,7 +11753,7 @@ function validateCoreStatePayload(state) {
|
|
|
11702
11753
|
}
|
|
11703
11754
|
return null;
|
|
11704
11755
|
}
|
|
11705
|
-
const VERSION = "0.5.
|
|
11756
|
+
const VERSION = "0.5.6";
|
|
11706
11757
|
export {
|
|
11707
11758
|
AudioSystem as A,
|
|
11708
11759
|
VERSION as V,
|
|
@@ -11710,4 +11761,4 @@ export {
|
|
|
11710
11761
|
VijiCoreError as b,
|
|
11711
11762
|
getDefaultExportFromCjs as g
|
|
11712
11763
|
};
|
|
11713
|
-
//# sourceMappingURL=index-
|
|
11764
|
+
//# sourceMappingURL=index-Yg6_UX8C.js.map
|