@libraz/libsonare 1.3.3 → 1.4.1

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.
@@ -107,20 +107,24 @@ export function detectKeyCandidates(
107
107
  options: KeyDetectionOptions = {},
108
108
  ): KeyCandidate[] {
109
109
  validateAnalysisInput('detectKeyCandidates', samples, sampleRate, options);
110
- return requireModule()
111
- ._detectKeyCandidates(
112
- samples,
113
- sampleRate,
114
- options.nFft ?? 4096,
115
- options.hopLength ?? 512,
116
- options.useHpss ?? false,
117
- options.loudnessWeighted ?? false,
118
- options.highPassHz ?? 0,
119
- keyModeValues(options.modes),
120
- keyProfileValue(options.profile),
121
- options.genreHint ?? '',
122
- )
123
- .map(convertKeyCandidate);
110
+ // The embind value marshalling returns an array whose constructor is not this
111
+ // realm's Array; chaining .map() onto it propagates that constructor via
112
+ // Symbol.species, leaving a result that structuredClone (and so postMessage to
113
+ // a Worker) rejects with "could not be cloned". Array.from() re-roots it as a
114
+ // plain Array before mapping.
115
+ const candidates = requireModule()._detectKeyCandidates(
116
+ samples,
117
+ sampleRate,
118
+ options.nFft ?? 4096,
119
+ options.hopLength ?? 512,
120
+ options.useHpss ?? false,
121
+ options.loudnessWeighted ?? false,
122
+ options.highPassHz ?? 0,
123
+ keyModeValues(options.modes),
124
+ keyProfileValue(options.profile),
125
+ options.genreHint ?? '',
126
+ );
127
+ return Array.from(candidates, convertKeyCandidate);
124
128
  }
125
129
 
126
130
  /**
@@ -1,7 +1,8 @@
1
+ import { panLawCode, panModeCode, sendTimingCode } from './codes';
1
2
  import { ErrorCode, SonareError } from './errors';
2
3
  import { getSonareModule } from './module_state';
3
4
  import type { SynthPatch } from './project';
4
- import type { EqBand } from './public_types';
5
+ import type { EqBand, PanLaw, PanMode, SendTiming } from './public_types';
5
6
  import type {
6
7
  WasmClipPageRequest,
7
8
  WasmEngineAutomationPoint,
@@ -14,9 +15,11 @@ import type {
14
15
  WasmEngineGraphSpec,
15
16
  WasmEngineMarker,
16
17
  WasmEngineMeterTelemetry,
18
+ WasmEngineMeterTelemetryWide,
17
19
  WasmEngineMetronomeConfig,
18
20
  WasmEngineParameterInfo,
19
21
  WasmEngineProcessWithMonitorResult,
22
+ WasmEngineScopeTelemetry,
20
23
  WasmEngineTelemetry,
21
24
  WasmEngineTempoSegment,
22
25
  WasmEngineTimeSignatureSegment,
@@ -38,6 +41,8 @@ export type EngineFreezeOptions = WasmEngineFreezeOptions;
38
41
  export type EngineFreezeResult = WasmEngineFreezeResult;
39
42
  export type EngineTelemetry = WasmEngineTelemetry;
40
43
  export type EngineMeterTelemetry = WasmEngineMeterTelemetry;
44
+ export type EngineMeterTelemetryWide = WasmEngineMeterTelemetryWide;
45
+ export type EngineScopeTelemetry = WasmEngineScopeTelemetry;
41
46
  export type EngineTransportState = WasmEngineTransportState;
42
47
  export type EngineTempoSegment = WasmEngineTempoSegment;
43
48
  export type EngineTimeSignatureSegment = WasmEngineTimeSignatureSegment;
@@ -46,16 +51,39 @@ export interface EngineTrackSend {
46
51
  busId: number;
47
52
  levelDb?: number;
48
53
  enabled?: boolean;
54
+ /**
55
+ * Pre/post-fader tap point. Defaults to post-fader when omitted, matching the
56
+ * historical lane-send behavior and the scene-JSON default.
57
+ */
58
+ sendTiming?: SendTiming | number;
49
59
  }
50
60
 
