@libraz/libsonare 1.5.3 → 1.5.5

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,9 +1,9 @@
1
1
  {
2
2
  "name": "@libraz/libsonare",
3
- "version": "1.5.3",
3
+ "version": "1.5.5",
4
4
  "type": "module",
5
5
  "packageManager": "yarn@4.15.0",
6
- "description": "Audio analysis library for music information retrieval",
6
+ "description": "Audio analysis, mastering, mixing, and MIDI synthesis in WebAssembly",
7
7
  "main": "dist/index.js",
8
8
  "types": "dist/index.d.ts",
9
9
  "exports": {
@@ -47,6 +47,17 @@
47
47
  "bpm",
48
48
  "key",
49
49
  "tempo",
50
+ "chords",
51
+ "audio-processing",
52
+ "dsp",
53
+ "mastering",
54
+ "mixing",
55
+ "loudness",
56
+ "lufs",
57
+ "midi",
58
+ "synthesizer",
59
+ "soundfont",
60
+ "room-acoustics",
50
61
  "wasm",
51
62
  "webassembly"
52
63
  ],
@@ -59,7 +70,7 @@
59
70
  "bugs": {
60
71
  "url": "https://github.com/libraz/libsonare/issues"
61
72
  },
62
- "homepage": "https://github.com/libraz/libsonare#readme",
73
+ "homepage": "https://libsonare.libraz.net",
63
74
  "engines": {
64
75
  "node": ">=18.0.0"
65
76
  },
@@ -22,25 +22,5 @@ export function flattenChainConfig(config: MasteringChainConfig): Record<string,
22
22
  };
23
23
  walk(config as ChainSection, '');
24
24
 
25
- // Compatibility aliases for the original WASM-only shorthand. Normalize at
26
- // this boundary so every native entry point receives the core parser's one
27
- // canonical vocabulary; public types can migrate to the nested spelling
28
- // without preserving a second C++ parser indefinitely.
29
- const aliases: Record<string, string> = {
30
- 'repair.denoise': 'repair.denoise.enabled',
31
- 'repair.nFft': 'repair.denoise.nFft',
32
- 'repair.hopLength': 'repair.denoise.hopLength',
33
- 'repair.ddAlpha': 'repair.denoise.ddAlpha',
34
- 'repair.gainFloor': 'repair.denoise.gainFloor',
35
- 'eq.tiltDb': 'eq.tilt.tiltDb',
36
- 'eq.pivotHz': 'eq.tilt.pivotHz',
37
- };
38
- for (const [legacy, canonical] of Object.entries(aliases)) {
39
- const value = out[legacy];
40
- if (value !== undefined) {
41
- out[canonical] = value;
42
- delete out[legacy];
43
- }
44
- }
45
25
  return out;
46
26
  }
@@ -16,6 +16,25 @@ import type {
16
16
  WasmKeyCandidateResult,
17
17
  } from './sonare.js';
18
18
 
