@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.
Files changed (66) hide show
  1. package/README.md +51 -20
  2. package/dist/index.d.ts +5416 -1
  3. package/dist/index.js +938 -583
  4. package/dist/index.js.map +1 -1
  5. package/dist/sonare.js +2 -2
  6. package/dist/sonare.wasm +0 -0
  7. package/dist/worklet.d.ts +1083 -5227
  8. package/dist/worklet.js +2683 -2451
  9. package/dist/worklet.js.map +1 -1
  10. package/package.json +4 -9
  11. package/src/clip_page_streamer.ts +298 -0
  12. package/src/effects_mastering.ts +85 -1089
  13. package/src/effects_transform.ts +286 -0
  14. package/src/effects_voice_change.ts +118 -0
  15. package/src/feature_music.ts +5 -2
  16. package/src/feature_spectrogram.ts +42 -2
  17. package/src/features.ts +1 -0
  18. package/src/index.ts +23 -0
  19. package/src/mastering_chain.ts +200 -0
  20. package/src/mastering_core.ts +248 -0
  21. package/src/mastering_dynamics.ts +105 -0
  22. package/src/mastering_repair.ts +161 -0
  23. package/src/mixer.ts +8 -0
  24. package/src/mixing_oneshot.ts +54 -0
  25. package/src/module_state.ts +1 -2
  26. package/src/project.ts +71 -1712
  27. package/src/project_class.ts +871 -0
  28. package/src/project_internal.ts +333 -0
  29. package/src/project_synth.ts +43 -0
  30. package/src/project_types.ts +570 -0
  31. package/src/public_types.ts +6 -1221
  32. package/src/public_types_acoustic.ts +115 -0
  33. package/src/public_types_mastering.ts +333 -0
  34. package/src/public_types_mixing.ts +97 -0
  35. package/src/public_types_music.ts +352 -0
  36. package/src/public_types_realtime.ts +163 -0
  37. package/src/public_types_spectral.ts +194 -0
  38. package/src/realtime_engine.ts +94 -0
  39. package/src/sonare.js.d.ts +117 -38
  40. package/src/stream_analyzer.ts +3 -0
  41. package/src/stream_types.ts +4 -0
  42. package/src/worklet/engine-automation.ts +73 -0
  43. package/src/worklet/engine-capture-facade.ts +80 -0
  44. package/src/worklet/engine-clips.ts +71 -0
  45. package/src/worklet/engine-markers.ts +93 -0
  46. package/src/worklet/engine-mixer-facade.ts +186 -0
  47. package/src/worklet/engine-node.ts +451 -0
  48. package/src/worklet/engine-offline.ts +162 -0
  49. package/src/worklet/engine-options.ts +13 -0
  50. package/src/worklet/engine-parameter-facade.ts +172 -0
  51. package/src/worklet/engine-processor.ts +764 -0
  52. package/src/worklet/engine-register.ts +136 -0
  53. package/src/worklet/engine-strips.ts +315 -0
  54. package/src/worklet/engine-sync.ts +94 -0
  55. package/src/worklet/engine-tempo-facade.ts +141 -0
  56. package/src/worklet/engine.ts +998 -0
  57. package/src/worklet/guards.ts +14 -1
  58. package/src/worklet/messages.ts +60 -20
  59. package/src/worklet/mixer-processor.ts +368 -0
  60. package/src/worklet/protocol.ts +3 -0
  61. package/src/worklet/voice-changer-processor.ts +246 -0
  62. package/src/worklet.ts +20 -3549
  63. package/dist/sonare-rt-module.js +0 -2
  64. package/dist/sonare-rt.js +0 -2
  65. package/dist/sonare-rt.wasm +0 -0
  66. package/src/sonare-rt.d.ts +0 -93
