@viji-dev/core 0.5.2 → 0.5.3

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.
@@ -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-DE6gem4m.js").then((n) => n.e);
1900
+ const wasmModule = await import("./essentia-wasm.web-CPrFAj59.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) {
@@ -6179,16 +6179,13 @@ class OnsetTapManager {
6179
6179
  modeChangeListeners = /* @__PURE__ */ new Set();
6180
6180
  sessionEndListeners = /* @__PURE__ */ new Set();
6181
6181
  muteChangeListeners = /* @__PURE__ */ new Set();
6182
- suppressEmissions = false;
6183
6182
  tap(instrument) {
6184
6183
  const s = this.state[instrument];
6185
6184
  const now = performance.now();
6186
6185
  if (s.muted) {
6187
6186
  s.muted = false;
6188
6187
  s.mutedAt = 0;
6189
- if (!this.suppressEmissions) {
6190
- this.fireMuteChange({ instrument, prevMuted: true, muted: false });
6191
- }
6188
+ this.fireMuteChange({ instrument, prevMuted: true, muted: false });
6192
6189
  }
6193
6190
  let ioi = -1;
6194
6191
  if (s.lastTapTime > 0) {
@@ -6241,7 +6238,7 @@ class OnsetTapManager {
6241
6238
  s.pendingTapEvents = [];
6242
6239
  s.envelope.reset();
6243
6240
  s.envelopeSmoothed.reset();
6244
- if (prevMuted && !this.suppressEmissions) {
6241
+ if (prevMuted) {
6245
6242
  this.fireMuteChange({ instrument, prevMuted: true, muted: false });
6246
6243
  }
6247
6244
  }
@@ -6273,9 +6270,7 @@ class OnsetTapManager {
6273
6270
  s.lastTapTime += pauseDuration;
6274
6271
  }
6275
6272
  }
6276
- if (!this.suppressEmissions) {
6277
- this.fireMuteChange({ instrument, prevMuted, muted: s.muted });
6278
- }
6273
+ this.fireMuteChange({ instrument, prevMuted, muted: s.muted });
6279
6274
  }
6280
6275
  isMuted(instrument) {
6281
6276
  return this.state[instrument].muted;
@@ -6330,22 +6325,22 @@ class OnsetTapManager {
6330
6325
  * is older than one pattern period (phase-preserving — events still land on
6331
6326
  * the original beat positions modulo `patternSum`).
6332
6327
  *
6333
- * Mutation is synchronous; no events are emitted (state replacement is not
6334
- * a transition). Throws nothing malformed payloads should be filtered by
6335
- * the caller via the `version` field.
6328
+ * Mutation is synchronous. Field-level events (`onModeChange`,
6329
+ * `onMuteChange`) fire when an imported value differs from the current one
6330
+ * consumer-visible state is consistent with what polling would have
6331
+ * observed. Session-boundary events (`onSessionEnd`) do NOT fire on
6332
+ * import; imports are state replacement, not session boundaries.
6333
+ *
6334
+ * Throws nothing — malformed payloads should be filtered by the caller
6335
+ * via the `version` field.
6336
6336
  */
6337
6337
  importSessionState(state, clockOffset) {
6338
6338
  if (state.version !== STATE_SCHEMA_VERSION) return;
6339
- this.suppressEmissions = true;
6340
- try {
6341
- const now = performance.now();
6342
- for (const inst of INSTRUMENTS) {
6343
- const payload = state.instruments[inst];
6344
- if (!payload) continue;
6345
- this.applyInstrumentPayload(inst, payload, clockOffset, now);
6346
- }
6347
- } finally {
6348
- this.suppressEmissions = false;
6339
+ const now = performance.now();
6340
+ for (const inst of INSTRUMENTS) {
6341
+ const payload = state.instruments[inst];
6342
+ if (!payload) continue;
6343
+ this.applyInstrumentPayload(inst, payload, clockOffset, now);
6349
6344
  }
6350
6345
  }
6351
6346
  /**
@@ -6482,18 +6477,16 @@ class OnsetTapManager {
6482
6477
  // Private helpers
6483
6478
  // ---------------------------------------------------------------------------
6484
6479
  /**
6485
- * Single mutation point for `s.mode`. Fires `onModeChange` (when not
6486
- * suppressed) so listeners stay consistent regardless of which code path
6487
- * triggered the transition.
6480
+ * Single mutation point for `s.mode`. Fires `onModeChange` so listeners
6481
+ * stay consistent regardless of which code path triggered the transition
6482
+ * (including imports, which fire via `applyInstrumentPayload` directly).
6488
6483
  */
6489
6484
  setMode(instrument, newMode) {
6490
6485
  const s = this.state[instrument];
6491
6486
  const prevMode = s.mode;
6492
6487
  if (prevMode === newMode) return;
6493
6488
  s.mode = newMode;
6494
- if (!this.suppressEmissions) {
6495
- this.fireModeChange({ instrument, prevMode, newMode });
6496
- }
6489
+ this.fireModeChange({ instrument, prevMode, newMode });
6497
6490
  }
6498
6491
  fireModeChange(ev) {
6499
6492
  for (const listener of this.modeChangeListeners) {
@@ -6612,6 +6605,8 @@ class OnsetTapManager {
6612
6605
  }
6613
6606
  applyInstrumentPayload(instrument, payload, clockOffset, now) {
6614
6607
  const s = this.state[instrument];
6608
+ const prevMode = s.mode;
6609
+ const prevMuted = s.muted;
6615
6610
  this.cancelSessionTimers(s);
6616
6611
  s.sessionActive = false;
6617
6612
  const translatedReplayLast = payload.replayLastEventTime !== null ? payload.replayLastEventTime + clockOffset : 0;
@@ -6628,7 +6623,6 @@ class OnsetTapManager {
6628
6623
  }
6629
6624
  }
6630
6625
  }
6631
- s.mode;
6632
6626
  s.mode = payload.mode;
6633
6627
  s.muted = payload.muted;
6634
6628
  s.mutedAt = translatedMutedAt;
@@ -6642,6 +6636,12 @@ class OnsetTapManager {
6642
6636
  s.pendingTapEvents = [];
6643
6637
  s.envelope.reset();
6644
6638
  s.envelopeSmoothed.reset();
6639
+ if (s.mode !== prevMode) {
6640
+ this.fireModeChange({ instrument, prevMode, newMode: s.mode });
6641
+ }
6642
+ if (s.muted !== prevMuted) {
6643
+ this.fireMuteChange({ instrument, prevMuted, muted: s.muted });
6644
+ }
6645
6645
  }
6646
6646
  /**
6647
6647
  * Handle a tap that arrives while already in pattern mode.
@@ -10671,8 +10671,12 @@ class VijiCore {
10671
10671
  /**
10672
10672
  * Replace the audio analysis + onset state from a serialized payload.
10673
10673
  * `clockOffset` is added to all sender-clocked fields (`0` for same-process,
10674
- * NTP-derived for cross-device). Mutation is synchronous; no `onModeChange`
10675
- * or `onSessionEnd` events are emitted (state replacement is not a transition).
10674
+ * NTP-derived for cross-device). Mutation is synchronous.
10675
+ *
10676
+ * Field-level events (`onModeChange`, `onMuteChange`) fire when imported
10677
+ * values differ from current — consumer-visible state stays consistent
10678
+ * with what polling would have observed. Session-boundary events
10679
+ * (`onSessionEnd`) do NOT fire on import.
10676
10680
  *
10677
10681
  * Validates `version`; on mismatch, fires `onStateImportError` and leaves
10678
10682
  * existing state intact.
@@ -11148,9 +11152,13 @@ class VijiCore {
11148
11152
  },
11149
11153
  /**
11150
11154
  * Listen for instrument mode transitions (`'auto' | 'tapping' | 'pattern'`).
11151
- * Fires on every transition including the first tap (`'auto' → 'tapping'`)
11152
- * and pattern recognition (`'tapping' → 'pattern'`). Imported state does
11153
- * NOT fire mode-change events state replacement is not a transition.
11155
+ * Fires whenever the underlying mode field actually changes, regardless
11156
+ * of which code path triggered it — first tap (`'auto' → 'tapping'`),
11157
+ * pattern recognition (`'tapping' 'pattern'`), the 5s tap timeout
11158
+ * (`'tapping' → 'auto'`), explicit `clear()`, or `importSessionState`
11159
+ * landing a different mode value.
11160
+ *
11161
+ * Idempotent transitions (already in target mode) do not fire.
11154
11162
  *
11155
11163
  * @returns Unsubscribe function. Call to remove the listener.
11156
11164
  */
@@ -11160,14 +11168,20 @@ class VijiCore {
11160
11168
  });
11161
11169
  },
11162
11170
  /**
11163
- * Listen for natural session-end events. Fires when:
11171
+ * Listen for natural session-end events. Fires only on natural session
11172
+ * boundaries:
11164
11173
  * - 500ms elapse since last tap with instrument in `'pattern'` mode
11165
11174
  * (outcome `'pattern'`), or
11166
11175
  * - 5s elapse in `'tapping'` mode without a recognized pattern
11167
11176
  * (outcome `'cleared'`; instrument transitions to `'auto'`).
11168
11177
  *
11169
- * Explicit `clear()` calls do NOT fire this event (caller-initiated;
11170
- * caller already knows). Imported state does NOT fire either.
11178
+ * Does NOT fire on:
11179
+ * - Explicit `clear()` calls (caller-initiated; not a natural boundary).
11180
+ * - `importSessionState` (state replacement; not a session boundary).
11181
+ *
11182
+ * Field-change events (`onModeChange`, `onMuteChange`) fire on imports
11183
+ * when fields differ; this event does not because session-end is a
11184
+ * different category — about session lifecycle, not field state.
11171
11185
  *
11172
11186
  * @returns Unsubscribe function. Call to remove the listener.
11173
11187
  */
@@ -11178,14 +11192,15 @@ class VijiCore {
11178
11192
  },
11179
11193
  /**
11180
11194
  * Listen for instrument mute-state transitions (`true ↔ false`). Fires
11181
- * whenever the underlying mute state actually changes, regardless of
11182
- * which method triggered it:
11183
- * - `setMuted(instrument, muted)` when `prev !== next`
11184
- * - `tap(instrument)` auto-unmute on first tap of a muted instrument
11185
- * - `clear(instrument)` when the instrument was muted
11195
+ * whenever the underlying mute field actually changes, regardless of
11196
+ * which code path triggered it:
11197
+ * - `setMuted(instrument, muted)` when `prev !== next`.
11198
+ * - `tap(instrument)` auto-unmute on first tap of a muted instrument.
11199
+ * - `clear(instrument)` when the instrument was muted at call time.
11200
+ * - `importSessionState` landing a different muted value.
11186
11201
  *
11187
11202
  * Idempotent calls (e.g. `setMuted(true)` on an already-muted instrument)
11188
- * do not fire. Imported state does NOT fire either.
11203
+ * do not fire.
11189
11204
  *
11190
11205
  * @returns Unsubscribe function. Call to remove the listener.
11191
11206
  */
@@ -11212,7 +11227,12 @@ class VijiCore {
11212
11227
  * Replace per-instrument onset state from a serialized payload.
11213
11228
  * `clockOffset` is added to all sender-clocked fields during import
11214
11229
  * (use `0` for same-process transfer; NTP-derived offset for cross-device).
11215
- * Mutation is synchronous; no events are emitted.
11230
+ * Mutation is synchronous.
11231
+ *
11232
+ * Field-level events fire when imported values differ from current:
11233
+ * `onModeChange` if the imported mode is different, `onMuteChange` if
11234
+ * the imported muted state is different. Session-boundary events
11235
+ * (`onSessionEnd`) do NOT fire — imports aren't session boundaries.
11216
11236
  *
11217
11237
  * Patterns are rebased forward by whole pattern cycles to eliminate
11218
11238
  * the catch-up burst that would otherwise occur on stale payloads
@@ -11629,7 +11649,7 @@ function validateCoreStatePayload(state) {
11629
11649
  }
11630
11650
  return null;
11631
11651
  }
11632
- const VERSION = "0.5.2";
11652
+ const VERSION = "0.5.3";
11633
11653
  export {
11634
11654
  AudioSystem as A,
11635
11655
  VERSION as V,
@@ -11637,4 +11657,4 @@ export {
11637
11657
  VijiCoreError as b,
11638
11658
  getDefaultExportFromCjs as g
11639
11659
  };
11640
- //# sourceMappingURL=index-B8LJ9m47.js.map
11660
+ //# sourceMappingURL=index-Bhq4eJe_.js.map