@libraz/libsonare 1.6.0 → 1.7.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 (53) hide show
  1. package/README.md +2 -2
  2. package/dist/analysis.d.ts +139 -11
  3. package/dist/analysis.js +162 -42
  4. package/dist/analysis.js.map +1 -1
  5. package/dist/index.d.ts +211 -39
  6. package/dist/index.js +501 -142
  7. package/dist/index.js.map +1 -1
  8. package/dist/schemas/realtime-voice-changer-preset.schema.json +2 -2
  9. package/dist/sonare-analysis.js +1 -1
  10. package/dist/sonare-analysis.wasm +0 -0
  11. package/dist/sonare.js +1 -1
  12. package/dist/sonare.wasm +0 -0
  13. package/dist/worker.js +56 -29
  14. package/dist/worker.js.map +1 -1
  15. package/dist/worklet.d.ts +67 -18
  16. package/dist/worklet.js +193 -42
  17. package/dist/worklet.js.map +1 -1
  18. package/package.json +3 -3
  19. package/src/_chain_config.ts +6 -1
  20. package/src/analysis_helpers.ts +34 -29
  21. package/src/audio.ts +7 -2
  22. package/src/codes.ts +47 -11
  23. package/src/effects_mastering.ts +5 -0
  24. package/src/effects_transform.ts +202 -22
  25. package/src/feature_music.ts +8 -1
  26. package/src/feature_spectral.ts +64 -2
  27. package/src/feature_spectrogram.ts +44 -3
  28. package/src/index.ts +28 -3
  29. package/src/mastering_core.ts +70 -0
  30. package/src/metering.ts +18 -0
  31. package/src/mixer.ts +3 -3
  32. package/src/project.ts +6 -0
  33. package/src/project_class.ts +143 -16
  34. package/src/project_internal.ts +14 -5
  35. package/src/project_types.ts +50 -9
  36. package/src/public_types_mastering.ts +9 -0
  37. package/src/public_types_mixing.ts +16 -0
  38. package/src/public_types_realtime.ts +23 -5
  39. package/src/public_types_spectral.ts +16 -2
  40. package/src/quick_analysis.ts +21 -3
  41. package/src/realtime_engine.ts +14 -3
  42. package/src/sonare.js.d.ts +87 -0
  43. package/src/stream_analyzer.ts +6 -1
  44. package/src/web_midi.ts +10 -7
  45. package/src/worklet/engine-node.ts +54 -19
  46. package/src/worklet/engine-parameter-facade.ts +30 -1
  47. package/src/worklet/engine-processor.ts +52 -20
  48. package/src/worklet/engine-register.ts +1 -1
  49. package/src/worklet/engine-strips.ts +2 -2
  50. package/src/worklet/engine.ts +10 -0
  51. package/src/worklet/guards.ts +99 -7
  52. package/src/worklet/messages.ts +41 -1
  53. package/src/worklet/protocol.ts +4 -0