19
+ const PITCH_CLASS_NAMES = [
20
+ 'C',
21
+ 'C#',
22
+ 'D',
23
+ 'D#',
24
+ 'E',
25
+ 'F',
26
+ 'F#',
27
+ 'G',
28
+ 'G#',
29
+ 'A',
30
+ 'A#',
31
+ 'B',
32
+ ] as const;
33
+
34
+ function pitchClassName(value: number): string {
35
+ return PITCH_CLASS_NAMES[value] ?? 'C';
36
+ }
37
+
19
38
  export function convertKeyCandidate(wasm: WasmKeyCandidateResult): KeyCandidate {
20
39
  return {
21
40
  key: {
@@ -89,6 +108,8 @@ export function convertChordAnalysisResult(wasm: WasmChordAnalysisResult): Chord
89
108
  chords: wasm.chords.map((c) => ({
90
109
  root: c.root as PitchClass,
91
110
  bass: c.bass as PitchClass,
111
+ rootName: pitchClassName(c.root),
112
+ bassName: pitchClassName(c.bass),
92
113
  quality: c.quality as ChordQuality,
93
114
  start: c.start,
94
115
  end: c.end,
@@ -129,6 +150,8 @@ export function convertAnalysisResult(wasm: WasmAnalysisResult): AnalysisResult
129
150
  chords: wasm.chords.map((c) => ({
130
151
  root: c.root as PitchClass,
131
152
  bass: c.bass as PitchClass,
153
+ rootName: pitchClassName(c.root),
154
+ bassName: pitchClassName(c.bass),
132
155
  quality: c.quality as ChordQuality,
133
156
  start: c.start,
134
157
  end: c.end,
package/src/audio.ts CHANGED
@@ -76,6 +76,7 @@ import {
76
76
  detectOnsets,
77
77
  } from './quick_analysis';
78
78
  import type { ProgressCallback, WasmNnlsChromaResult } from './sonare.js';
79
+ import { validateAudioBuffer } from './validation';
79
80
 
80
81
  // ============================================================================
81
82
  // Audio Class
@@ -177,7 +178,8 @@ export class Audio {
177
178
  * Node/Python surfaces).
178
179
  */
179
180
  static fromBuffer(samples: Float32Array, sampleRate = 48000): Audio {
180
- return new Audio(samples, sampleRate);
181
+ validateAudioBuffer(samples, sampleRate);
182
+ return new Audio(samples.slice(), sampleRate);
181
183
  }
182
184
 
183
185
  /**
@@ -472,7 +474,7 @@ export class Audio {
472
474
  hopLength = 512,
473
475
  fmin = 65.0,
474
476
  fmax = 2093.0,
475
- threshold = 0.3,
477
+ threshold = 0.1,
476
478
  fillNa = false,
477
479
  ): PitchResult {
478
480
  return pitchYin(
@@ -492,7 +494,7 @@ export class Audio {
492
494
  hopLength = 512,
493
495
  fmin = 65.0,
494
496
  fmax = 2093.0,
495
- threshold = 0.3,
497
+ threshold = 0.1,
496
498
  fillNa = false,
497
499
  ): PitchResult {
498
500
  return pitchPyin(
package/src/codes.ts CHANGED
@@ -20,6 +20,8 @@ export function panLawCode(panLaw: PanLaw | number): number {
20
20
  return panLaw;
21
21
  }
22
22
  switch (panLaw) {
23
+ case 'const3dB':
24
+ return 0;
23
25
  case 'const4.5dB':
24
26
  return 1;
25
27
  case 'const6dB':
@@ -27,7 +29,7 @@ export function panLawCode(panLaw: PanLaw | number): number {
27
29
  case 'linear0dB':
28
30
  return 3;
29
31
  default:
30
- return 0;
32
+ throw new Error(`Invalid pan law: ${panLaw}`);
31
33
  }
32
34
  }
33
35
 
@@ -36,6 +38,8 @@ export function panModeCode(panMode: PanMode | number): number {
36
38
  return panMode;
37
39
  }
38
40
  switch (panMode) {
41
+ case 'balance':
42
+ return 0;
39
43
  case 'stereoPan':
40
44
  case 'stereo-pan':
41
45
  return 1;
@@ -43,19 +47,38 @@ export function panModeCode(panMode: PanMode | number): number {
43
47
  case 'dual-pan':
44
48
  return 2;
45
49
  default:
46
- return 0;
50
+ throw new Error(`Invalid pan mode: ${panMode}`);
47
51
  }
48
52
  }
49
53
 
50
54
  export function meterTapCode(tap: MeterTap | number): number {
51
- return tap === 'preFader' || tap === 0 ? 0 : 1;
55
+ if (typeof tap === 'number') {
56
+ return tap;
57
+ }
58
+ switch (tap) {
59
+ case 'preFader':
60
+ return 0;
61
+ case 'postFader':
62
+ return 1;
63
+ default:
64
+ throw new Error(`Invalid meter tap: ${tap}`);
65
+ }
52
66
  }
53
67
 
54
68
  export function sendTimingCode(timing: SendTiming | number): number {
55
69
  // Mirrors SonareSendTiming: post-fader is 0 (so an omitted/zeroed value is
56
70
  // post-fader), pre-fader is 1. A raw number is passed through as the C ABI int.
71
+ // An unknown string is rejected rather than silently routed to post-fader,
72
+ // matching the sibling enum-code helpers and Node's sendTimingValue.
57
73
  if (typeof timing === 'number') {
58
74
  return timing;
59
75
  }
60
- return timing === 'preFader' ? 1 : 0;
76
+ switch (timing) {
77
+ case 'postFader':
78
+ return 0;
79
+ case 'preFader':
80
+ return 1;
81
+ default:
82
+ throw new Error(`Invalid send timing: ${timing}`);
83
+ }
61
84
  }
@@ -34,13 +34,13 @@ export interface PercussiveRequest extends ValidateOptions {
34
34
 
35
35
  export interface TimeStretchRequest extends ValidateOptions {
36
36
  samples: Float32Array;
37
- sampleRate: number;
37
+ sampleRate?: number;
38
38
  rate: number;
39
39
  }
40
40
 
41
41
  export interface PitchShiftRequest extends ValidateOptions {
42
42
  samples: Float32Array;
43
- sampleRate: number;
43
+ sampleRate?: number;
44
44
  semitones: number;
45
45
  }
46
46
 
@@ -79,7 +79,7 @@ export interface NoteMoveRequest extends NoteMoveOptions, ValidateOptions {
79
79
 
80
80
  export interface NormalizeRequest extends ValidateOptions {
81
81
  samples: Float32Array;
82
- sampleRate: number;
82
+ sampleRate?: number;
83
83
  targetDb?: number;
84
84
  }
85
85
 
@@ -177,7 +177,7 @@ export function percussive(
177
177
  * Time-stretch audio without changing pitch.
178
178
  *
179
179
  * @param samples - Audio samples (mono, float32)
180
- * @param sampleRate - Sample rate in Hz
180
+ * @param sampleRate - Sample rate in Hz (default: 22050)
181
181
  * @param rate - Time stretch rate (0.5 = double duration, 2.0 = half duration)
182
182
  * @returns Time-stretched audio
183
183
  */
@@ -196,17 +196,17 @@ export function timeStretch(
196
196
  ): Float32Array {
197
197
  const request: TimeStretchRequest =
198
198
  samples instanceof Float32Array
199
- ? { samples, sampleRate: sampleRate as number, rate: rate as number, ...options }
199
+ ? { samples, sampleRate, rate: rate as number, ...options }
200
200
  : samples;
201
201
  assertSamples('timeStretch', request.samples, request.validate !== false);
202
- return requireModule().timeStretch(request.samples, request.sampleRate, request.rate);
202
+ return requireModule().timeStretch(request.samples, request.sampleRate ?? 22050, request.rate);
203
203
  }
204
204
 
205
205
  /**
206
206
  * Pitch-shift audio without changing duration.
207
207
  *
208
208
  * @param samples - Audio samples (mono, float32)
209
- * @param sampleRate - Sample rate in Hz
209
+ * @param sampleRate - Sample rate in Hz (default: 22050)
210
210
  * @param semitones - Pitch shift in semitones (+12 = one octave up, -12 = one octave down)
211
211
  * @returns Pitch-shifted audio
212
212
  */
@@ -225,15 +225,23 @@ export function pitchShift(
225
225
  ): Float32Array {
226
226
  const request: PitchShiftRequest =
227
227
  samples instanceof Float32Array
228
- ? { samples, sampleRate: sampleRate as number, semitones: semitones as number, ...options }
228
+ ? { samples, sampleRate, semitones: semitones as number, ...options }
229
229
  : samples;
230
230
  assertSamples('pitchShift', request.samples, request.validate !== false);
231
- return requireModule().pitchShift(request.samples, request.sampleRate, request.semitones);
231
+ return requireModule().pitchShift(
232
+ request.samples,
233
+ request.sampleRate ?? 22050,
234
+ request.semitones,
235
+ );
232
236
  }
233
237
 
234
238
  /**
235
239
  * Pitch-correct audio from a current MIDI note to a target MIDI note.
236
240
  *
241
+ * Applies one constant, immediate transpose with no retune glide and preserves
242
+ * the input buffer length. Use {@link pitchCorrectToMidiTimevarying} for a
243
+ * caller-supplied pitch contour.
244
+ *
237
245
  * @param samples - Audio samples (mono, float32)
238
246
  * @param sampleRate - Sample rate in Hz
239
247
  * @param currentMidi - Detected/current MIDI note number
@@ -275,7 +283,8 @@ export function pitchCorrectToMidi(
275
283
  * the caller-supplied per-frame `f0Hz` contour and retunes every voiced frame
276
284
  * toward `targetMidi`, so vibrato/drift in the source is tracked rather than
277
285
  * flattened. `voiced` (non-zero = voiced) and `voicedProb` ([0,1]) are optional;
278
- * omitting them treats every frame as voiced.
286
+ * omitting them treats every frame as voiced. An `f0Hz` NaN is accepted only
287
+ * when the corresponding `voiced` entry is zero, matching pYIN output.
279
288
  *
280
289
  * @param samples - Audio samples (mono, float32)
281
290
  * @param f0Hz - Per-frame measured F0 in Hz (one entry per analysis frame)
@@ -350,7 +359,8 @@ export function pitchCorrectToMidiTimevarying(
350
359
  * Generalises {@link pitchCorrectToMidiTimevarying}: the same caller-supplied
351
360
  * per-frame `f0Hz` contour drives correction, but `options.mode` selects between
352
361
  * a fixed-MIDI target (`'midi'`, default) and scale quantisation (`'scale'`),
353
- * and the retune knobs shape natural-vs-robotic correction.
362
+ * and the retune knobs shape natural-vs-robotic correction. An `f0Hz` NaN is
363
+ * accepted only for a frame marked unvoiced.
354
364
  *
355
365
  * @param samples - Audio samples (mono, float32)
356
366
  * @param f0Hz - Per-frame measured F0 in Hz (one entry per analysis frame)
@@ -407,7 +417,7 @@ export function pitchCorrectTimevarying(
407
417
  * @param sampleRate - Sample rate in Hz
408
418
  * @param onsetSample - Note onset position in samples
409
419
  * @param offsetSample - Note offset position in samples
410
- * @param stretchRatio - Stretch ratio (0.5 = double duration, 2.0 = half duration)
420
+ * @param stretchRatio - Stretch ratio (0.5 = half duration, 2.0 = double duration)
411
421
  * @returns Audio with the note region stretched
412
422
  */
413
423
  export function noteStretch(request: NoteStretchRequest): Float32Array;
@@ -427,7 +437,7 @@ export function noteStretch(
427
437
  request.samples,
428
438
  request.sampleRate ?? 22050,
429
439
  request.onsetSample ?? 0,
430
- request.offsetSample ?? 0,
440
+ request.offsetSample ?? request.samples.length,
431
441
  request.stretchRatio ?? 1.0,
432
442
  );
433
443
  }
@@ -450,7 +460,7 @@ export function noteMove(
450
460
  request.samples,
451
461
  request.sampleRate ?? 22050,
452
462
  request.onsetSample ?? 0,
453
- request.offsetSample ?? 0,
463
+ request.offsetSample ?? request.samples.length,
454
464
  request.targetOnsetSample ?? 0,
455
465
  );
456
466
  }
@@ -459,8 +469,8 @@ export function noteMove(
459
469
  * Normalize audio to target peak level.
460
470
  *
461
471
  * @param samples - Audio samples (mono, float32)
462
- * @param sampleRate - Sample rate in Hz
463
- * @param targetDb - Target peak level in dB (default: 0 dB = full scale)
472
+ * @param sampleRate - Sample rate in Hz (default: 22050)
473
+ * @param targetDb - Finite target at or below 0 dBFS (default: 0 dB = full scale)
464
474
  * @returns Normalized audio
465
475
  */
466
476
  export function normalize(request: NormalizeRequest): Float32Array;
@@ -477,11 +487,13 @@ export function normalize(
477
487
  options: ValidateOptions = {},
478
488
  ): Float32Array {
479
489
  const request: NormalizeRequest =
480
- samples instanceof Float32Array
481
- ? { samples, sampleRate: sampleRate as number, targetDb, ...options }
482
- : samples;
490
+ samples instanceof Float32Array ? { samples, sampleRate, targetDb, ...options } : samples;
483
491
  assertSamples('normalize', request.samples, request.validate !== false);
484
- return requireModule().normalize(request.samples, request.sampleRate, request.targetDb ?? 0.0);
492
+ return requireModule().normalize(
493
+ request.samples,
494
+ request.sampleRate ?? 22050,
495
+ request.targetDb ?? 0.0,
496
+ );
485
497
  }
486
498
 
487
499
  /**
@@ -81,6 +81,14 @@ export interface PcenRequest {
81
81
  values: Float32Array;
82
82
  nBins: number;
83
83
  nFrames: number;
84
+ sampleRate?: number;
85
+ hopLength?: number;
86
+ timeConstant?: number;
87
+ gain?: number;
88
+ bias?: number;
89
+ power?: number;
90
+ eps?: number;
91
+ /** @deprecated Put PCEN fields directly on the request object. */
84
92
  options?: Record<string, number>;
85
93
  }
86
94
  export interface TempogramRequest {
@@ -449,7 +457,17 @@ export function pcen(
449
457
  ): Float32Array {
450
458
  if (!(values instanceof Float32Array)) {
451
459
  const r = values;
452
- return pcen(r.values, r.nBins, r.nFrames, r.options);
460
+ const {
461
+ values: requestValues,
462
+ nBins: requestBins,
463
+ nFrames: requestFrames,
464
+ options: legacyOptions,
465
+ ...flatOptions
466
+ } = r;
467
+ return pcen(requestValues, requestBins, requestFrames, {
468
+ ...legacyOptions,
469
+ ...flatOptions,
470
+ });
453
471
  }
454
472
  return requireModule().pcen(values, nBins, nFrames, options);
455
473
  }
@@ -1,3 +1,4 @@
1
+ import { ErrorCode, SonareError } from './errors';
1
2
  import { getSonareModule } from './module_state';
2
3
  import type {
3
4
  AnalyzeSectionsOptions,
@@ -97,6 +98,9 @@ export interface LufsRequest extends ValidateOptions {
97
98
  export interface NnlsChromaRequest extends GuardedOptions {
98
99
  samples: Float32Array;
99
100
  sampleRate?: number;
101
+ enableStftBlend?: boolean;
102
+ stftBlendWeight?: number;
103
+ stftBlendNFft?: number;
100
104
  }
101
105
 
102
106
  function validateMusicSamples(
@@ -139,18 +143,24 @@ export function nnlsChroma(request: NnlsChromaRequest): WasmNnlsChromaResult;
139
143
  export function nnlsChroma(
140
144
  samples: Float32Array,
141
145
  sampleRate?: number,
142
- options?: GuardedOptions,
146
+ options?: Omit<NnlsChromaRequest, 'samples' | 'sampleRate'>,
143
147
  ): WasmNnlsChromaResult;
144
148
  export function nnlsChroma(
145
149
  samples: Float32Array | NnlsChromaRequest,
146
150
  sampleRate = 22050,
147
- options: GuardedOptions = {},
151
+ options: Omit<NnlsChromaRequest, 'samples' | 'sampleRate'> = {},
148
152
  ): WasmNnlsChromaResult {
149
153
  if (!(samples instanceof Float32Array)) {
150
154
  return nnlsChroma(samples.samples, samples.sampleRate, samples);
151
155
  }
152
156
  validateMusicSamples('nnlsChroma', samples, sampleRate, options);
153
- return requireModule().nnlsChroma(samples, sampleRate);
157
+ return requireModule().nnlsChroma(
158
+ samples,
159
+ sampleRate,
160
+ options.enableStftBlend ?? true,
161
+ options.stftBlendWeight ?? 0.55,
162
+ options.stftBlendNFft ?? 4096,
163
+ );
154
164
  }
155
165
 
156
166
  /**
@@ -306,7 +316,8 @@ export function hybridCqt(
306
316
  * @param fmin - Minimum frequency in Hz (default: 32.70319566257483, C1)
307
317
  * @param nBins - Number of frequency bins (default: 84)
308
318
  * @param binsPerOctave - Bins per octave (default: 12)
309
- * @param gamma - Bandwidth offset; 0 is equivalent to CQT (default: 0)
319
+ * @param gamma - Bandwidth offset; negative selects the automatic ERB-derived
320
+ * value, while 0 is equivalent to CQT (default: -1)
310
321
  * @returns VQT magnitude result (same shape as CQT)
311
322
  */
312
323
  export function vqt(request: VqtRequest): CqtResult;
@@ -327,7 +338,7 @@ export function vqt(
327
338
  fmin = 32.70319566257483,
328
339
  nBins = 84,
329
340
  binsPerOctave = 12,
330
- gamma = 0,
341
+ gamma = -1,
331
342
  options: GuardedOptions = {},
332
343
  ): CqtResult {
333
344
  if (!(samples instanceof Float32Array)) {
@@ -347,9 +358,6 @@ export function vqt(
347
358
  validatePositiveIntegers('vqt', { hopLength, nBins, binsPerOctave });
348
359
  validateFrequencyBounds('vqt', fmin);
349
360
  assertFiniteScalar('vqt', gamma, 'gamma');
350
- if (gamma < 0) {
351
- throw new RangeError('vqt: gamma must be non-negative');
352
- }
353
361
  return requireModule().vqt(samples, sampleRate, hopLength, fmin, nBins, binsPerOctave, gamma);
354
362
  }
355
363
 
@@ -464,7 +472,7 @@ export function vqtToAudio(
464
472
  hopLength = 512,
465
473
  fmin = 32.70319566257483,
466
474
  binsPerOctave = 12,
467
- gamma = 0,
475
+ gamma = -1,
468
476
  nIter = 32,
469
477
  options: GuardedOptions = {},
470
478
  ): Float32Array {
@@ -496,9 +504,6 @@ export function vqtToAudio(
496
504
  options,
497
505
  );
498
506
  assertFiniteScalar('vqtToAudio', gamma, 'gamma');
499
- if (gamma < 0) {
500
- throw new RangeError('vqtToAudio: gamma must be non-negative');
501
- }
502
507
  return requireModule().vqtToAudio(
503
508
  magnitude,
504
509
  nBins,
@@ -543,8 +548,8 @@ export function analyzeSections(
543
548
  hopLength: options.hopLength ?? 512,
544
549
  });
545
550
  assertFiniteScalar('analyzeSections', options.minSectionSec ?? 4.0, 'minSectionSec');
546
- if ((options.minSectionSec ?? 4.0) <= 0) {
547
- throw new RangeError('analyzeSections: minSectionSec must be positive');
551
+ if ((options.minSectionSec ?? 4.0) < 0) {
552
+ throw new RangeError('analyzeSections: minSectionSec must be non-negative');
548
553
  }
549
554
  // The embind value marshalling returns an array whose constructor is not this
550
555
  // realm's Array; chaining .map() onto it propagates that constructor via
@@ -614,10 +619,15 @@ export function analyzeMelody(
614
619
  const fmax = options.fmax ?? 2093.0;
615
620
  validateFrequencyBounds('analyzeMelody', fmin, fmax);
616
621
  // The melody tracker's fmin is a YIN pitch floor: 0 is meaningless, and the
617
- // flat C ABI (sonare_analyze_melody) rejects it. validateFrequencyBounds only
618
- // guards fmin >= 0, so enforce strict positivity here for parity.
622
+ // flat C ABI (sonare_analyze_melody) rejects it with InvalidParameter.
623
+ // validateFrequencyBounds only guards fmin >= 0, so enforce strict positivity
624
+ // here and report the same branded error the Node/Python surfaces raise.
619
625
  if (fmin <= 0) {
620
- throw new RangeError('analyzeMelody: fmin must be positive');
626
+ throw new SonareError(
627
+ ErrorCode.InvalidParameter,
628
+ 'InvalidParameter',
629
+ 'analyzeMelody: fmin must be positive',
630
+ );
621
631
  }
622
632
  validatePositiveIntegers('analyzeMelody', {
623
633
  frameLength: options.frameLength ?? 2048,
@@ -626,7 +636,11 @@ export function analyzeMelody(
626
636
  const threshold = options.threshold ?? 0.1;
627
637
  assertFiniteScalar('analyzeMelody', threshold, 'threshold');
628
638
  if (threshold <= 0) {
629
- throw new RangeError('analyzeMelody: threshold must be positive');
639
+ throw new SonareError(
640
+ ErrorCode.InvalidParameter,
641
+ 'InvalidParameter',
642
+ 'analyzeMelody: threshold must be positive',
643
+ );
630
644
  }
631
645
  return requireModule().analyzeMelody(
632
646
  samples,
@@ -18,8 +18,8 @@ function requireModule() {
18
18
  * @param hopLength - Hop length (default: 512)
19
19
  * @param fmin - Minimum frequency in Hz (default: 65)
20
20
  * @param fmax - Maximum frequency in Hz (default: 2093)
21
- * @param threshold - YIN threshold (default: 0.3)
22
- * @param fillNa - If true, return 0 for unvoiced f0 frames; otherwise keep NaN (default: false)
21
+ * @param threshold - YIN threshold (default: 0.1)
22
+ * @param fillNa - Retained for compatibility; YIN always returns a finite per-frame estimate.
23
23
  * @returns Pitch detection result
24
24
  */
25
25
  export interface PitchYinRequest {
@@ -51,7 +51,7 @@ export function pitchYin(
51
51
  hopLength = 512,
52
52
  fmin = 65.0,
53
53
  fmax = 2093.0,
54
- threshold = 0.3,
54
+ threshold = 0.1,
55
55
  fillNa = false,
56
56
  ): PitchResult {
57
57
  if (!(samples instanceof Float32Array)) {
@@ -88,7 +88,7 @@ export function pitchYin(
88
88
  * @param hopLength - Hop length (default: 512)
89
89
  * @param fmin - Minimum frequency in Hz (default: 65)
90
90
  * @param fmax - Maximum frequency in Hz (default: 2093)
91
- * @param threshold - YIN threshold (default: 0.3)
91
+ * @param threshold - YIN threshold (default: 0.1)
92
92
  * @param fillNa - If true, return 0 for unvoiced f0 frames; otherwise keep NaN (default: false)
93
93
  * @returns Pitch detection result
94
94
  */
@@ -112,7 +112,7 @@ export function pitchPyin(
112
112
  hopLength = 512,
113
113
  fmin = 65.0,
114
114
  fmax = 2093.0,
115
- threshold = 0.3,
115
+ threshold = 0.1,
116
116
  fillNa = false,
117
117
  ): PitchResult {
118
118
  if (!(samples instanceof Float32Array)) {
@@ -438,21 +438,21 @@ export function remix(
438
438
  export function phaseVocoder(request: PhaseVocoderRequest): Float32Array;
439
439
  export function phaseVocoder(
440
440
  samples: Float32Array,
441
+ sampleRate: number,
441
442
  rate: number,
442
- sampleRate?: number,
443
443
  nFft?: number,
444
444
  hopLength?: number,
445
445
  ): Float32Array;
446
446
  export function phaseVocoder(
447
447
  samples: Float32Array | PhaseVocoderRequest,
448
- rate = 1,
449
448
  sampleRate = 22050,
449
+ rate = 1,
450
450
  nFft = 2048,
451
451
  hopLength = 512,
452
452
  ): Float32Array {
453
453
  if (!(samples instanceof Float32Array)) {
454
454
  const r = samples;
455
- return phaseVocoder(r.samples, r.rate, r.sampleRate, r.nFft, r.hopLength);
455
+ return phaseVocoder(r.samples, r.sampleRate ?? 22050, r.rate, r.nFft, r.hopLength);
456
456
  }
457
457
  return requireModule().phaseVocoder(samples, sampleRate, rate, nFft, hopLength);
458
458
  }