@@ -0,0 +1,871 @@
1
+ import {
2
+ assertProjectMidiEvents,
3
+ projectLoopModeValue,
4
+ projectMidi1Event,
5
+ projectModule,
6
+ projectTrackKindValue,
7
+ projectWarpModeValue,
8
+ type WasmProject,
9
+ } from './project_internal';
10
+ import type {
11
+ BuiltinSynthBinding,
12
+ MidiCcLearnOptions,
13
+ ProjectAssistSidecar,
14
+ ProjectAutomationLaneDesc,
15
+ ProjectAutomationPoint,
16
+ ProjectBounceOptions,
17
+ ProjectChordSymbol,
18
+ ProjectClipCompSegment,
19
+ ProjectClipDesc,
20
+ ProjectClipFade,
21
+ ProjectClipTake,
22
+ ProjectCompileResult,
23
+ ProjectDeserializeResult,
24
+ ProjectKeySegment,
25
+ ProjectLoopMode,
26
+ ProjectLoopRecordingDesc,
27
+ ProjectLoopRecordingResult,
28
+ ProjectMarker,
29
+ ProjectMidiCcBinding,
30
+ ProjectMidiClipResult,
31
+ ProjectMidiEvent,
32
+ ProjectMidiRouteConfig,
33
+ ProjectMidiRouteResult,
34
+ ProjectNotePairValidation,
35
+ ProjectTempoSegment,
36
+ ProjectTimeSignatureSegment,
37
+ ProjectTrackDesc,
38
+ ProjectTrackKind,
39
+ ProjectWarpMapDesc,
40
+ ProjectWarpMode,
41
+ Sf2InstrumentConfig,
42
+ Sf2ProgramStatus,
43
+ SynthPatch,
44
+ } from './project_types';
45
+
46
+ /**
47
+ * Headless DAW project (control-thread-only arrangement model).
48
+ *
49
+ * Wraps the embind `Project` class over the C-ABI keystone
50
+ * `sonare_c_project.{h,cpp}`. Construct an empty project with `new Project()`,
51
+ * or deserialize one with {@link Project.fromJson}; serialize back with
52
+ * {@link toJson}; compile to a renderable timeline with {@link compile}; render
53
+ * offline to interleaved float audio with {@link bounce}. The edit and MIDI
54
+ * methods mirror the Node/Python project bindings.
55
+ *
56
+ * Call {@link delete} (or use a `try/finally`) to release the underlying WASM
57
+ * object — the embind handle is not garbage-collected automatically.
58
+ *
59
+ * @example
60
+ * ```typescript
61
+ * const project = new Project();
62
+ * try {
63
+ * project.setSampleRate(48000);
64
+ * const json = project.toJson();
65
+ * const restored = Project.fromJson(json);
66
+ * restored.delete();
67
+ * } finally {
68
+ * project.delete();
69
+ * }
70
+ * ```
71
+ */
72
+ export class Project {
73
+ private native: WasmProject;
74
+
75
+ constructor() {
76
+ this.native = new (projectModule().Project)();
77
+ }
78
+
79
+ /** Pack a MIDI 1.0 note-on event accepted by {@link setMidiEvents}. */
80
+ static midiNoteOn(
81
+ ppq: number,
82
+ group: number,
83
+ channel: number,
84
+ note: number,
85
+ velocity: number,
86
+ ): ProjectMidiEvent {
87
+ return projectMidi1Event('Project.midiNoteOn', ppq, group, 0x9, channel, note, velocity);
88
+ }
89
+
90
+ /** Pack a MIDI 1.0 note-off event accepted by {@link setMidiEvents}. */
91
+ static midiNoteOff(
92
+ ppq: number,
93
+ group: number,
94
+ channel: number,
95
+ note: number,
96
+ velocity = 0,
97
+ ): ProjectMidiEvent {
98
+ return projectMidi1Event('Project.midiNoteOff', ppq, group, 0x8, channel, note, velocity);
99
+ }
100
+
101
+ /** Pack a MIDI 1.0 control-change event. */
102
+ static midiCc(
103
+ ppq: number,
104
+ group: number,
105
+ channel: number,
106
+ controller: number,
107
+ value: number,
108
+ ): ProjectMidiEvent {
109
+ return projectMidi1Event('Project.midiCc', ppq, group, 0xb, channel, controller, value);
110
+ }
111
+
112
+ /** Pack a MIDI 1.0 poly-pressure event. */
113
+ static midiPolyPressure(
114
+ ppq: number,
115
+ group: number,
116
+ channel: number,
117
+ note: number,
118
+ pressure: number,
119
+ ): ProjectMidiEvent {
120
+ return projectMidi1Event('Project.midiPolyPressure', ppq, group, 0xa, channel, note, pressure);
121
+ }
122
+
123
+ /** Pack a MIDI 1.0 program-change event. */
124
+ static midiProgram(
125
+ ppq: number,
126
+ group: number,
127
+ channel: number,
128
+ program: number,
129
+ ): ProjectMidiEvent {
130
+ return projectMidi1Event('Project.midiProgram', ppq, group, 0xc, channel, program, 0);
131
+ }
132
+
133
+ /** Return the General MIDI instrument name for `program`, or `null` when out of range. */
134
+ static gmInstrumentName(program: number): string | null {
135
+ return projectModule().midiGmInstrumentName(program);
136
+ }
137
+
138
+ /** Return the General MIDI program number for a canonical instrument name, or `-1`. */
139
+ static gmProgramForName(name: string): number {
140
+ return projectModule().midiGmProgramForName(name);
141
+ }
142
+
143
+ /** Return the General MIDI family name for `family`, or `null` when out of range. */
144
+ static gmFamilyName(family: number): string | null {
145
+ return projectModule().midiGmFamilyName(family);
146
+ }
147
+
148
+ /** Return the first General MIDI program number in `family`, or `-1`. */
149
+ static gmFamilyFirstProgram(family: number): number {
150
+ return projectModule().midiGmFamilyFirstProgram(family);
151
+ }
152
+
153
+ /** Return the GM2 bank/program instrument variation name, or `null` when unavailable. */
154
+ static gm2InstrumentName(bankLsb: number, program: number): string | null {
155
+ return projectModule().midiGm2InstrumentName(bankLsb, program);
156
+ }
157
+
158
+ /** Return the General MIDI drum name for `note`, or `null` when out of range. */
159
+ static gmDrumName(note: number): string | null {
160
+ return projectModule().midiGmDrumName(note);
161
+ }
162
+
163
+ /** Return the General MIDI drum note for a canonical drum name, or `-1`. */
164
+ static gmDrumNoteForName(name: string): number {
165
+ return projectModule().midiGmDrumNoteForName(name);
166
+ }
167
+
168
+ /** Return the GM2 drum-set name for `bankLsb`, or `null` when unavailable. */
169
+ static gm2DrumSetName(bankLsb: number): string | null {
170
+ return projectModule().midiGm2DrumSetName(bankLsb);
171
+ }
172
+
173
+ /** Return the GM2 drum name for `bankLsb`/`note`, or `null` when unavailable. */
174
+ static gm2DrumName(bankLsb: number, note: number): string | null {
175
+ return projectModule().midiGm2DrumName(bankLsb, note);
176
+ }
177
+
178
+ /** Return the MIDI CC name for `controller`, or `null` when out of range. */
179
+ static midiCcName(controller: number): string | null {
180
+ return projectModule().midiCcName(controller);
181
+ }
182
+
183
+ /** Return the MIDI CC number for a canonical controller name, or `-1`. */
184
+ static midiCcIndexForName(name: string): number {
185
+ return projectModule().midiCcIndexForName(name);
186
+ }
187
+
188
+ /** Return the MIDI 2.0 per-note controller name for `index`, or `null`. */
189
+ static perNoteControllerName(index: number): string | null {
190
+ return projectModule().midiPerNoteControllerName(index);
191
+ }
192
+
193
+ /** Expand bank-select + program-change into MIDI events accepted by {@link setMidiEvents}. */
194
+ static midiBankProgram(
195
+ ppq: number,
196
+ group: number,
197
+ channel: number,
198
+ bankMsb: number,
199
+ bankLsb: number,
200
+ program: number,
201
+ ): ProjectMidiEvent[] {
202
+ return projectModule().midiBankProgram(ppq, group, channel, bankMsb, bankLsb, program);
203
+ }
204
+
205
+ /** Route MIDI events through the native MidiRouter filter/remap/thru logic. */
206
+ static midiRouteEvents(
207
+ events: ReadonlyArray<ProjectMidiEvent>,
208
+ config: ProjectMidiRouteConfig = {},
209
+ ): ProjectMidiRouteResult {
210
+ return projectModule().midiRouteEvents(events, config);
211
+ }
212
+
213
+ /** Run native MIDI learn over an event stream; returns `null` when nothing is learned. */
214
+ static midiCcLearn(
215
+ events: ReadonlyArray<ProjectMidiEvent>,
216
+ paramId: number,
217
+ options: MidiCcLearnOptions = {},
218
+ ): ProjectMidiCcBinding | null {
219
+ return projectModule().midiCcLearn(
220
+ events,
221
+ paramId,
222
+ options.minValue ?? 0,
223
+ options.maxValue ?? 1,
224
+ options.minMovement ?? 0,
225
+ );
226
+ }
227
+
228
+ /** Convert one CC event to an automation breakpoint using native CcMap. */
229
+ static midiCcToBreakpoint(
230
+ bindings: ReadonlyArray<ProjectMidiCcBinding>,
231
+ event: ProjectMidiEvent,
232
+ ): ProjectAutomationPoint | null {
233
+ return projectModule().midiCcToBreakpoint(bindings, event);
234
+ }
235
+
236
+ /** Convert one automation value back to a CC UMP event using native CcMap. */
237
+ static midiParamToCc(
238
+ bindings: ReadonlyArray<ProjectMidiCcBinding>,
239
+ paramId: number,
240
+ unitValue: number,
241
+ group: number,
242
+ ppq = 0,
243
+ ): ProjectMidiEvent | null {
244
+ return projectModule().midiParamToCc(bindings, paramId, unitValue, group, ppq);
245
+ }
246
+
247
+ /** Pack a MIDI 1.0 channel-pressure event. */
248
+ static midiChannelPressure(
249
+ ppq: number,
250
+ group: number,
251
+ channel: number,
252
+ pressure: number,
253
+ ): ProjectMidiEvent {
254
+ return projectMidi1Event('Project.midiChannelPressure', ppq, group, 0xd, channel, pressure, 0);
255
+ }
256
+
257
+ /** Pack a MIDI 1.0 pitch-bend event (`bend` is unsigned 14-bit, center = 8192). */
258
+ static midiPitchBend(
259
+ ppq: number,
260
+ group: number,
261
+ channel: number,
262
+ bend: number,
263
+ ): ProjectMidiEvent {
264
+ if (!Number.isInteger(bend) || bend < 0 || bend > 0x3fff) {
265
+ throw new RangeError('Project.midiPitchBend: bend must be an integer in [0, 16383]');
266
+ }
267
+ return projectMidi1Event(
268
+ 'Project.midiPitchBend',
269
+ ppq,
270
+ group,
271
+ 0xe,
272
+ channel,
273
+ bend & 0x7f,
274
+ bend >> 7,
275
+ );
276
+ }
277
+
278
+ /**
279
+ * Deserialize project JSON into a new {@link Project}. Throws if the JSON is
280
+ * malformed, surfacing the joined diagnostic messages.
281
+ */
282
+ static fromJson(json: string): Project {
283
+ const project = new Project();
284
+ // Replace the freshly-created empty handle with the deserialized one. If
285
+ // fromJson throws (malformed JSON) the empty handle is released first so no
286
+ // WASM object leaks.
287
+ const restored = (() => {
288
+ try {
289
+ return projectModule().Project.fromJson(json);
290
+ } catch (error) {
291
+ project.native.delete();
292
+ throw error;
293
+ }
294
+ })();
295
+ project.native.delete();
296
+ project.native = restored;
297
+ return project;
298
+ }
299
+
300
+ /**
301
+ * Deserialize project JSON and return native warning diagnostics emitted on
302
+ * successful loads, such as dangling source references preserved for repair.
303
+ */
304
+ static fromJsonWithDiagnostics(json: string): ProjectDeserializeResult {
305
+ const project = new Project();
306
+ const restored = (() => {
307
+ try {
308
+ return projectModule().Project.fromJsonWithDiagnostics(json);
309
+ } catch (error) {
310
+ project.native.delete();
311
+ throw error;
312
+ }
313
+ })();
314
+ project.native.delete();
315
+ project.native = restored.project;
316
+ return { project, diagnostics: restored.diagnostics };
317
+ }
318
+
319
+ /** Serialize the project (+ MIDI content) to deterministic JSON. */
320
+ toJson(): string {
321
+ return this.native.toJson();
322
+ }
323
+
324
+ /** Set the project sample rate in Hz. Must be > 0. */
325
+ setSampleRate(sampleRate: number): void {
326
+ this.native.setSampleRate(sampleRate);
327
+ }
328
+
329
+ /** Add a track and return its allocated stable id. */
330
+ addTrack(desc: ProjectTrackDesc = {}): number {
331
+ return this.native.addTrack({ ...desc, kind: projectTrackKindValue(desc.kind) });
332
+ }
333
+
334
+ /** Add an audio or MIDI clip and return its allocated clip id. */
335
+ addClip(desc: ProjectClipDesc): number {
336
+ return this.native.addClip(desc);
337
+ }
338
+
339
+ /** Split captured loop-recording audio into takes and add one clip. */
340
+ addLoopRecordingTakes(desc: ProjectLoopRecordingDesc): ProjectLoopRecordingResult {
341
+ return this.native.addLoopRecordingTakes(desc);
342
+ }
343
+
344
+ /** Create a MIDI track + clip; returns `{ trackId, clipId }`. */
345
+ addMidiClip(startPpq: number, lengthPpq: number): ProjectMidiClipResult {
346
+ return this.native.addMidiClip(startPpq, lengthPpq);
347
+ }
348
+
349
+ /** Split a clip at `splitPpq` and return the new clip id. */
350
+ splitClip(clipId: number, splitPpq: number): number {
351
+ return this.native.splitClip(clipId, splitPpq);
352
+ }
353
+
354
+ /** Trim a clip's start / length in PPQ. */
355
+ trimClip(clipId: number, newStartPpq: number, newLengthPpq: number): void {
356
+ this.native.trimClip(clipId, newStartPpq, newLengthPpq);
357
+ }
358
+
359
+ /** Move a clip to `newStartPpq` and optionally another track. */
360
+ moveClip(clipId: number, newStartPpq: number, newTrackId = 0): void {
361
+ this.native.moveClip(clipId, newStartPpq, newTrackId);
362
+ }
363
+
364
+ /** Change a track kind via an undoable edit. */
365
+ setTrackKind(trackId: number, kind: ProjectTrackKind): void {
366
+ this.native.setTrackKind(trackId, projectTrackKindValue(kind));
367
+ }
368
+
369
+ /** Set a clip's warp reference id (0 clears it). */
370
+ setClipWarpRef(clipId: number, warpRefId: number): void {
371
+ this.native.setClipWarpRef(clipId, warpRefId);
372
+ }
373
+
374
+ /** Set a clip's warp playback mode. */
375
+ setClipWarpMode(clipId: number, mode: ProjectWarpMode): void {
376
+ this.native.setClipWarpMode(clipId, projectWarpModeValue(mode));
377
+ }
378
+
379
+ /** Add or replace a first-class warp map referenced by clip warp ids. */
380
+ setWarpMap(map: ProjectWarpMapDesc): void {
381
+ this.native.setWarpMap(map);
382
+ }
383
+
384
+ /** Remove a first-class warp map by id. */
385
+ removeWarpMap(warpRefId: number): void {
386
+ this.native.removeWarpMap(warpRefId);
387
+ }
388
+
389
+ /**
390
+ * Route a track's MIDI to host-instrument `destinationId` (0 = default). The
391
+ * compiler stamps every MIDI clip on the track with this id so the engine
392
+ * dispatches its events to the instrument registered for that destination.
393
+ * Routes through an undoable edit command.
394
+ */
395
+ setTrackMidiDestination(trackId: number, destinationId: number): void {
396
+ this.native.setTrackMidiDestination(trackId, destinationId);
397
+ }
398
+
399
+ /** Set a track's linear playback gain (1.0 = unity; >= 0) via an undoable edit. */
400
+ setTrackGain(trackId: number, gain: number): void {
401
+ this.native.setTrackGain(trackId, gain);
402
+ }
403
+
404
+ /** Set a track's mute flag via an undoable edit (a muted track is silent). */
405
+ setTrackMute(trackId: number, mute: boolean): void {
406
+ this.native.setTrackMute(trackId, mute);
407
+ }
408
+
409
+ /** Set a track's solo flag via an undoable edit (when any track is soloed, only soloed tracks sound). */
410
+ setTrackSolo(trackId: number, solo: boolean): void {
411
+ this.native.setTrackSolo(trackId, solo);
412
+ }
413
+
414
+ /** Set a track's stereo balance in [-1, +1] (0 = center) via an undoable edit. */
415
+ setTrackPan(trackId: number, pan: number): void {
416
+ this.native.setTrackPan(trackId, pan);
417
+ }
418
+
419
+ /** Undo the most recent edit. */
420
+ undo(): void {
421
+ this.native.undo();
422
+ }
423
+
424
+ /** Redo the most recently undone edit. */
425
+ redo(): void {
426
+ this.native.redo();
427
+ }
428
+
429
+ /** Replace a MIDI clip's entire event list. */
430
+ setMidiEvents(
431
+ clipId: number,
432
+ events: ReadonlyArray<ProjectMidiEvent | readonly [number, number, number]>,
433
+ ): void {
434
+ assertProjectMidiEvents('Project.setMidiEvents', events);
435
+ this.native.setMidiEvents(clipId, events);
436
+ }
437
+
438
+ /** Import an in-memory SMF buffer; returns the first added clip id. */
439
+ importSmf(data: Uint8Array): number {
440
+ return this.native.importSmf(data);
441
+ }
442
+
443
+ /** Export the project's tempo map + MIDI clips to an SMF byte buffer. */
444
+ exportSmf(): Uint8Array {
445
+ return this.native.exportSmf();
446
+ }
447
+
448
+ /**
449
+ * Import a MIDI 2.0 Clip File (`SMF2CLIP`); returns the first added clip id.
450
+ * Unlike {@link importSmf}, MIDI 2.0 channel-voice messages (16-bit velocity,
451
+ * 32-bit CC, per-note / registered controllers, bank-valid Program Change)
452
+ * survive without loss.
453
+ */
454
+ importClipFile(data: Uint8Array): number {
455
+ return this.native.importClipFile(data);
456
+ }
457
+
458
+ /**
459
+ * Export the project's tempo map + MIDI clips to a MIDI 2.0 Clip File
460
+ * (`SMF2CLIP`) byte buffer. MIDI 2.0-only events are written without loss —
461
+ * prefer this over {@link exportSmf} when MIDI 2.0 fidelity matters.
462
+ */
463
+ exportClipFile(): Uint8Array {
464
+ return this.native.exportClipFile();
465
+ }
466
+
467
+ /**
468
+ * Set a MIDI clip's channel-0 program / bank at source PPQ 0. `bank` defaults
469
+ * to `-1` (no Bank Select emitted), matching `setProgramOnChannel` and the
470
+ * Node/Python surfaces; pass `>= 0` to emit a Bank Select.
471
+ */
472
+ setProgram(clipId: number, program: number, bank = -1): void {
473
+ this.native.setProgram(clipId, program, bank);
474
+ }
475
+
476
+ /** Set a MIDI clip's program / bank for one UMP group and channel. */
477
+ setProgramOnChannel(
478
+ clipId: number,
479
+ group: number,
480
+ channel: number,
481
+ program: number,
482
+ bank = -1,
483
+ ): void {
484
+ this.native.setProgramOnChannel(clipId, group, channel, program, bank);
485
+ }
486
+
487
+ /** Destructively bake a MIDI-FX chain into a clip's stored MIDI events. */
488
+ bakeMidiFx(clipId: number, configJson: string): void {
489
+ this.native.bakeMidiFx(clipId, configJson);
490
+ }
491
+
492
+ /** Backward alias for {@link bakeMidiFx}. */
493
+ setMidiFx(clipId: number, configJson: string): void {
494
+ this.bakeMidiFx(clipId, configJson);
495
+ }
496
+
497
+ /**
498
+ * Pre-flight check for hanging / unmatched notes in a MIDI clip: reports
499
+ * whether every note-on has a matching note-off (FIFO per channel+note).
500
+ * Useful before bouncing to catch a stuck note. Throws if `clipId` is unknown
501
+ * or not a MIDI clip.
502
+ */
503
+ validateMidiNotes(clipId: number): ProjectNotePairValidation {
504
+ return this.native.validateMidiNotes(clipId);
505
+ }
506
+
507
+ /** Detect tempo from a mono buffer and install it; returns the primary BPM. */
508
+ autoTempo(audio: Float32Array, sampleRate: number): number {
509
+ return this.native.autoTempo(audio, sampleRate);
510
+ }
511
+
512
+ /** Snap a PPQ coordinate to the nearest beat of the project grid. */
513
+ snapToGrid(ppq: number, strength = 1.0): number {
514
+ return this.native.snapToGrid(ppq, strength);
515
+ }
516
+
517
+ /** Compile the project into a renderable timeline, surfacing diagnostics. */
518
+ compile(): ProjectCompileResult {
519
+ return this.native.compile();
520
+ }
521
+
522
+ /**
523
+ * Compile + render the project offline to interleaved float audio. MIDI
524
+ * tracks render silently here (no instrument is bound) — use
525
+ * {@link bounceWithBuiltinInstrument} to make MIDI audible.
526
+ *
527
+ * When `totalFrames` is omitted (or `<= 0`) the render length is auto-derived
528
+ * from the arrangement, so a project with content renders without computing a
529
+ * frame count; an empty project yields an empty buffer.
530
+ *
531
+ * @example
532
+ * ```typescript
533
+ * const audio = project.bounce({ numChannels: 2 });
534
+ * ```
535
+ */
536
+ bounce(options: ProjectBounceOptions = {}): Float32Array {
537
+ return this.native.bounce(options);
538
+ }
539
+
540
+ /**
541
+ * Compile + render the project offline, routing MIDI tracks through the
542
+ * built-in oscillator synth so a MIDI-only arrangement bounces to audible
543
+ * audio. Pass a {@link BuiltinSynthBinding} (or an array of them) to choose
544
+ * the patch and MIDI destination; omit it (or pass `{}`) for one
545
+ * default-destination sine patch. Because the parameter defaults to `{}`,
546
+ * omission and explicit `undefined` both create that one default binding.
547
+ * Use an explicitly empty array `[]` (or runtime `null`) for zero bindings,
548
+ * so MIDI tracks render silently.
549
+ *
550
+ * Like {@link bounce}, omitting `totalFrames` auto-derives the render length
551
+ * from the arrangement plus the synth's release tail.
552
+ *
553
+ * @example
554
+ * ```typescript
555
+ * // MIDI-only project -> non-silent stereo audio.
556
+ * const audio = project.bounceWithBuiltinInstrument(
557
+ * { waveform: 'saw' },
558
+ * { numChannels: 2 },
559
+ * );
560
+ * ```
561
+ */
562
+ bounceWithBuiltinInstrument(
563
+ instrument: BuiltinSynthBinding | ReadonlyArray<BuiltinSynthBinding> = {},
564
+ options: ProjectBounceOptions = {},
565
+ ): Float32Array {
566
+ return this.native.bounceWithBuiltinInstrument(instrument, options);
567
+ }
568
+
569
+ /**
570
+ * Compile + render the project offline, routing MIDI tracks through the
571
+ * patch-driven NativeSynth — the full synthesizer (subtractive / FM /
572
+ * Karplus-Strong / modal / additive / percussion / extended-waveguide-piano
573
+ * engines plus the realism layer). Pass a {@link SynthPatch}, a preset-name
574
+ * string (`'saw-lead'` / `'va:saw-lead'`; see {@link synthPresetNames}), or
575
+ * an array of either; each object entry may carry a `destinationId` binding
576
+ * convenience (default 0), which is not part of the NativeSynth patch itself.
577
+ * Because the parameter defaults to `{}`, omission and explicit `undefined`
578
+ * both create one default binding. Use an explicitly empty array `[]` (or
579
+ * runtime `null`) for zero bindings. Unknown preset names throw.
580
+ * Deterministic for a fixed project + options + patch.
581
+ */
582
+ bounceWithSynthInstrument(
583
+ instrument: SynthPatch | string | ReadonlyArray<SynthPatch | string> = {},
584
+ options: ProjectBounceOptions = {},
585
+ ): Float32Array {
586
+ return this.native.bounceWithSynthInstrument(instrument, options);
587
+ }
588
+
589
+ /**
590
+ * Load (parse) SoundFont 2 bytes into the project: presets / instruments /
591
+ * sample headers plus the sample PCM decoded to a float pool. The host
592
+ * fetches the `.sf2` and passes the raw bytes; they are copied into linear
593
+ * memory for the call and not referenced afterwards. Replaces any previously
594
+ * loaded SoundFont; throws on malformed input (the previous SoundFont is
595
+ * kept).
596
+ */
597
+ loadSoundFont(data: Uint8Array): void {
598
+ this.native.loadSoundFont(data);
599
+ }
600
+
601
+ /** Release the project's loaded SoundFont (no-op when none is loaded). */
602
+ clearSoundFont(): void {
603
+ this.native.clearSoundFont();
604
+ }
605
+
606
+ /** Number of presets in the loaded SoundFont (0 when none is loaded). */
607
+ soundFontPresetCount(): number {
608
+ return this.native.soundFontPresetCount();
609
+ }
610
+
611
+ /**
612
+ * Enumerate every (channel, bank, program) combination the arrangement plays
613
+ * a note through, in first-use order, reporting whether each resolves in the
614
+ * loaded SoundFont (`'sf2'`, GS variation/drum fallbacks included) or would
615
+ * fall back to the built-in synth (`'synth'`). Without a loaded SoundFont
616
+ * every entry is a synth fallback.
617
+ */
618
+ soundFontManifest(): Sf2ProgramStatus[] {
619
+ return this.native.soundFontManifest();
620
+ }
621
+
622
+ /**
623
+ * Like {@link bounceWithBuiltinInstrument}, but each bound destination
624
+ * renders through a GS-compatible SoundFont player fed by the project's
625
+ * loaded SoundFont ({@link loadSoundFont}): 16 MIDI channels per player,
626
+ * channel 10 drums via bank 128, GS NRPN part edits and GS/GM SysEx resets
627
+ * honored. Programs the SoundFont does not cover — including bouncing with
628
+ * no SoundFont loaded at all — play through the built-in synthesizer GM
629
+ * fallback bank (the data-free floor; see {@link soundFontManifest} for the
630
+ * per-program backend). Because the parameter defaults to `{}`, omission and
631
+ * explicit `undefined` both create one default binding. Use an explicitly
632
+ * empty array `[]` (or runtime `null`) for zero bindings, so MIDI tracks
633
+ * render silently.
634
+ */
635
+ bounceWithSf2Instrument(
636
+ instrument: Sf2InstrumentConfig | ReadonlyArray<Sf2InstrumentConfig> = {},
637
+ options: ProjectBounceOptions = {},
638
+ ): Float32Array {
639
+ return this.native.bounceWithSf2Instrument(instrument, options);
640
+ }
641
+
642
+ /** Remove a clip (undoable). */
643
+ removeClip(clipId: number): void {
644
+ this.native.removeClip(clipId);
645
+ }
646
+
647
+ /** Set a clip's linear playback gain (>= 0; undoable). */
648
+ setClipGain(clipId: number, gain: number): void {
649
+ this.native.setClipGain(clipId, gain);
650
+ }
651
+
652
+ /** Set a clip's fade-in / fade-out regions (undoable). */
653
+ setClipFade(clipId: number, fadeIn: ProjectClipFade = {}, fadeOut: ProjectClipFade = {}): void {
654
+ this.native.setClipFade(clipId, fadeIn, fadeOut);
655
+ }
656
+
657
+ /** Replace a clip's take list and active take id (undoable). */
658
+ setClipTakes(clipId: number, takes: ReadonlyArray<ProjectClipTake>, activeTakeId = 0): void {
659
+ this.native.setClipTakes(clipId, takes, activeTakeId);
660
+ }
661
+
662
+ /** Replace a clip's comp segments (undoable). */
663
+ setClipCompSegments(clipId: number, segments: ReadonlyArray<ProjectClipCompSegment>): void {
664
+ this.native.setClipCompSegments(clipId, segments);
665
+ }
666
+
667
+ /**
668
+ * Set a clip's loop mode + loop length in PPQ (undoable). `loopCrossfadePpq`
669
+ * is an optional equal-power crossfade at the loop seam (PPQ, finite and >= 0;
670
+ * 0 = hard loop); the engine clamps it to the clip's pre-roll and half the loop.
671
+ */
672
+ setClipLoop(
673
+ clipId: number,
674
+ loopMode: ProjectLoopMode,
675
+ loopLengthPpq = 0,
676
+ loopCrossfadePpq = 0,
677
+ ): void {
678
+ this.native.setClipLoop(
679
+ clipId,
680
+ projectLoopModeValue(loopMode),
681
+ loopLengthPpq,
682
+ loopCrossfadePpq,
683
+ );
684
+ }
685
+
686
+ /** Rebind a clip to a different (already-registered) source (undoable). */
687
+ setClipSource(clipId: number, sourceId: number): void {
688
+ this.native.setClipSource(clipId, sourceId);
689
+ }
690
+
691
+ /** Duplicate a clip at `newStartPpq` (same track); returns the new clip id. */
692
+ duplicateClip(clipId: number, newStartPpq: number): number {
693
+ return this.native.duplicateClip(clipId, newStartPpq);
694
+ }
695
+
696
+ /** Remove a track and its clips (undoable). */
697
+ removeTrack(trackId: number): void {
698
+ this.native.removeTrack(trackId);
699
+ }
700
+
701
+ /** Rename a track (undoable). */
702
+ renameTrack(trackId: number, name: string): void {
703
+ this.native.renameTrack(trackId, name);
704
+ }
705
+
706
+ /** Set a track's mixer-strip binding + output target (undoable; omit / '' clears). */
707
+ setTrackRoute(trackId: number, channelStripRef?: string, outputTarget?: string): void {
708
+ this.native.setTrackRoute(trackId, channelStripRef ?? '', outputTarget ?? '');
709
+ }
710
+
711
+ /** Append an automation lane to a track; returns the lane index (undoable). */
712
+ addAutomationLane(trackId: number, desc: ProjectAutomationLaneDesc): number {
713
+ return this.native.addAutomationLane(trackId, {
714
+ targetParamId: desc.targetParamId,
715
+ points: desc.points,
716
+ });
717
+ }
718
+
719
+ /** Replace an existing automation lane in place (undoable). */
720
+ editAutomationLane(trackId: number, laneIndex: number, desc: ProjectAutomationLaneDesc): void {
721
+ this.native.editAutomationLane(trackId, laneIndex, {
722
+ targetParamId: desc.targetParamId,
723
+ points: desc.points,
724
+ });
725
+ }
726
+
727
+ /** Remove an automation lane from a track (undoable). */
728
+ removeAutomationLane(trackId: number, laneIndex: number): void {
729
+ this.native.removeAutomationLane(trackId, laneIndex);
730
+ }
731
+
732
+ /** Replace the project's key annotation stream (undoable). */
733
+ annotateKeys(keys: ReadonlyArray<ProjectKeySegment>): void {
734
+ this.native.annotateKeys(keys);
735
+ }
736
+
737
+ /** Replace the project's chord-symbol annotation stream (undoable). */
738
+ annotateChords(chords: ReadonlyArray<ProjectChordSymbol>): void {
739
+ this.native.annotateChords(chords);
740
+ }
741
+
742
+ /** Add or update an opaque assist sidecar by module id + target scope (undoable). */
743
+ setAssistSidecar(
744
+ moduleId: string,
745
+ schemaVersion: number,
746
+ targetTrackId: number,
747
+ regionStartPpq: number,
748
+ regionEndPpq: number,
749
+ payload: Uint8Array,
750
+ ): void {
751
+ this.native.setAssistSidecar(
752
+ moduleId,
753
+ schemaVersion,
754
+ targetTrackId,
755
+ regionStartPpq,
756
+ regionEndPpq,
757
+ payload,
758
+ );
759
+ }
760
+
761
+ /** Number of assist sidecars currently stored on the project. */
762
+ assistSidecarCount(): number {
763
+ return this.native.assistSidecarCount();
764
+ }
765
+
766
+ /** Read one assist sidecar by stable project order. */
767
+ getAssistSidecar(index: number): ProjectAssistSidecar {
768
+ return this.native.getAssistSidecar(index);
769
+ }
770
+
771
+ /** Set the project's clip-overlap policy (SonareProjectOverlapPolicy ordinal). */
772
+ setOverlapPolicy(policy: number): void {
773
+ this.native.setOverlapPolicy(policy);
774
+ }
775
+
776
+ /** Read the project's clip-overlap policy (SonareProjectOverlapPolicy ordinal). */
777
+ getOverlapPolicy(): number {
778
+ return this.native.getOverlapPolicy();
779
+ }
780
+
781
+ /** Read the project sample rate in Hz. */
782
+ getSampleRate(): number {
783
+ return this.native.getSampleRate();
784
+ }
785
+
786
+ /** Replace the project's mixer scene from a scene JSON string. */
787
+ setMixerSceneJson(sceneJson: string): void {
788
+ this.native.setMixerSceneJson(sceneJson);
789
+ }
790
+
791
+ /**
792
+ * Add or replace a marker. Pass `markerId` 0 to allocate a new id; returns the
793
+ * stable marker id (the allocated id when 0 was passed).
794
+ */
795
+ setMarker(markerId: number, ppq: number, name: string): number {
796
+ return this.native.setMarker(markerId, ppq, name);
797
+ }
798
+
799
+ /**
800
+ * Add or replace a marker from a full {@link ProjectMarker}, including its
801
+ * {@link MarkerKind} and (for key signatures) the key. Pass `id` 0 to allocate
802
+ * a new id; returns the stable marker id.
803
+ */
804
+ setMarkerEx(marker: ProjectMarker): number {
805
+ return this.native.setMarkerEx(marker);
806
+ }
807
+
808
+ /** Read a project marker by index (0-based, in stored order). */
809
+ markerByIndex(index: number): ProjectMarker {
810
+ return this.native.markerByIndex(index);
811
+ }
812
+
813
+ /** Number of markers in the project. */
814
+ markerCount(): number {
815
+ return this.native.markerCount();
816
+ }
817
+
818
+ /** Number of tracks in the project. */
819
+ trackCount(): number {
820
+ return this.native.trackCount();
821
+ }
822
+
823
+ /** Number of clips in the project. */
824
+ clipCount(): number {
825
+ return this.native.clipCount();
826
+ }
827
+
828
+ /** Number of audio sources registered on the project. */
829
+ sourceCount(): number {
830
+ return this.native.sourceCount();
831
+ }
832
+
833
+ /** Number of tempo-map segments on the project. */
834
+ tempoSegmentCount(): number {
835
+ return this.native.tempoSegmentCount();
836
+ }
837
+
838
+ /** Number of time-signature segments on the project. */
839
+ timeSignatureCount(): number {
840
+ return this.native.timeSignatureCount();
841
+ }
842
+
843
+ /** Replace the project's tempo map with the given segments. */
844
+ setTempoSegments(segments: ReadonlyArray<ProjectTempoSegment>): void {
845
+ this.native.setTempoSegments(segments);
846
+ }
847
+
848
+ /** Replace the project's time-signature map with the given segments. */
849
+ setTimeSignatures(segments: ReadonlyArray<ProjectTimeSignatureSegment>): void {
850
+ this.native.setTimeSignatures(segments);
851
+ }
852
+
853
+ /**
854
+ * Compile diagnostics produced by the most recent bounce on this project
855
+ * (e.g. MIDI clips rendering silently without a bound instrument). When no
856
+ * bounce has run, the result is empty with `hasTimeline` set.
857
+ */
858
+ lastBounceCompileResult(): ProjectCompileResult {
859
+ return this.native.lastBounceCompileResult();
860
+ }
861
+
862
+ /** Release the underlying WASM object. Safe to call only once. */
863
+ delete(): void {
864
+ this.native.delete();
865
+ }
866
+
867
+ /** Alias for {@link delete}, provided for cross-binding (Node) compatibility. */
868
+ destroy(): void {
869
+ this.delete();
870
+ }
871
+ }