51
61
  export interface EngineTrackLane {
52
62
  trackId: number;
53
63
  sends?: EngineTrackSend[];
64
+ /**
65
+ * Bus the lane's post-fader output sums into instead of the master mix
66
+ * (group/folder routing); 0 or absent keeps the lane on the master mix.
67
+ */
68
+ outputBusId?: number;
69
+ /**
70
+ * Input channel layout of the source feeding this lane (`SonareChannelLayout`:
71
+ * 0 mono, 1 stereo, 2 5.1, 3 7.1). Absent defaults to stereo. Stored but inert
72
+ * until the surround DSP path lands.
73
+ */
74
+ sourceChannelLayout?: number;
54
75
  }
55
76
 
56
77
  export interface EngineBus {
57
78
  busId: number;
58
79
  gainDb?: number;
80
+ /**
81
+ * Channel layout of the bus (`SonareChannelLayout`: 0 mono, 1 stereo, 2 5.1,
82
+ * 3 7.1). A surround layout makes this a surround group bus: lanes routed to
83
+ * it are surround-panned and it sums into the master plane-by-plane. Defaults
84
+ * to stereo.
85
+ */
86
+ channelLayout?: number;
59
87
  }
60
88
 
61
89
  export interface EngineMidiEvent {
@@ -382,6 +410,16 @@ export class RealtimeEngine {
382
410
  this.native.seekSample(timelineSample, renderFrame);
383
411
  }
384
412
 
413
+ /**
414
+ * Snaps every in-flight parameter ramp (engine-level smoothed params, mixer
415
+ * lane fader/pan/gate, bus gains) to its target value. Offline renders call
416
+ * this after a priming process() block so the first audible block renders at
417
+ * settled values instead of ramping in from defaults.
418
+ */
419
+ settleParameters(): void {
420
+ this.native.settleParameters();
421
+ }
422
+
385
423
  seekPpq(ppq: number, renderFrame = -1): void {
386
424
  this.native.seekPpq(ppq, renderFrame);
387
425
  }
@@ -500,10 +538,35 @@ export class RealtimeEngine {
500
538
 
501
539
  setTrackLanes(lanes: Array<number | EngineTrackLane>): void {
502
540
  this.native.setTrackLanes(
503
- lanes.map((lane) => (typeof lane === 'number' ? { trackId: lane } : lane)),
541
+ lanes.map((lane) => {
542
+ if (typeof lane === 'number') {
543
+ return { trackId: lane };
544
+ }
545
+ if (!lane.sends) {
546
+ return lane;
547
+ }
548
+ // Normalize each send's pre/post tap point to the integer the native
549
+ // layer reads (defaults to post-fader when omitted).
550
+ return {
551
+ ...lane,
552
+ sends: lane.sends.map((send) => ({
553
+ ...send,
554
+ // Post-fader (0) is the default for an omitted sendTiming.
555
+ sendTiming: send.sendTiming === undefined ? 0 : sendTimingCode(send.sendTiming),
556
+ })),
557
+ };
558
+ }),
504
559
  );
505
560
  }
506
561
 
562
+ /**
563
+ * Keys one insert of a lane strip from another lane's post-strip audio
564
+ * (ducking/sidechainRouter inserts). sourceTrackId 0 removes the binding.
565
+ */
566
+ setLaneSidechain(trackId: number, insertIndex: number, sourceTrackId: number): void {
567
+ this.native.setLaneSidechain(trackId, insertIndex, sourceTrackId);
568
+ }
569
+
507
570
  setTrackBuses(buses: EngineBus[]): void {
508
571
  this.native.setTrackBuses(buses);
509
572
  }
@@ -578,6 +641,55 @@ export class RealtimeEngine {
578
641
  this.native.setMasterStripInsertBypassed(insertIndex, bypassed, resetOnBypass);
579
642
  }
580
643
 
644
+ /**
645
+ * Changes one track-strip insert parameter in realtime, addressed by the
646
+ * processor's JSON-key parameter name (see {@link masteringInsertParamInfo}).
647
+ * Applied at the next block head via the engine command queue; safe during
648
+ * playback. Throws if the track, insert, or name is unknown, the param is not
649
+ * realtime-safe, or the command queue is full.
650
+ */
651
+ setTrackStripInsertParamByName(
652
+ trackId: number,
653
+ insertIndex: number,
654
+ paramName: string,
655
+ value: number,
656
+ ): void {
657
+ this.native.setTrackStripInsertParamByName(trackId, insertIndex, paramName, value);
658
+ }
659
+
660
+ /** Master-strip counterpart of {@link setTrackStripInsertParamByName}. */
661
+ setMasterStripInsertParamByName(insertIndex: number, paramName: string, value: number): void {
662
+ this.native.setMasterStripInsertParamByName(insertIndex, paramName, value);
663
+ }
664
+
665
+ /** Sets a track lane strip's pan position in realtime (glitch-free). */
666
+ setTrackStripPan(trackId: number, pan: number): void {
667
+ this.native.setTrackStripPan(trackId, pan);
668
+ }
669
+
670
+ /** Sets a track lane strip's pan law in realtime. */
671
+ setTrackStripPanLaw(trackId: number, panLaw: PanLaw | number): void {
672
+ this.native.setTrackStripPanLaw(trackId, panLawCode(panLaw));
673
+ }
674
+
675
+ /** Sets a track lane strip's pan mode in realtime. */
676
+ setTrackStripPanMode(trackId: number, panMode: PanMode | number): void {
677
+ this.native.setTrackStripPanMode(trackId, panModeCode(panMode));
678
+ }
679
+
680
+ /** Sets a track lane strip's dual-pan left/right positions in realtime. */
681
+ setTrackStripDualPan(trackId: number, leftPan: number, rightPan: number): void {
682
+ this.native.setTrackStripDualPan(trackId, leftPan, rightPan);
683
+ }
684
+
685
+ /**
686
+ * Sets a track lane strip's inter-channel alignment delay (whole samples).
687
+ * Adjusts strip latency, so PDC and reported graph latency are refreshed.
688
+ */
689
+ setTrackStripChannelDelaySamples(trackId: number, delaySamples: number): void {
690
+ this.native.setTrackStripChannelDelaySamples(trackId, delaySamples);
691
+ }
692
+
581
693
  createClipPageProvider(
582
694
  numChannels: number,
583
695
  numSamples: number,
@@ -695,6 +807,33 @@ export class RealtimeEngine {
695
807
  return this.native.drainMeterTelemetry(maxRecords);
696
808
  }
697
809
 
810
+ /**
811
+ * Drains pending meter telemetry as per-plane (wide) records for a surround
812
+ * target. Use this for a surround mix target; {@link drainMeterTelemetry}
813
+ * stays the stereo fast path. The two share one queue — call only one per
814
+ * target. The live AudioWorklet path owns the queue via the stereo drain, so
815
+ * this wide drain is for an offline (non-worklet) engine instance; per-plane
816
+ * surround meters are not delivered over the live worklet meter ring.
817
+ */
818
+ drainMeterTelemetryWide(maxRecords = 1024): EngineMeterTelemetryWide[] {
819
+ return this.native.drainMeterTelemetryWide(maxRecords);
820
+ }
821
+
822
+ /**
823
+ * Enables per-target spectrum + vectorscope capture. @param intervalFrames is
824
+ * the minimum render-frame gap between snapshots (0 disables). @param bandCount
825
+ * is the FFT band resolution (1..64); changing it re-prepares the tap. Returns
826
+ * the band count actually applied.
827
+ */
828
+ configureScopeTelemetry(intervalFrames: number, bandCount: number): number {
829
+ return this.native.configureScopeTelemetry(intervalFrames, bandCount);
830
+ }
831
+
832
+ /** Drains pending spectrum + vectorscope snapshots (per mix target). */
833
+ drainScopeTelemetry(maxRecords = 1024): EngineScopeTelemetry[] {
834
+ return this.native.drainScopeTelemetry(maxRecords);
835
+ }
836
+
698
837
  destroy(): void {
699
838
  this.native.delete();
700
839
  }
@@ -259,7 +259,8 @@ export class RealtimeVoiceChanger {
259
259
  }
260
260
 
261
261
  export function realtimeVoiceChangerPresetNames(): VoicePresetId[] {
262
- return getSonareModule().realtimeVoiceChangerPresetNames() as VoicePresetId[];
262
+ // Array.from re-roots embind's vector as a plain, structured-cloneable Array.
263
+ return Array.from(getSonareModule().realtimeVoiceChangerPresetNames()) as VoicePresetId[];
263
264
  }
264
265
 
265
266
  export function realtimeVoiceChangerPresetJson(name: VoicePresetId): string {
@@ -2,6 +2,8 @@
2
2
  * Type declarations for the Emscripten-generated WASM module with embind
3
3
  */
4
4
 
5
+ import type { SpectralRegionOp, SurroundPan } from './public_types';
6
+
5
7
  export interface SonareModuleOptions {
6
8
  locateFile?: (path: string, prefix: string) => string;
7
9
  wasmBinary?: ArrayBuffer | Uint8Array;
@@ -542,11 +544,22 @@ export interface WasmEngineTrackSend {
542
544
  export interface WasmEngineTrackLane {
543
545
  trackId: number;
544
546
  sends?: WasmEngineTrackSend[];
547
+ /**
548
+ * Bus the lane's post-fader output sums into instead of the master mix
549
+ * (group/folder routing); 0 or absent keeps the lane on the master mix.
550
+ */
551
+ outputBusId?: number;
552
+ /**
553
+ * Input channel layout of the source feeding this lane (`SonareChannelLayout`:
554
+ * 0 mono, 1 stereo, 2 5.1, 3 7.1). Absent defaults to stereo.
555
+ */
556
+ sourceChannelLayout?: number;
545
557
  }
546
558
 
547
559
  export interface WasmEngineBus {
548
560
  busId: number;
549
561
  gainDb?: number;
562
+ channelLayout?: number;
550
563
  }
551
564
 
552
565
  export interface WasmClipPageRequest {
@@ -576,6 +589,18 @@ export interface WasmEngineMarker {
576
589
  id: number;
577
590
  ppq: number;
578
591
  name?: string;
592
+ kind?: number;
593
+ keyFifths?: number;
594
+ keyMinor?: boolean;
595
+ }
596
+
597
+ export interface WasmProjectMarker {
598
+ id: number;
599
+ ppq: number;
600
+ name?: string;
601
+ kind?: number;
602
+ keyFifths?: number;
603
+ keyMinor?: boolean;
579
604
  }
580
605
 
581
606
  export interface WasmEngineMetronomeConfig {
@@ -646,6 +671,38 @@ export interface WasmEngineMeterTelemetry {
646
671
  droppedRecords: number;
647
672
  }
648
673
 
674
+ export interface WasmEngineMeterTelemetryWide {
675
+ targetId: number;
676
+ renderFrame: number;
677
+ seq: number;
678
+ channelCount: number;
679
+ peakDb: number[];
680
+ rmsDb: number[];
681
+ truePeakDb: number[];
682
+ maxTruePeakDb: number;
683
+ correlation: number;
684
+ monoCompatWidth: number;
685
+ momentaryLufs: number;
686
+ shortTermLufs: number;
687
+ integratedLufs: number;
688
+ gainReductionDb: number;
689
+ droppedRecords: number;
690
+ }
691
+
692
+ export interface WasmEngineScopeTelemetry {
693
+ targetId: number;
694
+ renderFrame: number;
695
+ seq: number;
696
+ droppedRecords: number;
697
+ bands: number[];
698
+ /**
699
+ * Goniometer/vectorscope sample points as `{ left, right }` objects. The
700
+ * Python surface exposes the same data as `(left, right)` tuples; this
701
+ * representation difference is intentional, the values match.
702
+ */
703
+ points: { left: number; right: number }[];
704
+ }
705
+
649
706
  export interface WasmEngineCaptureStatus {
650
707
  capturedFrames: number;
651
708
  overflowCount: number;
@@ -740,6 +797,7 @@ export interface WasmRealtimeEngine {
740
797
  play: (renderFrame: number) => void;
741
798
  stop: (renderFrame: number) => void;
742
799
  seekSample: (timelineSample: number, renderFrame: number) => void;
800
+ settleParameters: () => void;
743
801
  seekPpq: (ppq: number, renderFrame: number) => void;
744
802
  setTempo: (bpm: number) => void;
745
803
  setTempoSegments: (segments: WasmEngineTempoSegment[]) => void;
@@ -768,6 +826,7 @@ export interface WasmRealtimeEngine {
768
826
  setClips: (clips: WasmEngineClip[]) => void;
769
827
  clipCount: () => number;
770
828
  setTrackLanes: (lanes: Array<number | WasmEngineTrackLane>) => void;
829
+ setLaneSidechain: (trackId: number, insertIndex: number, sourceTrackId: number) => void;
771
830
  setTrackBuses: (buses: WasmEngineBus[]) => void;
772
831
  setBusStripJson: (busId: number, sceneJson: string) => void;
773
832
  setTrackStripJson: (trackId: number, sceneJson: string) => void;
@@ -785,6 +844,18 @@ export interface WasmRealtimeEngine {
785
844
  bypassed: boolean,
786
845
  resetOnBypass: boolean,
787
846
  ) => void;
847
+ setTrackStripInsertParamByName: (
848
+ trackId: number,
849
+ insertIndex: number,
850
+ paramName: string,
851
+ value: number,
852
+ ) => void;
853
+ setMasterStripInsertParamByName: (insertIndex: number, paramName: string, value: number) => void;
854
+ setTrackStripPan: (trackId: number, pan: number) => void;
855
+ setTrackStripPanLaw: (trackId: number, panLaw: number) => void;
856
+ setTrackStripPanMode: (trackId: number, panMode: number) => void;
857
+ setTrackStripDualPan: (trackId: number, leftPan: number, rightPan: number) => void;
858
+ setTrackStripChannelDelaySamples: (trackId: number, delaySamples: number) => void;
788
859
  createClipPageProvider: (numChannels: number, numSamples: number, pageFrames: number) => number;
789
860
  supplyClipPage: (providerId: number, pageIndex: number, channels: Float32Array[]) => void;
790
861
  clearClipPage: (providerId: number, pageIndex: number) => void;
@@ -877,6 +948,9 @@ export interface WasmRealtimeEngine {
877
948
  freezeOffline: (options: WasmEngineFreezeOptions) => WasmEngineFreezeResult;
878
949
  drainTelemetry: (maxRecords: number) => WasmEngineTelemetry[];
879
950
  drainMeterTelemetry: (maxRecords: number) => WasmEngineMeterTelemetry[];
951
+ drainMeterTelemetryWide: (maxRecords: number) => WasmEngineMeterTelemetryWide[];
952
+ configureScopeTelemetry: (intervalFrames: number, bandCount: number) => number;
953
+ drainScopeTelemetry: (maxRecords: number) => WasmEngineScopeTelemetry[];
880
954
  delete: () => void;
881
955
  }
882
956
 
@@ -1283,6 +1357,8 @@ export interface SonareModule {
1283
1357
  targetLufs: number,
1284
1358
  ceilingDb: number,
1285
1359
  truePeakOversample: number,
1360
+ releaseMs: number,
1361
+ applyGainAtInputRate: boolean,
1286
1362
  ) => WasmMasteringResult;
1287
1363
  masteringProcessorNames: () => string[];
1288
1364
  masteringPairProcessorNames: () => string[];
@@ -1449,6 +1525,12 @@ export interface SonareModule {
1449
1525
  options: Record<string, unknown>,
1450
1526
  ) => WasmMixResult;
1451
1527
  trim: (samples: Float32Array, sampleRate: number, thresholdDb: number) => Float32Array;
1528
+ spectralEdit: (
1529
+ samples: Float32Array,
1530
+ sampleRate: number,
1531
+ ops: SpectralRegionOp[],
1532
+ options: Record<string, unknown>,
1533
+ ) => Float32Array;
1452
1534
 
1453
1535
  // Features - Spectrogram
1454
1536
  stft: (
@@ -2033,6 +2115,7 @@ export interface WasmMixer {
2033
2115
  setChannelDelaySamples: (stripIndex: number, delaySamples: number) => void;
2034
2116
  setVcaOffsetDb: (stripIndex: number, offsetDb: number) => void;
2035
2117
  setDualPan: (stripIndex: number, leftPan: number, rightPan: number) => void;
2118
+ setSurroundPan: (stripIndex: number, pan: SurroundPan) => void;
2036
2119
  addSend: (
2037
2120
  stripIndex: number,
2038
2121
  id: string,
@@ -75,6 +75,11 @@ export interface FrameBuffer {
75
75
  /** Number of mel bands; flat `mel` is `[nFrames * nMels]` row-major. */
76
76
  nMels: number;
77
77
  timestamps: Float32Array;
78
+ /**
79
+ * Mel spectrogram in LINEAR power (not dB) — the raw per-frame mel energies.
80
+ * The quantized read paths (`readFramesU8` / `readFramesI16`) convert to dB
81
+ * before packing, so their `mel` is dB-scaled; this float buffer is not.
82
+ */
78
83
  mel: Float32Array;
79
84
  chroma: Float32Array;
80
85
  onsetStrength: Float32Array;
@@ -109,6 +114,7 @@ export interface StreamFramesU8 {
109
114
  nFrames: number;
110
115
  nMels: number;
111
116
  timestamps: Float32Array;
117
+ /** Row-major `[nFrames * nMels]` mel in dB, quantized over `[melDbMin, melDbMax]`. */
112
118
  mel: Uint8Array;
113
119
  chroma: Uint8Array;
114
120
  onsetStrength: Uint8Array;
@@ -121,6 +127,7 @@ export interface StreamFramesI16 {
121
127
  nFrames: number;
122
128
  nMels: number;
123
129
  timestamps: Float32Array;
130
+ /** Row-major `[nFrames * nMels]` mel in dB, quantized over `[melDbMin, melDbMax]`. */
124
131
  mel: Int16Array;
125
132
  chroma: Int16Array;
126
133
  onsetStrength: Int16Array;
package/src/validation.ts CHANGED
@@ -2,6 +2,13 @@
2
2
  * Per-call validation options accepted by guarded wrappers. Empty-buffer
3
3
  * checks are always performed; pass `{ validate: false }` to opt out of the
4
4
  * O(n) NaN/Inf scan on hot paths.
5
+ *
6
+ * `{ validate: false }` only skips this JS-side pre-scan (which raises a
7
+ * `RangeError` naming the exact offending index). It is NOT a way to push
8
+ * non-finite samples into the core: the native layer always re-validates the
9
+ * buffer (see `validate_offline_audio_input` in the C++ core), matching the C
10
+ * ABI / Node / Python surfaces, so an NaN/Inf buffer still throws — just with a
11
+ * generic native message instead of the indexed JS one.
5
12
  */
6
13
  export interface ValidateOptions {
7
14
  validate?: boolean;
@@ -0,0 +1,2 @@
1
+ export type WorkletInput = readonly (readonly Float32Array[])[];
2
+ export type WorkletOutput = Float32Array[][];
@@ -0,0 +1,146 @@
1
+ import type {
2
+ SonareEngineCaptureRequestMessage,
3
+ SonareEngineCaptureResponseMessage,
4
+ SonareEngineSyncMessage,
5
+ SonareEngineTransportRequestMessage,
6
+ SonareEngineTransportResponseMessage,
7
+ SonareRealtimeVoiceChangerMessage,
8
+ SonareWorkletMessage,
9
+ } from './messages';
10
+ import {
11
+ isRecord,
12
+ type SonareEngineCommandRecord,
13
+ type SonareEngineTelemetryRecord,
14
+ type SonareWorkletMeterSnapshot,
15
+ } from './protocol';
16
+
17
+ export function isWorkletMessage(value: unknown): value is SonareWorkletMessage {
18
+ if (!isRecord(value) || typeof value.type !== 'string') {
19
+ return false;
20
+ }
21
+ return (
22
+ value.type === 'scheduleInsertAutomation' ||
23
+ value.type === 'setMeterInterval' ||
24
+ value.type === 'destroy'
25
+ );
26
+ }
27
+
28
+ export function isEngineCommandRecord(value: unknown): value is SonareEngineCommandRecord {
29
+ return isRecord(value) && typeof value.type === 'number';
30
+ }
31
+
32
+ export function isEngineSyncMessage(value: unknown): value is SonareEngineSyncMessage {
33
+ if (!isRecord(value) || typeof value.type !== 'string') {
34
+ return false;
35
+ }
36
+ return (
37
+ value.type === 'syncClips' ||
38
+ value.type === 'syncClipsDelta' ||
39
+ value.type === 'syncMidiClips' ||
40
+ value.type === 'syncMarkers' ||
41
+ value.type === 'syncMetronome' ||
42
+ value.type === 'syncAutomation' ||
43
+ value.type === 'syncTempo' ||
44
+ value.type === 'syncMixer' ||
45
+ value.type === 'syncCapture' ||
46
+ value.type === 'syncTrackStripEqBand' ||
47
+ value.type === 'syncMasterStripEqBand' ||
48
+ value.type === 'syncTrackStripInsertBypassed' ||
49
+ value.type === 'syncMasterStripInsertBypassed' ||
50
+ value.type === 'syncTrackStripInsertParamByName' ||
51
+ value.type === 'syncMasterStripInsertParamByName' ||
52
+ value.type === 'syncTrackStripPan' ||
53
+ value.type === 'syncTrackStripPanLaw' ||
54
+ value.type === 'syncTrackStripPanMode' ||
55
+ value.type === 'syncTrackStripDualPan' ||
56
+ value.type === 'syncTrackStripChannelDelaySamples' ||
57
+ value.type === 'syncBuiltinInstrument' ||
58
+ value.type === 'syncSynthInstrument' ||
59
+ value.type === 'syncSf2Instrument' ||
60
+ value.type === 'syncLoadSoundFont' ||
61
+ value.type === 'syncMidiNoteOn' ||
62
+ value.type === 'syncMidiNoteOff' ||
63
+ value.type === 'syncMidiCc' ||
64
+ value.type === 'syncMidiPanic'
65
+ );
66
+ }
67
+
68
+ export function isEngineCaptureRequestMessage(
69
+ value: unknown,
70
+ ): value is SonareEngineCaptureRequestMessage {
71
+ return (
72
+ isRecord(value) &&
73
+ value.type === 'captureRequest' &&
74
+ typeof value.requestId === 'number' &&
75
+ (value.op === 'status' || value.op === 'read' || value.op === 'reset')
76
+ );
77
+ }
78
+
79
+ export function isEngineCaptureResponseMessage(
80
+ value: unknown,
81
+ ): value is SonareEngineCaptureResponseMessage {
82
+ return (
83
+ isRecord(value) &&
84
+ value.type === 'captureResponse' &&
85
+ typeof value.requestId === 'number' &&
86
+ typeof value.ok === 'boolean'
87
+ );
88
+ }
89
+
90
+ export function isEngineTransportRequestMessage(
91
+ value: unknown,
92
+ ): value is SonareEngineTransportRequestMessage {
93
+ return (
94
+ isRecord(value) &&
95
+ value.type === 'transportRequest' &&
96
+ typeof value.requestId === 'number' &&
97
+ value.op === 'state'
98
+ );
99
+ }
100
+
101
+ export function isEngineTransportResponseMessage(
102
+ value: unknown,
103
+ ): value is SonareEngineTransportResponseMessage {
104
+ return (
105
+ isRecord(value) &&
106
+ value.type === 'transportResponse' &&
107
+ typeof value.requestId === 'number' &&
108
+ typeof value.ok === 'boolean'
109
+ );
110
+ }
111
+
112
+ export function isRealtimeVoiceChangerMessage(
113
+ value: unknown,
114
+ ): value is SonareRealtimeVoiceChangerMessage {
115
+ if (!isRecord(value) || typeof value.type !== 'string') {
116
+ return false;
117
+ }
118
+ return value.type === 'setConfig' || value.type === 'reset' || value.type === 'destroy';
119
+ }
120
+
121
+ export function isEngineTelemetryRecord(value: unknown): value is SonareEngineTelemetryRecord {
122
+ return (
123
+ isRecord(value) &&
124
+ typeof value.type === 'number' &&
125
+ typeof value.error === 'number' &&
126
+ typeof value.renderFrame === 'number' &&
127
+ typeof value.timelineSample === 'number' &&
128
+ typeof value.audibleTimelineSample === 'number' &&
129
+ typeof value.graphLatencySamplesQ8 === 'number' &&
130
+ typeof value.value === 'number'
131
+ );
132
+ }
133
+
134
+ export function isMeterSnapshot(value: unknown): value is SonareWorkletMeterSnapshot {
135
+ return (
136
+ isRecord(value) &&
137
+ value.type === 'meter' &&
138
+ typeof value.frame === 'number' &&
139
+ typeof value.peakDbL === 'number' &&
140
+ typeof value.peakDbR === 'number' &&
141
+ typeof value.rmsDbL === 'number' &&
142
+ typeof value.rmsDbR === 'number' &&
143
+ typeof value.correlation === 'number' &&
144
+ (typeof value.targetId === 'number' || value.targetId === undefined)
145
+ );
146
+ }