@libraz/libsonare 1.4.1 → 1.5.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.
- package/README.md +51 -20
- package/dist/index.d.ts +5416 -1
- package/dist/index.js +938 -583
- package/dist/index.js.map +1 -1
- package/dist/sonare.js +2 -2
- package/dist/sonare.wasm +0 -0
- package/dist/worklet.d.ts +1083 -5227
- package/dist/worklet.js +2683 -2451
- package/dist/worklet.js.map +1 -1
- package/package.json +4 -9
- package/src/clip_page_streamer.ts +298 -0
- package/src/effects_mastering.ts +85 -1089
- package/src/effects_transform.ts +286 -0
- package/src/effects_voice_change.ts +118 -0
- package/src/feature_music.ts +5 -2
- package/src/feature_spectrogram.ts +42 -2
- package/src/features.ts +1 -0
- package/src/index.ts +23 -0
- package/src/mastering_chain.ts +200 -0
- package/src/mastering_core.ts +248 -0
- package/src/mastering_dynamics.ts +105 -0
- package/src/mastering_repair.ts +161 -0
- package/src/mixer.ts +8 -0
- package/src/mixing_oneshot.ts +54 -0
- package/src/module_state.ts +1 -2
- package/src/project.ts +71 -1712
- package/src/project_class.ts +871 -0
- package/src/project_internal.ts +333 -0
- package/src/project_synth.ts +43 -0
- package/src/project_types.ts +570 -0
- package/src/public_types.ts +6 -1221
- package/src/public_types_acoustic.ts +115 -0
- package/src/public_types_mastering.ts +333 -0
- package/src/public_types_mixing.ts +97 -0
- package/src/public_types_music.ts +352 -0
- package/src/public_types_realtime.ts +163 -0
- package/src/public_types_spectral.ts +194 -0
- package/src/realtime_engine.ts +94 -0
- package/src/sonare.js.d.ts +117 -38
- package/src/stream_analyzer.ts +3 -0
- package/src/stream_types.ts +4 -0
- package/src/worklet/engine-automation.ts +73 -0
- package/src/worklet/engine-capture-facade.ts +80 -0
- package/src/worklet/engine-clips.ts +71 -0
- package/src/worklet/engine-markers.ts +93 -0
- package/src/worklet/engine-mixer-facade.ts +186 -0
- package/src/worklet/engine-node.ts +451 -0
- package/src/worklet/engine-offline.ts +162 -0
- package/src/worklet/engine-options.ts +13 -0
- package/src/worklet/engine-parameter-facade.ts +172 -0
- package/src/worklet/engine-processor.ts +764 -0
- package/src/worklet/engine-register.ts +136 -0
- package/src/worklet/engine-strips.ts +315 -0
- package/src/worklet/engine-sync.ts +94 -0
- package/src/worklet/engine-tempo-facade.ts +141 -0
- package/src/worklet/engine.ts +998 -0
- package/src/worklet/guards.ts +14 -1
- package/src/worklet/messages.ts +60 -20
- package/src/worklet/mixer-processor.ts +368 -0
- package/src/worklet/protocol.ts +3 -0
- package/src/worklet/voice-changer-processor.ts +246 -0
- package/src/worklet.ts +20 -3549
- package/dist/sonare-rt-module.js +0 -2
- package/dist/sonare-rt.js +0 -2
- package/dist/sonare-rt.wasm +0 -0
- package/src/sonare-rt.d.ts +0 -93
package/src/realtime_engine.ts
CHANGED
|
@@ -24,9 +24,12 @@ import type {
|
|
|
24
24
|
WasmEngineTempoSegment,
|
|
25
25
|
WasmEngineTimeSignatureSegment,
|
|
26
26
|
WasmEngineTransportState,
|
|
27
|
+
WasmExternalMidiEvent,
|
|
27
28
|
WasmRealtimeEngine,
|
|
28
29
|
} from './sonare.js';
|
|
29
30
|
|
|
31
|
+
export type ExternalMidiEvent = WasmExternalMidiEvent;
|
|
32
|
+
|
|
30
33
|
export type EngineClip = WasmEngineClip;
|
|
31
34
|
export type ClipPageRequest = WasmClipPageRequest;
|
|
32
35
|
export type EngineParameterInfo = WasmEngineParameterInfo;
|
|
@@ -193,6 +196,15 @@ export class RealtimeEngine {
|
|
|
193
196
|
this.native.setParameterSmoothed(paramId, value, renderFrame);
|
|
194
197
|
}
|
|
195
198
|
|
|
199
|
+
/**
|
|
200
|
+
* Set the default ramp time (ms) for engine-level smoothed parameters —
|
|
201
|
+
* fader/pan glides, insert-parameter automation, and MIDI-CC mappings. The
|
|
202
|
+
* default is 20 ms; pass `0` for instant (un-ramped) changes.
|
|
203
|
+
*/
|
|
204
|
+
setParamSmoothingMs(smoothingMs: number): void {
|
|
205
|
+
this.native.setParamSmoothingMs(smoothingMs);
|
|
206
|
+
}
|
|
207
|
+
|
|
196
208
|
setSoloMute(laneIndex: number, solo: boolean, mute: boolean, renderFrame = -1): void {
|
|
197
209
|
this.native.setSoloMute(laneIndex, solo, mute, renderFrame);
|
|
198
210
|
}
|
|
@@ -308,6 +320,39 @@ export class RealtimeEngine {
|
|
|
308
320
|
return this.native.midiInputPendingCount();
|
|
309
321
|
}
|
|
310
322
|
|
|
323
|
+
/**
|
|
324
|
+
* Route a destination's (track lane's) MIDI to the external output queue
|
|
325
|
+
* instead of the internal instrument rack, so the track plays an external
|
|
326
|
+
* device. Clearing it restores internal-synth playback.
|
|
327
|
+
*/
|
|
328
|
+
setMidiDestinationExternal(destinationId: number, external: boolean): void {
|
|
329
|
+
this.native.setMidiDestinationExternal(destinationId, external);
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
/**
|
|
333
|
+
* Enable/disable forwarding MIDI clock + transport (start/continue/stop) to
|
|
334
|
+
* the external output queue so external gear tracks the transport tempo.
|
|
335
|
+
*/
|
|
336
|
+
setExternalMidiClockEnabled(enabled: boolean): void {
|
|
337
|
+
this.native.setExternalMidiClockEnabled(enabled);
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
/** Count of external-MIDI events dropped because the output queue was full. */
|
|
341
|
+
externalMidiDroppedCount(): number {
|
|
342
|
+
return this.native.externalMidiDroppedCount();
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
/**
|
|
346
|
+
* Drain queued external-MIDI events, already lowered to MIDI 1.0 byte
|
|
347
|
+
* messages ready to write to a Web MIDI output port. Call once per audio
|
|
348
|
+
* block / animation frame. `maxRecords` caps the number of output events
|
|
349
|
+
* returned — the shared unit across every surface. Events past the cap stay
|
|
350
|
+
* queued for the next call (lossless); call again to drain the rest.
|
|
351
|
+
*/
|
|
352
|
+
drainExternalMidi(maxRecords = 1024): WasmExternalMidiEvent[] {
|
|
353
|
+
return this.native.drainExternalMidi(maxRecords);
|
|
354
|
+
}
|
|
355
|
+
|
|
311
356
|
pushMidiInputNoteOn(
|
|
312
357
|
group: number,
|
|
313
358
|
channel: number,
|
|
@@ -377,6 +422,16 @@ export class RealtimeEngine {
|
|
|
377
422
|
this.native.pushMidiCc(destinationId, group, channel, controller, value, renderFrame);
|
|
378
423
|
}
|
|
379
424
|
|
|
425
|
+
/**
|
|
426
|
+
* Queue an immediate (live) MIDI SysEx frame to a MIDI destination. `data` is
|
|
427
|
+
* the full message including the leading 0xF0 and trailing 0xF7 (1..512
|
|
428
|
+
* bytes). `renderFrame` is the frame to fire at, or -1 for immediate. Mirrors
|
|
429
|
+
* the Node/Python/C-ABI `pushMidiSysex`.
|
|
430
|
+
*/
|
|
431
|
+
pushMidiSysex(destinationId: number, data: Uint8Array, renderFrame = -1): void {
|
|
432
|
+
this.native.pushMidiSysex(destinationId, data, renderFrame);
|
|
433
|
+
}
|
|
434
|
+
|
|
380
435
|
/**
|
|
381
436
|
* Queue a MIDI panic (all-notes-off) releasing every sounding note at
|
|
382
437
|
* `renderFrame` (-1 = immediate). Mirrors the C-ABI `pushMidiPanic`.
|
|
@@ -662,6 +717,45 @@ export class RealtimeEngine {
|
|
|
662
717
|
this.native.setMasterStripInsertParamByName(insertIndex, paramName, value);
|
|
663
718
|
}
|
|
664
719
|
|
|
720
|
+
/** Bus-strip counterpart of {@link setTrackStripInsertParamByName}. */
|
|
721
|
+
setBusStripInsertParamByName(
|
|
722
|
+
busId: number,
|
|
723
|
+
insertIndex: number,
|
|
724
|
+
paramName: string,
|
|
725
|
+
value: number,
|
|
726
|
+
): void {
|
|
727
|
+
this.native.setBusStripInsertParamByName(busId, insertIndex, paramName, value);
|
|
728
|
+
}
|
|
729
|
+
|
|
730
|
+
/** Bus-strip counterpart of {@link setTrackStripInsertBypassed}. */
|
|
731
|
+
setBusStripInsertBypassed(
|
|
732
|
+
busId: number,
|
|
733
|
+
insertIndex: number,
|
|
734
|
+
bypassed: boolean,
|
|
735
|
+
resetOnBypass = false,
|
|
736
|
+
): void {
|
|
737
|
+
this.native.setBusStripInsertBypassed(busId, insertIndex, bypassed, resetOnBypass);
|
|
738
|
+
}
|
|
739
|
+
|
|
740
|
+
/**
|
|
741
|
+
* Resolves a track-lane insert parameter (by its JSON-key name) to the
|
|
742
|
+
* reserved automation id usable with `setAutomationLane` / `setParameter`.
|
|
743
|
+
* Returns `-1` when the track, insert, or name is unknown. (The Python binding
|
|
744
|
+
* raises a `SonareError` for an unknown id where Node/WASM return the `-1`
|
|
745
|
+
* sentinel.)
|
|
746
|
+
*/
|
|
747
|
+
resolveTrackInsertAutomationId(trackId: number, insertIndex: number, paramName: string): number {
|
|
748
|
+
return this.native.resolveTrackInsertAutomationId(trackId, insertIndex, paramName);
|
|
749
|
+
}
|
|
750
|
+
|
|
751
|
+
resolveMasterInsertAutomationId(insertIndex: number, paramName: string): number {
|
|
752
|
+
return this.native.resolveMasterInsertAutomationId(insertIndex, paramName);
|
|
753
|
+
}
|
|
754
|
+
|
|
755
|
+
resolveBusInsertAutomationId(busId: number, insertIndex: number, paramName: string): number {
|
|
756
|
+
return this.native.resolveBusInsertAutomationId(busId, insertIndex, paramName);
|
|
757
|
+
}
|
|
758
|
+
|
|
665
759
|
/** Sets a track lane strip's pan position in realtime (glitch-free). */
|
|
666
760
|
setTrackStripPan(trackId: number, pan: number): void {
|
|
667
761
|
this.native.setTrackStripPan(trackId, pan);
|
package/src/sonare.js.d.ts
CHANGED
|
@@ -26,45 +26,45 @@ export interface WasmKeyCandidateResult {
|
|
|
26
26
|
correlation: number;
|
|
27
27
|
}
|
|
28
28
|
|
|
29
|
-
// Flat POD mirror of SonareRealtimeVoiceChangerConfig.
|
|
30
|
-
// C ABI / Python POD
|
|
29
|
+
// Flat POD mirror of SonareRealtimeVoiceChangerConfig. Keys are camelCase to
|
|
30
|
+
// match the Node addon getter (the underlying C ABI / Python POD is snake_case).
|
|
31
31
|
export interface WasmRealtimeVoiceChangerPodConfig {
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
32
|
+
inputGainDb: number;
|
|
33
|
+
outputGainDb: number;
|
|
34
|
+
wetMix: number;
|
|
35
|
+
retuneSemitones: number;
|
|
36
|
+
retuneMix: number;
|
|
37
|
+
retuneGrainSize: number;
|
|
38
|
+
formantFactor: number;
|
|
39
|
+
formantAmount: number;
|
|
40
|
+
formantBody: number;
|
|
41
|
+
formantBrightness: number;
|
|
42
|
+
formantNasal: number;
|
|
43
|
+
eqHighpassHz: number;
|
|
44
|
+
eqBodyDb: number;
|
|
45
|
+
eqPresenceDb: number;
|
|
46
|
+
eqAirDb: number;
|
|
47
|
+
gateThresholdDb: number;
|
|
48
|
+
gateAttackMs: number;
|
|
49
|
+
gateReleaseMs: number;
|
|
50
|
+
gateRangeDb: number;
|
|
51
|
+
compressorThresholdDb: number;
|
|
52
|
+
compressorRatio: number;
|
|
53
|
+
compressorAttackMs: number;
|
|
54
|
+
compressorReleaseMs: number;
|
|
55
|
+
compressorMakeupGainDb: number;
|
|
56
|
+
deesserFrequencyHz: number;
|
|
57
|
+
deesserThresholdDb: number;
|
|
58
|
+
deesserRatio: number;
|
|
59
|
+
deesserRangeDb: number;
|
|
60
|
+
reverbMix: number;
|
|
61
|
+
reverbTimeMs: number;
|
|
62
|
+
reverbDamping: number;
|
|
63
|
+
reverbSeed: number;
|
|
64
|
+
limiterCeilingDb: number;
|
|
65
|
+
limiterReleaseMs: number;
|
|
66
|
+
limiterEnableIspLimiter: boolean;
|
|
67
|
+
limiterIspCeilingDbtp: number;
|
|
68
68
|
}
|
|
69
69
|
|
|
70
70
|
export interface WasmBeatResult {
|
|
@@ -194,6 +194,7 @@ export interface WasmStreamConfigDefaults {
|
|
|
194
194
|
computeSpectral: boolean;
|
|
195
195
|
emitEveryNFrames: number;
|
|
196
196
|
magnitudeDownsample: number;
|
|
197
|
+
maxPendingFrames: number;
|
|
197
198
|
keyUpdateIntervalSec: number;
|
|
198
199
|
bpmUpdateIntervalSec: number;
|
|
199
200
|
window: number;
|
|
@@ -510,6 +511,10 @@ export interface WasmMixMeterSnapshot {
|
|
|
510
511
|
truePeakDbR: number;
|
|
511
512
|
maxTruePeakDb: number;
|
|
512
513
|
seq: number;
|
|
514
|
+
channelCount: number;
|
|
515
|
+
peakDb: number[];
|
|
516
|
+
rmsDb: number[];
|
|
517
|
+
truePeakDb: number[];
|
|
513
518
|
}
|
|
514
519
|
|
|
515
520
|
export interface WasmMixResult {
|
|
@@ -783,6 +788,23 @@ export interface WasmEngineTimeSignatureSegment {
|
|
|
783
788
|
denominator: number;
|
|
784
789
|
}
|
|
785
790
|
|
|
791
|
+
/**
|
|
792
|
+
* One external-MIDI event drained from the engine, already lowered to MIDI 1.0
|
|
793
|
+
* bytes ready to write to a Web MIDI output port.
|
|
794
|
+
*/
|
|
795
|
+
export interface WasmExternalMidiEvent {
|
|
796
|
+
/**
|
|
797
|
+
* Originating track lane (Track.midi_destination_id), or the transport
|
|
798
|
+
* sentinel 0xFFFFFFFF for clock / start / continue / stop bytes meant for
|
|
799
|
+
* every external port.
|
|
800
|
+
*/
|
|
801
|
+
destinationId: number;
|
|
802
|
+
/** Sample position within the producing block at which the event fires. */
|
|
803
|
+
renderFrame: number;
|
|
804
|
+
/** MIDI 1.0 status + data bytes (1..3 entries). */
|
|
805
|
+
bytes: number[];
|
|
806
|
+
}
|
|
807
|
+
|
|
786
808
|
export interface WasmRealtimeEngine {
|
|
787
809
|
prepare: (
|
|
788
810
|
sampleRate: number,
|
|
@@ -792,6 +814,7 @@ export interface WasmRealtimeEngine {
|
|
|
792
814
|
) => void;
|
|
793
815
|
setParameter: (paramId: number, value: number, renderFrame: number) => void;
|
|
794
816
|
setParameterSmoothed: (paramId: number, value: number, renderFrame: number) => void;
|
|
817
|
+
setParamSmoothingMs: (smoothingMs: number) => void;
|
|
795
818
|
setSoloMute: (laneIndex: number, solo: boolean, mute: boolean, renderFrame: number) => void;
|
|
796
819
|
getTransportState: () => WasmEngineTransportState;
|
|
797
820
|
play: (renderFrame: number) => void;
|
|
@@ -851,6 +874,25 @@ export interface WasmRealtimeEngine {
|
|
|
851
874
|
value: number,
|
|
852
875
|
) => void;
|
|
853
876
|
setMasterStripInsertParamByName: (insertIndex: number, paramName: string, value: number) => void;
|
|
877
|
+
setBusStripInsertParamByName: (
|
|
878
|
+
busId: number,
|
|
879
|
+
insertIndex: number,
|
|
880
|
+
paramName: string,
|
|
881
|
+
value: number,
|
|
882
|
+
) => void;
|
|
883
|
+
setBusStripInsertBypassed: (
|
|
884
|
+
busId: number,
|
|
885
|
+
insertIndex: number,
|
|
886
|
+
bypassed: boolean,
|
|
887
|
+
resetOnBypass: boolean,
|
|
888
|
+
) => void;
|
|
889
|
+
resolveTrackInsertAutomationId: (
|
|
890
|
+
trackId: number,
|
|
891
|
+
insertIndex: number,
|
|
892
|
+
paramName: string,
|
|
893
|
+
) => number;
|
|
894
|
+
resolveMasterInsertAutomationId: (insertIndex: number, paramName: string) => number;
|
|
895
|
+
resolveBusInsertAutomationId: (busId: number, insertIndex: number, paramName: string) => number;
|
|
854
896
|
setTrackStripPan: (trackId: number, pan: number) => void;
|
|
855
897
|
setTrackStripPanLaw: (trackId: number, panLaw: number) => void;
|
|
856
898
|
setTrackStripPanMode: (trackId: number, panMode: number) => void;
|
|
@@ -936,7 +978,12 @@ export interface WasmRealtimeEngine {
|
|
|
936
978
|
value: number,
|
|
937
979
|
renderFrame: number,
|
|
938
980
|
) => void;
|
|
981
|
+
pushMidiSysex: (destinationId: number, data: Uint8Array, renderFrame: number) => void;
|
|
939
982
|
pushMidiPanic: (renderFrame: number) => void;
|
|
983
|
+
setMidiDestinationExternal: (destinationId: number, external: boolean) => void;
|
|
984
|
+
setExternalMidiClockEnabled: (enabled: boolean) => void;
|
|
985
|
+
externalMidiDroppedCount: () => number;
|
|
986
|
+
drainExternalMidi: (maxRecords: number) => WasmExternalMidiEvent[];
|
|
940
987
|
clearParameters: () => void;
|
|
941
988
|
process: (channels: Float32Array[]) => Float32Array[];
|
|
942
989
|
prepareChannels: (numChannels: number, maxFrames: number) => void;
|
|
@@ -1110,6 +1157,7 @@ export interface SonareModule {
|
|
|
1110
1157
|
detectKeyCandidates: (samples: Float32Array, sampleRate: number) => WasmKeyCandidateResult[];
|
|
1111
1158
|
hasFfmpegSupport: () => boolean;
|
|
1112
1159
|
version: () => string;
|
|
1160
|
+
abiVersion: () => number;
|
|
1113
1161
|
engineAbiVersion: () => number;
|
|
1114
1162
|
voiceChangerAbiVersion: () => number;
|
|
1115
1163
|
voiceCharacterPresetId: (preset: number) => string | null;
|
|
@@ -1337,6 +1385,25 @@ export interface SonareModule {
|
|
|
1337
1385
|
voiced?: Float32Array,
|
|
1338
1386
|
voicedProb?: Float32Array,
|
|
1339
1387
|
) => Float32Array;
|
|
1388
|
+
pitchCorrectTimevarying: (
|
|
1389
|
+
samples: Float32Array,
|
|
1390
|
+
sampleRate: number,
|
|
1391
|
+
f0Hz: Float32Array,
|
|
1392
|
+
hopLength: number,
|
|
1393
|
+
options: {
|
|
1394
|
+
mode?: string;
|
|
1395
|
+
targetMidi?: number;
|
|
1396
|
+
scaleRoot?: number;
|
|
1397
|
+
scaleModeMask?: number;
|
|
1398
|
+
referenceMidi?: number;
|
|
1399
|
+
retuneAmount?: number;
|
|
1400
|
+
maxCorrectionSemitones?: number;
|
|
1401
|
+
retuneSpeedMs?: number;
|
|
1402
|
+
vibratoThresholdCents?: number;
|
|
1403
|
+
voiced?: Float32Array;
|
|
1404
|
+
voicedProb?: Float32Array;
|
|
1405
|
+
},
|
|
1406
|
+
) => Float32Array;
|
|
1340
1407
|
noteStretch: (
|
|
1341
1408
|
samples: Float32Array,
|
|
1342
1409
|
sampleRate: number,
|
|
@@ -1567,6 +1634,7 @@ export interface SonareModule {
|
|
|
1567
1634
|
fmin: number,
|
|
1568
1635
|
fmax: number,
|
|
1569
1636
|
htk: boolean,
|
|
1637
|
+
lifter: number,
|
|
1570
1638
|
) => WasmMfccResult;
|
|
1571
1639
|
|
|
1572
1640
|
// Features - Inverse reconstruction
|
|
@@ -1792,6 +1860,12 @@ export interface SonareModule {
|
|
|
1792
1860
|
hopLength: number,
|
|
1793
1861
|
nChroma: number,
|
|
1794
1862
|
) => WasmChromaResult;
|
|
1863
|
+
chromaCqt: (
|
|
1864
|
+
samples: Float32Array,
|
|
1865
|
+
sampleRate: number,
|
|
1866
|
+
hopLength: number,
|
|
1867
|
+
nChroma: number,
|
|
1868
|
+
) => WasmChromaResult;
|
|
1795
1869
|
bassChroma: (
|
|
1796
1870
|
samples: Float32Array,
|
|
1797
1871
|
sampleRate: number,
|
|
@@ -1876,6 +1950,7 @@ export interface SonareModule {
|
|
|
1876
1950
|
winLength: number,
|
|
1877
1951
|
sampleRate: number,
|
|
1878
1952
|
hopLength: number,
|
|
1953
|
+
factors?: Float32Array | number[],
|
|
1879
1954
|
) => Float32Array;
|
|
1880
1955
|
lufs: (samples: Float32Array, sampleRate: number) => WasmLufsResult;
|
|
1881
1956
|
momentaryLufs: (samples: Float32Array, sampleRate: number) => Float32Array;
|
|
@@ -1952,6 +2027,7 @@ export interface SonareModule {
|
|
|
1952
2027
|
computeSpectral: boolean,
|
|
1953
2028
|
emitEveryNFrames: number,
|
|
1954
2029
|
magnitudeDownsample: number,
|
|
2030
|
+
maxPendingFrames: number,
|
|
1955
2031
|
keyUpdateIntervalSec: number,
|
|
1956
2032
|
bpmUpdateIntervalSec: number,
|
|
1957
2033
|
window: number,
|
|
@@ -2162,6 +2238,7 @@ export interface WasmMixer {
|
|
|
2162
2238
|
vcaGroupCount: () => number;
|
|
2163
2239
|
toSceneJson: () => string;
|
|
2164
2240
|
tailSamples: () => number;
|
|
2241
|
+
latencySamples: () => number;
|
|
2165
2242
|
drainTailStereo: (numSamples: number) => {
|
|
2166
2243
|
left: Float32Array;
|
|
2167
2244
|
right: Float32Array;
|
|
@@ -2225,6 +2302,8 @@ export interface WasmAnalyzerStats {
|
|
|
2225
2302
|
totalFrames: number;
|
|
2226
2303
|
totalSamples: number;
|
|
2227
2304
|
durationSeconds: number;
|
|
2305
|
+
pendingFrames: number;
|
|
2306
|
+
droppedOutputFrames: number;
|
|
2228
2307
|
estimate: WasmProgressiveEstimate;
|
|
2229
2308
|
}
|
|
2230
2309
|
|
package/src/stream_analyzer.ts
CHANGED
|
@@ -71,6 +71,7 @@ export class StreamAnalyzer {
|
|
|
71
71
|
config.computeSpectral ?? defaults.computeSpectral,
|
|
72
72
|
config.emitEveryNFrames ?? defaults.emitEveryNFrames,
|
|
73
73
|
config.magnitudeDownsample ?? defaults.magnitudeDownsample,
|
|
74
|
+
config.maxPendingFrames ?? defaults.maxPendingFrames,
|
|
74
75
|
config.keyUpdateIntervalSec ?? defaults.keyUpdateIntervalSec,
|
|
75
76
|
config.bpmUpdateIntervalSec ?? defaults.bpmUpdateIntervalSec,
|
|
76
77
|
config.window ?? defaults.window,
|
|
@@ -163,6 +164,8 @@ export class StreamAnalyzer {
|
|
|
163
164
|
totalFrames: s.totalFrames,
|
|
164
165
|
totalSamples: s.totalSamples,
|
|
165
166
|
durationSeconds: s.durationSeconds,
|
|
167
|
+
pendingFrames: s.pendingFrames,
|
|
168
|
+
droppedOutputFrames: s.droppedOutputFrames,
|
|
166
169
|
estimate: {
|
|
167
170
|
bpm: s.estimate.bpm,
|
|
168
171
|
bpmConfidence: s.estimate.bpmConfidence,
|
package/src/stream_types.ts
CHANGED
|
@@ -64,6 +64,8 @@ export interface AnalyzerStats {
|
|
|
64
64
|
totalFrames: number;
|
|
65
65
|
totalSamples: number;
|
|
66
66
|
durationSeconds: number;
|
|
67
|
+
pendingFrames: number;
|
|
68
|
+
droppedOutputFrames: number;
|
|
67
69
|
estimate: ProgressiveEstimate;
|
|
68
70
|
}
|
|
69
71
|
|
|
@@ -159,6 +161,8 @@ export interface StreamConfig {
|
|
|
159
161
|
computeSpectral?: boolean;
|
|
160
162
|
emitEveryNFrames?: number;
|
|
161
163
|
magnitudeDownsample?: number;
|
|
164
|
+
/** Maximum unread frames; overflow drops the oldest frame. */
|
|
165
|
+
maxPendingFrames?: number;
|
|
162
166
|
keyUpdateIntervalSec?: number;
|
|
163
167
|
bpmUpdateIntervalSec?: number;
|
|
164
168
|
window?: number;
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import type { EngineAutomationPoint, RealtimeEngine } from '../index';
|
|
2
|
+
import { curveCode } from './engine-sync';
|
|
3
|
+
import type { SonareEngineSyncMessage } from './messages';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Collaborator surface the automation-lane helpers need from the owning
|
|
7
|
+
* {@link SonareEngine}: the lane store they mutate, the offline engine they
|
|
8
|
+
* mirror into, the sync poster, and the parameter-id resolver.
|
|
9
|
+
*/
|
|
10
|
+
export interface EngineAutomationContext {
|
|
11
|
+
readonly offlineEngine: RealtimeEngine;
|
|
12
|
+
readonly automationLanes: Map<number, EngineAutomationPoint[]>;
|
|
13
|
+
postSync(message: SonareEngineSyncMessage): void;
|
|
14
|
+
resolveParamId(nodeId: string, param: string | number): number;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export function scheduleParam(
|
|
18
|
+
ctx: EngineAutomationContext,
|
|
19
|
+
nodeId: string,
|
|
20
|
+
param: string | number,
|
|
21
|
+
ppq: number,
|
|
22
|
+
value: number,
|
|
23
|
+
curve: number | 'linear' | 'exponential' = 'linear',
|
|
24
|
+
): void {
|
|
25
|
+
const paramId = ctx.resolveParamId(nodeId, param);
|
|
26
|
+
const lane = ctx.automationLanes.get(paramId) ?? [];
|
|
27
|
+
lane.push({ ppq, value, curveToNext: curveCode(curve) });
|
|
28
|
+
lane.sort((a, b) => a.ppq - b.ppq);
|
|
29
|
+
ctx.automationLanes.set(paramId, lane);
|
|
30
|
+
ctx.offlineEngine.setAutomationLane(paramId, lane);
|
|
31
|
+
// Mirror the lane to the live worklet engine so scheduled automation plays
|
|
32
|
+
// back in real time, not just in renderOffline(). Lanes can exceed the
|
|
33
|
+
// fixed-size SAB command record, so they ride an out-of-band 'syncAutomation'
|
|
34
|
+
// message applied outside process() (like syncClips/syncMarkers).
|
|
35
|
+
ctx.postSync({ type: 'syncAutomation', paramId, points: lane });
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export function addAutomationPoint(
|
|
39
|
+
ctx: EngineAutomationContext,
|
|
40
|
+
laneId: string | number,
|
|
41
|
+
ppq: number,
|
|
42
|
+
value: number,
|
|
43
|
+
curve: number | 'linear' | 'exponential' = 'linear',
|
|
44
|
+
): void {
|
|
45
|
+
scheduleParam(ctx, '', laneId, ppq, value, curve);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Replaces the automation lane for `paramId` with the given breakpoints.
|
|
50
|
+
*
|
|
51
|
+
* Unlike scheduleParam (which appends a single point), this sets the whole
|
|
52
|
+
* lane at once; an empty array clears the lane. The points are defensively
|
|
53
|
+
* copied and sorted by ppq before being mirrored to the offline engine and
|
|
54
|
+
* the live worklet engine.
|
|
55
|
+
*
|
|
56
|
+
* @param paramId Automation target id (registered parameter or a reserved
|
|
57
|
+
* engine mixer target from automationParamId/busAutomationParamId).
|
|
58
|
+
* @param points Lane breakpoints; order does not matter.
|
|
59
|
+
*/
|
|
60
|
+
export function setAutomationLane(
|
|
61
|
+
ctx: EngineAutomationContext,
|
|
62
|
+
paramId: number,
|
|
63
|
+
points: ReadonlyArray<EngineAutomationPoint>,
|
|
64
|
+
): void {
|
|
65
|
+
const sorted = points.map((point) => ({ ...point })).sort((a, b) => a.ppq - b.ppq);
|
|
66
|
+
if (sorted.length === 0) {
|
|
67
|
+
ctx.automationLanes.delete(paramId);
|
|
68
|
+
} else {
|
|
69
|
+
ctx.automationLanes.set(paramId, sorted);
|
|
70
|
+
}
|
|
71
|
+
ctx.offlineEngine.setAutomationLane(paramId, sorted);
|
|
72
|
+
ctx.postSync({ type: 'syncAutomation', paramId, points: sorted });
|
|
73
|
+
}
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import type { EngineCaptureStatus, RealtimeEngine } from '../index';
|
|
2
|
+
import type { SonareRealtimeEngineNode } from './engine-node';
|
|
3
|
+
import { buildCaptureConfig, type CaptureOptions } from './engine-offline';
|
|
4
|
+
import type { SonareEngineSyncCaptureMessage, SonareEngineSyncMessage } from './messages';
|
|
5
|
+
import { SonareEngineCommandType } from './protocol';
|
|
6
|
+
|
|
7
|
+
type CaptureConfig = Omit<SonareEngineSyncCaptureMessage, 'type'>;
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Collaborator surface the capture/record/punch setters need from the owning
|
|
11
|
+
* {@link SonareEngine}: the offline engine they mirror into, the realtime node
|
|
12
|
+
* they command and query, the channel count, the out-of-band sync poster, the
|
|
13
|
+
* capture-config accessor, and the target-id resolver.
|
|
14
|
+
*/
|
|
15
|
+
export interface EngineCaptureContext {
|
|
16
|
+
readonly offlineEngine: RealtimeEngine;
|
|
17
|
+
readonly realtimeNode: SonareRealtimeEngineNode;
|
|
18
|
+
readonly offlineChannelCount: number;
|
|
19
|
+
postSync(message: SonareEngineSyncMessage): void;
|
|
20
|
+
getCaptureConfig(): CaptureConfig | undefined;
|
|
21
|
+
setCaptureConfig(config: CaptureConfig): void;
|
|
22
|
+
resolveTargetId(target: string | number): number;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export function configureCapture(ctx: EngineCaptureContext, options: CaptureOptions): void {
|
|
26
|
+
const config = buildCaptureConfig(options, ctx.offlineChannelCount);
|
|
27
|
+
ctx.offlineEngine.setCaptureBuffer(config.channels, config.bufferFrames);
|
|
28
|
+
ctx.offlineEngine.setCaptureSource(config.source);
|
|
29
|
+
ctx.offlineEngine.setRecordOffsetSamples(config.recordOffsetSamples);
|
|
30
|
+
ctx.offlineEngine.setInputMonitor(config.inputMonitor.enabled, config.inputMonitor.gain);
|
|
31
|
+
ctx.setCaptureConfig(config);
|
|
32
|
+
ctx.postSync({ type: 'syncCapture', ...config });
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export function armRecord(
|
|
36
|
+
ctx: EngineCaptureContext,
|
|
37
|
+
trackId: string | number,
|
|
38
|
+
enabled: boolean,
|
|
39
|
+
): boolean {
|
|
40
|
+
if (enabled && !ctx.getCaptureConfig()) {
|
|
41
|
+
throw new Error('Capture buffer is not configured');
|
|
42
|
+
}
|
|
43
|
+
ctx.offlineEngine.armCapture(enabled);
|
|
44
|
+
return ctx.realtimeNode.sendCommand({
|
|
45
|
+
type: SonareEngineCommandType.ArmRecord,
|
|
46
|
+
targetId: ctx.resolveTargetId(trackId),
|
|
47
|
+
sampleTime: -1,
|
|
48
|
+
argInt: enabled ? 1 : 0,
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export function punch(ctx: EngineCaptureContext, inPpq: number, outPpq: number): boolean {
|
|
53
|
+
const inSample = ctx.offlineEngine.sampleAtPpq(inPpq);
|
|
54
|
+
const outSample = ctx.offlineEngine.sampleAtPpq(outPpq);
|
|
55
|
+
ctx.offlineEngine.setCapturePunch(inSample, outSample, true);
|
|
56
|
+
// Carry BOTH endpoints as already-converted SAMPLES so the realtime engine
|
|
57
|
+
// agrees with the offline engine. The previous code sent the raw PPQ out
|
|
58
|
+
// point and let the consumer multiply by sampleRate (treating PPQ as
|
|
59
|
+
// seconds), which ignored tempo and produced a punch-out ~2x too large at
|
|
60
|
+
// 120 BPM. argInt = in sample, argFloat = out sample (full-precision double).
|
|
61
|
+
return ctx.realtimeNode.sendCommand({
|
|
62
|
+
type: SonareEngineCommandType.Punch,
|
|
63
|
+
sampleTime: -1,
|
|
64
|
+
argInt: inSample,
|
|
65
|
+
argFloat: outSample,
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
export function captureStatus(ctx: EngineCaptureContext): Promise<EngineCaptureStatus> {
|
|
70
|
+
return ctx.realtimeNode.requestCaptureStatus();
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
export function capturedAudio(ctx: EngineCaptureContext): Promise<Float32Array[]> {
|
|
74
|
+
return ctx.realtimeNode.requestCapturedAudio();
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
export async function resetCapture(ctx: EngineCaptureContext): Promise<void> {
|
|
78
|
+
ctx.offlineEngine.resetCapture();
|
|
79
|
+
await ctx.realtimeNode.requestCaptureReset();
|
|
80
|
+
}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import type { EngineClip, EngineMidiClipSchedule, RealtimeEngine } from '../index';
|
|
2
|
+
import type { SonareEngineSyncMessage } from './messages';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Collaborator surface the audio/MIDI clip scheduling helpers need from the
|
|
6
|
+
* owning {@link SonareEngine}: the clip stores they mutate, the offline engine
|
|
7
|
+
* they mirror into, the sync poster, and the clip-id allocator / lane resolvers.
|
|
8
|
+
*/
|
|
9
|
+
export interface EngineClipContext {
|
|
10
|
+
readonly offlineEngine: RealtimeEngine;
|
|
11
|
+
readonly clips: Map<number, EngineClip>;
|
|
12
|
+
readonly midiClips: Map<number, EngineMidiClipSchedule>;
|
|
13
|
+
allocateClipId(): number;
|
|
14
|
+
postSync(message: SonareEngineSyncMessage): void;
|
|
15
|
+
resolveTargetId(target: string | number): number;
|
|
16
|
+
ensureTrackLane(target: string | number): number;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export function addClip(
|
|
20
|
+
ctx: EngineClipContext,
|
|
21
|
+
trackId: string | number,
|
|
22
|
+
buffer: Float32Array[],
|
|
23
|
+
startPpq: number,
|
|
24
|
+
opts: Partial<Omit<EngineClip, 'channels' | 'startPpq'>> = {},
|
|
25
|
+
): number {
|
|
26
|
+
const id = opts.id ?? ctx.allocateClipId();
|
|
27
|
+
const clip: EngineClip = {
|
|
28
|
+
...opts,
|
|
29
|
+
id,
|
|
30
|
+
channels: buffer,
|
|
31
|
+
startPpq,
|
|
32
|
+
trackId: ctx.resolveTargetId(trackId),
|
|
33
|
+
};
|
|
34
|
+
ctx.ensureTrackLane(trackId);
|
|
35
|
+
ctx.clips.set(id, clip);
|
|
36
|
+
syncClipsDelta(ctx, [clip], []);
|
|
37
|
+
return id;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export function removeClip(ctx: EngineClipContext, clipId: number): void {
|
|
41
|
+
ctx.clips.delete(clipId);
|
|
42
|
+
syncClipsDelta(ctx, [], [clipId]);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export function setMidiClips(
|
|
46
|
+
ctx: EngineClipContext,
|
|
47
|
+
clips: readonly EngineMidiClipSchedule[],
|
|
48
|
+
): void {
|
|
49
|
+
ctx.midiClips.clear();
|
|
50
|
+
for (const clip of clips) {
|
|
51
|
+
const id = clip.id ?? ctx.allocateClipId();
|
|
52
|
+
ctx.midiClips.set(id, { ...clip, id, events: clip.events.map((event) => ({ ...event })) });
|
|
53
|
+
}
|
|
54
|
+
syncMidiClips(ctx);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function syncClipsDelta(ctx: EngineClipContext, upserts: EngineClip[], removeIds: number[]): void {
|
|
58
|
+
const clips = Array.from(ctx.clips.values());
|
|
59
|
+
ctx.offlineEngine.setClips(clips);
|
|
60
|
+
ctx.postSync({
|
|
61
|
+
type: 'syncClipsDelta',
|
|
62
|
+
upserts,
|
|
63
|
+
removeIds,
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function syncMidiClips(ctx: EngineClipContext): void {
|
|
68
|
+
const clips = Array.from(ctx.midiClips.values());
|
|
69
|
+
ctx.offlineEngine.setMidiClips(clips);
|
|
70
|
+
ctx.postSync({ type: 'syncMidiClips', clips });
|
|
71
|
+
}
|