@libraz/libsonare 1.5.2 → 1.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.
- package/README.md +18 -3
- package/dist/index.d.ts +1147 -181
- package/dist/index.js +4826 -3941
- package/dist/index.js.map +1 -1
- package/dist/sonare.js +1 -1
- package/dist/sonare.wasm +0 -0
- package/dist/worklet.d.ts +29 -1
- package/dist/worklet.js +1067 -973
- package/dist/worklet.js.map +1 -1
- package/package.json +2 -1
- package/src/_chain_config.ts +46 -0
- package/src/audio.ts +24 -4
- package/src/effects_mastering.ts +25 -1
- package/src/effects_transform.ts +283 -43
- package/src/effects_voice_change.ts +46 -53
- package/src/feature_core.ts +316 -12
- package/src/feature_music.ts +357 -0
- package/src/feature_pitch.ts +61 -0
- package/src/feature_resample.ts +17 -2
- package/src/feature_spectral.ts +349 -2
- package/src/feature_spectrogram.ts +336 -0
- package/src/index.ts +130 -1
- package/src/mastering_chain.ts +305 -50
- package/src/mastering_core.ts +232 -16
- package/src/mastering_dynamics.ts +66 -10
- package/src/mastering_repair.ts +119 -7
- package/src/metering.ts +366 -81
- package/src/mixer.ts +9 -2
- package/src/mixing_oneshot.ts +25 -5
- package/src/module_state.ts +1 -1
- package/src/project_class.ts +17 -6
- package/src/project_internal.ts +10 -2
- package/src/project_types.ts +25 -2
- package/src/public_types_mastering.ts +114 -4
- package/src/public_types_spectral.ts +7 -0
- package/src/quick_analysis.ts +325 -117
- package/src/realtime_engine.ts +8 -0
- package/src/sonare.js.d.ts +31 -0
- package/src/stream_analyzer.ts +20 -1
- package/src/stream_types.ts +12 -1
- package/src/validation.ts +17 -2
- package/src/worklet/engine-clips.ts +67 -2
- package/src/worklet/engine-processor.ts +33 -5
- package/src/worklet/engine.ts +7 -3
- package/src/worklet/guards.ts +3 -0
- package/src/worklet/messages.ts +27 -0
package/src/realtime_engine.ts
CHANGED
|
@@ -587,6 +587,14 @@ export class RealtimeEngine {
|
|
|
587
587
|
);
|
|
588
588
|
}
|
|
589
589
|
|
|
590
|
+
/**
|
|
591
|
+
* Returns the PCM generated for a tempo-sync clip by the control-thread
|
|
592
|
+
* setter, or `null` when the clip did not require a tempo-sync bake.
|
|
593
|
+
*/
|
|
594
|
+
prebakedClipChannels(clipId: number): Float32Array[] | null {
|
|
595
|
+
return this.native.prebakedClipChannels(clipId);
|
|
596
|
+
}
|
|
597
|
+
|
|
590
598
|
clipCount(): number {
|
|
591
599
|
return this.native.clipCount();
|
|
592
600
|
}
|
package/src/sonare.js.d.ts
CHANGED
|
@@ -470,8 +470,16 @@ export interface WasmMasteringResult {
|
|
|
470
470
|
latencySamples?: number;
|
|
471
471
|
}
|
|
472
472
|
|
|
473
|
+
export interface WasmStageGainReduction {
|
|
474
|
+
stage: string;
|
|
475
|
+
gainReductionDb: number;
|
|
476
|
+
}
|
|
477
|
+
|
|
473
478
|
export interface WasmMasteringChainResult extends WasmMasteringResult {
|
|
474
479
|
stages: string[];
|
|
480
|
+
outputTruePeakDbtp: number;
|
|
481
|
+
outputLra: number;
|
|
482
|
+
stageGainReductions: WasmStageGainReduction[];
|
|
475
483
|
}
|
|
476
484
|
|
|
477
485
|
export interface WasmMasteringStereoChainResult {
|
|
@@ -482,6 +490,9 @@ export interface WasmMasteringStereoChainResult {
|
|
|
482
490
|
outputLufs: number;
|
|
483
491
|
appliedGainDb: number;
|
|
484
492
|
stages: string[];
|
|
493
|
+
outputTruePeakDbtp: number;
|
|
494
|
+
outputLra: number;
|
|
495
|
+
stageGainReductions: WasmStageGainReduction[];
|
|
485
496
|
}
|
|
486
497
|
|
|
487
498
|
export interface WasmMasteringStereoResult {
|
|
@@ -848,6 +859,7 @@ export interface WasmRealtimeEngine {
|
|
|
848
859
|
graphNodeCount: () => number;
|
|
849
860
|
graphConnectionCount: () => number;
|
|
850
861
|
setClips: (clips: WasmEngineClip[]) => void;
|
|
862
|
+
prebakedClipChannels: (clipId: number) => Float32Array[] | null;
|
|
851
863
|
clipCount: () => number;
|
|
852
864
|
setTrackLanes: (lanes: Array<number | WasmEngineTrackLane>) => void;
|
|
853
865
|
setLaneSidechain: (trackId: number, insertIndex: number, sourceTrackId: number) => void;
|
|
@@ -1412,12 +1424,25 @@ export interface SonareModule {
|
|
|
1412
1424
|
offsetSample: number,
|
|
1413
1425
|
stretchRatio: number,
|
|
1414
1426
|
) => Float32Array;
|
|
1427
|
+
noteMove: (
|
|
1428
|
+
samples: Float32Array,
|
|
1429
|
+
sampleRate: number,
|
|
1430
|
+
onsetSample: number,
|
|
1431
|
+
offsetSample: number,
|
|
1432
|
+
targetOnsetSample: number,
|
|
1433
|
+
) => Float32Array;
|
|
1415
1434
|
voiceChange: (
|
|
1416
1435
|
samples: Float32Array,
|
|
1417
1436
|
sampleRate: number,
|
|
1418
1437
|
pitchSemitones: number,
|
|
1419
1438
|
formantFactor: number,
|
|
1420
1439
|
) => Float32Array;
|
|
1440
|
+
voiceChangeRealtime: (
|
|
1441
|
+
samples: Float32Array,
|
|
1442
|
+
sampleRate: number,
|
|
1443
|
+
preset: string,
|
|
1444
|
+
channels: number,
|
|
1445
|
+
) => Float32Array;
|
|
1421
1446
|
normalize: (samples: Float32Array, sampleRate: number, targetDb: number) => Float32Array;
|
|
1422
1447
|
mastering: (
|
|
1423
1448
|
samples: Float32Array,
|
|
@@ -2336,6 +2361,8 @@ export interface WasmFrameBuffer {
|
|
|
2336
2361
|
nFrames: number;
|
|
2337
2362
|
/** Number of mel bands; flat `mel` is `[nFrames * nMels]` row-major. */
|
|
2338
2363
|
nMels: number;
|
|
2364
|
+
nChroma: number;
|
|
2365
|
+
featureFlags: number;
|
|
2339
2366
|
timestamps: Float32Array;
|
|
2340
2367
|
mel: Float32Array;
|
|
2341
2368
|
chroma: Float32Array;
|
|
@@ -2351,6 +2378,8 @@ export interface WasmFrameBuffer {
|
|
|
2351
2378
|
export interface WasmStreamFramesU8 {
|
|
2352
2379
|
nFrames: number;
|
|
2353
2380
|
nMels: number;
|
|
2381
|
+
nChroma: number;
|
|
2382
|
+
featureFlags: number;
|
|
2354
2383
|
timestamps: Float32Array;
|
|
2355
2384
|
mel: Uint8Array;
|
|
2356
2385
|
chroma: Uint8Array;
|
|
@@ -2363,6 +2392,8 @@ export interface WasmStreamFramesU8 {
|
|
|
2363
2392
|
export interface WasmStreamFramesI16 {
|
|
2364
2393
|
nFrames: number;
|
|
2365
2394
|
nMels: number;
|
|
2395
|
+
nChroma: number;
|
|
2396
|
+
featureFlags: number;
|
|
2366
2397
|
timestamps: Float32Array;
|
|
2367
2398
|
mel: Int16Array;
|
|
2368
2399
|
chroma: Int16Array;
|
package/src/stream_analyzer.ts
CHANGED
|
@@ -39,6 +39,16 @@ export function streamAnalyzerConfigDefaults(): StreamConfigDefaults {
|
|
|
39
39
|
* console.log('Key:', stats.estimate.key);
|
|
40
40
|
* console.log('Chord progression:', stats.estimate.chordProgression);
|
|
41
41
|
* ```
|
|
42
|
+
*
|
|
43
|
+
* The native analyzer supports one serialized producer (`process`,
|
|
44
|
+
* `processWithOffset`, or `finalize`) concurrently with one serialized consumer
|
|
45
|
+
* (`availableFrames`, a `readFrames*` method, `stats`, `frameCount`, or
|
|
46
|
+
* `currentTime`) when a threaded WASM host shares the native instance.
|
|
47
|
+
* Publication is allocation-free release/acquire. `reset`, setters, and
|
|
48
|
+
* deletion require both roles to be stopped. A full pending ring drops the
|
|
49
|
+
* newly produced output frame while analysis totals keep advancing. Ordinary
|
|
50
|
+
* browser builds remain single-threaded unless the host explicitly provisions
|
|
51
|
+
* shared-memory worker support.
|
|
42
52
|
*/
|
|
43
53
|
export class StreamAnalyzer {
|
|
44
54
|
private analyzer: WasmStreamAnalyzer;
|
|
@@ -54,6 +64,15 @@ export class StreamAnalyzer {
|
|
|
54
64
|
'computeMagnitude is not supported because magnitude frames are not exposed by StreamAnalyzer read paths.',
|
|
55
65
|
);
|
|
56
66
|
}
|
|
67
|
+
if (
|
|
68
|
+
config.outputFormat !== undefined &&
|
|
69
|
+
(typeof config.outputFormat !== 'number' ||
|
|
70
|
+
!Number.isFinite(config.outputFormat) ||
|
|
71
|
+
!Number.isInteger(config.outputFormat) ||
|
|
72
|
+
config.outputFormat !== 0)
|
|
73
|
+
) {
|
|
74
|
+
throw new TypeError('outputFormat must be the integer 0 (Float32)');
|
|
75
|
+
}
|
|
57
76
|
const module = getSonareModule();
|
|
58
77
|
const defaults = streamAnalyzerConfigDefaults();
|
|
59
78
|
this.analyzer = new module.StreamAnalyzer(
|
|
@@ -101,7 +120,7 @@ export class StreamAnalyzer {
|
|
|
101
120
|
}
|
|
102
121
|
|
|
103
122
|
/**
|
|
104
|
-
*
|
|
123
|
+
* Drain any high-rate resampler tail, then zero-pad the final partial frame.
|
|
105
124
|
*/
|
|
106
125
|
finalize(): void {
|
|
107
126
|
this.analyzer.finalize();
|
package/src/stream_types.ts
CHANGED
|
@@ -78,6 +78,10 @@ export interface FrameBuffer {
|
|
|
78
78
|
nFrames: number;
|
|
79
79
|
/** Number of mel bands; flat `mel` is `[nFrames * nMels]` row-major. */
|
|
80
80
|
nMels: number;
|
|
81
|
+
/** Chroma stride: 12 when enabled, otherwise 0. */
|
|
82
|
+
nChroma: number;
|
|
83
|
+
/** MEL=1, CHROMA=2, ONSET=4, SPECTRAL=8. */
|
|
84
|
+
featureFlags: number;
|
|
81
85
|
timestamps: Float32Array;
|
|
82
86
|
/**
|
|
83
87
|
* Mel spectrogram in LINEAR power (not dB) — the raw per-frame mel energies.
|
|
@@ -117,6 +121,9 @@ export interface StreamQuantizeConfig {
|
|
|
117
121
|
export interface StreamFramesU8 {
|
|
118
122
|
nFrames: number;
|
|
119
123
|
nMels: number;
|
|
124
|
+
nChroma: number;
|
|
125
|
+
/** MEL=1, CHROMA=2, ONSET=4, SPECTRAL=8. */
|
|
126
|
+
featureFlags: number;
|
|
120
127
|
timestamps: Float32Array;
|
|
121
128
|
/** Row-major `[nFrames * nMels]` mel in dB, quantized over `[melDbMin, melDbMax]`. */
|
|
122
129
|
mel: Uint8Array;
|
|
@@ -130,6 +137,9 @@ export interface StreamFramesU8 {
|
|
|
130
137
|
export interface StreamFramesI16 {
|
|
131
138
|
nFrames: number;
|
|
132
139
|
nMels: number;
|
|
140
|
+
nChroma: number;
|
|
141
|
+
/** MEL=1, CHROMA=2, ONSET=4, SPECTRAL=8. */
|
|
142
|
+
featureFlags: number;
|
|
133
143
|
timestamps: Float32Array;
|
|
134
144
|
/** Row-major `[nFrames * nMels]` mel in dB, quantized over `[melDbMin, melDbMax]`. */
|
|
135
145
|
mel: Int16Array;
|
|
@@ -163,13 +173,14 @@ export interface StreamConfig {
|
|
|
163
173
|
computeSpectral?: boolean;
|
|
164
174
|
emitEveryNFrames?: number;
|
|
165
175
|
magnitudeDownsample?: number;
|
|
166
|
-
/** Maximum unread frames; overflow drops the
|
|
176
|
+
/** Maximum unread frames; overflow drops the newly produced frame. */
|
|
167
177
|
maxPendingFrames?: number;
|
|
168
178
|
/** Maximum retained chord and bar progression entries; overflow drops oldest. */
|
|
169
179
|
maxProgressionEntries?: number;
|
|
170
180
|
keyUpdateIntervalSec?: number;
|
|
171
181
|
bpmUpdateIntervalSec?: number;
|
|
172
182
|
window?: number;
|
|
183
|
+
/** @deprecated Must be 0 (Float32). Use readFramesU8/readFramesI16 explicitly. */
|
|
173
184
|
outputFormat?: number;
|
|
174
185
|
}
|
|
175
186
|
|
package/src/validation.ts
CHANGED
|
@@ -14,6 +14,15 @@ export interface ValidateOptions {
|
|
|
14
14
|
validate?: boolean;
|
|
15
15
|
}
|
|
16
16
|
|
|
17
|
+
/**
|
|
18
|
+
* Offline-analysis sample-rate bounds, mirroring the C++ core limits
|
|
19
|
+
* (`sonare::kMinAudioSampleRate` / `kMaxAudioSampleRate` in `core/audio.h`).
|
|
20
|
+
* Every guarded WASM entry point rejects the same out-of-range rates the C ABI,
|
|
21
|
+
* Node, and Python surfaces do.
|
|
22
|
+
*/
|
|
23
|
+
export const MIN_AUDIO_SAMPLE_RATE = 8000;
|
|
24
|
+
export const MAX_AUDIO_SAMPLE_RATE = 384000;
|
|
25
|
+
|
|
17
26
|
function assertNonEmptySamples(
|
|
18
27
|
fnName: string,
|
|
19
28
|
samples: ArrayLike<number>,
|
|
@@ -58,8 +67,14 @@ export function assertFiniteScalar(fnName: string, value: number, argName: strin
|
|
|
58
67
|
}
|
|
59
68
|
|
|
60
69
|
export function assertSampleRate(fnName: string, sampleRate: number): void {
|
|
61
|
-
if (
|
|
62
|
-
|
|
70
|
+
if (
|
|
71
|
+
!Number.isInteger(sampleRate) ||
|
|
72
|
+
sampleRate < MIN_AUDIO_SAMPLE_RATE ||
|
|
73
|
+
sampleRate > MAX_AUDIO_SAMPLE_RATE
|
|
74
|
+
) {
|
|
75
|
+
throw new RangeError(
|
|
76
|
+
`${fnName}: sampleRate out of supported range [${MIN_AUDIO_SAMPLE_RATE}, ${MAX_AUDIO_SAMPLE_RATE}]`,
|
|
77
|
+
);
|
|
63
78
|
}
|
|
64
79
|
}
|
|
65
80
|
|
|
@@ -11,11 +11,17 @@ export interface EngineClipContext {
|
|
|
11
11
|
readonly clips: Map<number, EngineClip>;
|
|
12
12
|
readonly midiClips: Map<number, EngineMidiClipSchedule>;
|
|
13
13
|
allocateClipId(): number;
|
|
14
|
-
postSync(message: SonareEngineSyncMessage): void;
|
|
14
|
+
postSync(message: SonareEngineSyncMessage, transfer?: Transferable[]): void;
|
|
15
15
|
resolveTargetId(target: string | number): number;
|
|
16
16
|
ensureTrackLane(target: string | number): number;
|
|
17
17
|
}
|
|
18
18
|
|
|
19
|
+
// Keep control-message work bounded even for long tempo-synced clips. The
|
|
20
|
+
// worklet creates a native page provider, receives these transferable PCM
|
|
21
|
+
// chunks, and only schedules the clip after all pages arrive.
|
|
22
|
+
const PREBAKED_CLIP_PAGE_THRESHOLD = 16_384;
|
|
23
|
+
const PREBAKED_CLIP_PAGE_FRAMES = 4_096;
|
|
24
|
+
|
|
19
25
|
export function addClip(
|
|
20
26
|
ctx: EngineClipContext,
|
|
21
27
|
trackId: string | number,
|
|
@@ -57,9 +63,68 @@ export function setMidiClips(
|
|
|
57
63
|
function syncClipsDelta(ctx: EngineClipContext, upserts: EngineClip[], removeIds: number[]): void {
|
|
58
64
|
const clips = Array.from(ctx.clips.values());
|
|
59
65
|
ctx.offlineEngine.setClips(clips);
|
|
66
|
+
const preparedById = new Map<number, EngineClip>();
|
|
67
|
+
for (const clip of clips) {
|
|
68
|
+
if (clip.id === undefined) {
|
|
69
|
+
continue;
|
|
70
|
+
}
|
|
71
|
+
const bakedChannels = ctx.offlineEngine.prebakedClipChannels(clip.id);
|
|
72
|
+
preparedById.set(
|
|
73
|
+
clip.id,
|
|
74
|
+
bakedChannels === null
|
|
75
|
+
? clip
|
|
76
|
+
: {
|
|
77
|
+
...clip,
|
|
78
|
+
channels: bakedChannels,
|
|
79
|
+
clipOffsetSamples: 0,
|
|
80
|
+
lengthSamples: bakedChannels[0]?.length ?? 0,
|
|
81
|
+
loop: false,
|
|
82
|
+
warpMode: 'off',
|
|
83
|
+
warpAnchors: undefined,
|
|
84
|
+
},
|
|
85
|
+
);
|
|
86
|
+
}
|
|
87
|
+
const inlineUpserts: EngineClip[] = [];
|
|
88
|
+
for (const clip of upserts) {
|
|
89
|
+
const prepared = clip.id === undefined ? clip : (preparedById.get(clip.id) ?? clip);
|
|
90
|
+
const channels = prepared.channels;
|
|
91
|
+
if (
|
|
92
|
+
prepared.id === undefined ||
|
|
93
|
+
prepared.warpMode !== 'off' ||
|
|
94
|
+
!channels ||
|
|
95
|
+
channels.length === 0 ||
|
|
96
|
+
channels[0].length <= PREBAKED_CLIP_PAGE_THRESHOLD
|
|
97
|
+
) {
|
|
98
|
+
inlineUpserts.push(prepared);
|
|
99
|
+
continue;
|
|
100
|
+
}
|
|
101
|
+
const numSamples = channels[0].length;
|
|
102
|
+
ctx.postSync({
|
|
103
|
+
type: 'syncClipPageProvider',
|
|
104
|
+
clipId: prepared.id,
|
|
105
|
+
clip: { ...prepared, channels: undefined, pageProvider: undefined },
|
|
106
|
+
numChannels: channels.length,
|
|
107
|
+
numSamples,
|
|
108
|
+
pageFrames: PREBAKED_CLIP_PAGE_FRAMES,
|
|
109
|
+
});
|
|
110
|
+
for (
|
|
111
|
+
let start = 0, pageIndex = 0;
|
|
112
|
+
start < numSamples;
|
|
113
|
+
start += PREBAKED_CLIP_PAGE_FRAMES, pageIndex++
|
|
114
|
+
) {
|
|
115
|
+
const page = channels.map((channel) =>
|
|
116
|
+
channel.slice(start, start + PREBAKED_CLIP_PAGE_FRAMES),
|
|
117
|
+
);
|
|
118
|
+
ctx.postSync(
|
|
119
|
+
{ type: 'syncClipPage', clipId: prepared.id, pageIndex, channels: page },
|
|
120
|
+
page.map((channel) => channel.buffer as Transferable),
|
|
121
|
+
);
|
|
122
|
+
}
|
|
123
|
+
ctx.postSync({ type: 'syncClipPageCommit', clipId: prepared.id });
|
|
124
|
+
}
|
|
60
125
|
ctx.postSync({
|
|
61
126
|
type: 'syncClipsDelta',
|
|
62
|
-
upserts,
|
|
127
|
+
upserts: inlineUpserts,
|
|
63
128
|
removeIds,
|
|
64
129
|
});
|
|
65
130
|
}
|
|
@@ -70,6 +70,8 @@ export class SonareRealtimeEngineWorkletProcessor {
|
|
|
70
70
|
// arrays on both heaps every block, an RT-safety hazard).
|
|
71
71
|
private channelBuffers: Float32Array[];
|
|
72
72
|
private readonly liveClips = new Map<number, EngineClip>();
|
|
73
|
+
private readonly pagedClipProviders = new Map<number, number>();
|
|
74
|
+
private readonly pendingPagedClips = new Map<number, EngineClip>();
|
|
73
75
|
|
|
74
76
|
constructor(
|
|
75
77
|
options: SonareRealtimeEngineWorkletProcessorOptions = {},
|
|
@@ -208,11 +210,10 @@ export class SonareRealtimeEngineWorkletProcessor {
|
|
|
208
210
|
}
|
|
209
211
|
}
|
|
210
212
|
|
|
211
|
-
// Applies an out-of-band control-plane sync message
|
|
212
|
-
//
|
|
213
|
-
//
|
|
214
|
-
//
|
|
215
|
-
// of the engine's control-thread RtPublisher setters.
|
|
213
|
+
// Applies an out-of-band control-plane sync message on the AudioWorklet
|
|
214
|
+
// thread. These handlers must remain bounded: expensive clip transforms are
|
|
215
|
+
// performed on the main-thread mirror, and long pre-baked PCM arrives in
|
|
216
|
+
// small pages before the final lightweight clip schedule is committed.
|
|
216
217
|
receiveSync(message: SonareEngineSyncMessage): void {
|
|
217
218
|
if (this.closed) {
|
|
218
219
|
return;
|
|
@@ -238,6 +239,33 @@ export class SonareRealtimeEngineWorkletProcessor {
|
|
|
238
239
|
}
|
|
239
240
|
this.engine.setClips(Array.from(this.liveClips.values()));
|
|
240
241
|
break;
|
|
242
|
+
case 'syncClipPageProvider': {
|
|
243
|
+
const provider = this.engine.createClipPageProvider(
|
|
244
|
+
message.numChannels,
|
|
245
|
+
message.numSamples,
|
|
246
|
+
message.pageFrames,
|
|
247
|
+
);
|
|
248
|
+
this.pagedClipProviders.set(message.clipId, provider.id);
|
|
249
|
+
this.pendingPagedClips.set(message.clipId, message.clip);
|
|
250
|
+
break;
|
|
251
|
+
}
|
|
252
|
+
case 'syncClipPage': {
|
|
253
|
+
const providerId = this.pagedClipProviders.get(message.clipId);
|
|
254
|
+
if (providerId !== undefined) {
|
|
255
|
+
this.engine.supplyClipPage(providerId, message.pageIndex, message.channels);
|
|
256
|
+
}
|
|
257
|
+
break;
|
|
258
|
+
}
|
|
259
|
+
case 'syncClipPageCommit': {
|
|
260
|
+
const providerId = this.pagedClipProviders.get(message.clipId);
|
|
261
|
+
const clip = this.pendingPagedClips.get(message.clipId);
|
|
262
|
+
if (providerId !== undefined && clip) {
|
|
263
|
+
this.liveClips.set(message.clipId, { ...clip, pageProvider: providerId });
|
|
264
|
+
this.pendingPagedClips.delete(message.clipId);
|
|
265
|
+
this.engine.setClips(Array.from(this.liveClips.values()));
|
|
266
|
+
}
|
|
267
|
+
break;
|
|
268
|
+
}
|
|
241
269
|
case 'syncMidiClips':
|
|
242
270
|
this.engine.setMidiClips(message.clips);
|
|
243
271
|
break;
|
package/src/worklet/engine.ts
CHANGED
|
@@ -787,11 +787,15 @@ export class SonareEngine {
|
|
|
787
787
|
// Posts an out-of-band control-sync message to the worklet engine processor.
|
|
788
788
|
// Sync messages use a string `type` so the worklet's message handler routes
|
|
789
789
|
// them to receiveSync() (numeric `type` is reserved for SonareEngineCommandRecord).
|
|
790
|
-
private postSync(message: SonareEngineSyncMessage): void {
|
|
790
|
+
private postSync(message: SonareEngineSyncMessage, transfer?: Transferable[]): void {
|
|
791
791
|
if (this.destroyed) {
|
|
792
792
|
return;
|
|
793
793
|
}
|
|
794
|
-
|
|
794
|
+
if (transfer && transfer.length > 0) {
|
|
795
|
+
this.realtimeNode.node.port.postMessage(message, transfer);
|
|
796
|
+
} else {
|
|
797
|
+
this.realtimeNode.node.port.postMessage(message);
|
|
798
|
+
}
|
|
795
799
|
}
|
|
796
800
|
|
|
797
801
|
// Collaborator surface handed to the mixer/routing free functions so they can
|
|
@@ -916,7 +920,7 @@ export class SonareEngine {
|
|
|
916
920
|
clips: this.clips,
|
|
917
921
|
midiClips: this.midiClips,
|
|
918
922
|
allocateClipId: () => this.nextClipId++,
|
|
919
|
-
postSync: (message) => this.postSync(message),
|
|
923
|
+
postSync: (message, transfer) => this.postSync(message, transfer),
|
|
920
924
|
ensureTrackLane: (target) => this.ensureTrackLane(target),
|
|
921
925
|
resolveTargetId: (target) => this.resolveTargetId(target),
|
|
922
926
|
};
|
package/src/worklet/guards.ts
CHANGED
|
@@ -37,6 +37,9 @@ export function isEngineSyncMessage(value: unknown): value is SonareEngineSyncMe
|
|
|
37
37
|
return (
|
|
38
38
|
value.type === 'syncClips' ||
|
|
39
39
|
value.type === 'syncClipsDelta' ||
|
|
40
|
+
value.type === 'syncClipPageProvider' ||
|
|
41
|
+
value.type === 'syncClipPage' ||
|
|
42
|
+
value.type === 'syncClipPageCommit' ||
|
|
40
43
|
value.type === 'syncMidiClips' ||
|
|
41
44
|
value.type === 'syncMarkers' ||
|
|
42
45
|
value.type === 'syncMetronome' ||
|
package/src/worklet/messages.ts
CHANGED
|
@@ -219,6 +219,30 @@ export interface SonareEngineSyncClipsDeltaMessage {
|
|
|
219
219
|
removeIds: number[];
|
|
220
220
|
}
|
|
221
221
|
|
|
222
|
+
/** Begins a paged, pre-baked clip transfer. PCM pages follow in FIFO order. */
|
|
223
|
+
export interface SonareEngineSyncClipPageProviderMessage {
|
|
224
|
+
type: 'syncClipPageProvider';
|
|
225
|
+
clipId: number;
|
|
226
|
+
clip: EngineClip;
|
|
227
|
+
numChannels: number;
|
|
228
|
+
numSamples: number;
|
|
229
|
+
pageFrames: number;
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
/** Supplies one bounded PCM page for a pending pre-baked clip transfer. */
|
|
233
|
+
export interface SonareEngineSyncClipPageMessage {
|
|
234
|
+
type: 'syncClipPage';
|
|
235
|
+
clipId: number;
|
|
236
|
+
pageIndex: number;
|
|
237
|
+
channels: Float32Array[];
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
/** Makes a fully supplied paged clip visible to the audio engine. */
|
|
241
|
+
export interface SonareEngineSyncClipPageCommitMessage {
|
|
242
|
+
type: 'syncClipPageCommit';
|
|
243
|
+
clipId: number;
|
|
244
|
+
}
|
|
245
|
+
|
|
222
246
|
export interface SonareEngineSyncMidiClipsMessage {
|
|
223
247
|
type: 'syncMidiClips';
|
|
224
248
|
clips: EngineMidiClipSchedule[];
|
|
@@ -433,6 +457,9 @@ export type SonareEngineInstrumentSyncMessage =
|
|
|
433
457
|
export type SonareEngineSyncMessage =
|
|
434
458
|
| SonareEngineSyncClipsMessage
|
|
435
459
|
| SonareEngineSyncClipsDeltaMessage
|
|
460
|
+
| SonareEngineSyncClipPageProviderMessage
|
|
461
|
+
| SonareEngineSyncClipPageMessage
|
|
462
|
+
| SonareEngineSyncClipPageCommitMessage
|
|
436
463
|
| SonareEngineSyncMidiClipsMessage
|
|
437
464
|
| SonareEngineSyncMarkersMessage
|
|
438
465
|
| SonareEngineSyncMetronomeMessage
|