@libraz/libsonare 1.5.5 → 1.6.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.
Files changed (75) hide show
  1. package/README.md +109 -20
  2. package/dist/analysis.d.ts +5730 -0
  3. package/dist/analysis.js +2464 -0
  4. package/dist/analysis.js.map +1 -0
  5. package/dist/index.d.ts +759 -35
  6. package/dist/index.js +904 -209
  7. package/dist/index.js.map +1 -1
  8. package/dist/schemas/realtime-voice-changer-preset-pack.schema.json +27 -0
  9. package/dist/schemas/realtime-voice-changer-preset.schema.json +140 -0
  10. package/dist/sonare-analysis.js +2 -0
  11. package/dist/sonare-analysis.wasm +0 -0
  12. package/dist/sonare.js +1 -1
  13. package/dist/sonare.wasm +0 -0
  14. package/dist/worker.d.ts +65 -0
  15. package/dist/worker.js +707 -0
  16. package/dist/worker.js.map +1 -0
  17. package/dist/worklet.d.ts +574 -86
  18. package/dist/worklet.js +1634 -235
  19. package/dist/worklet.js.map +1 -1
  20. package/package.json +23 -11
  21. package/src/_chain_config.ts +5 -3
  22. package/src/analysis.ts +137 -0
  23. package/src/analysis_helpers.ts +6 -0
  24. package/src/clip_page_streamer.ts +62 -8
  25. package/src/codes.ts +51 -64
  26. package/src/effects_mastering.ts +1 -0
  27. package/src/errors.ts +1 -0
  28. package/src/feature_core.ts +140 -2
  29. package/src/feature_music.ts +17 -2
  30. package/src/feature_pitch.ts +70 -1
  31. package/src/feature_spectral.ts +231 -5
  32. package/src/feature_spectrogram.ts +159 -0
  33. package/src/features.ts +16 -1
  34. package/src/index.ts +101 -7
  35. package/src/mastering_chain.ts +20 -8
  36. package/src/mastering_core.ts +5 -0
  37. package/src/mastering_dynamics.ts +7 -1
  38. package/src/metering.ts +6 -1
  39. package/src/opfs_clip_pages.ts +26 -1
  40. package/src/project.ts +7 -0
  41. package/src/project_class.ts +62 -8
  42. package/src/project_internal.ts +24 -29
  43. package/src/project_types.ts +69 -10
  44. package/src/public_types.ts +68 -0
  45. package/src/public_types_mastering.ts +22 -0
  46. package/src/public_types_mixing.ts +11 -2
  47. package/src/public_types_music.ts +9 -0
  48. package/src/public_types_spectral.ts +33 -0
  49. package/src/quick_analysis.ts +25 -7
  50. package/src/realtime_engine.ts +153 -3
  51. package/src/realtime_voice_changer.ts +51 -77
  52. package/src/sonare-analysis.js.d.ts +8 -0
  53. package/src/sonare.js.d.ts +276 -6
  54. package/src/streaming_processors.ts +15 -0
  55. package/src/web_midi.ts +36 -4
  56. package/src/worker.ts +196 -0
  57. package/src/worker_client.ts +352 -0
  58. package/src/worker_protocol.ts +59 -0
  59. package/src/worklet/engine-automation.ts +2 -1
  60. package/src/worklet/engine-capture-facade.ts +13 -4
  61. package/src/worklet/engine-clips.ts +12 -3
  62. package/src/worklet/engine-node.ts +212 -4
  63. package/src/worklet/engine-offline.ts +7 -2
  64. package/src/worklet/engine-parameter-facade.ts +26 -5
  65. package/src/worklet/engine-processor.ts +383 -42
  66. package/src/worklet/engine-register.ts +8 -0
  67. package/src/worklet/engine-strips.ts +17 -1
  68. package/src/worklet/engine-sync.ts +2 -6
  69. package/src/worklet/engine-tempo-facade.ts +8 -6
  70. package/src/worklet/engine.ts +380 -13
  71. package/src/worklet/guards.ts +22 -1
  72. package/src/worklet/messages.ts +125 -7
  73. package/src/worklet/protocol.ts +301 -17
  74. package/src/worklet/voice-changer-processor.ts +12 -15
  75. package/src/worklet.ts +20 -1
