@libraz/libsonare 1.8.1 → 1.8.2

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 (107) hide show
  1. package/README.md +5 -5
  2. package/dist/analysis.js +2 -0
  3. package/dist/analysis.js.map +1 -1
  4. package/dist/index.d.ts +8 -2
  5. package/dist/index.d.ts.map +1 -1
  6. package/dist/index.js +1927 -140
  7. package/dist/index.js.map +1 -1
  8. package/dist/instrument_types.d.ts +27 -3
  9. package/dist/instrument_types.d.ts.map +1 -1
  10. package/dist/mastering_core.d.ts +3 -2
  11. package/dist/mastering_core.d.ts.map +1 -1
  12. package/dist/mixer.d.ts +2 -2
  13. package/dist/project.d.ts +5 -4
  14. package/dist/project.d.ts.map +1 -1
  15. package/dist/project_class.d.ts +57 -3
  16. package/dist/project_class.d.ts.map +1 -1
  17. package/dist/project_internal.d.ts +35 -1
  18. package/dist/project_internal.d.ts.map +1 -1
  19. package/dist/project_types.d.ts +10 -1
  20. package/dist/project_types.d.ts.map +1 -1
  21. package/dist/public_types.d.ts +1 -0
  22. package/dist/public_types.d.ts.map +1 -1
  23. package/dist/public_types_mastering.d.ts +1 -1
  24. package/dist/public_types_mastering.d.ts.map +1 -1
  25. package/dist/public_types_vocal_edit.d.ts +250 -0
  26. package/dist/public_types_vocal_edit.d.ts.map +1 -0
  27. package/dist/public_types_vocal_project.d.ts +63 -0
  28. package/dist/public_types_vocal_project.d.ts.map +1 -0
  29. package/dist/realtime_engine.d.ts +57 -10
  30. package/dist/realtime_engine.d.ts.map +1 -1
  31. package/dist/repair_dereverb.d.ts +1 -0
  32. package/dist/repair_dereverb.d.ts.map +1 -1
  33. package/dist/repair_noise.d.ts +1 -0
  34. package/dist/repair_noise.d.ts.map +1 -1
  35. package/dist/sonare-analysis.wasm +0 -0
  36. package/dist/sonare.d.ts +53 -0
  37. package/dist/sonare.js +1 -1
  38. package/dist/sonare.wasm +0 -0
  39. package/dist/vocal_edit.d.ts +131 -0
  40. package/dist/vocal_edit.d.ts.map +1 -0
  41. package/dist/vocal_edit_worker.d.ts +51 -0
  42. package/dist/vocal_edit_worker.d.ts.map +1 -0
  43. package/dist/vocal_edit_worker.js +1462 -0
  44. package/dist/vocal_edit_worker.js.map +1 -0
  45. package/dist/vocal_edit_worker_client.d.ts +118 -0
  46. package/dist/vocal_edit_worker_client.d.ts.map +1 -0
  47. package/dist/vocal_edit_worker_protocol.d.ts +139 -0
  48. package/dist/vocal_edit_worker_protocol.d.ts.map +1 -0
  49. package/dist/vocal_project.d.ts +17 -0
  50. package/dist/vocal_project.d.ts.map +1 -0
  51. package/dist/web_midi.d.ts +4 -0
  52. package/dist/web_midi.d.ts.map +1 -1
  53. package/dist/worker.js.map +1 -1
  54. package/dist/worklet/engine-mixer-facade.d.ts +2 -1
  55. package/dist/worklet/engine-mixer-facade.d.ts.map +1 -1
  56. package/dist/worklet/engine-node.d.ts +8 -0
  57. package/dist/worklet/engine-node.d.ts.map +1 -1
  58. package/dist/worklet/engine-processor.d.ts +2 -1
  59. package/dist/worklet/engine-processor.d.ts.map +1 -1
  60. package/dist/worklet/engine-register.d.ts.map +1 -1
  61. package/dist/worklet/engine-strips.d.ts +4 -2
  62. package/dist/worklet/engine-strips.d.ts.map +1 -1
  63. package/dist/worklet/engine.d.ts +15 -3
  64. package/dist/worklet/engine.d.ts.map +1 -1
  65. package/dist/worklet/guards.d.ts +3 -1
  66. package/dist/worklet/guards.d.ts.map +1 -1
  67. package/dist/worklet/messages.d.ts +50 -5
  68. package/dist/worklet/messages.d.ts.map +1 -1
  69. package/dist/worklet/protocol.d.ts +10 -0
  70. package/dist/worklet/protocol.d.ts.map +1 -1
  71. package/dist/worklet.d.ts +1 -1
  72. package/dist/worklet.d.ts.map +1 -1
  73. package/dist/worklet.js +223 -16
  74. package/dist/worklet.js.map +1 -1
  75. package/package.json +6 -1
  76. package/src/index.ts +33 -0
  77. package/src/instrument_types.ts +29 -3
  78. package/src/mastering_core.ts +3 -2
  79. package/src/mixer.ts +2 -2
  80. package/src/project.ts +8 -1
  81. package/src/project_class.ts +136 -1
  82. package/src/project_internal.ts +90 -0
  83. package/src/project_types.ts +11 -1
  84. package/src/public_types.ts +1 -0
  85. package/src/public_types_mastering.ts +2 -0
  86. package/src/public_types_vocal_edit.ts +289 -0
  87. package/src/public_types_vocal_project.ts +69 -0
  88. package/src/realtime_engine.ts +86 -9
  89. package/src/repair_dereverb.ts +1 -0
  90. package/src/repair_noise.ts +1 -0
  91. package/src/sonare.js.d.ts +53 -0
  92. package/src/vocal_edit.ts +885 -0
  93. package/src/vocal_edit_worker.ts +832 -0
  94. package/src/vocal_edit_worker_client.ts +812 -0
  95. package/src/vocal_edit_worker_protocol.ts +206 -0
  96. package/src/vocal_project.ts +327 -0
  97. package/src/web_midi.ts +100 -5
  98. package/src/worklet/engine-mixer-facade.ts +2 -1
  99. package/src/worklet/engine-node.ts +47 -0
  100. package/src/worklet/engine-processor.ts +46 -1
  101. package/src/worklet/engine-register.ts +3 -0
  102. package/src/worklet/engine-strips.ts +31 -4
  103. package/src/worklet/engine.ts +27 -3
  104. package/src/worklet/guards.ts +26 -0
  105. package/src/worklet/messages.ts +59 -2
  106. package/src/worklet/protocol.ts +10 -0
  107. package/src/worklet.ts +2 -0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@libraz/libsonare",
