@libraz/libsonare 1.3.3 → 1.4.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@libraz/libsonare",
3
- "version": "1.3.3",
3
+ "version": "1.4.1",
4
4
  "type": "module",
5
5
  "packageManager": "yarn@4.15.0",
6
6
  "description": "Audio analysis library for music information retrieval",
package/src/codes.ts CHANGED
@@ -52,5 +52,10 @@ export function meterTapCode(tap: MeterTap | number): number {
52
52
  }
53
53
 
54
54
  export function sendTimingCode(timing: SendTiming | number): number {
55
- return timing === 'preFader' || timing === 0 ? 0 : 1;
55
+ // Mirrors SonareSendTiming: post-fader is 0 (so an omitted/zeroed value is
56
+ // post-fader), pre-fader is 1. A raw number is passed through as the C ABI int.
57
+ if (typeof timing === 'number') {
58
+ return timing;
59
+ }
60
+ return timing === 'preFader' ? 1 : 0;
56
61
  }
@@ -16,13 +16,15 @@ import type {
16
16
  PairProcessor,
17
17
  RealtimeVoiceChangerConfigInput,
18
18
  SoloProcessor,
19
+ SpectralEditOptions,
20
+ SpectralRegionOp,
19
21
  StereoAnalysis,
20
22
  StreamingPlatform,
21
23
  } from './public_types';
22
24
  import type { ProgressCallback } from './sonare.js';
23
25
  import { RealtimeVoiceChanger } from './streaming_mixing';
24
26
  import type { ValidateOptions } from './validation';
25
- import { assertSamples } from './validation';
27
+ import { assertSampleRate, assertSamples } from './validation';
26
28
 
