@libraz/libsonare 1.5.4 → 1.6.0

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 (79) hide show
  1. package/README.md +109 -20
  2. package/dist/analysis.d.ts +5730 -0
  3. package/dist/analysis.js +2464 -0
  4. package/dist/analysis.js.map +1 -0
  5. package/dist/index.d.ts +873 -63
  6. package/dist/index.js +1037 -265
  7. package/dist/index.js.map +1 -1
  8. package/dist/schemas/realtime-voice-changer-preset-pack.schema.json +27 -0
  9. package/dist/schemas/realtime-voice-changer-preset.schema.json +140 -0
  10. package/dist/sonare-analysis.js +2 -0
  11. package/dist/sonare-analysis.wasm +0 -0
  12. package/dist/sonare.js +1 -1
  13. package/dist/sonare.wasm +0 -0
  14. package/dist/worker.d.ts +65 -0
  15. package/dist/worker.js +707 -0
  16. package/dist/worker.js.map +1 -0
  17. package/dist/worklet.d.ts +596 -86
  18. package/dist/worklet.js +1658 -235
  19. package/dist/worklet.js.map +1 -1
  20. package/package.json +23 -11
  21. package/src/_chain_config.ts +5 -23
  22. package/src/analysis.ts +137 -0
  23. package/src/analysis_helpers.ts +29 -0
  24. package/src/audio.ts +5 -3
  25. package/src/clip_page_streamer.ts +62 -8
  26. package/src/codes.ts +51 -64
  27. package/src/effects_mastering.ts +1 -0
  28. package/src/effects_transform.ts +12 -6
  29. package/src/errors.ts +1 -0
  30. package/src/feature_core.ts +159 -3
  31. package/src/feature_music.ts +35 -16
  32. package/src/feature_pitch.ts +75 -6
  33. package/src/feature_spectral.ts +234 -8
  34. package/src/feature_spectrogram.ts +186 -5
  35. package/src/features.ts +16 -1
  36. package/src/index.ts +104 -7
  37. package/src/mastering_chain.ts +20 -8
  38. package/src/mastering_core.ts +7 -0
  39. package/src/mastering_dynamics.ts +7 -1
  40. package/src/metering.ts +51 -5
  41. package/src/mixer.ts +14 -1
  42. package/src/opfs_clip_pages.ts +26 -1
  43. package/src/project.ts +7 -0
  44. package/src/project_class.ts +74 -13
  45. package/src/project_internal.ts +24 -29
  46. package/src/project_types.ts +69 -10
  47. package/src/public_types.ts +68 -0
  48. package/src/public_types_mastering.ts +28 -0
  49. package/src/public_types_mixing.ts +11 -2
  50. package/src/public_types_music.ts +13 -0
  51. package/src/public_types_spectral.ts +43 -3
  52. package/src/quick_analysis.ts +48 -12
  53. package/src/realtime_engine.ts +162 -4
  54. package/src/realtime_voice_changer.ts +51 -77
  55. package/src/sonare-analysis.js.d.ts +8 -0
  56. package/src/sonare.js.d.ts +305 -9
  57. package/src/streaming_processors.ts +22 -1
  58. package/src/validation.ts +6 -0
  59. package/src/web_midi.ts +36 -4
  60. package/src/worker.ts +196 -0
  61. package/src/worker_client.ts +352 -0
  62. package/src/worker_protocol.ts +59 -0
  63. package/src/worklet/engine-automation.ts +2 -1
  64. package/src/worklet/engine-capture-facade.ts +13 -4
  65. package/src/worklet/engine-clips.ts +12 -3
  66. package/src/worklet/engine-node.ts +212 -4
  67. package/src/worklet/engine-offline.ts +7 -2
  68. package/src/worklet/engine-parameter-facade.ts +26 -5
  69. package/src/worklet/engine-processor.ts +383 -42
  70. package/src/worklet/engine-register.ts +8 -0
  71. package/src/worklet/engine-strips.ts +17 -1
  72. package/src/worklet/engine-sync.ts +2 -6
  73. package/src/worklet/engine-tempo-facade.ts +8 -6
  74. package/src/worklet/engine.ts +380 -13
  75. package/src/worklet/guards.ts +22 -1
  76. package/src/worklet/messages.ts +141 -9
  77. package/src/worklet/protocol.ts +303 -17
  78. package/src/worklet/voice-changer-processor.ts +12 -15
  79. package/src/worklet.ts +20 -1
