@libraz/libsonare 1.3.2 → 1.4.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.
@@ -2,8 +2,11 @@
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;
9
+ wasmBinary?: ArrayBuffer | Uint8Array;
7
10
  onRuntimeInitialized?: () => void;
8
11
  print?: (text: string) => void;
9
12
  printErr?: (text: string) => void;
@@ -518,6 +521,7 @@ export interface WasmMixResult {
518
521
 
519
522
  export interface WasmEngineClip {
520
523
  id?: number;
524
+ trackId?: number;
521
525
  channels?: Float32Array[];
522
526
  startPpq: number;
523
527
  lengthSamples?: number;
@@ -531,6 +535,33 @@ export interface WasmEngineClip {
531
535
  pageProvider?: number | { readonly id: number };
532
536
  }
533
537
 
538
+ export interface WasmEngineTrackSend {
539
+ busId: number;
540
+ levelDb?: number;
541
+ enabled?: boolean;
542
+ }
543
+
544
+ export interface WasmEngineTrackLane {
545
+ trackId: number;
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;
557
+ }
558
+
559
+ export interface WasmEngineBus {
560
+ busId: number;
561
+ gainDb?: number;
562
+ channelLayout?: number;
563
+ }
564
+
534
565
  export interface WasmClipPageRequest {
535
566
  clipId: number;
536
567
  channel: number;
@@ -558,6 +589,18 @@ export interface WasmEngineMarker {
558
589
  id: number;
559
590
  ppq: number;
560
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;
561
604
  }
562
605
 
563
606
  export interface WasmEngineMetronomeConfig {
@@ -628,6 +671,38 @@ export interface WasmEngineMeterTelemetry {
628
671
  droppedRecords: number;
629
672
  }
630
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
+
631
706
  export interface WasmEngineCaptureStatus {
632
707
  capturedFrames: number;
633
708
  overflowCount: number;
@@ -696,6 +771,18 @@ export interface WasmEngineProcessWithMonitorResult {
696
771
  monitor: Float32Array[];
697
772
  }
698
773
 
774
+ export interface WasmEngineTempoSegment {
775
+ startPpq: number;
776
+ bpm: number;
777
+ endBpm?: number;
778
+ }
779
+
780
+ export interface WasmEngineTimeSignatureSegment {
781
+ startPpq: number;
782
+ numerator: number;
783
+ denominator: number;
784
+ }
785
+
699
786
  export interface WasmRealtimeEngine {
700
787
  prepare: (
701
788
  sampleRate: number,
@@ -705,13 +792,18 @@ export interface WasmRealtimeEngine {
705
792
  ) => void;
706
793
  setParameter: (paramId: number, value: number, renderFrame: number) => void;
707
794
  setParameterSmoothed: (paramId: number, value: number, renderFrame: number) => void;
795
+ setSoloMute: (laneIndex: number, solo: boolean, mute: boolean, renderFrame: number) => void;
708
796
  getTransportState: () => WasmEngineTransportState;
709
797
  play: (renderFrame: number) => void;
710
798
  stop: (renderFrame: number) => void;
711
799
  seekSample: (timelineSample: number, renderFrame: number) => void;
800
+ settleParameters: () => void;
712
801
  seekPpq: (ppq: number, renderFrame: number) => void;
713
802
  setTempo: (bpm: number) => void;
803
+ setTempoSegments: (segments: WasmEngineTempoSegment[]) => void;
714
804
  setTimeSignature: (numerator: number, denominator: number) => void;
805
+ setTimeSignatureSegments: (segments: WasmEngineTimeSignatureSegment[]) => void;
806
+ sampleAtPpq: (ppq: number) => number;
715
807
  setLoop: (startPpq: number, endPpq: number, enabled: boolean) => void;
716
808
  addParameter: (info: WasmEngineParameterInfo) => void;
717
809
  parameterCount: () => number;
@@ -733,6 +825,37 @@ export interface WasmRealtimeEngine {
733
825
  graphConnectionCount: () => number;
734
826
  setClips: (clips: WasmEngineClip[]) => void;
735
827
  clipCount: () => number;
828
+ setTrackLanes: (lanes: Array<number | WasmEngineTrackLane>) => void;
829
+ setLaneSidechain: (trackId: number, insertIndex: number, sourceTrackId: number) => void;
830
+ setTrackBuses: (buses: WasmEngineBus[]) => void;
831
+ setBusStripJson: (busId: number, sceneJson: string) => void;
832
+ setTrackStripJson: (trackId: number, sceneJson: string) => void;
833
+ setTrackStripEqBandJson: (trackId: number, bandIndex: number, bandJson: string) => void;
834
+ setTrackStripInsertBypassed: (
835
+ trackId: number,
836
+ insertIndex: number,
837
+ bypassed: boolean,
838
+ resetOnBypass: boolean,
839
+ ) => void;
840
+ setMasterStripJson: (sceneJson: string) => void;
841
+ setMasterStripEqBandJson: (bandIndex: number, bandJson: string) => void;
842
+ setMasterStripInsertBypassed: (
843
+ insertIndex: number,
844
+ bypassed: boolean,
845
+ resetOnBypass: boolean,
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;
736
859
  createClipPageProvider: (numChannels: number, numSamples: number, pageFrames: number) => number;
737
860
  supplyClipPage: (providerId: number, pageIndex: number, channels: Float32Array[]) => void;
738
861
  clearClipPage: (providerId: number, pageIndex: number) => void;
@@ -747,7 +870,11 @@ export interface WasmRealtimeEngine {
747
870
  resetCapture: () => void;
748
871
  captureStatus: () => WasmEngineCaptureStatus;
749
872
  capturedAudio: () => Float32Array[];
873
+ setMidiClips: (clips: readonly object[]) => void;
750
874
  setBuiltinInstrument: (destinationId: number, config: object) => void;
875
+ setSynthInstrument: (destinationId: number, patch: object | string) => void;
876
+ loadSoundFont: (data: Uint8Array) => void;
877
+ setSf2Instrument: (destinationId: number, config: object) => void;
751
878
  clearMidiInstrument: (destinationId: number) => void;
752
879
  midiInstrumentCount: () => number;
753
880
  bindMidiCc: (
@@ -759,6 +886,8 @@ export interface WasmRealtimeEngine {
759
886
  ) => void;
760
887
  clearMidiCcBindings: () => void;
761
888
  midiCcBindingCount: () => number;
889
+ setMidiFx: (destinationId: number, configJson: string) => void;
890
+ clearMidiFx: (destinationId: number) => void;
762
891
  setMidiInputSource: (destinationId: number) => void;
763
892
  clearMidiInputSource: () => void;
764
893
  midiInputPendingCount: () => number;
@@ -808,6 +937,7 @@ export interface WasmRealtimeEngine {
808
937
  renderFrame: number,
809
938
  ) => void;
810
939
  pushMidiPanic: (renderFrame: number) => void;
940
+ clearParameters: () => void;
811
941
  process: (channels: Float32Array[]) => Float32Array[];
812
942
  prepareChannels: (numChannels: number, maxFrames: number) => void;
813
943
  getChannelBuffer: (channel: number, numFrames: number) => Float32Array;
@@ -818,6 +948,9 @@ export interface WasmRealtimeEngine {
818
948
  freezeOffline: (options: WasmEngineFreezeOptions) => WasmEngineFreezeResult;
819
949
  drainTelemetry: (maxRecords: number) => WasmEngineTelemetry[];
820
950
  drainMeterTelemetry: (maxRecords: number) => WasmEngineMeterTelemetry[];
951
+ drainMeterTelemetryWide: (maxRecords: number) => WasmEngineMeterTelemetryWide[];
952
+ configureScopeTelemetry: (intervalFrames: number, bandCount: number) => number;
953
+ drainScopeTelemetry: (maxRecords: number) => WasmEngineScopeTelemetry[];
821
954
  delete: () => void;
822
955
  }
823
956
 
@@ -1390,6 +1523,12 @@ export interface SonareModule {
1390
1523
  options: Record<string, unknown>,
1391
1524
  ) => WasmMixResult;
1392
1525
  trim: (samples: Float32Array, sampleRate: number, thresholdDb: number) => Float32Array;
1526
+ spectralEdit: (
1527
+ samples: Float32Array,
1528
+ sampleRate: number,
1529
+ ops: SpectralRegionOp[],
1530
+ options: Record<string, unknown>,
1531
+ ) => Float32Array;
1393
1532
 
1394
1533
  // Features - Spectrogram
1395
1534
  stft: (
@@ -1974,6 +2113,7 @@ export interface WasmMixer {
1974
2113
  setChannelDelaySamples: (stripIndex: number, delaySamples: number) => void;
1975
2114
  setVcaOffsetDb: (stripIndex: number, offsetDb: number) => void;
1976
2115
  setDualPan: (stripIndex: number, leftPan: number, rightPan: number) => void;
2116
+ setSurroundPan: (stripIndex: number, pan: SurroundPan) => void;
1977
2117
  addSend: (
1978
2118
  stripIndex: number,
1979
2119
  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;
package/src/web_midi.ts CHANGED
@@ -93,6 +93,7 @@ export interface WebMidiBinding {
93
93
  type BoundInput = {
94
94
  input: MidiInputLike;
95
95
  listener: (event: MidiMessageEventLike) => void;
96
+ runningStatus: number;
96
97
  };
97
98
 
98
99
  export function isWebMidiAvailable(): boolean {
@@ -128,7 +129,6 @@ export async function bindWebMidi(
128
129
 
129
130
  const bound = new Map<string, BoundInput>();
130
131
  let closed = false;
131
- let runningStatus = 0;
132
132
 
133
133
  const shouldBind = (input: MidiInputLike) =>
134
134
  input.state !== 'disconnected' && (selectedIds.size === 0 || selectedIds.has(input.id));
@@ -147,22 +147,26 @@ export async function bindWebMidi(
147
147
  if (bound.has(input.id) || !shouldBind(input)) {
148
148
  return;
149
149
  }
150
- const listener = (event: MidiMessageEventLike) => {
151
- const status = dispatchMidiMessage(
152
- engine,
153
- event,
154
- group,
155
- runningStatus,
156
- options.timestampToSamples,
157
- );
158
- runningStatus = status;
150
+ const entry: BoundInput = {
151
+ input,
152
+ listener: (event: MidiMessageEventLike) => {
153
+ entry.runningStatus = dispatchMidiMessage(
154
+ engine,
155
+ event,
156
+ group,
157
+ entry.runningStatus,
158
+ options.timestampToSamples,
159
+ );
160
+ },
161
+ runningStatus: 0,
159
162
  };
163
+ const listener = entry.listener;
160
164
  if (input.addEventListener) {
161
165
  input.addEventListener('midimessage', listener);
162
166
  } else {
163
167
  input.onmidimessage = listener;
164
168
  }
165
- bound.set(input.id, { input, listener });
169
+ bound.set(input.id, entry);
166
170
  };
167
171
 
168
172
  const unbindInput = (input: MidiInputLike) => {
@@ -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
+ }