@@ -1,5 +1,6 @@
1
1
  import {
2
2
  assertProjectMidiEvents,
3
+ projectAutomationTargetKindValue,
3
4
  projectLoopModeValue,
4
5
  projectMidi1Event,
5
6
  projectModule,
@@ -13,8 +14,10 @@ import type {
13
14
  ExternalSeparatedStemImportResult,
14
15
  MidiCcLearnOptions,
15
16
  ProjectAssistSidecar,
17
+ ProjectAssistSidecarInput,
16
18
  ProjectAutomationLaneDesc,
17
19
  ProjectAutomationPoint,
20
+ ProjectAutomationTargetKind,
18
21
  ProjectBounceOptions,
19
22
  ProjectChordSymbol,
20
23
  ProjectClip,
@@ -49,6 +52,41 @@ import type {
49
52
  SynthPatch,
50
53
  } from './project_types';
51
54
 
55
+ function validateAssistSidecarUint32(value: unknown, field: string): number {
56
+ if (
57
+ typeof value !== 'number' ||
58
+ !Number.isSafeInteger(value) ||
59
+ value < 0 ||
60
+ value > 0xffff_ffff
61
+ ) {
62
+ throw new RangeError(`Project.setAssistSidecar: ${field} must be a uint32`);
63
+ }
64
+ return value;
65
+ }
66
+
67
+ function validateAssistSidecarPpq(value: unknown, field: string): number {
68
+ if (typeof value !== 'number' || !Number.isFinite(value) || value < 0) {
69
+ throw new RangeError(
70
+ `Project.setAssistSidecar: ${field} must be a finite, non-negative number`,
71
+ );
72
+ }
73
+ return value;
74
+ }
75
+
76
+ function validateAssistSidecarPayload(value: unknown): Uint8Array {
77
+ if (!(value instanceof Uint8Array)) {
78
+ throw new TypeError('Project.setAssistSidecar: payload must be a Uint8Array');
79
+ }
80
+ return value;
81
+ }
82
+
83
+ function validateAssistSidecarModuleId(value: unknown): string {
84
+ if (typeof value !== 'string' || value.length === 0) {
85
+ throw new TypeError('Project.setAssistSidecar: moduleId must be a non-empty string');
86
+ }
87
+ return value;
88
+ }
89
+
52
90
  /**
53
91
  * Headless DAW project (control-thread-only arrangement model).
54
92
  *
@@ -82,6 +120,11 @@ export class Project {
82
120
  this.native = new (projectModule().Project)();
83
121
  }
84
122
 
123
+ /** Create a new empty project. */
124
+ static create(): Project {
125
+ return new Project();
126
+ }
127
+
85
128
  /** Pack a MIDI 1.0 note-on event accepted by {@link setMidiEvents}. */
86
129
  static midiNoteOn(
87
130
  ppq: number,
@@ -462,6 +505,19 @@ export class Project {
462
505
  this.native.setMaxUndoDepth(depth);
463
506
  }
464
507
 
508
+ /** Set the combined undo/redo history byte cap. Zero disables retention. */
509
+ setMaxHistoryBytes(bytes: number): void {
510
+ if (typeof bytes !== 'number') {
511
+ throw new TypeError('Project.setMaxHistoryBytes: bytes must be a number');
512
+ }
513
+ if (!Number.isFinite(bytes) || !Number.isInteger(bytes) || bytes < 0 || bytes > 0xffff_ffff) {
514
+ throw new RangeError(
515
+ 'Project.setMaxHistoryBytes: bytes must be a finite integer in the uint32 range',
516
+ );
517
+ }
518
+ this.native.setMaxHistoryBytes(bytes);
519
+ }
520
+
465
521
  /** Replace a MIDI clip's entire event list. */
466
522
  setMidiEvents(
467
523
  clipId: number,
@@ -625,8 +681,11 @@ export class Project {
625
681
  * Karplus-Strong / modal / additive / percussion / extended-waveguide-piano
626
682
  * engines plus the realism layer). Pass a {@link SynthPatch}, a preset-name
627
683
  * string (`'saw-lead'` / `'va:saw-lead'`; see {@link synthPresetNames}), or
628
- * an array of either; each object entry may carry a `destinationId` binding
629
- * convenience (default 0), which is not part of the NativeSynth patch itself.
684
+ * an array of either; each object entry may carry `destinationId` (default
685
+ * 0) and `useGmPrograms` (default `false`) binding conveniences, neither of
686
+ * which is part of the NativeSynth patch itself. When enabled, MIDI program
687
+ * changes select the corresponding General MIDI voice while the patch remains
688
+ * the fallback.
630
689
  * Because the parameter defaults to `{}`, omission and explicit `undefined`
631
690
  * both create one default binding. Use an explicitly empty array `[]` (or
632
691
  * runtime `null`) for zero bindings. Unknown preset names throw.
@@ -722,6 +781,11 @@ export class Project {
722
781
  this.native.setSourceAudio(sourceId, audio, channels, sampleRate);
723
782
  }
724
783
 
784
+ /** Replace an audio source's metadata strings as one undoable edit. */
785
+ setAudioSourceMetadata(sourceId: number, contentHash: string, externalStemRole: string): void {
786
+ this.native.setAudioSourceMetadata(sourceId, contentHash, externalStemRole);
787
+ }
788
+
725
789
  /** Replace a clip's take list and active take id (undoable). */
726
790
  setClipTakes(clipId: number, takes: ReadonlyArray<ProjectClipTake>, activeTakeId = 0): void {
727
791
  this.native.setClipTakes(clipId, takes, activeTakeId);
@@ -778,10 +842,16 @@ export class Project {
778
842
 
779
843
  /** Append an automation lane; returns its stable target parameter id (undoable). */
780
844
  addAutomationLane(trackId: number, desc: ProjectAutomationLaneDesc): number {
781
- return this.native.addAutomationLane(trackId, {
782
- targetParamId: desc.targetParamId,
783
- points: desc.points,
784
- });
845
+ if (desc.targetParamId === 0) {
846
+ throw new RangeError('project automation lane targetParamId must be non-zero');
847
+ }
848
+ const nativeDesc: ProjectAutomationLaneDesc = { ...desc };
849
+ if (Object.keys(desc).includes('targetKind')) {
850
+ nativeDesc.targetKind = projectAutomationTargetKindValue(
851
+ desc.targetKind as ProjectAutomationTargetKind,
852
+ ) as ProjectAutomationTargetKind;
853
+ }
854
+ return this.native.addAutomationLane(trackId, nativeDesc);
785
855
  }
786
856
 
787
857
  /** Replace the lane identified by its stable target parameter id (undoable). */
@@ -790,10 +860,16 @@ export class Project {
790
860
  targetParamId: number,
791
861
  desc: ProjectAutomationLaneDesc,
792
862
  ): void {
793
- this.native.editAutomationLane(trackId, targetParamId, {
794
- targetParamId: desc.targetParamId,
795
- points: desc.points,
796
- });
863
+ if (desc.targetParamId === 0) {
864
+ throw new RangeError('project automation lane targetParamId must be non-zero');
865
+ }
866
+ const nativeDesc: ProjectAutomationLaneDesc = { ...desc };
867
+ if (Object.keys(desc).includes('targetKind')) {
868
+ nativeDesc.targetKind = projectAutomationTargetKindValue(
869
+ desc.targetKind as ProjectAutomationTargetKind,
870
+ ) as ProjectAutomationTargetKind;
871
+ }
872
+ this.native.editAutomationLane(trackId, targetParamId, nativeDesc);
797
873
  }
798
874
 
799
875
  /** Remove the lane identified by its stable target parameter id (undoable). */
@@ -811,7 +887,14 @@ export class Project {
811
887
  this.native.annotateChords(chords);
812
888
  }
813
889
 
814
- /** Add or update an opaque assist sidecar by module id + target scope (undoable). */
890
+ /**
891
+ * Add or update an opaque assist sidecar via an undoable edit.
892
+ *
893
+ * The descriptor form is the canonical API and matches the Node binding;
894
+ * the positional form remains available for compatibility with the original
895
+ * WASM facade.
896
+ */
897
+ setAssistSidecar(sidecar: ProjectAssistSidecarInput): void;
815
898
  setAssistSidecar(
816
899
  moduleId: string,
817
900
  schemaVersion: number,
@@ -819,14 +902,52 @@ export class Project {
819
902
  regionStartPpq: number,
820
903
  regionEndPpq: number,
821
904
  payload: Uint8Array,
905
+ ): void;
906
+ setAssistSidecar(
907
+ sidecarOrModuleId: ProjectAssistSidecarInput | string,
908
+ schemaVersion?: number,
909
+ targetTrackId?: number,
910
+ regionStartPpq?: number,
911
+ regionEndPpq?: number,
912
+ payload?: Uint8Array,
822
913
  ): void {
914
+ if (typeof sidecarOrModuleId === 'string') {
915
+ if (
916
+ schemaVersion === undefined ||
917
+ targetTrackId === undefined ||
918
+ regionStartPpq === undefined ||
919
+ regionEndPpq === undefined ||
920
+ payload === undefined
921
+ ) {
922
+ throw new TypeError('Project.setAssistSidecar: positional form requires six arguments');
923
+ }
924
+ this.native.setAssistSidecar(
925
+ validateAssistSidecarModuleId(sidecarOrModuleId),
926
+ validateAssistSidecarUint32(schemaVersion, 'schemaVersion'),
927
+ validateAssistSidecarUint32(targetTrackId, 'targetTrackId'),
928
+ validateAssistSidecarPpq(regionStartPpq, 'regionStartPpq'),
929
+ validateAssistSidecarPpq(regionEndPpq, 'regionEndPpq'),
930
+ validateAssistSidecarPayload(payload),
931
+ );
932
+ return;
933
+ }
934
+
935
+ if (
936
+ sidecarOrModuleId === null ||
937
+ typeof sidecarOrModuleId !== 'object' ||
938
+ Array.isArray(sidecarOrModuleId)
939
+ ) {
940
+ throw new TypeError('Project.setAssistSidecar: expected a sidecar descriptor object');
941
+ }
942
+ const sidecar = sidecarOrModuleId;
943
+ const moduleId = validateAssistSidecarModuleId(sidecar.moduleId);
823
944
  this.native.setAssistSidecar(
824
945
  moduleId,
825
- schemaVersion,
826
- targetTrackId,
827
- regionStartPpq,
828
- regionEndPpq,
829
- payload,
946
+ validateAssistSidecarUint32(sidecar.schemaVersion ?? 0, 'schemaVersion'),
947
+ validateAssistSidecarUint32(sidecar.targetTrackId ?? 0, 'targetTrackId'),
948
+ validateAssistSidecarPpq(sidecar.regionStartPpq ?? 0, 'regionStartPpq'),
949
+ validateAssistSidecarPpq(sidecar.regionEndPpq ?? 0, 'regionEndPpq'),
950
+ validateAssistSidecarPayload(sidecar.payload ?? new Uint8Array()),
830
951
  );
831
952
  }
832
953
 
@@ -840,6 +961,12 @@ export class Project {
840
961
  return this.native.getAssistSidecar(index);
841
962
  }
842
963
 
964
+ /** Read every stored assist sidecar in the same order as the index getter. */
965
+ assistSidecars(): ProjectAssistSidecar[] {
966
+ const count = this.assistSidecarCount();
967
+ return Array.from({ length: count }, (_, index) => this.getAssistSidecar(index));
968
+ }
969
+
843
970
  /** Set the project's clip-overlap policy (SonareProjectOverlapPolicy ordinal). */
844
971
  setOverlapPolicy(policy: number): void {
845
972
  this.native.setOverlapPolicy(policy);
@@ -3,7 +3,9 @@ import { getSonareModule } from './module_state';
3
3
  import type {
4
4
  BuiltinSynthBinding,
5
5
  ProjectAssistSidecar,
6
+ ProjectAutomationLaneDesc,
6
7
  ProjectAutomationPoint,
8
+ ProjectAutomationTargetKind,
7
9
  ProjectBounceOptions,
8
10
  ProjectChordSymbol,
9
11
  ProjectClip,
@@ -65,6 +67,7 @@ export interface WasmProject {
65
67
  redo: () => void;
66
68
  clearHistory: () => void;
67
69
  setMaxUndoDepth: (depth: number) => void;
70
+ setMaxHistoryBytes: (bytes: number) => void;
68
71
  setMidiEvents: (
69
72
  clipId: number,
70
73
  events: ReadonlyArray<ProjectMidiEvent | readonly [number, number, number]>,
@@ -120,6 +123,7 @@ export interface WasmProject {
120
123
  channels: number,
121
124
  sampleRate: number,
122
125
  ) => void;
126
+ setAudioSourceMetadata: (sourceId: number, contentHash: string, externalStemRole: string) => void;
123
127
  setClipTakes: (
124
128
  clipId: number,
125
129
  takes: ReadonlyArray<ProjectClipTake>,
@@ -137,14 +141,11 @@ export interface WasmProject {
137
141
  removeTrack: (trackId: number) => void;
138
142
  renameTrack: (trackId: number, name: string) => void;
139
143
  setTrackRoute: (trackId: number, channelStripRef: string, outputTarget: string) => void;
140
- addAutomationLane: (
141
- trackId: number,
142
- desc: { targetParamId: number; points: ReadonlyArray<ProjectAutomationPoint> },
143
- ) => number;
144
+ addAutomationLane: (trackId: number, desc: ProjectAutomationLaneDesc) => number;
144
145
  editAutomationLane: (
145
146
  trackId: number,
146
147
  targetParamId: number,
147
- desc: { targetParamId: number; points: ReadonlyArray<ProjectAutomationPoint> },
148
+ desc: ProjectAutomationLaneDesc,
148
149
  ) => void;
149
150
  removeAutomationLane: (trackId: number, targetParamId: number) => void;
150
151
  annotateKeys: (keys: ReadonlyArray<ProjectKeySegment>) => void;
@@ -325,6 +326,14 @@ export function projectTrackKindValue(kind: ProjectTrackKind | undefined): numbe
325
326
  return resolveEnumOrdinal(kind ?? 'audio', { audio: 0, midi: 1, aux: 2 }, 'project track kind');
326
327
  }
327
328
 
329
+ export function projectAutomationTargetKindValue(kind: ProjectAutomationTargetKind): number {
330
+ return resolveEnumOrdinal(
331
+ kind,
332
+ { opaque: 0, 'track-fader-db': 1, 'track-pan': 2 },
333
+ 'project automation target kind',
334
+ );
335
+ }
336
+
328
337
  export function projectWarpModeValue(mode: ProjectWarpMode | undefined): number {
329
338
  return resolveEnumOrdinal(
330
339
  mode ?? 'off',
@@ -110,6 +110,10 @@ export interface ProjectSource {
110
110
  storageHandleId: number;
111
111
  sampleRateHint: number;
112
112
  nameOrUri: string;
113
+ /** Owning content hash for audio sources; empty for MIDI sources. */
114
+ contentHash: string;
115
+ /** External source-separation role for audio sources; empty for MIDI sources. */
116
+ externalStemRole: string;
113
117
  }
114
118
 
115
119
  /** Names accepted by the minimal built-in oscillator synth. */
@@ -296,12 +300,11 @@ export interface SynthModRouting {
296
300
  *
297
301
  * The patch starts from a BASE — the named `preset` (see
298
302
  * {@link synthPresetNames}; a `"va:"` routing prefix is accepted) or, when
299
- * `preset` is omitted, the default subtractive patch. Every numeric field then
300
- * uses "0 / omit => keep the base value" (non-zero values override, clamped to
301
- * their audible ranges) and the enum fields reserve `'default'` as keep. The
302
- * frozen C ABI has no per-field presence bits, so explicit zero numeric
303
- * overrides (for example `ampSustain: 0`) cannot be represented; they keep the
304
- * base value. A non-empty `modRoutings` REPLACES the base mod matrix.
303
+ * `preset` is omitted, the default subtractive patch. Omitting a numeric field
304
+ * keeps the base value; supplying one overrides it (clamped to its audible
305
+ * range), including an explicit `0` such as `stereoSpread: 0`. The enum fields
306
+ * reserve `'default'` as keep. A `modRoutings` array REPLACES the base mod
307
+ * matrix, and an empty array clears it, while omitting the key keeps it.
305
308
  *
306
309
  * Mode-specific deep parameters (FM operator stacks, modal mode tables,
307
310
  * drawbar registrations, kit pieces, piano strings) travel inside the named
@@ -314,6 +317,8 @@ export interface SynthPatch {
314
317
  * `(destination_id, patch)` bindings instead. Defaults to `0`.
315
318
  */
316
319
  destinationId?: number;
320
+ /** Resolve MIDI channels from incoming GM bank/program changes; defaults to false. */
321
+ useGmPrograms?: boolean;
317
322
  /** Base preset name (see {@link synthPresetNames}); omit for the init patch. */
318
323
  preset?: string;
319
324
  engineMode?: SynthEngineMode | number;
@@ -335,12 +340,10 @@ export interface SynthPatch {
335
340
  velToCutoffCents?: number;
336
341
  ampAttackMs?: number;
337
342
  ampDecayMs?: number;
338
- /** 0 / omit keeps the base value; explicit zero sustain is not representable. */
339
343
  ampSustain?: number;
340
344
  ampReleaseMs?: number;
341
345
  filterAttackMs?: number;
342
346
  filterDecayMs?: number;
343
- /** 0 / omit keeps the base value; explicit zero sustain is not representable. */
344
347
  filterSustain?: number;
345
348
  filterReleaseMs?: number;
346
349
  lfoRateHz?: number;
@@ -448,12 +451,34 @@ export interface ProjectAutomationPoint {
448
451
  curve?: ProjectAutomationCurve;
449
452
  }
450
453
 
454
+ /**
455
+ * Persistent target classification for an automation lane.
456
+ *
457
+ * The numeric ordinals mirror `SonareAutomationTargetKind`; the string names
458
+ * are the public WASM spellings accepted by the project facade.
459
+ */
460
+ export const AutomationTargetKind = {
461
+ opaque: 0,
462
+ trackFaderDb: 1,
463
+ trackPan: 2,
464
+ } as const;
465
+
466
+ /** Accepted automation target kind name or ABI ordinal. */
467
+ export type ProjectAutomationTargetKind = 'opaque' | 'track-fader-db' | 'track-pan' | 0 | 1 | 2;
468
+
469
+ /** Numeric aliases matching the C-ABI enum names. */
470
+ export const PROJECT_AUTOMATION_TARGET_OPAQUE = AutomationTargetKind.opaque;
471
+ export const PROJECT_AUTOMATION_TARGET_TRACK_FADER_DB = AutomationTargetKind.trackFaderDb;
472
+ export const PROJECT_AUTOMATION_TARGET_TRACK_PAN = AutomationTargetKind.trackPan;
473
+
451
474
  /** Automation-lane descriptor for {@link Project.addAutomationLane}. */
452
475
  export interface ProjectAutomationLaneDesc {
453
- /** Host-defined id of the parameter the lane drives. */
476
+ /** Host-defined, non-zero id of the parameter the lane drives (zero is reserved). */
454
477
  targetParamId: number;
455
478
  /** Breakpoints (stored verbatim). */
456
479
  points: ReadonlyArray<ProjectAutomationPoint>;
480
+ /** Optional persistent target classification; omission retains the legacy opaque route. */
481
+ targetKind?: ProjectAutomationTargetKind;
457
482
  }
458
483
 
459
484
  /** One tempo segment for {@link Project.setTempoSegments}. */
@@ -515,6 +540,22 @@ export interface ProjectChordSymbol {
515
540
  modulationBoundary?: boolean;
516
541
  }
517
542
 
543
+ /** Descriptor accepted by {@link Project.setAssistSidecar}. */
544
+ export interface ProjectAssistSidecarInput {
545
+ /** Non-empty module id key. */
546
+ moduleId: string;
547
+ /** Module-defined schema version. Defaults to `0`. */
548
+ schemaVersion?: number;
549
+ /** Target track id (`0` = project scope). Defaults to `0`. */
550
+ targetTrackId?: number;
551
+ /** Region start in PPQ. Defaults to `0`. */
552
+ regionStartPpq?: number;
553
+ /** Region end in PPQ. Defaults to `0`. */
554
+ regionEndPpq?: number;
555
+ /** Opaque module-owned payload bytes. Defaults to an empty payload. */
556
+ payload?: Uint8Array;
557
+ }
558
+
518
559
  /** Assist sidecar snapshot returned by {@link Project.getAssistSidecar}. */
519
560
  export interface ProjectAssistSidecar {
520
561
  moduleId: string;
@@ -352,6 +352,15 @@ export interface MasteringChainConfig {
352
352
  releaseMs?: number;
353
353
  applyGainAtInputRate?: boolean;
354
354
  };
355
+ /**
356
+ * Dot-notation spelling of any leaf above, e.g. `'loudness.targetLufs': -20`
357
+ * beside or instead of `loudness: { targetLufs: -20 }`. It is the form the C
358
+ * ABI carries parameters in, so a caller assembling overrides dynamically can
359
+ * emit it directly; the core validates the key and rejects an unknown one.
360
+ * The nested spelling is canonical — prefer it in hand-written code, where it
361
+ * is checked field by field while a dotted key is only checked at run time.
362
+ */
363
+ [flatKey: `${string}.${string}`]: number | boolean | undefined;
355
364
  }
356
365
 
357
366
  /**
@@ -90,6 +90,22 @@ export type AutomationCurve = 'linear' | 'exponential' | 'hold' | 's-curve';
90
90
  */
91
91
  export type PanLaw = 'const3dB' | 'const4.5dB' | 'const6dB' | 'linear0dB';
92
92
 
93
+ /** Accepted pan-law name aliases for mixer and realtime-engine inputs. */
94
+ export type PanLawName =
95
+ | PanLaw
96
+ | 'const-3db'
97
+ | '-3db'
98
+ | 'const-4.5db'
99
+ | '-4.5db'
100
+ | 'const-6db'
101
+ | '-6db'
102
+ | 'linear-0db'
103
+ | 'linear'
104
+ | '0db';
105
+
106
+ /** Pan-law name or raw C ABI ordinal. */
107
+ export type PanLawInput = PanLawName | number;
108
+
93
109
  /**
94
110
  * Meter tap point for reading a strip's meter snapshot
95
111
  * (see {@link Mixer.meterTap} and {@link Mixer.stripMeter}).
@@ -105,15 +105,33 @@ export type VoicePresetId =
105
105
  | 'robot-mascot'
106
106
  | 'dark-villain';
107
107
 
108
- export interface RealtimeVoiceChangerPreset {
108
+ export type VoicePresetCategory =
109
+ | 'monitor'
110
+ | 'bright'
111
+ | 'soft'
112
+ | 'deep'
113
+ | 'robot'
114
+ | 'dark'
115
+ | 'custom';
116
+
117
+ export interface RealtimeVoiceChangerPresetMetadata {
109
118
  schemaVersion: 1;
110
- id?: string;
111
- name?: string;
119
+ id: string;
120
+ name: string;
112
121
  description?: string;
113
- macros?: Record<string, number>;
114
- dsp?: Record<string, unknown>;
122
+ category: VoicePresetCategory;
115
123
  }
116
124
 
125
+ export type RealtimeVoiceChangerPreset =
126
+ | (RealtimeVoiceChangerPresetMetadata & {
127
+ dsp: Record<string, unknown>;
128
+ macros?: never;
129
+ })
130
+ | (RealtimeVoiceChangerPresetMetadata & {
131
+ macros: Record<string, number>;
132
+ dsp?: never;
133
+ });
134
+
117
135
  export type RealtimeVoiceChangerConfigInput = VoicePresetId | RealtimeVoiceChangerPreset;
118
136
 
119
137
  /**
@@ -1,5 +1,19 @@
1
1
  import type { ValidateOptions } from './validation';
2
2
 
3
+ /**
4
+ * Per-frame voicing decision, one entry per `f0Hz` frame. A truthy or non-zero
5
+ * entry marks the frame voiced. The union covers what the analysis side hands
6
+ * back — `PitchResult.voicedFlag` is a `boolean[]` — as well as the typed and
7
+ * plain numeric arrays a caller may build directly, so a pitch track can be fed
8
+ * straight into pitch correction without a conversion step.
9
+ */
10
+ export type VoicedFlags =
11
+ | Int32Array
12
+ | Uint8Array
13
+ | Float32Array
14
+ | readonly number[]
15
+ | readonly boolean[];
16
+
3
17
  /** Options for `pitchCorrectTimevarying`. All fields are optional. */
4
18
  export interface PitchCorrectOptions extends ValidateOptions {
5
19
  /** `'midi'` retunes toward `targetMidi`; `'scale'` snaps to the key. Default `'midi'`. */
@@ -20,8 +34,8 @@ export interface PitchCorrectOptions extends ValidateOptions {
20
34
  retuneSpeedMs?: number;
21
35
  /** Corrections below this are bypassed to preserve vibrato (cents). Default 20. */
22
36
  vibratoThresholdCents?: number;
23
- /** Per-frame voiced flags (non-zero = voiced); omit to treat all frames as voiced. */
24
- voiced?: Int32Array;
37
+ /** Per-frame voiced flags (truthy = voiced); omit to treat all frames as voiced. */
38
+ voiced?: VoicedFlags;
25
39
  /** Per-frame voicing probability in `[0, 1]`; omit to derive from `voiced`. */
26
40
  voicedProb?: Float32Array;
27
41
  }
@@ -29,7 +29,12 @@ import type {
29
29
  import { Mode, PitchClass } from './public_types';
30
30
  import type { ProgressCallback, WasmAcousticResult } from './sonare.js';
31
31
  import type { ValidateOptions } from './validation';
32
- import { assertNonNegativeInteger, assertSampleRate, assertSamples } from './validation';
32
+ import {
33
+ assertFiniteScalar,
34
+ assertNonNegativeInteger,
35
+ assertSampleRate,
36
+ assertSamples,
37
+ } from './validation';
33
38
 
34
39
  function requireModule() {
35
40
  return getSonareModule();
@@ -81,6 +86,7 @@ export interface ChordFunctionalAnalysisRequest extends DetectChordsRequest {
81
86
  /** Canonical request form for impulse-response analysis. */
82
87
  export interface AnalyzeImpulseResponseRequest extends SamplesRequest {
83
88
  nOctaveBands?: number;
89
+ minDecayDb?: number;
84
90
  }
85
91
 
86
92
  /** Canonical request form for acoustic analysis. */
@@ -435,23 +441,35 @@ export function analyzeImpulseResponse(
435
441
  samples: Float32Array,
436
442
  sampleRate?: number,
437
443
  nOctaveBands?: number,
444
+ minDecayDb?: number,
438
445
  ): AcousticResult;
439
446
  export function analyzeImpulseResponse(
440
447
  samples: Float32Array | AnalyzeImpulseResponseRequest,
441
448
  sampleRate = 48000,
442
449
  nOctaveBands = 6,
450
+ minDecayDb?: number,
443
451
  ): AcousticResult {
444
- const request = samples instanceof Float32Array ? { samples, sampleRate, nOctaveBands } : samples;
452
+ const request =
453
+ samples instanceof Float32Array ? { samples, sampleRate, nOctaveBands, minDecayDb } : samples;
454
+ if (request.minDecayDb === null) {
455
+ throw new TypeError('analyzeImpulseResponse: minDecayDb must be a finite number');
456
+ }
457
+ const resolvedMinDecayDb = request.minDecayDb === undefined ? 30.0 : request.minDecayDb;
458
+ assertFiniteScalar('analyzeImpulseResponse', resolvedMinDecayDb, 'minDecayDb');
459
+ if (resolvedMinDecayDb <= 0) {
460
+ throw new RangeError('analyzeImpulseResponse: minDecayDb must be greater than zero');
461
+ }
445
462
  validateAnalysisInput(
446
463
  'analyzeImpulseResponse',
447
464
  request.samples,
448
465
  request.sampleRate ?? 48000,
449
466
  request,
450
467
  );
451
- const result: WasmAcousticResult = requireModule().analyzeImpulseResponse(
468
+ const result: WasmAcousticResult = requireModule().analyzeImpulseResponseEx(
452
469
  request.samples,
453
470
  request.sampleRate ?? 48000,
454
471
  request.nOctaveBands ?? 6,
472
+ resolvedMinDecayDb,
455
473
  );
456
474
  return result;
457
475
  }
@@ -1,8 +1,8 @@
1
- import { panLawCode, panModeCode, sendTimingCode } from './codes';
1
+ import { panLawCode, panModeCode, sendTimingCode, trackMonitorModeCode } from './codes';
2
2
  import { ErrorCode, SonareError } from './errors';
3
3
  import { getSonareModule } from './module_state';
4
4
  import type { ProjectMidiCcBinding, SynthPatch } from './project';
5
- import type { EqBand, PanLaw, PanMode, SendTiming } from './public_types';
5
+ import type { EqBand, PanLawInput, PanMode, SendTiming } from './public_types';
6
6
  import type {
7
7
  WasmClipPageRequest,
8
8
  WasmEngineAutomationPoint,
@@ -78,6 +78,12 @@ export interface EngineTrackLane {
78
78
  sourceChannelLayout?: number;
79
79
  }
80
80
 
81
+ /** Per-track cue/monitor tap mode: off, pre-fader listen, or after-fader listen. */
82
+ export type EngineTrackMonitorMode = 'off' | 'pfl' | 'afl' | 0 | 1 | 2;
83
+
84
+ /** Short alias for {@link EngineTrackMonitorMode}. */
85
+ export type TrackMonitorMode = EngineTrackMonitorMode;
86
+
81
87
  export interface EngineBus {
82
88
  busId: number;
83
89
  gainDb?: number;
@@ -219,6 +225,11 @@ export class RealtimeEngine {
219
225
  this.native.setSoloMute(laneIndex, solo, mute, renderFrame);
220
226
  }
221
227
 
228
+ /** Queue a per-track PFL/AFL monitor tap mode change. */
229
+ setTrackMonitorMode(laneIndex: number, mode: EngineTrackMonitorMode, renderFrame = -1): void {
230
+ this.native.setTrackMonitorMode(laneIndex, trackMonitorModeCode(mode), renderFrame);
231
+ }
232
+
222
233
  setMidiClips(clips: readonly EngineMidiClipSchedule[]): void {
223
234
  this.native.setMidiClips(clips);
224
235
  }
@@ -831,7 +842,7 @@ export class RealtimeEngine {
831
842
  }
832
843
 
833
844
  /** Sets a track lane strip's pan law in realtime. */
834
- setTrackStripPanLaw(trackId: number, panLaw: PanLaw | number): void {
845
+ setTrackStripPanLaw(trackId: number, panLaw: PanLawInput): void {
835
846
  this.native.setTrackStripPanLaw(trackId, panLawCode(panLaw));
836
847
  }
837
848