@@ -22,6 +22,7 @@ export interface MasteringChainRequest {
22
22
  sampleRate?: number;
23
23
  config?: MasteringChainConfig;
24
24
  onProgress?: ProgressCallback;
25
+ cancel?: () => boolean;
25
26
  }
26
27
 
27
28
  export interface MasteringChainStereoRequest {
@@ -30,6 +31,7 @@ export interface MasteringChainStereoRequest {
30
31
  sampleRate?: number;
31
32
  config?: MasteringChainConfig;
32
33
  onProgress?: ProgressCallback;
34
+ cancel?: () => boolean;
33
35
  }
34
36
 
35
37
  /** Canonical request form for one-shot preset mastering. */
@@ -39,6 +41,7 @@ export interface MasterAudioRequest {
39
41
  preset?: MasteringPreset;
40
42
  overrides?: MasteringChainConfig;
41
43
  onProgress?: ProgressCallback;
44
+ cancel?: () => boolean;
42
45
  }
43
46
 
44
47
  /** Canonical request form for one-shot stereo preset mastering. */
@@ -49,6 +52,7 @@ export interface MasterAudioStereoRequest {
49
52
  preset?: MasteringPreset;
50
53
  overrides?: MasteringChainConfig;
51
54
  onProgress?: ProgressCallback;
55
+ cancel?: () => boolean;
52
56
  }
53
57
 
54
58
  function masterAudioRequest(
@@ -114,12 +118,13 @@ export function masteringChain(
114
118
  ): MasteringChainResult {
115
119
  const request =
116
120
  samples instanceof Float32Array ? { samples, sampleRate, config, onProgress } : samples;
117
- if (request.onProgress) {
121
+ if (request.onProgress || request.cancel) {
118
122
  return requireModule().masteringChainWithProgress(
119
123
  request.samples,
120
124
  request.sampleRate ?? 22050,
121
125
  canonicalChainConfig(request.config ?? {}),
122
- request.onProgress,
126
+ request.onProgress ?? (() => {}),
127
+ request.cancel ?? (() => false),
123
128
  );
124
129
  }
125
130
  return requireModule().masteringChain(
@@ -162,13 +167,14 @@ export function masteringChainStereo(
162
167
  if (request.left.length !== request.right.length) {
163
168
  throw new Error('Stereo channel lengths must match.');
164
169
  }
165
- if (request.onProgress) {
170
+ if (request.onProgress || request.cancel) {
166
171
  return requireModule().masteringChainStereoWithProgress(
167
172
  request.left,
168
173
  request.right,
169
174
  request.sampleRate ?? 22050,
170
175
  canonicalChainConfig(request.config ?? {}),
171
- request.onProgress,
176
+ request.onProgress ?? (() => {}),
177
+ request.cancel ?? (() => false),
172
178
  );
173
179
  }
174
180
  return requireModule().masteringChainStereo(
@@ -213,6 +219,7 @@ export function masteringChainWithProgress(
213
219
  request.sampleRate ?? 22050,
214
220
  canonicalChainConfig(request.config ?? {}),
215
221
  request.onProgress,
222
+ request.cancel ?? (() => false),
216
223
  );
217
224
  }
218
225
 
@@ -259,6 +266,7 @@ export function masteringChainStereoWithProgress(
259
266
  request.sampleRate ?? 22050,
260
267
  canonicalChainConfig(request.config ?? {}),
261
268
  request.onProgress,
269
+ request.cancel ?? (() => false),
262
270
  );
263
271
  }
264
272
 
@@ -298,13 +306,14 @@ export function masterAudio(
298
306
  ): MasteringChainResult {
299
307
  const request = masterAudioRequest(samples, sampleRate, presetName, overrides, onProgress);
300
308
  const flat = flattenChainConfig(request.overrides ?? {});
301
- if (request.onProgress) {
309
+ if (request.onProgress || request.cancel) {
302
310
  return requireModule().masterAudioWithProgress(
303
311
  request.preset ?? 'pop',
304
312
  request.samples,
305
313
  request.sampleRate ?? 22050,
306
314
  flat,
307
- request.onProgress,
315
+ request.onProgress ?? (() => {}),
316
+ request.cancel ?? (() => false),
308
317
  );
309
318
  }
310
319
  return requireModule().masterAudio(
@@ -355,14 +364,15 @@ export function masterAudioStereo(
355
364
  if (request.left.length !== request.right.length) {
356
365
  throw new Error('Stereo channel lengths must match.');
357
366
  }
358
- if (request.onProgress) {
367
+ if (request.onProgress || request.cancel) {
359
368
  return requireModule().masterAudioStereoWithProgress(
360
369
  request.preset ?? 'pop',
361
370
  request.left,
362
371
  request.right,
363
372
  request.sampleRate ?? 22050,
364
373
  flat,
365
- request.onProgress,
374
+ request.onProgress ?? (() => {}),
375
+ request.cancel ?? (() => false),
366
376
  );
367
377
  }
368
378
  return requireModule().masterAudioStereo(
@@ -405,6 +415,7 @@ export function masterAudioWithProgress(
405
415
  request.sampleRate ?? 22050,
406
416
  flattenChainConfig(request.overrides ?? {}),
407
417
  request.onProgress,
418
+ request.cancel ?? (() => false),
408
419
  );
409
420
  }
410
421
 
@@ -451,5 +462,6 @@ export function masterAudioStereoWithProgress(
451
462
  request.sampleRate ?? 22050,
452
463
  flattenChainConfig(request.overrides ?? {}),
453
464
  request.onProgress,
465
+ request.cancel ?? (() => false),
454
466
  );
455
467
  }
@@ -186,6 +186,9 @@ export type MasteringChannelPolicy =
186
186
  | 'perChannel'
187
187
  | 'passthrough';
188
188
 
189
+ /** Coarse algorithmic work estimate for a realtime insert; not a benchmark. */
190
+ export type MasteringRealtimeCost = 'low' | 'moderate' | 'high';
191
+
189
192
  /** One processor's realtime/offline/pair classification in the catalog. */
190
193
  export interface MasteringProcessorCatalogEntry {
191
194
  /** Processor id (the name used for scene inserts / named processors). */
@@ -210,6 +213,8 @@ export interface MasteringProcessorCatalogEntry {
210
213
  * offline, dry-only, and no-tail processors.
211
214
  */
212
215
  tailSamples: number;
216
+ /** Coarse realtime work estimate, or null when the processor is not an insert. */
217
+ realtimeCost: MasteringRealtimeCost | null;
213
218
  /**
214
219
  * How the mixer wraps the processor on a >2-channel (surround) bus insert:
215
220
  * "multichannel" (one full-buffer call) or "stereoPairOnly" (front L/R pair,
@@ -107,7 +107,13 @@ export function masteringDynamicsCompressor(
107
107
  typeof request.detector === 'string'
108
108
  ? COMPRESSOR_DETECTOR_MAP[request.detector]
109
109
  : request.detector;
110
- const opts: Record<string, unknown> = { ...request };
110
+ // Preserve inherited option values as well as own properties. The embind
111
+ // boundary's hasProperty() intentionally follows the prototype chain, so a
112
+ // spread copy here would otherwise erase values before C++ can read them.
113
+ const opts: Record<string, unknown> = Object.assign(
114
+ Object.create(Object.getPrototypeOf(request)) as Record<string, unknown>,
115
+ request,
116
+ );
111
117
  if (detector !== undefined) {
112
118
  opts.detector = detector;
113
119
  }
package/src/metering.ts CHANGED
@@ -1,3 +1,4 @@
1
+ import { ErrorCode, SonareError } from './errors';
1
2
  import { getSonareModule } from './module_state';
2
3
  import type { ValidateOptions } from './validation';
3
4
  import { assertSamples } from './validation';
@@ -17,7 +18,11 @@ function assertOversampleFactor(fnName: string, factor: number): void {
17
18
  normalized > 16 ||
18
19
  (normalized & (normalized - 1)) !== 0
19
20
  ) {
20
- throw new RangeError(`${fnName}: oversampleFactor must be 0 or a power of two from 1 to 16`);
21
+ throw new SonareError(
22
+ ErrorCode.InvalidParameter,
23
+ 'InvalidParameter',
24
+ `${fnName}: oversampleFactor must be 0 or a power of two from 1 to 16`,
25
+ );
21
26
  }
22
27
  }
23
28
 
@@ -8,12 +8,20 @@ export interface OpfsClipPageProviderOptions {
8
8
  dataOffsetBytes?: number;
9
9
  worker?: Worker;
10
10
  terminateWorkerOnClose?: boolean;
11
+ /** Internal bridge hook used to mirror a supplied page into an AudioWorklet. */
12
+ onPageSupplied?: (pageIndex: number, channels: Float32Array[]) => void;
13
+ /** Internal bridge hook used to mirror an eviction into an AudioWorklet. */
14
+ onPageCleared?: (pageIndex: number) => void;
15
+ /** Internal bridge hook called after the local provider is destroyed. */
16
+ onClose?: () => void;
11
17
  }
12
18
 
13
19
  export interface OpfsClipPageProviderBinding {
14
20
  provider: ClipPageProvider;
15
21
  supplyPage(pageIndex: number): Promise<boolean>;
16
22
  supplyRequest(request: ClipPageRequest): Promise<boolean>;
23
+ /** Evict one resident page from every configured consumer. */
24
+ clearPage?(pageIndex: number): void;
17
25
  close(): void;
18
26
  }
19
27
 
@@ -116,7 +124,15 @@ self.onmessage = async (event) => {
116
124
 
117
125
  export function createOpfsClipPageWorker(): Worker {
118
126
  const blob = new Blob([opfsClipPageWorkerSource], { type: 'text/javascript' });
119
- return new Worker(URL.createObjectURL(blob));
127
+ const url = URL.createObjectURL(blob);
128
+ try {
129
+ return new Worker(url);
130
+ } finally {
131
+ // Worker construction retains the script URL internally; keeping this
132
+ // object URL alive after construction leaks one browser registration per
133
+ // attach/close cycle.
134
+ URL.revokeObjectURL(url);
135
+ }
120
136
  }
121
137
 
122
138
  export function createOpfsClipPageProvider(
@@ -166,6 +182,7 @@ export function createOpfsClipPageProvider(
166
182
  }
167
183
  try {
168
184
  provider.supply(response.pageIndex, channels);
185
+ options.onPageSupplied?.(response.pageIndex, channels);
169
186
  } catch {
170
187
  entry.resolve(false);
171
188
  return;
@@ -218,6 +235,13 @@ export function createOpfsClipPageProvider(
218
235
  supplyRequest(request: ClipPageRequest) {
219
236
  return supplyPage(Math.floor(request.sample / options.pageFrames));
220
237
  },
238
+ clearPage(pageIndex: number) {
239
+ if (closed) {
240
+ return;
241
+ }
242
+ provider.clear(pageIndex);
243
+ options.onPageCleared?.(pageIndex);
244
+ },
221
245
  close() {
222
246
  if (closed) {
223
247
  return;
@@ -229,6 +253,7 @@ export function createOpfsClipPageProvider(
229
253
  }
230
254
  pending.clear();
231
255
  provider.destroy();
256
+ options.onClose?.();
232
257
  if (ownsWorker) {
233
258
  worker.terminate();
234
259
  }
package/src/project.ts CHANGED
@@ -10,6 +10,9 @@ export type {
10
10
  BuiltinSynthBinding,
11
11
  BuiltinSynthConfig,
12
12
  BuiltinSynthWaveform,
13
+ ExternalSeparatedStem,
14
+ ExternalSeparatedStemImportRequest,
15
+ ExternalSeparatedStemImportResult,
13
16
  MidiCcLearnOptions,
14
17
  ProjectAssistSidecar,
15
18
  ProjectAutomationCurve,
@@ -17,6 +20,7 @@ export type {
17
20
  ProjectAutomationPoint,
18
21
  ProjectBounceOptions,
19
22
  ProjectChordSymbol,
23
+ ProjectClip,
20
24
  ProjectClipCompSegment,
21
25
  ProjectClipDesc,
22
26
  ProjectClipFade,
@@ -37,8 +41,10 @@ export type {
37
41
  ProjectMidiRouteConfig,
38
42
  ProjectMidiRouteResult,
39
43
  ProjectNotePairValidation,
44
+ ProjectSource,
40
45
  ProjectTempoSegment,
41
46
  ProjectTimeSignatureSegment,
47
+ ProjectTrack,
42
48
  ProjectTrackDesc,
43
49
  ProjectTrackKind,
44
50
  ProjectWarpAnchor,
@@ -59,6 +65,7 @@ export type {
59
65
  SynthPatch,
60
66
  } from './project_types';
61
67
  export {
68
+ BUILTIN_SYNTH_WAVEFORMS,
62
69
  EXPECTED_PROJECT_ABI_VERSION,
63
70
  MarkerKind,
64
71
  SYNTH_BODY_TYPES,
@@ -9,12 +9,15 @@ import {
9
9
  } from './project_internal';
10
10
  import type {
11
11
  BuiltinSynthBinding,
12
+ ExternalSeparatedStemImportRequest,
13
+ ExternalSeparatedStemImportResult,
12
14
  MidiCcLearnOptions,
13
15
  ProjectAssistSidecar,
14
16
  ProjectAutomationLaneDesc,
15
17
  ProjectAutomationPoint,
16
18
  ProjectBounceOptions,
17
19
  ProjectChordSymbol,
20
+ ProjectClip,
18
21
  ProjectClipCompSegment,
19
22
  ProjectClipDesc,
20
23
  ProjectClipFade,
@@ -32,9 +35,11 @@ import type {
32
35
  ProjectMidiRouteConfig,
33
36
  ProjectMidiRouteResult,
34
37
  ProjectNotePairValidation,
38
+ ProjectSource,
35
39
  ProjectTempoCandidate,
36
40
  ProjectTempoSegment,
37
41
  ProjectTimeSignatureSegment,
42
+ ProjectTrack,
38
43
  ProjectTrackDesc,
39
44
  ProjectTrackKind,
40
45
  ProjectWarpMapDesc,
@@ -337,6 +342,19 @@ export class Project {
337
342
  return this.native.addClip(desc);
338
343
  }
339
344
 
345
+ /** Import host-separated PCM through the normal audio-track/clip path. */
346
+ importExternalStems(
347
+ request: ExternalSeparatedStemImportRequest,
348
+ ): ExternalSeparatedStemImportResult {
349
+ return this.native.importExternalStems({
350
+ ...request,
351
+ stems: request.stems.map((stem) => ({
352
+ ...stem,
353
+ layout: stem.layout === 'mono' ? 1 : stem.layout === 'stereo' ? 2 : stem.layout,
354
+ })),
355
+ });
356
+ }
357
+
340
358
  /** Split captured loop-recording audio into takes and add one clip. */
341
359
  addLoopRecordingTakes(desc: ProjectLoopRecordingDesc): ProjectLoopRecordingResult {
342
360
  return this.native.addLoopRecordingTakes(desc);
@@ -391,7 +409,11 @@ export class Project {
391
409
  * Route a track's MIDI to host-instrument `destinationId` (0 = default). The
392
410
  * compiler stamps every MIDI clip on the track with this id so the engine
393
411
  * dispatches its events to the instrument registered for that destination.
394
- * Routes through an undoable edit command.
412
+ * Routes through an undoable edit command. Builtin, NativeSynth, and SF2
413
+ * instruments retain source-track provenance inside a shared destination
414
+ * voice pool. With only zero-latency instruments bound, live lanes and
415
+ * channel-strip bounces remain aligned. Configure one live lane per source
416
+ * track that needs strip processing.
395
417
  */
396
418
  setTrackMidiDestination(trackId: number, destinationId: number): void {
397
419
  this.native.setTrackMidiDestination(trackId, destinationId);
@@ -685,6 +707,21 @@ export class Project {
685
707
  this.native.setClipFade(clipId, fadeIn, fadeOut);
686
708
  }
687
709
 
710
+ /** Audio source ids that need decoded PCM after deserialization. */
711
+ unresolvedAudioSourceIds(): number[] {
712
+ return this.native.unresolvedAudioSourceIds();
713
+ }
714
+
715
+ /** Register decoded interleaved PCM for an existing audio source (undoable). */
716
+ setSourceAudio(
717
+ sourceId: number,
718
+ audio: Float32Array,
719
+ channels: number,
720
+ sampleRate: number,
721
+ ): void {
722
+ this.native.setSourceAudio(sourceId, audio, channels, sampleRate);
723
+ }
724
+
688
725
  /** Replace a clip's take list and active take id (undoable). */
689
726
  setClipTakes(clipId: number, takes: ReadonlyArray<ProjectClipTake>, activeTakeId = 0): void {
690
727
  this.native.setClipTakes(clipId, takes, activeTakeId);
@@ -739,7 +776,7 @@ export class Project {
739
776
  this.native.setTrackRoute(trackId, channelStripRef ?? '', outputTarget ?? '');
740
777
  }
741
778
 
742
- /** Append an automation lane to a track; returns the lane index (undoable). */
779
+ /** Append an automation lane; returns its stable target parameter id (undoable). */
743
780
  addAutomationLane(trackId: number, desc: ProjectAutomationLaneDesc): number {
744
781
  return this.native.addAutomationLane(trackId, {
745
782
  targetParamId: desc.targetParamId,
@@ -747,17 +784,21 @@ export class Project {
747
784
  });
748
785
  }
749
786
 
750
- /** Replace an existing automation lane in place (undoable). */
751
- editAutomationLane(trackId: number, laneIndex: number, desc: ProjectAutomationLaneDesc): void {
752
- this.native.editAutomationLane(trackId, laneIndex, {
787
+ /** Replace the lane identified by its stable target parameter id (undoable). */
788
+ editAutomationLane(
789
+ trackId: number,
790
+ targetParamId: number,
791
+ desc: ProjectAutomationLaneDesc,
792
+ ): void {
793
+ this.native.editAutomationLane(trackId, targetParamId, {
753
794
  targetParamId: desc.targetParamId,
754
795
  points: desc.points,
755
796
  });
756
797
  }
757
798
 
758
- /** Remove an automation lane from a track (undoable). */
759
- removeAutomationLane(trackId: number, laneIndex: number): void {
760
- this.native.removeAutomationLane(trackId, laneIndex);
799
+ /** Remove the lane identified by its stable target parameter id (undoable). */
800
+ removeAutomationLane(trackId: number, targetParamId: number): void {
801
+ this.native.removeAutomationLane(trackId, targetParamId);
761
802
  }
762
803
 
763
804
  /** Replace the project's key annotation stream (undoable). */
@@ -841,6 +882,19 @@ export class Project {
841
882
  return this.native.markerByIndex(index);
842
883
  }
843
884
 
885
+ /** Read a stored project track by 0-based index. */
886
+ trackByIndex(index: number): ProjectTrack {
887
+ return this.native.trackByIndex(index);
888
+ }
889
+ /** Read a stored project clip by 0-based index. */
890
+ clipByIndex(index: number): ProjectClip {
891
+ return this.native.clipByIndex(index);
892
+ }
893
+ /** Read a stored project source by 0-based index. */
894
+ sourceByIndex(index: number): ProjectSource {
895
+ return this.native.sourceByIndex(index);
896
+ }
897
+
844
898
  /** Number of markers in the project. */
845
899
  markerCount(): number {
846
900
  return this.native.markerCount();
@@ -1,3 +1,4 @@
1
+ import { resolveEnumOrdinal } from './codes';
1
2
  import { getSonareModule } from './module_state';
2
3
  import type {
3
4
  BuiltinSynthBinding,
@@ -5,6 +6,7 @@ import type {
5
6
  ProjectAutomationPoint,
6
7
  ProjectBounceOptions,
7
8
  ProjectChordSymbol,
9
+ ProjectClip,
8
10
  ProjectClipCompSegment,
9
11
  ProjectClipDesc,
10
12
  ProjectClipFade,
@@ -21,9 +23,11 @@ import type {
21
23
  ProjectMidiRouteConfig,
22
24
  ProjectMidiRouteResult,
23
25
  ProjectNotePairValidation,
26
+ ProjectSource,
24
27
  ProjectTempoCandidate,
25
28
  ProjectTempoSegment,
26
29
  ProjectTimeSignatureSegment,
30
+ ProjectTrack,
27
31
  ProjectTrackKind,
28
32
  ProjectWarpMapDesc,
29
33
  ProjectWarpMode,
@@ -41,6 +45,7 @@ export interface WasmProject {
41
45
  setSampleRate: (sampleRate: number) => void;
42
46
  addTrack: (desc: { kind?: number | string; name?: string }) => number;
43
47
  addClip: (desc: ProjectClipDesc) => number;
48
+ importExternalStems: (request: unknown) => { trackIds: number[]; clipIds: number[] };
44
49
  addLoopRecordingTakes: (desc: ProjectLoopRecordingDesc) => ProjectLoopRecordingResult;
45
50
  addMidiClip: (startPpq: number, lengthPpq: number) => ProjectMidiClipResult;
46
51
  splitClip: (clipId: number, splitPpq: number) => number;
@@ -108,6 +113,13 @@ export interface WasmProject {
108
113
  removeClip: (clipId: number) => void;
109
114
  setClipGain: (clipId: number, gain: number) => void;
110
115
  setClipFade: (clipId: number, fadeIn: ProjectClipFade, fadeOut: ProjectClipFade) => void;
116
+ unresolvedAudioSourceIds: () => number[];
117
+ setSourceAudio: (
118
+ sourceId: number,
119
+ audio: Float32Array,
120
+ channels: number,
121
+ sampleRate: number,
122
+ ) => void;
111
123
  setClipTakes: (
112
124
  clipId: number,
113
125
  takes: ReadonlyArray<ProjectClipTake>,
@@ -131,10 +143,10 @@ export interface WasmProject {
131
143
  ) => number;
132
144
  editAutomationLane: (
133
145
  trackId: number,
134
- laneIndex: number,
146
+ targetParamId: number,
135
147
  desc: { targetParamId: number; points: ReadonlyArray<ProjectAutomationPoint> },
136
148
  ) => void;
137
- removeAutomationLane: (trackId: number, laneIndex: number) => void;
149
+ removeAutomationLane: (trackId: number, targetParamId: number) => void;
138
150
  annotateKeys: (keys: ReadonlyArray<ProjectKeySegment>) => void;
139
151
  annotateChords: (chords: ReadonlyArray<ProjectChordSymbol>) => void;
140
152
  setAssistSidecar: (
@@ -154,6 +166,9 @@ export interface WasmProject {
154
166
  setMarker: (markerId: number, ppq: number, name: string) => number;
155
167
  setMarkerEx: (marker: ProjectMarker) => number;
156
168
  markerByIndex: (index: number) => ProjectMarker;
169
+ trackByIndex: (index: number) => ProjectTrack;
170
+ clipByIndex: (index: number) => ProjectClip;
171
+ sourceByIndex: (index: number) => ProjectSource;
157
172
  markerCount: () => number;
158
173
  trackCount: () => number;
159
174
  clipCount: () => number;
@@ -307,37 +322,17 @@ export function assertProjectMidiEvents(
307
322
  }
308
323
 
309
324
  export function projectTrackKindValue(kind: ProjectTrackKind | undefined): number {
310
- if (kind === undefined || kind === 'audio') {
311
- return 0;
312
- }
313
- if (kind === 'midi') {
314
- return 1;
315
- }
316
- if (kind === 'aux') {
317
- return 2;
318
- }
319
- return kind;
325
+ return resolveEnumOrdinal(kind ?? 'audio', { audio: 0, midi: 1, aux: 2 }, 'project track kind');
320
326
  }
321
327
 
322
328
  export function projectWarpModeValue(mode: ProjectWarpMode | undefined): number {
323
- if (mode === undefined || mode === 'off') {
324
- return 0;
325
- }
326
- if (mode === 'repitch') {
327
- return 1;
328
- }
329
- if (mode === 'tempo-sync') {
330
- return 2;
331
- }
332
- return mode;
329
+ return resolveEnumOrdinal(
330
+ mode ?? 'off',
331
+ { off: 0, repitch: 1, 'tempo-sync': 2 },
332
+ 'project warp mode',
333
+ );
333
334
  }
334
335
 
335
336
  export function projectLoopModeValue(mode: ProjectLoopMode | undefined): number {
336
- if (mode === undefined || mode === 'off') {
337
- return 0;
338
- }
339
- if (mode === 'loop') {
340
- return 1;
341
- }
342
- return mode;
337
+ return resolveEnumOrdinal(mode ?? 'off', { off: 0, loop: 1 }, 'project loop mode');
343
338
  }
@@ -25,6 +25,29 @@ export interface ProjectBounceOptions {
25
25
  instrumentLatencySamples?: number;
26
26
  }
27
27
 
28
+ /** One decoded output from a host-owned source-separation model. */
29
+ export interface ExternalSeparatedStem {
30
+ name: string;
31
+ /** Optional host metadata; it does not change DSP. */
32
+ role?: string;
33
+ layout: 'mono' | 'stereo' | 1 | 2;
34
+ planarSamples: Float32Array[];
35
+ /** Absolute project-rate frame at which this stem begins. */
36
+ startFrame?: number;
37
+ }
38
+
39
+ /** Request for {@link Project.importExternalStems}. */
40
+ export interface ExternalSeparatedStemImportRequest {
41
+ sampleRate: number;
42
+ stems: ExternalSeparatedStem[];
43
+ }
44
+
45
+ /** Ids of the normal tracks and clips created by the import. */
46
+ export interface ExternalSeparatedStemImportResult {
47
+ trackIds: number[];
48
+ clipIds: number[];
49
+ }
50
+
28
51
  /**
29
52
  * Marker kind ordinals. Mirrors `SonareMarkerKind` in `src/sonare_c_types.h`;
30
53
  * the values are part of the ABI and must not be renumbered.
@@ -53,17 +76,47 @@ export interface ProjectMarker {
53
76
  keyMinor?: boolean;
54
77
  }
55
78
 
79
+ /** Read-only stored project track returned by {@link Project.trackByIndex}. */
80
+ export interface ProjectTrack {
81
+ id: number;
82
+ kind: number;
83
+ midiDestinationId: number;
84
+ gain: number;
85
+ pan: number;
86
+ mute: boolean;
87
+ solo: boolean;
88
+ name: string;
89
+ }
90
+
91
+ /** Read-only stored project clip returned by {@link Project.clipByIndex}. */
92
+ export interface ProjectClip {
93
+ id: number;
94
+ trackId: number;
95
+ sourceId: number;
96
+ sourceKind: number;
97
+ startPpq: number;
98
+ lengthPpq: number;
99
+ sourceOffsetPpq: number;
100
+ gain: number;
101
+ loopMode: number;
102
+ loopLengthPpq: number;
103
+ }
104
+
105
+ /** Read-only stored project source returned by {@link Project.sourceByIndex}. */
106
+ export interface ProjectSource {
107
+ id: number;
108
+ kind: number;
109
+ channelCount: number;
110
+ storageHandleId: number;
111
+ sampleRateHint: number;
112
+ nameOrUri: string;
113
+ }
114
+
115
+ /** Names accepted by the minimal built-in oscillator synth. */
116
+ export const BUILTIN_SYNTH_WAVEFORMS = ['sine', 'saw', 'sawtooth', 'square', 'triangle'] as const;
117
+
56
118
  /** Oscillator waveform for the built-in synth. */
57
- export type BuiltinSynthWaveform =
58
- | 'sine'
59
- | 'saw'
60
- | 'sawtooth'
61
- | 'square'
62
- | 'triangle'
63
- | 0
64
- | 1
65
- | 2
66
- | 3;
119
+ export type BuiltinSynthWaveform = (typeof BUILTIN_SYNTH_WAVEFORMS)[number] | 0 | 1 | 2 | 3;
67
120
 
68
121
  /**
69
122
  * Built-in synth patch + MIDI routing for
@@ -111,6 +164,8 @@ export interface Sf2InstrumentConfig {
111
164
  gain?: number;
112
165
  /** Max simultaneous voices (0 => 48, clamped to [1, 64]). */
113
166
  polyphony?: number;
167
+ /** Prefer dedicated physical models for covered melodic GM programs. Defaults to false; drums stay SF2-first. */
168
+ preferModelForModeledFamilies?: boolean;
114
169
  }
115
170
 
116
171
  /** Source backend a resolved MIDI program renders through. */
@@ -198,6 +253,7 @@ export const SYNTH_MOD_DESTINATIONS = [
198
253
  export interface SynthEnumTables {
199
254
  engineModes: string[];
200
255
  waveforms: string[];
256
+ builtinWaveforms: string[];
201
257
  filterModels: string[];
202
258
  filterOutputs: string[];
203
259
  bodyTypes: string[];
@@ -312,8 +368,11 @@ export type ProjectFadeCurve =
312
368
  | 'equal-power'
313
369
  | 'equal_power'
314
370
  | 'equalPower'
371
+ | 'equalpower'
315
372
  | 'exponential'
373
+ | 'exp'
316
374
  | 'logarithmic'
375
+ | 'log'
317
376
  | 0
318
377
  | 1
319
378
  | 2