3
- "version": "1.8.1",
3
+ "version": "1.8.2",
4
4
  "type": "module",
5
5
  "packageManager": "yarn@4.18.0",
6
6
  "description": "Audio analysis, mastering, mixing, and MIDI synthesis in WebAssembly",
@@ -23,6 +23,10 @@
23
23
  "types": "./dist/worker.d.ts",
24
24
  "import": "./dist/worker.js"
25
25
  },
26
+ "./vocal-edit-worker": {
27
+ "types": "./dist/vocal_edit_worker.d.ts",
28
+ "import": "./dist/vocal_edit_worker.js"
29
+ },
26
30
  "./wasm": "./dist/sonare.wasm",
27
31
  "./schemas/realtime-voice-changer-preset.schema.json": "./dist/schemas/realtime-voice-changer-preset.schema.json",
28
32
  "./schemas/realtime-voice-changer-preset-pack.schema.json": "./dist/schemas/realtime-voice-changer-preset-pack.schema.json",
@@ -60,6 +64,7 @@
60
64
  "test:browser": "node scripts/audio-worklet-smoke.mjs",
61
65
  "test:browser:worker": "node scripts/offline-worker-smoke.mjs",
62
66
  "test:browser:boundary": "node scripts/boundary-smoke.mjs",
67
+ "test:browser:vocal-worker": "node scripts/vocal-edit-worker-smoke.mjs",
63
68
  "test:watch": "vitest",
64
69
  "prepublishOnly": "yarn clean && cp ../../LICENSE LICENSE && cp ../../NOTICE NOTICE && yarn build && yarn lint && yarn test && yarn test:types"
65
70
  },