27
29
  function requireModule() {
28
30
  return getSonareModule();
@@ -339,6 +341,31 @@ export function normalize(
339
341
  return requireModule().normalize(samples, sampleRate, targetDb);
340
342
  }
341
343
 
344
+ /**
345
+ * Apply region-based spectral edits (gain/attenuate/mute/heal) to mono audio.
346
+ *
347
+ * Each op is a time x frequency rectangle applied in array order over a single
348
+ * STFT buffer, so a later op observes the result of earlier ops. The output has
349
+ * the same length and sample rate as the input; an empty `ops` list is an
350
+ * identity transform (within the iSTFT's own tolerance).
351
+ *
352
+ * @param samples - Audio samples (mono, float32)
353
+ * @param sampleRate - Sample rate in Hz
354
+ * @param ops - Region edit ops applied in order ({@link SpectralRegionOp})
355
+ * @param options - STFT + heal configuration ({@link SpectralEditOptions})
356
+ * @returns Edited audio
357
+ */
358
+ export function spectralEdit(
359
+ samples: Float32Array,
360
+ sampleRate: number,
361
+ ops: SpectralRegionOp[] = [],
362
+ options: SpectralEditOptions & ValidateOptions = {},
363
+ ): Float32Array {
364
+ assertSamples('spectralEdit', samples, options.validate !== false);
365
+ assertSampleRate('spectralEdit', sampleRate);
366
+ return requireModule().spectralEdit(samples, sampleRate, ops, options as Record<string, unknown>);
367
+ }
368
+
342
369
  /**
343
370
  * Apply mastering loudness normalization with a true-peak ceiling.
344
371
  *
@@ -358,11 +385,16 @@ export function mastering(
358
385
  options.targetLufs ?? -14.0,
359
386
  options.ceilingDb ?? -1.0,
360
387
  options.truePeakOversample ?? 4,
388
+ options.releaseMs ?? 0, // 0 => library default (50 ms)
389
+ options.applyGainAtInputRate ?? false,
361
390
  );
362
391
  }
363
392
 
364
393
  export function masteringProcessorNames(): SoloProcessor[] {
365
- return requireModule().masteringProcessorNames() as SoloProcessor[];
394
+ // embind hands back a vector whose constructor is not this realm's Array, so the
395
+ // result is not structured-cloneable (breaks postMessage to a Worker).
396
+ // Array.from() re-roots it as a plain Array. Same for the sibling *Names() below.
397
+ return Array.from(requireModule().masteringProcessorNames()) as SoloProcessor[];
366
398
  }
367
399
 
368
400
  /**
@@ -387,21 +419,98 @@ export function masteringInsertNames(): string[] {
387
419
  * @param name - Insert processor name (see {@link masteringInsertNames}).
388
420
  */
389
421
  export function masteringInsertParamNames(name: string): string[] {
390
- return (
391
- requireModule() as unknown as { masteringInsertParamNames: (name: string) => string[] }
392
- ).masteringInsertParamNames(name);
422
+ return Array.from(
423
+ (
424
+ requireModule() as unknown as { masteringInsertParamNames: (name: string) => string[] }
425
+ ).masteringInsertParamNames(name),
426
+ );
427
+ }
428
+
429
+ /** One realtime-automatable parameter of an insert processor. */
430
+ export interface MasteringInsertParamInfo {
431
+ /** JSON-key parameter name, as used in scene insert params. */
432
+ name: string;
433
+ /** Integer param id for realtime automation lanes / MIDI-CC binding. */
434
+ id: number;
435
+ /** Whether the param can be changed live from the audio thread. */
436
+ rtSafe: boolean;
437
+ }
438
+
439
+ /**
440
+ * Returns the realtime-automatable parameter descriptors for an insert / FX
441
+ * processor: each entry maps a JSON-key parameter name to the integer id used by
442
+ * realtime automation and reports whether it is realtime-safe. Unlike
443
+ * {@link masteringInsertParamNames} (every construction key), this lists only the
444
+ * realtime-controllable subset — the keys accepted by
445
+ * {@link RealtimeEngine.setTrackStripInsertParamByName}. Returns an empty array
446
+ * for an unknown name or a processor with no automatable parameters.
447
+ *
448
+ * @param name - Insert processor name (see {@link masteringInsertNames}).
449
+ */
450
+ export function masteringInsertParamInfo(name: string): MasteringInsertParamInfo[] {
451
+ const json = (
452
+ requireModule() as unknown as { masteringInsertParamInfo: (name: string) => string }
453
+ ).masteringInsertParamInfo(name);
454
+ return JSON.parse(json) as MasteringInsertParamInfo[];
455
+ }
456
+
457
+ /**
458
+ * How a processor handles a buffer with more than two channels (a surround
459
+ * bed). "multichannel" processes every plane in one call; "stereoPairOnly"
460
+ * operates on the front L/R pair and passes any surround planes through dry.
461
+ * "perChannel"/"passthrough" are reserved and unused by the current catalog.
462
+ */
463
+ export type MasteringChannelPolicy =
464
+ | 'multichannel'
465
+ | 'stereoPairOnly'
466
+ | 'perChannel'
467
+ | 'passthrough';
468
+
469
+ /** One processor's realtime/offline/pair classification in the catalog. */
470
+ export interface MasteringProcessorCatalogEntry {
471
+ /** Processor id (the name used for scene inserts / named processors). */
472
+ id: string;
473
+ /**
474
+ * Primary classification, by precedence pair > realtime > offline: "pair" for
475
+ * two-input match.* processors, "realtime" for ids that build as a realtime
476
+ * scene insert, "offline" for whole-file-only processors.
477
+ */
478
+ kind: 'realtime' | 'offline' | 'pair';
479
+ /** True exactly for ids that always succeed as a realtime scene insert. */
480
+ realtimeInsertable: boolean;
481
+ /** True for processors with no mono implementation (stereo-only). */
482
+ stereoOnly: boolean;
483
+ /**
484
+ * How the mixer wraps the processor on a >2-channel (surround) bus insert:
485
+ * "multichannel" (one full-buffer call) or "stereoPairOnly" (front L/R pair,
486
+ * surround planes passed through dry).
487
+ */
488
+ channelPolicy: MasteringChannelPolicy;
489
+ }
490
+
491
+ /**
492
+ * Returns the machine-readable classification catalog for every named processor
493
+ * id, merging the offline registry, the realtime insert factory, and the pair
494
+ * registry. Lets a host filter a processor picker by realtime insertability
495
+ * instead of offering ids the realtime strip would reject.
496
+ */
497
+ export function masteringProcessorCatalog(): MasteringProcessorCatalogEntry[] {
498
+ const json = (
499
+ requireModule() as unknown as { masteringProcessorCatalog: () => string }
500
+ ).masteringProcessorCatalog();
501
+ return JSON.parse(json) as MasteringProcessorCatalogEntry[];
393
502
  }
394
503
 
395
504
  export function masteringPairProcessorNames(): PairProcessor[] {
396
- return requireModule().masteringPairProcessorNames() as PairProcessor[];
505
+ return Array.from(requireModule().masteringPairProcessorNames()) as PairProcessor[];
397
506
  }
398
507
 
399
508
  export function masteringPairAnalysisNames(): PairAnalysis[] {
400
- return requireModule().masteringPairAnalysisNames() as PairAnalysis[];
509
+ return Array.from(requireModule().masteringPairAnalysisNames()) as PairAnalysis[];
401
510
  }
402
511
 
403
512
  export function masteringStereoAnalysisNames(): StereoAnalysis[] {
404
- return requireModule().masteringStereoAnalysisNames() as StereoAnalysis[];
513
+ return Array.from(requireModule().masteringStereoAnalysisNames()) as StereoAnalysis[];
405
514
  }
406
515
 
407
516
  export function masteringProcess(
@@ -843,7 +952,7 @@ export function masteringChainStereoWithProgress(
843
952
  * @returns Preset names in display order (e.g. "pop", "edm", "aiMusic")
844
953
  */
845
954
  export function masteringPresetNames(): MasteringPreset[] {
846
- return requireModule().masteringPresetNames() as MasteringPreset[];
955
+ return Array.from(requireModule().masteringPresetNames()) as MasteringPreset[];
847
956
  }
848
957
 
849
958
  /**
@@ -932,7 +1041,7 @@ export function masterAudioStereoWithProgress(
932
1041
  }
933
1042
 
934
1043
  export function mixingScenePresetNames(): string[] {
935
- return requireModule().mixingScenePresetNames();
1044
+ return Array.from(requireModule().mixingScenePresetNames());
936
1045
  }
937
1046
 
938
1047
  /**
@@ -204,15 +204,19 @@ export function analyzeSections(
204
204
  if ((options.minSectionSec ?? 4.0) <= 0) {
205
205
  throw new RangeError('analyzeSections: minSectionSec must be positive');
206
206
  }
207
- return requireModule()
208
- .analyzeSections(
209
- samples,
210
- sampleRate,
211
- options.nFft ?? 2048,
212
- options.hopLength ?? 512,
213
- options.minSectionSec ?? 4.0,
214
- )
215
- .map((s) => ({ ...s, type: s.type as SectionType }));
207
+ // The embind value marshalling returns an array whose constructor is not this
208
+ // realm's Array; chaining .map() onto it propagates that constructor via
209
+ // Symbol.species, leaving a result that structuredClone (and so postMessage to
210
+ // a Worker) rejects with "could not be cloned". Array.from() re-roots it as a
211
+ // plain Array before mapping.
212
+ const sections = requireModule().analyzeSections(
213
+ samples,
214
+ sampleRate,
215
+ options.nFft ?? 2048,
216
+ options.hopLength ?? 512,
217
+ options.minSectionSec ?? 4.0,
218
+ );
219
+ return Array.from(sections, (s) => ({ ...s, type: s.type as SectionType }));
216
220
  }
217
221
 
218
222
  /** Options for {@link analyzeMelody}. All fields are optional. */
@@ -257,11 +261,21 @@ export function analyzeMelody(
257
261
  const fmin = options.fmin ?? 65.0;
258
262
  const fmax = options.fmax ?? 2093.0;
259
263
  validateFrequencyBounds('analyzeMelody', fmin, fmax);
264
+ // The melody tracker's fmin is a YIN pitch floor: 0 is meaningless, and the
265
+ // flat C ABI (sonare_analyze_melody) rejects it. validateFrequencyBounds only
266
+ // guards fmin >= 0, so enforce strict positivity here for parity.
267
+ if (fmin <= 0) {
268
+ throw new RangeError('analyzeMelody: fmin must be positive');
269
+ }
260
270
  validatePositiveIntegers('analyzeMelody', {
261
271
  frameLength: options.frameLength ?? 2048,
262
272
  hopLength: options.hopLength ?? 256,
263
273
  });
264
- assertFiniteScalar('analyzeMelody', options.threshold ?? 0.1, 'threshold');
274
+ const threshold = options.threshold ?? 0.1;
275
+ assertFiniteScalar('analyzeMelody', threshold, 'threshold');
276
+ if (threshold <= 0) {
277
+ throw new RangeError('analyzeMelody: threshold must be positive');
278
+ }
265
279
  return requireModule().analyzeMelody(
266
280
  samples,
267
281
  sampleRate,
@@ -371,7 +385,9 @@ export function tempogramRatio(
371
385
  * Measure loudness (EBU R128 / ITU-R BS.1770).
372
386
  *
373
387
  * @param samples - Audio samples (mono, float32)
374
- * @param sampleRate - Sample rate in Hz (default: 22050)
388
+ * @param sampleRate - Sample rate in Hz. The default (22050) is non-standard for
389
+ * audio; pass the buffer's actual rate, as K-weighting is sample-rate
390
+ * dependent and a wrong rate yields wrong loudness.
375
391
  * @returns Loudness measurement result
376
392
  */
377
393
  export function lufs(
@@ -388,7 +404,8 @@ export function lufs(
388
404
  * Compute the momentary loudness (LUFS) over time.
389
405
  *
390
406
  * @param samples - Audio samples (mono, float32)
391
- * @param sampleRate - Sample rate in Hz (default: 22050)
407
+ * @param sampleRate - Sample rate in Hz. The default (22050) is non-standard and
408
+ * K-weighting is sample-rate dependent; pass the buffer's actual rate.
392
409
  * @returns Momentary LUFS values over time
393
410
  */
394
411
  export function momentaryLufs(
@@ -405,7 +422,8 @@ export function momentaryLufs(
405
422
  * Compute the short-term loudness (LUFS) over time.
406
423
  *
407
424
  * @param samples - Audio samples (mono, float32)
408
- * @param sampleRate - Sample rate in Hz (default: 22050)
425
+ * @param sampleRate - Sample rate in Hz. The default (22050) is non-standard and
426
+ * K-weighting is sample-rate dependent; pass the buffer's actual rate.
409
427
  * @returns Short-term LUFS values over time
410
428
  */
411
429
  export function shortTermLufs(
@@ -218,6 +218,10 @@ export function hpssWithResidual(
218
218
  * Channel-weighted multichannel integrated loudness + LRA (ITU-R BS.1770 /
219
219
  * EBU R128) from an interleaved buffer of `frames * channels` samples. The
220
220
  * per-channel frame count is derived from the buffer length and `channels`.
221
+ *
222
+ * Pass the buffer's actual `sampleRate`: the default (22050) is non-standard for
223
+ * audio, and K-weighting is sample-rate dependent, so a wrong rate yields wrong
224
+ * loudness.
221
225
  */
222
226
  export function lufsInterleaved(
223
227
  samples: Float32Array,
@@ -231,7 +235,9 @@ export function lufsInterleaved(
231
235
  }
232
236
 
233
237
  /**
234
- * Standards-compliant EBU R128 loudness range (LRA) in LU.
238
+ * Standards-compliant EBU R128 loudness range (LRA) in LU. Pass the buffer's
239
+ * actual `sampleRate`: the default (22050) is non-standard and K-weighting is
240
+ * sample-rate dependent.
235
241
  */
236
242
  export function ebur128LoudnessRange(samples: Float32Array, sampleRate = 22050): number {
237
243
  return requireModule().ebur128LoudnessRange(samples, sampleRate);
package/src/index.ts CHANGED
@@ -43,6 +43,9 @@ export type {
43
43
  DereverbClassicalOptions,
44
44
  DynamicsResult,
45
45
  GateOptions,
46
+ MasteringChannelPolicy,
47
+ MasteringInsertParamInfo,
48
+ MasteringProcessorCatalogEntry,
46
49
  TransientShaperOptions,
47
50
  TrimSilenceMode,
48
51
  TrimSilenceOptions,
@@ -67,6 +70,7 @@ export {
67
70
  masteringDynamicsGate,
68
71
  masteringDynamicsTransientShaper,
69
72
  masteringInsertNames,
73
+ masteringInsertParamInfo,
70
74
  masteringInsertParamNames,
71
75
  masteringPairAnalysisNames,
72
76
  masteringPairAnalyze,
@@ -74,6 +78,7 @@ export {
74
78
  masteringPairProcessorNames,
75
79
  masteringPresetNames,
76
80
  masteringProcess,
81
+ masteringProcessorCatalog,
77
82
  masteringProcessorNames,
78
83
  masteringProcessStereo,
79
84
  masteringRepairDeclick,
@@ -95,6 +100,7 @@ export {
95
100
  pitchCorrectToMidi,
96
101
  pitchCorrectToMidiTimevarying,
97
102
  pitchShift,
103
+ spectralEdit,
98
104
  timeStretch,
99
105
  voiceChange,
100
106
  voiceChangeRealtime,
@@ -245,6 +251,7 @@ export type {
245
251
  ProjectLoopMode,
246
252
  ProjectLoopRecordingDesc,
247
253
  ProjectLoopRecordingResult,
254
+ ProjectMarker,
248
255
  ProjectMidiClipResult,
249
256
  ProjectMidiEvent,
250
257
  ProjectNotePairValidation,
@@ -268,6 +275,7 @@ export type {
268
275
  } from './project';
269
276
  export {
270
277
  EXPECTED_PROJECT_ABI_VERSION,
278
+ MarkerKind,
271
279
  Project,
272
280
  projectAbiVersion,
273
281
  SYNTH_BODY_TYPES,
@@ -348,6 +356,10 @@ export type {
348
356
  Section,
349
357
  SendTiming,
350
358
  SoloProcessor,
359
+ SpectralEditMode,
360
+ SpectralEditOptions,
361
+ SpectralEditWindow,
362
+ SpectralRegionOp,
351
363
  StereoAnalysis,
352
364
  StftPowerResult,
353
365
  StftResult,
@@ -410,10 +422,12 @@ export type {
410
422
  EngineGraphSpec,
411
423
  EngineMarker,
412
424
  EngineMeterTelemetry,
425
+ EngineMeterTelemetryWide,
413
426
  EngineMetronomeConfig,
414
427
  EngineMidiClipSchedule,
415
428
  EngineMidiEvent,
416
429
  EngineParameterInfo,
430
+ EngineScopeTelemetry,
417
431
  EngineTelemetry,
418
432
  EngineTempoSegment,
419
433
  EngineTimeSignatureSegment,
package/src/mixer.ts CHANGED
@@ -15,6 +15,7 @@ import type {
15
15
  PanLaw,
16
16
  PanMode,
17
17
  SendTiming,
18
+ SurroundPan,
18
19
  } from './public_types';
19
20
 
20
21
  export interface MixerRealtimeBuffer {
@@ -348,6 +349,14 @@ export class Mixer {
348
349
  this.mixer.setDualPan(stripIndex, leftPan, rightPan);
349
350
  }
350
351
 
352
+ /**
353
+ * Set the strip's surround pan position, used when it feeds a >2-channel bus.
354
+ * Stored on the scene; inert until the surround DSP path applies it.
355
+ */
356
+ setSurroundPan(stripIndex: number, pan: SurroundPan): void {
357
+ this.mixer.setSurroundPan(stripIndex, pan);
358
+ }
359
+
351
360
  /**
352
361
  * Add a send to a strip after construction.
353
362
  *
package/src/project.ts CHANGED
@@ -25,6 +25,34 @@ export interface ProjectBounceOptions {
25
25
  instrumentLatencySamples?: number;
26
26
  }
27
27
 
28
+ /**
29
+ * Marker kind ordinals. Mirrors `SonareMarkerKind` in `src/sonare_c_types.h`;
30
+ * the values are part of the ABI and must not be renumbered.
31
+ */
32
+ export const MarkerKind = {
33
+ marker: 0,
34
+ text: 1,
35
+ lyric: 2,
36
+ cuePoint: 3,
37
+ keySignature: 4,
38
+ } as const;
39
+
40
+ /** A project timeline marker with its kind and (for key signatures) the key. */
41
+ export interface ProjectMarker {
42
+ /** Stable marker id (0 when allocating a new id via {@link Project.setMarkerEx}). */
43
+ id: number;
44
+ /** Marker position in PPQ (quarter notes). */
45
+ ppq: number;
46
+ /** Marker label. */
47
+ name?: string;
48
+ /** {@link MarkerKind} ordinal (default 0 = marker). */
49
+ kind?: number;
50
+ /** Key signature only: -7..7 (sharps positive). */
51
+ keyFifths?: number;
52
+ /** Key signature only: false = major, true = minor. */
53
+ keyMinor?: boolean;
54
+ }
55
+
28
56
  /** Oscillator waveform for the built-in synth. */
29
57
  export type BuiltinSynthWaveform =
30
58
  | 'sine'
@@ -544,6 +572,10 @@ interface WasmProject {
544
572
  setWarpMap: (map: ProjectWarpMapDesc) => void;
545
573
  removeWarpMap: (warpRefId: number) => void;
546
574
  setTrackMidiDestination: (trackId: number, destinationId: number) => void;
575
+ setTrackGain: (trackId: number, gain: number) => void;
576
+ setTrackMute: (trackId: number, mute: boolean) => void;
577
+ setTrackSolo: (trackId: number, solo: boolean) => void;
578
+ setTrackPan: (trackId: number, pan: number) => void;
547
579
  undo: () => void;
548
580
  redo: () => void;
549
581
  setMidiEvents: (
@@ -594,7 +626,12 @@ interface WasmProject {
594
626
  activeTakeId: number,
595
627
  ) => void;
596
628
  setClipCompSegments: (clipId: number, segments: ReadonlyArray<ProjectClipCompSegment>) => void;
597
- setClipLoop: (clipId: number, loopMode: number, loopLengthPpq: number) => void;
629
+ setClipLoop: (
630
+ clipId: number,
631
+ loopMode: number,
632
+ loopLengthPpq: number,
633
+ loopCrossfadePpq: number,
634
+ ) => void;
598
635
  setClipSource: (clipId: number, sourceId: number) => void;
599
636
  duplicateClip: (clipId: number, newStartPpq: number) => number;
600
637
  removeTrack: (trackId: number) => void;
@@ -627,6 +664,9 @@ interface WasmProject {
627
664
  getSampleRate: () => number;
628
665
  setMixerSceneJson: (sceneJson: string) => void;
629
666
  setMarker: (markerId: number, ppq: number, name: string) => number;
667
+ setMarkerEx: (marker: ProjectMarker) => number;
668
+ markerByIndex: (index: number) => ProjectMarker;
669
+ markerCount: () => number;
630
670
  trackCount: () => number;
631
671
  sourceCount: () => number;
632
672
  tempoSegmentCount: () => number;
@@ -792,7 +832,8 @@ export function projectAbiVersion(): number {
792
832
  * names instead of hardcoding magic strings.
793
833
  */
794
834
  export function synthPresetNames(): string[] {
795
- return projectModule().synthPresetNames();
835
+ // Array.from re-roots embind's vector as a plain, structured-cloneable Array.
836
+ return Array.from(projectModule().synthPresetNames());
796
837
  }
797
838
 
798
839
  /**
@@ -802,7 +843,11 @@ export function synthPresetNames(): string[] {
802
843
  * throw.
803
844
  */
804
845
  export function synthPresetPatch(name: string): SynthPatch {
805
- return projectModule().synthPresetPatch(name);
846
+ // embind returns a val::object whose constructor is not this realm's Object, so a
847
+ // direct return is not structured-cloneable (breaks postMessage to a Worker).
848
+ // Spreading into a fresh literal re-roots it as a plain Object; modRoutings is
849
+ // already a plain member array.
850
+ return { ...projectModule().synthPresetPatch(name) };
806
851
  }
807
852
 
808
853
  export function synthEnumTables(): SynthEnumTables {
@@ -1166,6 +1211,26 @@ export class Project {
1166
1211
  this.native.setTrackMidiDestination(trackId, destinationId);
1167
1212
  }
1168
1213
 
1214
+ /** Set a track's linear playback gain (1.0 = unity; >= 0) via an undoable edit. */
1215
+ setTrackGain(trackId: number, gain: number): void {
1216
+ this.native.setTrackGain(trackId, gain);
1217
+ }
1218
+
1219
+ /** Set a track's mute flag via an undoable edit (a muted track is silent). */
1220
+ setTrackMute(trackId: number, mute: boolean): void {
1221
+ this.native.setTrackMute(trackId, mute);
1222
+ }
1223
+
1224
+ /** Set a track's solo flag via an undoable edit (when any track is soloed, only soloed tracks sound). */
1225
+ setTrackSolo(trackId: number, solo: boolean): void {
1226
+ this.native.setTrackSolo(trackId, solo);
1227
+ }
1228
+
1229
+ /** Set a track's stereo balance in [-1, +1] (0 = center) via an undoable edit. */
1230
+ setTrackPan(trackId: number, pan: number): void {
1231
+ this.native.setTrackPan(trackId, pan);
1232
+ }
1233
+
1169
1234
  /** Undo the most recent edit. */
1170
1235
  undo(): void {
1171
1236
  this.native.undo();
@@ -1409,9 +1474,23 @@ export class Project {
1409
1474
  this.native.setClipCompSegments(clipId, segments);
1410
1475
  }
1411
1476
 
1412
- /** Set a clip's loop mode + loop length in PPQ (undoable). */
1413
- setClipLoop(clipId: number, loopMode: ProjectLoopMode, loopLengthPpq = 0): void {
1414
- this.native.setClipLoop(clipId, projectLoopModeValue(loopMode), loopLengthPpq);
1477
+ /**
1478
+ * Set a clip's loop mode + loop length in PPQ (undoable). `loopCrossfadePpq`
1479
+ * is an optional equal-power crossfade at the loop seam (PPQ, finite and >= 0;
1480
+ * 0 = hard loop); the engine clamps it to the clip's pre-roll and half the loop.
1481
+ */
1482
+ setClipLoop(
1483
+ clipId: number,
1484
+ loopMode: ProjectLoopMode,
1485
+ loopLengthPpq = 0,
1486
+ loopCrossfadePpq = 0,
1487
+ ): void {
1488
+ this.native.setClipLoop(
1489
+ clipId,
1490
+ projectLoopModeValue(loopMode),
1491
+ loopLengthPpq,
1492
+ loopCrossfadePpq,
1493
+ );
1415
1494
  }
1416
1495
 
1417
1496
  /** Rebind a clip to a different (already-registered) source (undoable). */
@@ -1527,6 +1606,25 @@ export class Project {
1527
1606
  return this.native.setMarker(markerId, ppq, name);
1528
1607
  }
1529
1608
 
1609
+ /**
1610
+ * Add or replace a marker from a full {@link ProjectMarker}, including its
1611
+ * {@link MarkerKind} and (for key signatures) the key. Pass `id` 0 to allocate
1612
+ * a new id; returns the stable marker id.
1613
+ */
1614
+ setMarkerEx(marker: ProjectMarker): number {
1615
+ return this.native.setMarkerEx(marker);
1616
+ }
1617
+
1618
+ /** Read a project marker by index (0-based, in stored order). */
1619
+ markerByIndex(index: number): ProjectMarker {
1620
+ return this.native.markerByIndex(index);
1621
+ }
1622
+
1623
+ /** Number of markers in the project. */
1624
+ markerCount(): number {
1625
+ return this.native.markerCount();
1626
+ }
1627
+
1530
1628
  /** Number of tracks in the project. */
1531
1629
  trackCount(): number {
1532
1630
  return this.native.trackCount();
@@ -355,6 +355,10 @@ export interface MasteringOptions {
355
355
  ceilingDb?: number;
356
356
  /** Oversampling factor used for peak estimation. Default 4. */
357
357
  truePeakOversample?: number;
358
+ /** Post true-peak limiter release in ms. Default 0 => library default (50 ms). */
359
+ releaseMs?: number;
360
+ /** Apply the static loudness gain at the input (pre-oversample) rate. Default false. */
361
+ applyGainAtInputRate?: boolean;
358
362
  }
359
363
 
360
364
  /** Options for `noteStretch`. All fields are optional. */
@@ -367,6 +371,40 @@ export interface NoteStretchOptions {
367
371
  stretchRatio?: number;
368
372
  }
369
373
 
374
+ /** How a `spectralEdit` region op modifies the masked bins. */
375
+ export type SpectralEditMode = 'gain' | 'attenuate' | 'mute' | 'heal';
376
+
377
+ /** Analysis/synthesis window used by `spectralEdit`. */
378
+ export type SpectralEditWindow = 'hann' | 'hamming' | 'blackman' | 'rectangular';
379
+
380
+ /** One time x frequency rectangle edit op for `spectralEdit`. */
381
+ export interface SpectralRegionOp {
382
+ /** Region time start (input samples); clamped to [0, length]. Default 0. */
383
+ startSample?: number;
384
+ /** Region time end, exclusive (input samples); clamped to [0, length]. Default = signal length. */
385
+ endSample?: number;
386
+ /** Region frequency low edge in Hz; clamped to [0, nyquist]. Default 0. */
387
+ lowHz?: number;
388
+ /** Region frequency high edge in Hz; <=0 or >= nyquist means nyquist. Default 0. */
389
+ highHz?: number;
390
+ /** Linear gain in dB for 'gain'/'attenuate'; ignored by 'mute'/'heal'. Default 0. */
391
+ gainDb?: number;
392
+ /** Edit mode. Default 'gain'. */
393
+ mode?: SpectralEditMode;
394
+ }
395
+
396
+ /** STFT + heal parameters for `spectralEdit`. All fields are optional. */
397
+ export interface SpectralEditOptions {
398
+ /** FFT size; must be a power of two (>= 2). Default 2048. */
399
+ nFft?: number;
400
+ /** Hop length; must satisfy 0 < hop <= nFft/2. Default 512. */
401
+ hopLength?: number;
402
+ /** Analysis + synthesis window. Default 'hann'. */
403
+ window?: SpectralEditWindow;
404
+ /** Neighbour frames each side used by 'heal' (>= 1). Default 2. */
405
+ healRadiusFrames?: number;
406
+ }
407
+
370
408
  /**
371
409
  * Detected beat
372
410
  */
@@ -655,6 +693,24 @@ export type MasteringProcessorParams = Record<string, number | boolean>;
655
693
 
656
694
  export type PanMode = 'balance' | 'stereoPan' | 'stereo-pan' | 'dualPan' | 'dual-pan' | number;
657
695
 
696
+ /**
697
+ * Surround pan position for a strip feeding a >2-channel bus. Phase 1 honors
698
+ * `azimuth`/`divergence`/`lfe`; `elevation`/`distance` are reserved. All fields
699
+ * are optional and default to a centered point source.
700
+ */
701
+ export interface SurroundPan {
702
+ /** -180..180 deg, 0 = front-center, positive = right. */
703
+ azimuth?: number;
704
+ /** Reserved (no height beds in phase 1). */
705
+ elevation?: number;
706
+ /** 0 = point source, 1 = spread across the front. */
707
+ divergence?: number;
708
+ /** 0..1 scalar send into the LFE plane. */
709
+ lfe?: number;
710
+ /** Reserved (focus/spread), defaults to 1. */
711
+ distance?: number;
712
+ }
713
+
658
714
  export interface MixOptions {
659
715
  inputTrimDb?: number | number[];
660
716
  faderDb?: number | number[];