@@ -238,6 +238,10 @@ export function pitchShift(
238
238
  /**
239
239
  * Pitch-correct audio from a current MIDI note to a target MIDI note.
240
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
+ *
241
245
  * @param samples - Audio samples (mono, float32)
242
246
  * @param sampleRate - Sample rate in Hz
243
247
  * @param currentMidi - Detected/current MIDI note number
@@ -279,7 +283,8 @@ export function pitchCorrectToMidi(
279
283
  * the caller-supplied per-frame `f0Hz` contour and retunes every voiced frame
280
284
  * toward `targetMidi`, so vibrato/drift in the source is tracked rather than
281
285
  * flattened. `voiced` (non-zero = voiced) and `voicedProb` ([0,1]) are optional;
282
- * 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.
283
288
  *
284
289
  * @param samples - Audio samples (mono, float32)
285
290
  * @param f0Hz - Per-frame measured F0 in Hz (one entry per analysis frame)
@@ -354,7 +359,8 @@ export function pitchCorrectToMidiTimevarying(
354
359
  * Generalises {@link pitchCorrectToMidiTimevarying}: the same caller-supplied
355
360
  * per-frame `f0Hz` contour drives correction, but `options.mode` selects between
356
361
  * a fixed-MIDI target (`'midi'`, default) and scale quantisation (`'scale'`),
357
- * 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.
358
364
  *
359
365
  * @param samples - Audio samples (mono, float32)
360
366
  * @param f0Hz - Per-frame measured F0 in Hz (one entry per analysis frame)
@@ -411,7 +417,7 @@ export function pitchCorrectTimevarying(
411
417
  * @param sampleRate - Sample rate in Hz
412
418
  * @param onsetSample - Note onset position in samples
413
419
  * @param offsetSample - Note offset position in samples
414
- * @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)
415
421
  * @returns Audio with the note region stretched
416
422
  */
417
423
  export function noteStretch(request: NoteStretchRequest): Float32Array;
@@ -431,7 +437,7 @@ export function noteStretch(
431
437
  request.samples,
432
438
  request.sampleRate ?? 22050,
433
439
  request.onsetSample ?? 0,
434
- request.offsetSample ?? 0,
440
+ request.offsetSample ?? request.samples.length,
435
441
  request.stretchRatio ?? 1.0,
436
442
  );
437
443
  }
@@ -454,7 +460,7 @@ export function noteMove(
454
460
  request.samples,
455
461
  request.sampleRate ?? 22050,
456
462
  request.onsetSample ?? 0,
457
- request.offsetSample ?? 0,
463
+ request.offsetSample ?? request.samples.length,
458
464
  request.targetOnsetSample ?? 0,
459
465
  );
460
466
  }