package/src/index.ts CHANGED
@@ -546,6 +546,9 @@ export type {
546
546
  MidiCcLearnOptions,
547
547
  MpeDimension,
548
548
  NoteTracking,
549
+ PartRig,
550
+ PartRigInsert,
551
+ PartRigMode,
549
552
  ProjectAssistSidecar,
550
553
  ProjectAssistSidecarInput,
551
554
  ProjectAutomationCurve,
@@ -560,6 +563,7 @@ export type {
560
563
  ProjectClipFade,
561
564
  ProjectClipTake,
562
565
  ProjectCompileResult,
566
+ ProjectCompileTimelineResult,
563
567
  ProjectFadeCurve,
564
568
  ProjectKeySegment,
565
569
  ProjectLoopMode,
@@ -617,10 +621,13 @@ export {
617
621
  MarkerKind,
618
622
  MPE_DIMENSIONS,
619
623
  NOTE_TRACKINGS,
624
+ PART_RIG_ALL_PARTS,
625
+ PART_RIG_MODES,
620
626
  PROJECT_AUTOMATION_TARGET_OPAQUE,
621
627
  PROJECT_AUTOMATION_TARGET_TRACK_FADER_DB,
622
628
  PROJECT_AUTOMATION_TARGET_TRACK_PAN,
623
629
  Project,
630
+ ProjectTimeline,
624
631
  projectAbiVersion,
625
632
  SAMPLE_KEY_TRACKS,
626
633
  SAMPLE_LOOP_MODES,
@@ -835,6 +842,7 @@ export {
835
842
  PitchClass,
836
843
  SectionType,
837
844
  } from './public_types';
845
+ export type * from './public_types_vocal_edit';
838
846
  export type {
839
847
  AnalyzeBpmRequest,
840
848
  AnalyzeDynamicsRequest,
@@ -959,6 +967,29 @@ export {
959
967
  export type { TranscribeRequest } from './transcribe';
960
968
  export { transcribe } from './transcribe';
961
969
  export type { ValidateOptions } from './validation';
970
+ export {
971
+ createVocalEditSession,
972
+ restoreVocalEditSession,
973
+ VocalEditDraft,
974
+ VocalEditSession,
975
+ VocalRenderJob,
976
+ VocalRenderSnapshot,
977
+ vocalEditApiVersion,
978
+ vocalEditAvailable,
979
+ } from './vocal_edit';
980
+ export type {
981
+ VocalEditWorker,
982
+ VocalEditWorkerCallOptions,
983
+ VocalEditWorkerClientOptions,
984
+ VocalEditWorkerTransferOptions,
985
+ } from './vocal_edit_worker_client';
986
+ export {
987
+ VocalEditWorkerClient,
988
+ VocalEditWorkerSession,
989
+ VocalEditWorkerStaleResultError,
990
+ VocalEditWorkerTask,
991
+ } from './vocal_edit_worker_client';
992
+ export type * from './vocal_edit_worker_protocol';
962
993
  export type {
963
994
  BindWebMidiOptions,
964
995
  WebMidiBinding,
@@ -1154,3 +1185,5 @@ export function realtimeVoiceChangerPresetConfig(
1154
1185
  // ============================================================================
1155
1186
 
1156
1187
  export { PitchClass as Pitch } from './public_types';
1188
+
1189
+ export type * from './public_types_vocal_project';
@@ -228,6 +228,16 @@ export const CONTROLLER_AXES = [
228
228
  */
229
229
  export const ARTICULATIONS = ['poly', 'mono-retrigger', 'mono-legato'] as const;
230
230
 
231
+ /**
232
+ * How a part's signal path is built. `'bank'` keeps whatever the instrument's
233
+ * own bank/program selection resolves to, `'none'` bypasses the rig stage, and
234
+ * `'chain'` replaces it with an explicit insert chain.
235
+ */
236
+ export const PART_RIG_MODES = ['bank', 'none', 'chain'] as const;
237
+
238
+ /** The `part` value that addresses a destination's default instead of one part (0-15). */
239
+ export const PART_RIG_ALL_PARTS = 0xff;
240
+
231
241
  /** The three dimensions MPE carries per note ({@link RealtimeEngine.setControllerNoteTracking}). */
232
242
  export const MPE_DIMENSIONS = ['bend', 'pressure', 'timbre'] as const;
233
243
 
@@ -380,6 +390,23 @@ export type ControllerAxis = (typeof CONTROLLER_AXES)[number];
380
390
  /** Per-channel note-overlap rule ({@link RealtimeEngine.setArticulation}). */
381
391
  export type Articulation = (typeof ARTICULATIONS)[number];
382
392
 
393
+ /** How a part's rig is built ({@link PART_RIG_MODES}). */
394
+ export type PartRigMode = (typeof PART_RIG_MODES)[number];
395
+
396
+ /** One stage of a `'chain'` part rig: a processor name and its parameters. */
397
+ export interface PartRigInsert {
398
+ processor: string;
399
+ /** Processor parameters, as an object or an already-serialized JSON object string. */
400
+ params?: Record<string, unknown> | string;
401
+ }
402
+
403
+ /** A part rig as stored on a project ({@link Project.getPartRig}). */
404
+ export interface PartRig {
405
+ mode: PartRigMode;
406
+ /** Present for `'chain'` only; `params` is the parsed parameter object. */
407
+ inserts?: { processor: string; params: Record<string, unknown> }[];
408
+ }
409
+
383
410
  /** One per-note MPE dimension ({@link MPE_DIMENSIONS}). */
384
411
  export type MpeDimension = (typeof MPE_DIMENSIONS)[number];
385
412
 
@@ -582,9 +609,8 @@ export interface SynthPatch {
582
609
  * whose per-note kit patch replaces this whole section and never reads it.
583
610
  *
584
611
  * Also automatable under this same name through
585
- * {@link RealtimeEngine.resolveInstrumentAutomationId}; it is one of the names
586
- * that reaches a voice that is already sounding, rather than waiting for the
587
- * next note.
612
+ * {@link RealtimeEngine.resolveInstrumentAutomationId}; it reaches
613
+ * already-sounding voices from the next processed block.
588
614
  */
589
615
  pitchOffsetCents?: number;
590
616
  ampAttackMs?: number;
@@ -924,11 +924,12 @@ export function masteringAssistantSuggestChainStereo(
924
924
  /**
925
925
  * Mastering assistant profile of a stereo pair, as shared JSON.
926
926
  *
927
- * Only the `loudness` block is measured from the two channels: integrated LUFS
927
+ * The `loudness` block is measured from the two channels: integrated LUFS
928
928
  * and LRA come from the channel-summed program and the true peak is the larger
929
929
  * of the two. The spectral, dynamics and tempo fields describe shape and timing
930
930
  * rather than absolute level and are measured on the downmix, which keeps them
931
- * comparable with {@link masteringAudioProfile}.
931
+ * comparable with {@link masteringAudioProfile}. Defect detectors run on each
932
+ * channel and their results are aggregated.
932
933
  */
933
934
  export function masteringAudioProfileStereo(request: MasteringStereoParamsRequest): string {
934
935
  return requireModule().masteringAudioProfileStereo(
package/src/mixer.ts CHANGED
@@ -504,8 +504,8 @@ export class Mixer {
504
504
  }
505
505
 
506
506
  /**
507
- * Set a per-strip channel delay in samples. This changes the strip's reported
508
- * latency; recompile to re-run latency compensation.
507
+ * Set a per-strip channel delay in samples, moving the strip later relative to
508
+ * the others. It is not latency: latency compensation does not undo it.
509
509
  */
510
510
  setChannelDelaySamples(stripIndex: number, delaySamples: number): void {
511
511
  this.mixer.setChannelDelaySamples(stripIndex, delaySamples);
package/src/project.ts CHANGED
@@ -8,6 +8,9 @@ export type {
8
8
  ControllerInput,
9
9
  MpeDimension,
10
10
  NoteTracking,
11
+ PartRig,
12
+ PartRigInsert,
13
+ PartRigMode,
11
14
  SampleDesc,
12
15
  SampleDescLoopMode,
13
16
  SampleKeyTrack,
@@ -35,6 +38,8 @@ export {
35
38
  CONTROLLER_INPUTS,
36
39
  MPE_DIMENSIONS,
37
40
  NOTE_TRACKINGS,
41
+ PART_RIG_ALL_PARTS,
42
+ PART_RIG_MODES,
38
43
  SAMPLE_KEY_TRACKS,
39
44
  SAMPLE_LOOP_MODES,
40
45
  SYNTH_BODY_TYPES,
@@ -46,7 +51,7 @@ export {
46
51
  SYNTH_OSC_WAVEFORMS,
47
52
  SYNTH_RETRIGGERS,
48
53
  } from './instrument_types';
49
- export { Project } from './project_class';
54
+ export { Project, ProjectTimeline } from './project_class';
50
55
  export {
51
56
  controllerProfileNames,
52
57
  projectAbiVersion,
@@ -79,6 +84,7 @@ export type {
79
84
  ProjectClipFade,
80
85
  ProjectClipTake,
81
86
  ProjectCompileResult,
87
+ ProjectCompileTimelineResult,
82
88
  ProjectDeserializeResult,
83
89
  ProjectDiagnostic,
84
90
  ProjectFadeCurve,
@@ -121,4 +127,5 @@ export {
121
127
  PROJECT_AUTOMATION_TARGET_TRACK_FADER_DB,
122
128
  PROJECT_AUTOMATION_TARGET_TRACK_PAN,
123
129
  } from './project_types';
130
+ export type * from './public_types_vocal_project';
124
131
  export { SampleBank } from './sample_bank';
@@ -1,5 +1,8 @@
1
1
  import type {
2
2
  BuiltinSynthBinding,
3
+ PartRig,
4
+ PartRigInsert,
5
+ PartRigMode,
3
6
  Sf2InstrumentConfig,
4
7
  Sf2ProgramStatus,
5
8
  SynthPatch,
@@ -7,6 +10,8 @@ import type {
7
10
  import {
8
11
  assertProjectMidiEvents,
9
12
  normalizeSynthInstrument,
13
+ partRigFromNative,
14
+ partRigInsertsJson,
10
15
  projectAutomationPointValue,
11
16
  projectAutomationTargetKindValue,
12
17
  projectLoopModeValue,
@@ -14,7 +19,10 @@ import {
14
19
  projectModule,
15
20
  projectTrackKindValue,
16
21
  projectWarpModeValue,
22
+ registerProjectTimeline,
23
+ unregisterProjectTimeline,
17
24
  type WasmProject,
25
+ type WasmProjectTimeline,
18
26
  } from './project_internal';
19
27
  import type {
20
28
  ExternalSeparatedStemImportRequest,
@@ -33,6 +41,7 @@ import type {
33
41
  ProjectClipFade,
34
42
  ProjectClipTake,
35
43
  ProjectCompileResult,
44
+ ProjectCompileTimelineResult,
36
45
  ProjectDeserializeResult,
37
46
  ProjectKeySegment,
38
47
  ProjectLoopMode,
@@ -60,6 +69,13 @@ import type {
60
69
  ProjectWarpMapDesc,
61
70
  ProjectWarpMode,
62
71
  } from './project_types';
72
+ import type {
73
+ ProjectVocalEditApplyRequest,
74
+ ProjectVocalEditApplyResult,
75
+ ProjectVocalEditDependency,
76
+ ProjectVocalOriginalSource,
77
+ ProjectVocalRehydrateItem,
78
+ } from './public_types_vocal_project';
63
79
  import {
64
80
  assertBoundedInteger,
65
81
  assertNibble,
@@ -67,6 +83,11 @@ import {
67
83
  assertSamples,
68
84
  assertU7,
69
85
  } from './validation';
86
+ import {
87
+ projectApplyVocalEdit,
88
+ projectGetVocalEditDependencies,
89
+ projectRehydrateVocalEdits,
90
+ } from './vocal_project';
70
91
 
71
92
  /**
72
93
  * Folds the positional and request call forms of `bakeMidiFx` into one shape,
@@ -171,6 +192,47 @@ function midi2Byte(fnName: string, value: number, argName: string): number {
171
192
  return value;
172
193
  }
173
194
 
195
+ /**
196
+ * An immutable compiled snapshot of a {@link Project}, produced by
197
+ * {@link Project.compileTimeline} and installed into a stopped realtime engine
198
+ * with `RealtimeEngine.applyProjectTimeline`.
199
+ *
200
+ * An engine keeps its own reference after an apply, so {@link dispose} may be
201
+ * called right afterwards. The embind handle is not garbage-collected; release
202
+ * it when no further apply needs it.
203
+ */
204
+ export class ProjectTimeline {
205
+ private native: WasmProjectTimeline | null;
206
+
207
+ /** @internal */
208
+ constructor(native: WasmProjectTimeline) {
209
+ this.native = native;
210
+ registerProjectTimeline(this, native);
211
+ }
212
+
213
+ /**
214
+ * Identity an engine names this timeline by, so no raw pointer crosses into JS.
215
+ *
216
+ * @internal
217
+ */
218
+ get nativeId(): number {
219
+ if (this.native === null) {
220
+ throw new TypeError('ProjectTimeline is disposed');
221
+ }
222
+ return this.native.id;
223
+ }
224
+
225
+ /** Release the underlying WASM object. Idempotent. */
226
+ dispose(): void {
227
+ if (this.native === null) {
228
+ return;
229
+ }
230
+ unregisterProjectTimeline(this);
231
+ this.native.delete();
232
+ this.native = null;
233
+ }
234
+ }
235
+
174
236
  /**
175
237
  * Headless DAW project (control-thread-only arrangement model).
176
238
  *
@@ -199,7 +261,33 @@ function midi2Byte(fnName: string, value: number, argName: string): number {
199
261
  */
200
262
  export class Project {
201
263
  private native: WasmProject;
264
+
265
+ applyVocalEdit(request: ProjectVocalEditApplyRequest): ProjectVocalEditApplyResult {
266
+ return projectApplyVocalEdit(this.native, request);
267
+ }
268
+
269
+ getVocalEditDependencies(): ProjectVocalEditDependency[] {
270
+ return projectGetVocalEditDependencies(this.native);
271
+ }
272
+
273
+ rehydrateVocalEdits(
274
+ originals: readonly ProjectVocalOriginalSource[],
275
+ cancel?: () => boolean,
276
+ ): ProjectVocalRehydrateItem[] {
277
+ this.activeVocalRehydrateCalls += 1;
278
+ try {
279
+ return projectRehydrateVocalEdits(this.native, originals, cancel);
280
+ } finally {
281
+ this.activeVocalRehydrateCalls -= 1;
282
+ if (this.activeVocalRehydrateCalls === 0 && this.deleteRequested) {
283
+ this.native.delete();
284
+ }
285
+ }
286
+ }
287
+
202
288
  private released = false;
289
+ private deleteRequested = false;
290
+ private activeVocalRehydrateCalls = 0;
203
291
 
204
292
  constructor() {
205
293
  this.native = new (projectModule().Project)();
@@ -798,6 +886,34 @@ export class Project {
798
886
  this.native.setTrackGain(trackId, gain);
799
887
  }
800
888
 
889
+ /**
890
+ * Set the rig of one part (0-15), or of the destination's default when `part`
891
+ * is {@link PART_RIG_ALL_PARTS}, on MIDI destination `destinationId`, via an
892
+ * undoable edit. `'chain'` takes 1-8 `inserts` and is the only mode that does;
893
+ * a destination no track references is stored and read back all the same.
894
+ * Refused: a malformed insert document (invalid format), an out-of-range part,
895
+ * an unknown mode or processor, or inserts on a non-chain mode (invalid
896
+ * parameter), and an insert chain in a build without mastering.
897
+ */
898
+ setPartRig(
899
+ destinationId: number,
900
+ part: number,
901
+ mode: PartRigMode | number,
902
+ inserts?: PartRigInsert[],
903
+ ): void {
904
+ this.native.setPartRig(destinationId, part, mode, partRigInsertsJson(inserts));
905
+ }
906
+
907
+ /** The stored rig for `(destinationId, part)`, or null when none is stored. */
908
+ getPartRig(destinationId: number, part: number): PartRig | null {
909
+ return partRigFromNative(this.native.getPartRig(destinationId, part));
910
+ }
911
+
912
+ /** Remove the stored rig for `(destinationId, part)` via an undoable edit (no-op when absent). */
913
+ clearPartRig(destinationId: number, part: number): void {
914
+ this.native.clearPartRig(destinationId, part);
915
+ }
916
+
801
917
  /** Set a track's mute flag via an undoable edit (a muted track is silent). */
802
918
  setTrackMute(trackId: number, mute: boolean): void {
803
919
  this.native.setTrackMute(trackId, mute);
@@ -1059,6 +1175,17 @@ export class Project {
1059
1175
  return this.native.compile();
1060
1176
  }
1061
1177
 
1178
+ /**
1179
+ * Compile the project and keep the resulting timeline. The result is
1180
+ * {@link compile}'s plus `timeline`: a {@link ProjectTimeline} to install with
1181
+ * `RealtimeEngine.applyProjectTimeline`, or `null` when an error diagnostic
1182
+ * means nothing compiled.
1183
+ */
1184
+ compileTimeline(): ProjectCompileTimelineResult {
1185
+ const { timeline, ...result } = this.native.compileTimeline();
1186
+ return { ...result, timeline: timeline === null ? null : new ProjectTimeline(timeline) };
1187
+ }
1188
+
1062
1189
  /**
1063
1190
  * Compile + render the project offline to interleaved float audio. MIDI
1064
1191
  * tracks render silently here (no instrument is bound) — use
@@ -1540,12 +1667,20 @@ export class Project {
1540
1667
  return this.native.lastBounceCompileResult();
1541
1668
  }
1542
1669
 
1543
- /** Release the underlying WASM object. Idempotent, as the Node facade is. */
1670
+ /**
1671
+ * Release the underlying WASM object. Idempotent, as the Node facade is.
1672
+ * Called from a rehydrate cancel callback, the release happens when the
1673
+ * outermost {@link rehydrateVocalEdits} call returns.
1674
+ */
1544
1675
  delete(): void {
1545
1676
  if (this.released) {
1546
1677
  return;
1547
1678
  }
1548
1679
  this.released = true;
1680
+ if (this.activeVocalRehydrateCalls !== 0) {
1681
+ this.deleteRequested = true;
1682
+ return;
1683
+ }
1549
1684
  this.native.delete();
1550
1685
  }
1551
1686
 
@@ -1,6 +1,9 @@
1
1
  import { projectAutomationCurveCode, resolveEnumOrdinal } from './codes';
2
2
  import type {
3
3
  BuiltinSynthBinding,
4
+ PartRig,
5
+ PartRigInsert,
6
+ PartRigMode,
4
7
  SampleDesc,
5
8
  SampleZoneDesc,
6
9
  Sf2InstrumentConfig,
@@ -77,6 +80,38 @@ export interface WasmSampleBank {
77
80
  delete: () => void;
78
81
  }
79
82
 
83
+ // Embind handle for the C++ `ProjectTimelineWasm` class. Registered next to the
84
+ // project, so a build without arrangement support has neither.
85
+ export interface WasmProjectTimeline {
86
+ readonly id: number;
87
+ delete: () => void;
88
+ }
89
+
90
+ const projectTimelineHandles = new WeakMap<object, WasmProjectTimeline>();
91
+
92
+ /** Internal: registers the facade object whose identity may cross to the engine. */
93
+ export function registerProjectTimeline(owner: object, native: WasmProjectTimeline): void {
94
+ projectTimelineHandles.set(owner, native);
95
+ }
96
+
97
+ /** Internal: removes a disposed facade from the identity registry. */
98
+ export function unregisterProjectTimeline(owner: object): void {
99
+ projectTimelineHandles.delete(owner);
100
+ }
101
+
102
+ /** Internal: resolves only a timeline facade created by this module. */
103
+ export function projectTimelineNativeId(owner: unknown): number | undefined {
104
+ if (owner === null || (typeof owner !== 'object' && typeof owner !== 'function')) {
105
+ return undefined;
106
+ }
107
+ return projectTimelineHandles.get(owner)?.id;
108
+ }
109
+
110
+ /** `compileTimeline` as the embind layer returns it, before the facade wraps the handle. */
111
+ export type NativeProjectCompileTimelineResult = ProjectCompileResult & {
112
+ timeline: WasmProjectTimeline | null;
113
+ };
114
+
80
115
  /**
81
116
  * Swaps a facade {@link SampleBank} in a synth patch descriptor for the id the
82
117
  * embind layer looks the native bank up by, leaving every other field alone.
@@ -114,12 +149,55 @@ export function normalizeSynthInstrument(patch: unknown): NativeSynthBinding | s
114
149
  return { ...rest, sampleBankId: sampleBank.nativeId };
115
150
  }
116
151
 
152
+ /**
153
+ * Serializes a part rig's inserts into the JSON array the native layer reads
154
+ * (`params` always a JSON object string), or undefined when none were given.
155
+ */
156
+ export function partRigInsertsJson(inserts: PartRigInsert[] | undefined): string | undefined {
157
+ if (inserts === undefined || inserts === null) {
158
+ return undefined;
159
+ }
160
+ if (!Array.isArray(inserts)) {
161
+ throw new TypeError('inserts must be an array');
162
+ }
163
+ return JSON.stringify(
164
+ inserts.map((insert) => {
165
+ const { processor, params } = insert;
166
+ return params === undefined
167
+ ? { processor }
168
+ : { processor, params: typeof params === 'string' ? params : JSON.stringify(params) };
169
+ }),
170
+ );
171
+ }
172
+
173
+ /** Reads a native `{ mode, insertsJson }` part rig back into the public shape. */
174
+ export function partRigFromNative(
175
+ native: { mode: PartRig['mode']; insertsJson: string | null } | null,
176
+ ): PartRig | null {
177
+ if (native === null) {
178
+ return null;
179
+ }
180
+ if (native.insertsJson === null) {
181
+ return { mode: native.mode };
182
+ }
183
+ const inserts = (JSON.parse(native.insertsJson) as { processor: string; params: string }[]).map(
184
+ ({ processor, params }) => ({
185
+ processor,
186
+ params: JSON.parse(params) as Record<string, unknown>,
187
+ }),
188
+ );
189
+ return { mode: native.mode, inserts };
190
+ }
191
+
117
192
  // Embind handle for the C++ `ProjectWasm` class. `SonareModule` describes the
118
193
  // raw module's free functions and does not carry bound-class handles, so the
119
194
  // module is narrowed through this shape here — which is also where the
120
195
  // arrangement surface is probed for, since a build without arrangement support
121
196
  // registers neither the class nor its free functions.
122
197
  export interface WasmProject {
198
+ applyVocalEdit: (request: unknown) => unknown;
199
+ getVocalEditDependencies: () => unknown;
200
+ rehydrateVocalEdits: (originals: unknown, cancel?: unknown) => unknown;
123
201
  toJson: () => string;
124
202
  setSampleRate: (sampleRate: number) => void;
125
203
  addTrack: (desc: { kind?: number | string; name?: string }) => number;
@@ -140,6 +218,17 @@ export interface WasmProject {
140
218
  setTrackMute: (trackId: number, mute: boolean) => void;
141
219
  setTrackSolo: (trackId: number, solo: boolean) => void;
142
220
  setTrackPan: (trackId: number, pan: number) => void;
221
+ setPartRig: (
222
+ destinationId: number,
223
+ part: number,
224
+ mode: PartRigMode | number,
225
+ insertsJson: string | undefined,
226
+ ) => void;
227
+ getPartRig: (
228
+ destinationId: number,
229
+ part: number,
230
+ ) => { mode: PartRigMode; insertsJson: string | null } | null;
231
+ clearPartRig: (destinationId: number, part: number) => void;
143
232
  undo: () => void;
144
233
  redo: () => void;
145
234
  clearHistory: () => void;
@@ -186,6 +275,7 @@ export interface WasmProject {
186
275
  ) => number;
187
276
  snapToGrid: (ppq: number, strength: number, division: number) => number;
188
277
  compile: () => ProjectCompileResult;
278
+ compileTimeline: () => NativeProjectCompileTimelineResult;
189
279
  bounce: (options: ProjectBounceOptions) => Float32Array;
190
280
  bounceWithBuiltinInstrument: (
191
281
  bindings: BuiltinSynthBinding | ReadonlyArray<BuiltinSynthBinding> | undefined,
@@ -1,6 +1,6 @@
1
1
  // Type-only, so the erased import adds no runtime edge to the codes module.
2
2
  import type { PROJECT_AUTOMATION_CURVE_VALUES } from './codes';
3
- import type { Project } from './project_class';
3
+ import type { Project, ProjectTimeline } from './project_class';
4
4
 
5
5
  // ============================================================================
6
6
  // Headless DAW Project
@@ -721,6 +721,16 @@ export interface ProjectCompileResult {
721
721
  diagnostics: ProjectDiagnostic[];
722
722
  }
723
723
 
724
+ /** Result of {@link Project.compileTimeline}: the compile diagnostics plus the timeline they produced. */
725
+ export interface ProjectCompileTimelineResult extends ProjectCompileResult {
726
+ /**
727
+ * The compiled timeline, or `null` when compilation produced none (an error
728
+ * diagnostic; read {@link ProjectCompileResult.diagnostics}). Release it with
729
+ * {@link ProjectTimeline.dispose}.
730
+ */
731
+ timeline: ProjectTimeline | null;
732
+ }
733
+
724
734
  export interface ProjectDeserializeResult {
725
735
  project: Project;
726
736
  diagnostics: string;
@@ -41,6 +41,7 @@ export interface SonareCapabilities {
41
41
  arrangement: boolean;
42
42
  acousticSim: boolean;
43
43
  pitchEditor: boolean;
44
+ vocalEdit: boolean;
44
45
  voiceChanger: boolean;
45
46
  /** True when the playback renderer (upmix, binaural, speaker output) is compiled in. */
46
47
  playback: boolean;
@@ -132,9 +132,11 @@ export const SOLO_PROCESSORS = [
132
132
  'repair.trimSilence',
133
133
  'saturation.ampSim',
134
134
  'saturation.bitcrusher',
135
+ 'saturation.distortion',
135
136
  'saturation.exciter',
136
137
  'saturation.hardClipper',
137
138
  'saturation.multibandExciter',
139
+ 'saturation.overdrive',
138
140
  'saturation.softClipper',
139
141
  'saturation.tape',
140
142
  'saturation.transformer',