@@ -464,7 +470,7 @@ export function noteMove(
464
470
  *
465
471
  * @param samples - Audio samples (mono, float32)
466
472
  * @param sampleRate - Sample rate in Hz (default: 22050)
467
- * @param targetDb - Target peak level in dB (default: 0 dB = full scale)
473
+ * @param targetDb - Finite target at or below 0 dBFS (default: 0 dB = full scale)
468
474
  * @returns Normalized audio
469
475
  */
470
476
  export function normalize(request: NormalizeRequest): Float32Array;
package/src/errors.ts CHANGED
@@ -12,6 +12,7 @@ export enum ErrorCode {
12
12
  OutOfMemory = 5,
13
13
  NotSupported = 6,
14
14
  InvalidState = 7,
15
+ Cancelled = 8,
15
16
  Unknown = 99,
16
17
  }
17
18
 
@@ -11,6 +11,85 @@ function requireModule() {
11
11
  return getSonareModule();
12
12
  }
13
13
 
14
+ export function tone(request?: ToneRequest): Float32Array;
15
+ export function tone(
16
+ frequency?: number,
17
+ sampleRate?: number,
18
+ duration?: number,
19
+ phase?: number,
20
+ amplitude?: number,
21
+ ): Float32Array;
22
+ export function tone(
23
+ frequency: number | ToneRequest = 440,
24
+ sampleRate = 22050,
25
+ duration = 1,
26
+ phase = 0,
27
+ amplitude = 1,
28
+ ): Float32Array {
29
+ const request =
30
+ typeof frequency === 'number'
31
+ ? { frequency, sampleRate, duration, phase, amplitude }
32
+ : frequency;
33
+ return requireModule().tone(
34
+ request.frequency ?? 440,
35
+ request.sampleRate ?? 22050,
36
+ request.duration ?? 1,
37
+ request.phase ?? 0,
38
+ request.amplitude ?? 1,
39
+ );
40
+ }
41
+
42
+ export function chirp(request?: ChirpRequest): Float32Array;
43
+ export function chirp(
44
+ fmin?: number,
45
+ fmax?: number,
46
+ sampleRate?: number,
47
+ duration?: number,
48
+ linear?: boolean,
49
+ ): Float32Array;
50
+ export function chirp(
51
+ fmin: number | ChirpRequest = 440,
52
+ fmax = 880,
53
+ sampleRate = 22050,
54
+ duration = 1,
55
+ linear = true,
56
+ ): Float32Array {
57
+ const request = typeof fmin === 'number' ? { fmin, fmax, sampleRate, duration, linear } : fmin;
58
+ return requireModule().chirp(
59
+ request.fmin ?? 440,
60
+ request.fmax ?? 880,
61
+ request.sampleRate ?? 22050,
62
+ request.duration ?? 1,
63
+ request.linear ?? true,
64
+ );
65
+ }
66
+
67
+ export function clicks(request: ClicksRequest): Float32Array;
68
+ export function clicks(
69
+ times: Float32Array,
70
+ sampleRate?: number,
71
+ length?: number,
72
+ frequency?: number,
73
+ clickDuration?: number,
74
+ ): Float32Array;
75
+ export function clicks(
76
+ times: Float32Array | ClicksRequest,
77
+ sampleRate = 22050,
78
+ length = 0,
79
+ frequency = 1000,
80
+ clickDuration = 0.1,
81
+ ): Float32Array {
82
+ const request =
83
+ times instanceof Float32Array ? { times, sampleRate, length, frequency, clickDuration } : times;
84
+ return requireModule().clicks(
85
+ request.times,
86
+ request.sampleRate ?? 22050,
87
+ request.length ?? 0,
88
+ request.frequency ?? 1000,
89
+ request.clickDuration ?? 0.1,
90
+ );
91
+ }
92
+
14
93
  export interface DbConversionRequest {
15
94
  values: Float32Array;
16
95
  ref?: number;
@@ -30,6 +109,27 @@ export interface FrameSignalRequest {
30
109
  frameLength: number;
31
110
  hopLength: number;
32
111
  }
112
+ export interface ToneRequest {
113
+ frequency?: number;
114
+ sampleRate?: number;
115
+ duration?: number;
116
+ phase?: number;
117
+ amplitude?: number;
118
+ }
119
+ export interface ChirpRequest {
120
+ fmin?: number;
121
+ fmax?: number;
122
+ sampleRate?: number;
123
+ duration?: number;
124
+ linear?: boolean;
125
+ }
126
+ export interface ClicksRequest {
127
+ times: Float32Array;
128
+ sampleRate?: number;
129
+ length?: number;
130
+ frequency?: number;
131
+ clickDuration?: number;
132
+ }
33
133
  /** Canonical request form for pre/de-emphasis filters. */
34
134
  export interface EmphasisRequest {
35
135
  samples: Float32Array;
@@ -55,6 +155,10 @@ export interface FixFramesRequest {
55
155
  xMax?: number;
56
156
  pad?: boolean;
57
157
  }
158
+ export interface OnsetBacktrackRequest {
159
+ events: Int32Array;
160
+ energy: Float32Array;
161
+ }
58
162
  /** Canonical request form for peak selection. */
59
163
  export interface PeakPickRequest {
60
164
  values: Float32Array;
@@ -81,6 +185,14 @@ export interface PcenRequest {
81
185
  values: Float32Array;
82
186
  nBins: number;
83
187
  nFrames: number;
188
+ sampleRate?: number;
189
+ hopLength?: number;
190
+ timeConstant?: number;
191
+ gain?: number;
192
+ bias?: number;
193
+ power?: number;
194
+ eps?: number;
195
+ /** @deprecated Put PCEN fields directly on the request object. */
84
196
  options?: Record<string, number>;
85
197
  }
86
198
  export interface TempogramRequest {
@@ -89,6 +201,8 @@ export interface TempogramRequest {
89
201
  hopLength?: number;
90
202
  winLength?: number;
91
203
  mode?: TempogramMode;
204
+ center?: boolean;
205
+ norm?: boolean;
92
206
  }
93
207
  export interface CyclicTempogramRequest {
94
208
  onsetEnvelope: Float32Array;
@@ -383,6 +497,18 @@ export function fixFrames(
383
497
  return requireModule().fixFrames(frames, xMin, xMax, pad);
384
498
  }
385
499
 
500
+ export function onsetBacktrack(request: OnsetBacktrackRequest): Int32Array;
501
+ export function onsetBacktrack(events: Int32Array, energy: Float32Array): Int32Array;
502
+ export function onsetBacktrack(
503
+ events: Int32Array | OnsetBacktrackRequest,
504
+ energy?: Float32Array,
505
+ ): Int32Array {
506
+ if (!(events instanceof Int32Array)) {
507
+ return onsetBacktrack(events.events, events.energy);
508
+ }
509
+ return requireModule().onsetBacktrack(events, energy as Float32Array);
510
+ }
511
+
386
512
  export function peakPick(request: PeakPickRequest): Int32Array;
387
513
  export function peakPick(
388
514
  values: Float32Array,
@@ -449,7 +575,17 @@ export function pcen(
449
575
  ): Float32Array {
450
576
  if (!(values instanceof Float32Array)) {
451
577
  const r = values;
452
- return pcen(r.values, r.nBins, r.nFrames, r.options);
578
+ const {
579
+ values: requestValues,
580
+ nBins: requestBins,
581
+ nFrames: requestFrames,
582
+ options: legacyOptions,
583
+ ...flatOptions
584
+ } = r;
585
+ return pcen(requestValues, requestBins, requestFrames, {
586
+ ...legacyOptions,
587
+ ...flatOptions,
588
+ });
453
589
  }
454
590
  return requireModule().pcen(values, nBins, nFrames, options);
455
591
  }
@@ -474,6 +610,8 @@ export function tempogram(
474
610
  hopLength?: number,
475
611
  winLength?: number,
476
612
  mode?: TempogramMode,
613
+ center?: boolean,
614
+ norm?: boolean,
477
615
  ): WasmTempogramResult;
478
616
  export function tempogram(
479
617
  onsetEnvelope: Float32Array | TempogramRequest,
@@ -481,12 +619,30 @@ export function tempogram(
481
619
  hopLength = 512,
482
620
  winLength = 384,
483
621
  mode: TempogramMode = 'autocorrelation',
622
+ center = true,
623
+ norm = true,
484
624
  ): WasmTempogramResult {
485
625
  if (!(onsetEnvelope instanceof Float32Array)) {
486
626
  const r = onsetEnvelope;
487
- return tempogram(r.onsetEnvelope, r.sampleRate, r.hopLength, r.winLength, r.mode);
627
+ return tempogram(
628
+ r.onsetEnvelope,
629
+ r.sampleRate,
630
+ r.hopLength,
631
+ r.winLength,
632
+ r.mode,
633
+ r.center,
634
+ r.norm,
635
+ );
488
636
  }
489
- return requireModule().tempogram(onsetEnvelope, sampleRate, hopLength, winLength, mode);
637
+ return requireModule().tempogram(
638
+ onsetEnvelope,
639
+ sampleRate,
640
+ hopLength,
641
+ winLength,
642
+ mode,
643
+ center,
644
+ norm,
645
+ );
490
646
  }
491
647
 
492
648
  export function cyclicTempogram(request: CyclicTempogramRequest): WasmCyclicTempogramResult;
@@ -58,6 +58,8 @@ export interface FourierTempogramRequest extends GuardedOptions {
58
58
  sampleRate?: number;
59
59
  hopLength?: number;
60
60
  winLength?: number;
61
+ center?: boolean;
62
+ norm?: boolean;
61
63
  }
62
64
 
63
65
  export interface TempogramRatioRequest extends GuardedOptions {
@@ -98,6 +100,9 @@ export interface LufsRequest extends ValidateOptions {
98
100
  export interface NnlsChromaRequest extends GuardedOptions {
99
101
  samples: Float32Array;
100
102
  sampleRate?: number;
103
+ enableStftBlend?: boolean;
104
+ stftBlendWeight?: number;
105
+ stftBlendNFft?: number;
101
106
  }
102
107
 
103
108
  function validateMusicSamples(
@@ -140,18 +145,24 @@ export function nnlsChroma(request: NnlsChromaRequest): WasmNnlsChromaResult;
140
145
  export function nnlsChroma(
141
146
  samples: Float32Array,
142
147
  sampleRate?: number,
143
- options?: GuardedOptions,
148
+ options?: Omit<NnlsChromaRequest, 'samples' | 'sampleRate'>,
144
149
  ): WasmNnlsChromaResult;
145
150
  export function nnlsChroma(
146
151
  samples: Float32Array | NnlsChromaRequest,
147
152
  sampleRate = 22050,
148
- options: GuardedOptions = {},
153
+ options: Omit<NnlsChromaRequest, 'samples' | 'sampleRate'> = {},
149
154
  ): WasmNnlsChromaResult {
150
155
  if (!(samples instanceof Float32Array)) {
151
156
  return nnlsChroma(samples.samples, samples.sampleRate, samples);
152
157
  }
153
158
  validateMusicSamples('nnlsChroma', samples, sampleRate, options);
154
- return requireModule().nnlsChroma(samples, sampleRate);
159
+ return requireModule().nnlsChroma(
160
+ samples,
161
+ sampleRate,
162
+ options.enableStftBlend ?? true,
163
+ options.stftBlendWeight ?? 0.55,
164
+ options.stftBlendNFft ?? 4096,
165
+ );
155
166
  }
156
167
 
157
168
  /**
@@ -307,7 +318,8 @@ export function hybridCqt(
307
318
  * @param fmin - Minimum frequency in Hz (default: 32.70319566257483, C1)
308
319
  * @param nBins - Number of frequency bins (default: 84)
309
320
  * @param binsPerOctave - Bins per octave (default: 12)
310
- * @param gamma - Bandwidth offset; 0 is equivalent to CQT (default: 0)
321
+ * @param gamma - Bandwidth offset; negative selects the automatic ERB-derived
322
+ * value, while 0 is equivalent to CQT (default: -1)
311
323
  * @returns VQT magnitude result (same shape as CQT)
312
324
  */
313
325
  export function vqt(request: VqtRequest): CqtResult;
@@ -328,7 +340,7 @@ export function vqt(
328
340
  fmin = 32.70319566257483,
329
341
  nBins = 84,
330
342
  binsPerOctave = 12,
331
- gamma = 0,
343
+ gamma = -1,
332
344
  options: GuardedOptions = {},
333
345
  ): CqtResult {
334
346
  if (!(samples instanceof Float32Array)) {
@@ -348,9 +360,6 @@ export function vqt(
348
360
  validatePositiveIntegers('vqt', { hopLength, nBins, binsPerOctave });
349
361
  validateFrequencyBounds('vqt', fmin);
350
362
  assertFiniteScalar('vqt', gamma, 'gamma');
351
- if (gamma < 0) {
352
- throw new RangeError('vqt: gamma must be non-negative');
353
- }
354
363
  return requireModule().vqt(samples, sampleRate, hopLength, fmin, nBins, binsPerOctave, gamma);
355
364
  }
356
365
 
@@ -465,7 +474,7 @@ export function vqtToAudio(
465
474
  hopLength = 512,
466
475
  fmin = 32.70319566257483,
467
476
  binsPerOctave = 12,
468
- gamma = 0,
477
+ gamma = -1,
469
478
  nIter = 32,
470
479
  options: GuardedOptions = {},
471
480
  ): Float32Array {
@@ -497,9 +506,6 @@ export function vqtToAudio(
497
506
  options,
498
507
  );
499
508
  assertFiniteScalar('vqtToAudio', gamma, 'gamma');
500
- if (gamma < 0) {
501
- throw new RangeError('vqtToAudio: gamma must be non-negative');
502
- }
503
509
  return requireModule().vqtToAudio(
504
510
  magnitude,
505
511
  nBins,
@@ -544,8 +550,8 @@ export function analyzeSections(
544
550
  hopLength: options.hopLength ?? 512,
545
551
  });
546
552
  assertFiniteScalar('analyzeSections', options.minSectionSec ?? 4.0, 'minSectionSec');
547
- if ((options.minSectionSec ?? 4.0) <= 0) {
548
- throw new RangeError('analyzeSections: minSectionSec must be positive');
553
+ if ((options.minSectionSec ?? 4.0) < 0) {
554
+ throw new RangeError('analyzeSections: minSectionSec must be non-negative');
549
555
  }
550
556
  // The embind value marshalling returns an array whose constructor is not this
551
557
  // realm's Array; chaining .map() onto it propagates that constructor via
@@ -580,7 +586,7 @@ export interface MelodyOptions {
580
586
  */
581
587
  usePyin?: boolean;
582
588
  /**
583
- * When {@link usePyin} is `true`, reflect-pad by `frameLength / 2` so frame
589
+ * When {@link usePyin} is `true`, zero-pad by `frameLength / 2` so frame
584
590
  * `i` is centered at `i * hopLength` (matches `librosa.pyin(center=True)`).
585
591
  * Ignored by the plain-YIN path. Defaults to `true`.
586
592
  */
@@ -756,6 +762,8 @@ export function fourierTempogram(
756
762
  sampleRate?: number,
757
763
  hopLength?: number,
758
764
  winLength?: number,
765
+ center?: boolean,
766
+ norm?: boolean,
759
767
  options?: GuardedOptions,
760
768
  ): WasmFourierTempogramResult;
761
769
  export function fourierTempogram(
@@ -763,6 +771,8 @@ export function fourierTempogram(
763
771
  sampleRate = 22050,
764
772
  hopLength = 512,
765
773
  winLength = 384,
774
+ center = true,
775
+ norm = true,
766
776
  options: GuardedOptions = {},
767
777
  ): WasmFourierTempogramResult {
768
778
  if (!(onsetEnvelope instanceof Float32Array)) {
@@ -772,13 +782,22 @@ export function fourierTempogram(
772
782
  request.sampleRate,
773
783
  request.hopLength,
774
784
  request.winLength,
785
+ request.center,
786
+ request.norm,
775
787
  request,
776
788
  );
777
789
  }
778
790
  assertSampleRate('fourierTempogram', sampleRate);
779
791
  assertSamples('fourierTempogram', onsetEnvelope, options.validate !== false, 'onsetEnvelope');
780
792
  validatePositiveIntegers('fourierTempogram', { hopLength, winLength });
781
- return requireModule().fourierTempogram(onsetEnvelope, sampleRate, hopLength, winLength);
793
+ return requireModule().fourierTempogram(
794
+ onsetEnvelope,
795
+ sampleRate,
796
+ hopLength,
797
+ winLength,
798
+ center,
799
+ norm,
800
+ );
782
801
  }
783
802
 
784
803
  /**
@@ -1,5 +1,5 @@
1
1
  import { getSonareModule } from './module_state';
2
- import type { PitchResult } from './public_types';
2
+ import type { NoteSegment, PiptrackResult, PitchResult } from './public_types';
3
3
 
4
4
  function requireModule() {
5
5
  return getSonareModule();
@@ -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 {
@@ -33,6 +33,51 @@ export interface PitchYinRequest {
33
33
  fillNa?: boolean;
34
34
  }
35
35
 
36
+ export interface PiptrackRequest {
37
+ samples: Float32Array;
38
+ sampleRate?: number;
39
+ nFft?: number;
40
+ hopLength?: number;
41
+ fmin?: number;
42
+ fmax?: number;
43
+ threshold?: number;
44
+ }
45
+
46
+ /** Per-bin spectral pitch candidates and peak magnitudes (librosa.piptrack). */
47
+ export function piptrack(request: PiptrackRequest): PiptrackResult;
48
+ export function piptrack(
49
+ samples: Float32Array,
50
+ sampleRate?: number,
51
+ nFft?: number,
52
+ hopLength?: number,
53
+ fmin?: number,
54
+ fmax?: number,
55
+ threshold?: number,
56
+ ): PiptrackResult;
57
+ export function piptrack(
58
+ samples: Float32Array | PiptrackRequest,
59
+ sampleRate = 22050,
60
+ nFft = 2048,
61
+ hopLength = 512,
62
+ fmin = 150,
63
+ fmax = 4000,
64
+ threshold = 0.1,
65
+ ): PiptrackResult {
66
+ if (!(samples instanceof Float32Array)) {
67
+ const request = samples;
68
+ return piptrack(
69
+ request.samples,
70
+ request.sampleRate,
71
+ request.nFft,
72
+ request.hopLength,
73
+ request.fmin,
74
+ request.fmax,
75
+ request.threshold,
76
+ );
77
+ }
78
+ return requireModule().piptrack(samples, sampleRate, nFft, hopLength, fmin, fmax, threshold);
79
+ }
80
+
36
81
  export function pitchYin(request: PitchYinRequest): PitchResult;
37
82
  export function pitchYin(
38
83
  samples: Float32Array,
@@ -51,7 +96,7 @@ export function pitchYin(
51
96
  hopLength = 512,
52
97
  fmin = 65.0,
53
98
  fmax = 2093.0,
54
- threshold = 0.3,
99
+ threshold = 0.1,
55
100
  fillNa = false,
56
101
  ): PitchResult {
57
102
  if (!(samples instanceof Float32Array)) {
@@ -88,7 +133,7 @@ export function pitchYin(
88
133
  * @param hopLength - Hop length (default: 512)
89
134
  * @param fmin - Minimum frequency in Hz (default: 65)
90
135
  * @param fmax - Maximum frequency in Hz (default: 2093)
91
- * @param threshold - YIN threshold (default: 0.3)
136
+ * @param threshold - YIN threshold (default: 0.1)
92
137
  * @param fillNa - If true, return 0 for unvoiced f0 frames; otherwise keep NaN (default: false)
93
138
  * @returns Pitch detection result
94
139
  */
@@ -112,7 +157,7 @@ export function pitchPyin(
112
157
  hopLength = 512,
113
158
  fmin = 65.0,
114
159
  fmax = 2093.0,
115
- threshold = 0.3,
160
+ threshold = 0.1,
116
161
  fillNa = false,
117
162
  ): PitchResult {
118
163
  if (!(samples instanceof Float32Array)) {
@@ -139,3 +184,27 @@ export function pitchPyin(
139
184
  fillNa,
140
185
  );
141
186
  }
187
+
188
+ /** Parameters for segmenting an F0 track into stable monophonic notes. */
189
+ export interface NoteSegmentsRequest {
190
+ f0Hz: Float32Array;
191
+ voicedProb: Float32Array;
192
+ frameRate: number;
193
+ segmentationThresholdCents?: number;
194
+ minNoteMs?: number;
195
+ referenceHz?: number;
196
+ }
197
+
198
+ /**
199
+ * Segment a caller-supplied monophonic F0 track into stable note regions.
200
+ *
201
+ * `f0Hz` and `voicedProb` must have the same non-zero length. Zero-Hz frames
202
+ * and probabilities below 0.5 are treated as unvoiced.
203
+ */
204
+ export function noteSegments(request: NoteSegmentsRequest): NoteSegment[] {
205
+ return requireModule().noteSegments(request.f0Hz, request.voicedProb, request.frameRate, {
206
+ segmentationThresholdCents: request.segmentationThresholdCents,
207
+ minNoteMs: request.minNoteMs,
208
+ referenceHz: request.referenceHz,
209
+ });
210
+ }