@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/dist/index.js CHANGED
@@ -200,6 +200,10 @@ function assertSampleRate(fnName, sampleRate) {
200
200
  );
201
201
  }
202
202
  }
203
+ function validateAudioBuffer(samples, sampleRate) {
204
+ assertSamples("Audio.fromBuffer", samples, true);
205
+ assertSampleRate("Audio.fromBuffer", sampleRate);
206
+ }
203
207
  function assertNonNegativeInteger(fnName, value, argName) {
204
208
  if (!Number.isInteger(value) || value < 0) {
205
209
  throw new RangeError(`${fnName}: ${argName} must be a non-negative integer`);
@@ -244,12 +248,16 @@ function percussive(samples, sampleRate = 22050, options = {}) {
244
248
  function timeStretch(samples, sampleRate, rate, options = {}) {
245
249
  const request = samples instanceof Float32Array ? { samples, sampleRate, rate, ...options } : samples;
246
250
  assertSamples("timeStretch", request.samples, request.validate !== false);
247
- return requireModule().timeStretch(request.samples, request.sampleRate, request.rate);
251
+ return requireModule().timeStretch(request.samples, request.sampleRate ?? 22050, request.rate);
248
252
  }
249
253
  function pitchShift(samples, sampleRate, semitones, options = {}) {
250
254
  const request = samples instanceof Float32Array ? { samples, sampleRate, semitones, ...options } : samples;
251
255
  assertSamples("pitchShift", request.samples, request.validate !== false);
252
- return requireModule().pitchShift(request.samples, request.sampleRate, request.semitones);
256
+ return requireModule().pitchShift(
257
+ request.samples,
258
+ request.sampleRate ?? 22050,
259
+ request.semitones
260
+ );
253
261
  }
254
262
  function pitchCorrectToMidi(samples, sampleRate = 22050, currentMidi = 69, targetMidi = 69, options = {}) {
255
263
  const request = samples instanceof Float32Array ? { samples, sampleRate, currentMidi, targetMidi, ...options } : samples;
@@ -318,7 +326,7 @@ function noteStretch(samples, sampleRate = 22050, options = {}) {
318
326
  request.samples,
319
327
  request.sampleRate ?? 22050,
320
328
  request.onsetSample ?? 0,
321
- request.offsetSample ?? 0,
329
+ request.offsetSample ?? request.samples.length,
322
330
  request.stretchRatio ?? 1
323
331
  );
324
332
  }
@@ -329,14 +337,18 @@ function noteMove(samples, sampleRate = 22050, options = {}) {
329
337
  request.samples,
330
338
  request.sampleRate ?? 22050,
331
339
  request.onsetSample ?? 0,
332
- request.offsetSample ?? 0,
340
+ request.offsetSample ?? request.samples.length,
333
341
  request.targetOnsetSample ?? 0
334
342
  );
335
343
  }
336
344
  function normalize(samples, sampleRate, targetDb = 0, options = {}) {
337
345
  const request = samples instanceof Float32Array ? { samples, sampleRate, targetDb, ...options } : samples;
338
346
  assertSamples("normalize", request.samples, request.validate !== false);
339
- return requireModule().normalize(request.samples, request.sampleRate, request.targetDb ?? 0);
347
+ return requireModule().normalize(
348
+ request.samples,
349
+ request.sampleRate ?? 22050,
350
+ request.targetDb ?? 0
351
+ );
340
352
  }
341
353
  function spectralEdit(samples, sampleRate, ops = [], options = {}) {
342
354
  const request = samples instanceof Float32Array ? { samples, sampleRate, ops, ...options } : samples;
@@ -397,22 +409,6 @@ function flattenChainConfig(config) {
397
409
  }
398
410
  };
399
411
  walk(config, "");
400
- const aliases = {
401
- "repair.denoise": "repair.denoise.enabled",
402
- "repair.nFft": "repair.denoise.nFft",
403
- "repair.hopLength": "repair.denoise.hopLength",
404
- "repair.ddAlpha": "repair.denoise.ddAlpha",
405
- "repair.gainFloor": "repair.denoise.gainFloor",
406
- "eq.tiltDb": "eq.tilt.tiltDb",
407
- "eq.pivotHz": "eq.tilt.pivotHz"
408
- };
409
- for (const [legacy, canonical] of Object.entries(aliases)) {
410
- const value = out[legacy];
411
- if (value !== void 0) {
412
- out[canonical] = value;
413
- delete out[legacy];
414
- }
415
- }
416
412
  return out;
417
413
  }
418
414
 
@@ -972,7 +968,17 @@ function vectorNormalize(values, normType = 0, threshold = 0) {
972
968
  function pcen(values, nBins = 0, nFrames = 0, options = {}) {
973
969
  if (!(values instanceof Float32Array)) {
974
970
  const r = values;
975
- return pcen(r.values, r.nBins, r.nFrames, r.options);
971
+ const {
972
+ values: requestValues,
973
+ nBins: requestBins,
974
+ nFrames: requestFrames,
975
+ options: legacyOptions,
976
+ ...flatOptions
977
+ } = r;
978
+ return pcen(requestValues, requestBins, requestFrames, {
979
+ ...legacyOptions,
980
+ ...flatOptions
981
+ });
976
982
  }
977
983
  return requireModule8().pcen(values, nBins, nFrames, options);
978
984
  }
@@ -1048,7 +1054,13 @@ function nnlsChroma(samples, sampleRate = 22050, options = {}) {
1048
1054
  return nnlsChroma(samples.samples, samples.sampleRate, samples);
1049
1055
  }
1050
1056
  validateMusicSamples("nnlsChroma", samples, sampleRate, options);
1051
- return requireModule9().nnlsChroma(samples, sampleRate);
1057
+ return requireModule9().nnlsChroma(
1058
+ samples,
1059
+ sampleRate,
1060
+ options.enableStftBlend ?? true,
1061
+ options.stftBlendWeight ?? 0.55,
1062
+ options.stftBlendNFft ?? 4096
1063
+ );
1052
1064
  }
1053
1065
  function cqt(samples, sampleRate = 22050, hopLength = 512, fmin = 32.70319566257483, nBins = 84, binsPerOctave = 12, options = {}) {
1054
1066
  if (!(samples instanceof Float32Array)) {
@@ -1104,7 +1116,7 @@ function hybridCqt(samples, sampleRate = 22050, hopLength = 512, fmin = 32.70319
1104
1116
  validateFrequencyBounds("hybridCqt", fmin);
1105
1117
  return requireModule9().hybridCqt(samples, sampleRate, hopLength, fmin, nBins, binsPerOctave);
1106
1118
  }
1107
- function vqt(samples, sampleRate = 22050, hopLength = 512, fmin = 32.70319566257483, nBins = 84, binsPerOctave = 12, gamma = 0, options = {}) {
1119
+ function vqt(samples, sampleRate = 22050, hopLength = 512, fmin = 32.70319566257483, nBins = 84, binsPerOctave = 12, gamma = -1, options = {}) {
1108
1120
  if (!(samples instanceof Float32Array)) {
1109
1121
  const request = samples;
1110
1122
  return vqt(
@@ -1122,9 +1134,6 @@ function vqt(samples, sampleRate = 22050, hopLength = 512, fmin = 32.70319566257
1122
1134
  validatePositiveIntegers("vqt", { hopLength, nBins, binsPerOctave });
1123
1135
  validateFrequencyBounds("vqt", fmin);
1124
1136
  assertFiniteScalar("vqt", gamma, "gamma");
1125
- if (gamma < 0) {
1126
- throw new RangeError("vqt: gamma must be non-negative");
1127
- }
1128
1137
  return requireModule9().vqt(samples, sampleRate, hopLength, fmin, nBins, binsPerOctave, gamma);
1129
1138
  }
1130
1139
  function validateCqtInverse(fnName, magnitude, nBins, nFrames, sampleRate, hopLength, fmin, binsPerOctave, nIter, options) {
@@ -1180,7 +1189,7 @@ function cqtToAudio(magnitude, nBins = 0, nFrames = 0, sampleRate = 22050, hopLe
1180
1189
  nIter
1181
1190
  );
1182
1191
  }
1183
- function vqtToAudio(magnitude, nBins = 0, nFrames = 0, sampleRate = 22050, hopLength = 512, fmin = 32.70319566257483, binsPerOctave = 12, gamma = 0, nIter = 32, options = {}) {
1192
+ function vqtToAudio(magnitude, nBins = 0, nFrames = 0, sampleRate = 22050, hopLength = 512, fmin = 32.70319566257483, binsPerOctave = 12, gamma = -1, nIter = 32, options = {}) {
1184
1193
  if (!(magnitude instanceof Float32Array)) {
1185
1194
  const request = magnitude;
1186
1195
  return vqtToAudio(
@@ -1209,9 +1218,6 @@ function vqtToAudio(magnitude, nBins = 0, nFrames = 0, sampleRate = 22050, hopLe
1209
1218
  options
1210
1219
  );
1211
1220
  assertFiniteScalar("vqtToAudio", gamma, "gamma");
1212
- if (gamma < 0) {
1213
- throw new RangeError("vqtToAudio: gamma must be non-negative");
1214
- }
1215
1221
  return requireModule9().vqtToAudio(
1216
1222
  magnitude,
1217
1223
  nBins,
@@ -1235,8 +1241,8 @@ function analyzeSections(samples, sampleRate = 22050, options = {}) {
1235
1241
  hopLength: options.hopLength ?? 512
1236
1242
  });
1237
1243
  assertFiniteScalar("analyzeSections", options.minSectionSec ?? 4, "minSectionSec");
1238
- if ((options.minSectionSec ?? 4) <= 0) {
1239
- throw new RangeError("analyzeSections: minSectionSec must be positive");
1244
+ if ((options.minSectionSec ?? 4) < 0) {
1245
+ throw new RangeError("analyzeSections: minSectionSec must be non-negative");
1240
1246
  }
1241
1247
  const sections = requireModule9().analyzeSections(
1242
1248
  samples,
@@ -1257,7 +1263,11 @@ function analyzeMelody(samples, sampleRate = 22050, options = {}) {
1257
1263
  const fmax = options.fmax ?? 2093;
1258
1264
  validateFrequencyBounds("analyzeMelody", fmin, fmax);
1259
1265
  if (fmin <= 0) {
1260
- throw new RangeError("analyzeMelody: fmin must be positive");
1266
+ throw new SonareError(
1267
+ 4 /* InvalidParameter */,
1268
+ "InvalidParameter",
1269
+ "analyzeMelody: fmin must be positive"
1270
+ );
1261
1271
  }
1262
1272
  validatePositiveIntegers("analyzeMelody", {
1263
1273
  frameLength: options.frameLength ?? 2048,
@@ -1266,7 +1276,11 @@ function analyzeMelody(samples, sampleRate = 22050, options = {}) {
1266
1276
  const threshold = options.threshold ?? 0.1;
1267
1277
  assertFiniteScalar("analyzeMelody", threshold, "threshold");
1268
1278
  if (threshold <= 0) {
1269
- throw new RangeError("analyzeMelody: threshold must be positive");
1279
+ throw new SonareError(
1280
+ 4 /* InvalidParameter */,
1281
+ "InvalidParameter",
1282
+ "analyzeMelody: threshold must be positive"
1283
+ );
1270
1284
  }
1271
1285
  return requireModule9().analyzeMelody(
1272
1286
  samples,
@@ -1378,7 +1392,7 @@ function shortTermLufs(samples, sampleRate = 22050, options = {}) {
1378
1392
  function requireModule10() {
1379
1393
  return getSonareModule();
1380
1394
  }
1381
- function pitchYin(samples, sampleRate = 22050, frameLength = 2048, hopLength = 512, fmin = 65, fmax = 2093, threshold = 0.3, fillNa = false) {
1395
+ function pitchYin(samples, sampleRate = 22050, frameLength = 2048, hopLength = 512, fmin = 65, fmax = 2093, threshold = 0.1, fillNa = false) {
1382
1396
  if (!(samples instanceof Float32Array)) {
1383
1397
  const request = samples;
1384
1398
  return pitchYin(
@@ -1403,7 +1417,7 @@ function pitchYin(samples, sampleRate = 22050, frameLength = 2048, hopLength = 5
1403
1417
  fillNa
1404
1418
  );
1405
1419
  }
1406
- function pitchPyin(samples, sampleRate = 22050, frameLength = 2048, hopLength = 512, fmin = 65, fmax = 2093, threshold = 0.3, fillNa = false) {
1420
+ function pitchPyin(samples, sampleRate = 22050, frameLength = 2048, hopLength = 512, fmin = 65, fmax = 2093, threshold = 0.1, fillNa = false) {
1407
1421
  if (!(samples instanceof Float32Array)) {
1408
1422
  const request = samples;
1409
1423
  return pitchPyin(
@@ -1559,10 +1573,10 @@ function remix(samples, intervals, sampleRate = 22050, alignZeros = false) {
1559
1573
  const intervalsI32 = intervals instanceof Int32Array ? intervals : Int32Array.from(intervals, (v) => Math.trunc(v));
1560
1574
  return requireModule12().remix(samples, intervalsI32, sampleRate, alignZeros);
1561
1575
  }
1562
- function phaseVocoder(samples, rate = 1, sampleRate = 22050, nFft = 2048, hopLength = 512) {
1576
+ function phaseVocoder(samples, sampleRate = 22050, rate = 1, nFft = 2048, hopLength = 512) {
1563
1577
  if (!(samples instanceof Float32Array)) {
1564
1578
  const r = samples;
1565
- return phaseVocoder(r.samples, r.rate, r.sampleRate, r.nFft, r.hopLength);
1579
+ return phaseVocoder(r.samples, r.sampleRate ?? 22050, r.rate, r.nFft, r.hopLength);
1566
1580
  }
1567
1581
  return requireModule12().phaseVocoder(samples, sampleRate, rate, nFft, hopLength);
1568
1582
  }
@@ -1692,7 +1706,7 @@ function stftDb(samples, sampleRate = 22050, nFft = 2048, hopLength = 512, optio
1692
1706
  validatePositiveIntegers2("stftDb", { nFft, hopLength });
1693
1707
  return requireModule13().stftDb(samples, sampleRate, nFft, hopLength);
1694
1708
  }
1695
- function chromaCens(samples, sampleRate = 22050, hopLength = 512, nChroma = 12, options = {}) {
1709
+ function chromaCens(samples, sampleRate = 22050, hopLength = 512, nChroma = 12, binsPerOctave = 36, options = {}) {
1696
1710
  if (!(samples instanceof Float32Array)) {
1697
1711
  const request = samples;
1698
1712
  return chromaCens(
@@ -1700,14 +1714,18 @@ function chromaCens(samples, sampleRate = 22050, hopLength = 512, nChroma = 12,
1700
1714
  request.sampleRate,
1701
1715
  request.hopLength,
1702
1716
  request.nChroma,
1717
+ request.binsPerOctave,
1703
1718
  request
1704
1719
  );
1705
1720
  }
1706
1721
  validateSpectrogramSamples("chromaCens", samples, sampleRate, options);
1707
- validatePositiveIntegers2("chromaCens", { hopLength, nChroma });
1708
- return requireModule13().chromaCens(samples, sampleRate, hopLength, nChroma);
1722
+ validatePositiveIntegers2("chromaCens", { hopLength, nChroma, binsPerOctave });
1723
+ if (binsPerOctave % nChroma !== 0) {
1724
+ throw new RangeError("chromaCens: binsPerOctave must be a multiple of nChroma");
1725
+ }
1726
+ return requireModule13().chromaCens(samples, sampleRate, hopLength, nChroma, binsPerOctave);
1709
1727
  }
1710
- function chromaCqt(samples, sampleRate = 22050, hopLength = 512, nChroma = 12, options = {}) {
1728
+ function chromaCqt(samples, sampleRate = 22050, hopLength = 512, nChroma = 12, binsPerOctave = 36, options = {}) {
1711
1729
  if (!(samples instanceof Float32Array)) {
1712
1730
  const request = samples;
1713
1731
  return chromaCqt(
@@ -1715,12 +1733,16 @@ function chromaCqt(samples, sampleRate = 22050, hopLength = 512, nChroma = 12, o
1715
1733
  request.sampleRate,
1716
1734
  request.hopLength,
1717
1735
  request.nChroma,
1736
+ request.binsPerOctave,
1718
1737
  request
1719
1738
  );
1720
1739
  }
1721
1740
  validateSpectrogramSamples("chromaCqt", samples, sampleRate, options);
1722
- validatePositiveIntegers2("chromaCqt", { hopLength, nChroma });
1723
- return requireModule13().chromaCqt(samples, sampleRate, hopLength, nChroma);
1741
+ validatePositiveIntegers2("chromaCqt", { hopLength, nChroma, binsPerOctave });
1742
+ if (binsPerOctave % nChroma !== 0) {
1743
+ throw new RangeError("chromaCqt: binsPerOctave must be a multiple of nChroma");
1744
+ }
1745
+ return requireModule13().chromaCqt(samples, sampleRate, hopLength, nChroma, binsPerOctave);
1724
1746
  }
1725
1747
  function bassChroma(samples, sampleRate = 22050, hopLength = 512, nChroma = 12, options = {}) {
1726
1748
  if (!(samples instanceof Float32Array)) {
@@ -1854,7 +1876,7 @@ function melToAudio(melPower, nMels = 0, nFrames = 0, sampleRate = 22050, nFft =
1854
1876
  htk
1855
1877
  );
1856
1878
  }
1857
- function mfccToMel(mfccCoefficients, nMfcc = 0, nFrames = 0, nMels = 128, options = {}) {
1879
+ function mfccToMel(mfccCoefficients, nMfcc = 0, nFrames = 0, nMels = 128, lifter = 0, options = {}) {
1858
1880
  if (!(mfccCoefficients instanceof Float32Array)) {
1859
1881
  const request = mfccCoefficients;
1860
1882
  return mfccToMel(
@@ -1862,6 +1884,7 @@ function mfccToMel(mfccCoefficients, nMfcc = 0, nFrames = 0, nMels = 128, option
1862
1884
  request.nMfcc,
1863
1885
  request.nFrames,
1864
1886
  request.nMels,
1887
+ request.lifter,
1865
1888
  request
1866
1889
  );
1867
1890
  }
@@ -1875,9 +1898,9 @@ function mfccToMel(mfccCoefficients, nMfcc = 0, nFrames = 0, nMels = 128, option
1875
1898
  options
1876
1899
  );
1877
1900
  validatePositiveIntegers2("mfccToMel", { nMels });
1878
- return requireModule13().mfccToMel(mfccCoefficients, nMfcc, nFrames, nMels);
1901
+ return requireModule13().mfccToMel(mfccCoefficients, nMfcc, nFrames, nMels, lifter);
1879
1902
  }
1880
- function mfccToAudio(mfccCoefficients, nMfcc = 0, nFrames = 0, nMels = 128, sampleRate = 22050, nFft = 2048, hopLength = 512, fmin = 0, fmax = 0, nIter = 32, htk = false, options = {}) {
1903
+ function mfccToAudio(mfccCoefficients, nMfcc = 0, nFrames = 0, nMels = 128, sampleRate = 22050, nFft = 2048, hopLength = 512, fmin = 0, fmax = 0, nIter = 32, htk = false, lifter = 0, options = {}) {
1881
1904
  if (!(mfccCoefficients instanceof Float32Array)) {
1882
1905
  const request = mfccCoefficients;
1883
1906
  return mfccToAudio(
@@ -1892,6 +1915,7 @@ function mfccToAudio(mfccCoefficients, nMfcc = 0, nFrames = 0, nMels = 128, samp
1892
1915
  request.fmax,
1893
1916
  request.nIter,
1894
1917
  request.htk,
1918
+ request.lifter,
1895
1919
  request
1896
1920
  );
1897
1921
  }
@@ -1918,7 +1942,8 @@ function mfccToAudio(mfccCoefficients, nMfcc = 0, nFrames = 0, nMels = 128, samp
1918
1942
  fmin,
1919
1943
  fmax,
1920
1944
  nIter,
1921
- htk
1945
+ htk,
1946
+ lifter
1922
1947
  );
1923
1948
  }
1924
1949
  function chroma(samples, sampleRate = 22050, nFft = 2048, hopLength = 512, options = {}) {
@@ -1995,6 +2020,23 @@ var SectionType = {
1995
2020
  };
1996
2021
 
1997
2022
  // src/analysis_helpers.ts
2023
+ var PITCH_CLASS_NAMES = [
2024
+ "C",
2025
+ "C#",
2026
+ "D",
2027
+ "D#",
2028
+ "E",
2029
+ "F",
2030
+ "F#",
2031
+ "G",
2032
+ "G#",
2033
+ "A",
2034
+ "A#",
2035
+ "B"
2036
+ ];
2037
+ function pitchClassName(value) {
2038
+ return PITCH_CLASS_NAMES[value] ?? "C";
2039
+ }
1998
2040
  function convertKeyCandidate(wasm) {
1999
2041
  return {
2000
2042
  key: {
@@ -2065,6 +2107,8 @@ function convertChordAnalysisResult(wasm) {
2065
2107
  chords: wasm.chords.map((c) => ({
2066
2108
  root: c.root,
2067
2109
  bass: c.bass,
2110
+ rootName: pitchClassName(c.root),
2111
+ bassName: pitchClassName(c.bass),
2068
2112
  quality: c.quality,
2069
2113
  start: c.start,
2070
2114
  end: c.end,
@@ -2103,6 +2147,8 @@ function convertAnalysisResult(wasm) {
2103
2147
  chords: wasm.chords.map((c) => ({
2104
2148
  root: c.root,
2105
2149
  bass: c.bass,
2150
+ rootName: pitchClassName(c.root),
2151
+ bassName: pitchClassName(c.bass),
2106
2152
  quality: c.quality,
2107
2153
  start: c.start,
2108
2154
  end: c.end,
@@ -2251,7 +2297,7 @@ function chordFunctionalAnalysis(samples, keyRoot, keyMode, sampleRate = 22050,
2251
2297
  function analyze(samples, sampleRate = 22050, options = {}) {
2252
2298
  const request = samples instanceof Float32Array ? { samples, sampleRate, ...options } : samples;
2253
2299
  validateAnalysisInput("analyze", request.samples, request.sampleRate ?? 22050, request);
2254
- const result = requireModule14().analyze(request.samples, request.sampleRate ?? 22050);
2300
+ const result = requireModule14().analyze(request.samples, request.sampleRate ?? 22050, request);
2255
2301
  return convertAnalysisResult(result);
2256
2302
  }
2257
2303
  function analyzeImpulseResponse(samples, sampleRate = 48e3, nOctaveBands = 6) {
@@ -2425,7 +2471,8 @@ var Audio = class _Audio {
2425
2471
  * Node/Python surfaces).
2426
2472
  */
2427
2473
  static fromBuffer(samples, sampleRate = 48e3) {
2428
- return new _Audio(samples, sampleRate);
2474
+ validateAudioBuffer(samples, sampleRate);
2475
+ return new _Audio(samples.slice(), sampleRate);
2429
2476
  }
2430
2477
  /**
2431
2478
  * Create an Audio instance by decoding audio bytes in memory.
@@ -2624,7 +2671,7 @@ var Audio = class _Audio {
2624
2671
  rmsEnergy(frameLength = 2048, hopLength = 512) {
2625
2672
  return rmsEnergy(this._samples, this._sampleRate, frameLength, hopLength);
2626
2673
  }
2627
- pitchYin(frameLength = 2048, hopLength = 512, fmin = 65, fmax = 2093, threshold = 0.3, fillNa = false) {
2674
+ pitchYin(frameLength = 2048, hopLength = 512, fmin = 65, fmax = 2093, threshold = 0.1, fillNa = false) {
2628
2675
  return pitchYin(
2629
2676
  this._samples,
2630
2677
  this._sampleRate,
@@ -2636,7 +2683,7 @@ var Audio = class _Audio {
2636
2683
  fillNa
2637
2684
  );
2638
2685
  }
2639
- pitchPyin(frameLength = 2048, hopLength = 512, fmin = 65, fmax = 2093, threshold = 0.3, fillNa = false) {
2686
+ pitchPyin(frameLength = 2048, hopLength = 512, fmin = 65, fmax = 2093, threshold = 0.1, fillNa = false) {
2640
2687
  return pitchPyin(
2641
2688
  this._samples,
2642
2689
  this._sampleRate,
@@ -3064,6 +3111,17 @@ function meteringRmsDb(samples, sampleRate = 22050, options = {}) {
3064
3111
  assertSamples("meteringRmsDb", request.samples, request.validate !== false);
3065
3112
  return requireModule15().meteringRmsDb(request.samples, request.sampleRate ?? 22050);
3066
3113
  }
3114
+ function meteringSilenceRatio(samples, sampleRate = 22050, thresholdDb = -45, frameLength = 1024, hopLength = 256, options = {}) {
3115
+ const request = samples instanceof Float32Array ? { samples, sampleRate, thresholdDb, frameLength, hopLength, ...options } : samples;
3116
+ assertSamples("meteringSilenceRatio", request.samples, request.validate !== false);
3117
+ return requireModule15().meteringSilenceRatio(
3118
+ request.samples,
3119
+ request.sampleRate ?? 22050,
3120
+ request.thresholdDb ?? -45,
3121
+ request.frameLength ?? 1024,
3122
+ request.hopLength ?? 256
3123
+ );
3124
+ }
3067
3125
  function meteringCrestFactorDb(samples, sampleRate = 22050, options = {}) {
3068
3126
  const request = samples instanceof Float32Array ? { samples, sampleRate, ...options } : samples;
3069
3127
  assertSamples("meteringCrestFactorDb", request.samples, request.validate !== false);
@@ -3084,11 +3142,15 @@ function meteringTruePeakDb(samples, sampleRate = 22050, oversampleFactor = 4, o
3084
3142
  function meteringDetectClipping(samples, sampleRate = 22050, options = {}) {
3085
3143
  const request = samples instanceof Float32Array ? { samples, sampleRate, ...options } : samples;
3086
3144
  assertSamples("meteringDetectClipping", request.samples, request.validate !== false);
3145
+ const minRegionSamples = request.minRegionSamples ?? 1;
3146
+ if (!Number.isInteger(minRegionSamples) || minRegionSamples < 0) {
3147
+ throw new RangeError("meteringDetectClipping: minRegionSamples must be a non-negative integer");
3148
+ }
3087
3149
  return requireModule15().meteringDetectClipping(
3088
3150
  request.samples,
3089
3151
  request.sampleRate ?? 22050,
3090
3152
  request.threshold ?? 0.999,
3091
- request.minRegionSamples ?? 1
3153
+ minRegionSamples
3092
3154
  );
3093
3155
  }
3094
3156
  function meteringDynamicRange(samples, sampleRate = 22050, options = {}) {
@@ -3555,12 +3617,27 @@ var Project = class _Project {
3555
3617
  redo() {
3556
3618
  this.native.redo();
3557
3619
  }
3620
+ /** Clear the undo/redo history without changing the current project state. */
3621
+ clearHistory() {
3622
+ this.native.clearHistory();
3623
+ }
3624
+ /** Cap the undo history depth (clamped to >= 1); evicts oldest entries beyond the cap. */
3625
+ setMaxUndoDepth(depth) {
3626
+ if (!Number.isInteger(depth) || depth < 1) {
3627
+ throw new RangeError("Project.setMaxUndoDepth: depth must be an integer >= 1");
3628
+ }
3629
+ this.native.setMaxUndoDepth(depth);
3630
+ }
3558
3631
  /** Replace a MIDI clip's entire event list. */
3559
3632
  setMidiEvents(clipId, events) {
3560
3633
  assertProjectMidiEvents("Project.setMidiEvents", events);
3561
3634
  this.native.setMidiEvents(clipId, events);
3562
3635
  }
3563
- /** Import an in-memory SMF buffer; returns the first added clip id. */
3636
+ /**
3637
+ * Import an in-memory SMF buffer; returns the first added clip id.
3638
+ * Malformed or partially truncated tracks are rejected instead of installing
3639
+ * a silently shortened clip.
3640
+ */
3564
3641
  importSmf(data) {
3565
3642
  return this.native.importSmf(data);
3566
3643
  }
@@ -3597,7 +3674,10 @@ var Project = class _Project {
3597
3674
  setProgramOnChannel(clipId, group, channel, program, bank = -1) {
3598
3675
  this.native.setProgramOnChannel(clipId, group, channel, program, bank);
3599
3676
  }
3600
- /** Destructively bake a MIDI-FX chain into a clip's stored MIDI events. */
3677
+ /**
3678
+ * Destructively bake a MIDI-FX chain into all stored events. Large clips are
3679
+ * drained without truncation; failure leaves the original clip unchanged.
3680
+ */
3601
3681
  bakeMidiFx(clipId, configJson) {
3602
3682
  this.native.bakeMidiFx(clipId, configJson);
3603
3683
  }
@@ -3607,9 +3687,9 @@ var Project = class _Project {
3607
3687
  }
3608
3688
  /**
3609
3689
  * Pre-flight check for hanging / unmatched notes in a MIDI clip: reports
3610
- * whether every note-on has a matching note-off (FIFO per channel+note).
3611
- * Useful before bouncing to catch a stuck note. Throws if `clipId` is unknown
3612
- * or not a MIDI clip.
3690
+ * whether every note-on in the exported half-open playback window has a
3691
+ * matching note-off (FIFO per group+channel+note). Useful before bouncing to
3692
+ * catch a stuck note. Throws if `clipId` is unknown or not a MIDI clip.
3613
3693
  */
3614
3694
  validateMidiNotes(clipId) {
3615
3695
  return this.native.validateMidiNotes(clipId);
@@ -4021,6 +4101,8 @@ function panLawCode(panLaw) {
4021
4101
  return panLaw;
4022
4102
  }
4023
4103
  switch (panLaw) {
4104
+ case "const3dB":
4105
+ return 0;
4024
4106
  case "const4.5dB":
4025
4107
  return 1;
4026
4108
  case "const6dB":
@@ -4028,7 +4110,7 @@ function panLawCode(panLaw) {
4028
4110
  case "linear0dB":
4029
4111
  return 3;
4030
4112
  default:
4031
- return 0;
4113
+ throw new Error(`Invalid pan law: ${panLaw}`);
4032
4114
  }
4033
4115
  }
4034
4116
  function panModeCode(panMode) {
@@ -4036,6 +4118,8 @@ function panModeCode(panMode) {
4036
4118
  return panMode;
4037
4119
  }
4038
4120
  switch (panMode) {
4121
+ case "balance":
4122
+ return 0;
4039
4123
  case "stereoPan":
4040
4124
  case "stereo-pan":
4041
4125
  return 1;
@@ -4043,17 +4127,34 @@ function panModeCode(panMode) {
4043
4127
  case "dual-pan":
4044
4128
  return 2;
4045
4129
  default:
4046
- return 0;
4130
+ throw new Error(`Invalid pan mode: ${panMode}`);
4047
4131
  }
4048
4132
  }
4049
4133
  function meterTapCode(tap) {
4050
- return tap === "preFader" || tap === 0 ? 0 : 1;
4134
+ if (typeof tap === "number") {
4135
+ return tap;
4136
+ }
4137
+ switch (tap) {
4138
+ case "preFader":
4139
+ return 0;
4140
+ case "postFader":
4141
+ return 1;
4142
+ default:
4143
+ throw new Error(`Invalid meter tap: ${tap}`);
4144
+ }
4051
4145
  }
4052
4146
  function sendTimingCode(timing) {
4053
4147
  if (typeof timing === "number") {
4054
4148
  return timing;
4055
4149
  }
4056
- return timing === "preFader" ? 1 : 0;
4150
+ switch (timing) {
4151
+ case "postFader":
4152
+ return 0;
4153
+ case "preFader":
4154
+ return 1;
4155
+ default:
4156
+ throw new Error(`Invalid send timing: ${timing}`);
4157
+ }
4057
4158
  }
4058
4159
 
4059
4160
  // src/realtime_engine.ts
@@ -4170,6 +4271,10 @@ var RealtimeEngine = class {
4170
4271
  options.maxValue ?? 1
4171
4272
  );
4172
4273
  }
4274
+ /** Bind a 7/14-bit CC, RPN, or NRPN descriptor to a live parameter. */
4275
+ bindMidiCcBinding(binding) {
4276
+ this.native.bindMidiCcBinding(binding);
4277
+ }
4173
4278
  clearMidiCcBindings() {
4174
4279
  this.native.clearMidiCcBindings();
4175
4280
  }
@@ -4294,6 +4399,7 @@ var RealtimeEngine = class {
4294
4399
  seekPpq(ppq, renderFrame = -1) {
4295
4400
  this.native.seekPpq(ppq, renderFrame);
4296
4401
  }
4402
+ /** Set a finite tempo in the range (0, 100000] BPM. */
4297
4403
  setTempo(bpm) {
4298
4404
  this.native.setTempo(bpm);
4299
4405
  }
@@ -4348,6 +4454,7 @@ var RealtimeEngine = class {
4348
4454
  setLoopFromMarkers(startMarkerId, endMarkerId) {
4349
4455
  this.native.setLoopFromMarkers(startMarkerId, endMarkerId);
4350
4456
  }
4457
+ /** Set a metronome config; click lengths are limited to one second. */
4351
4458
  setMetronome(config) {
4352
4459
  this.native.setMetronome(config);
4353
4460
  }
@@ -4555,6 +4662,7 @@ var RealtimeEngine = class {
4555
4662
  setCaptureSource(source) {
4556
4663
  this.native.setCaptureSource(source);
4557
4664
  }
4665
+ /** Positive values delay capture relative to the punch window. */
4558
4666
  setRecordOffsetSamples(offsetSamples) {
4559
4667
  this.native.setRecordOffsetSamples(offsetSamples);
4560
4668
  }
@@ -4932,7 +5040,10 @@ var Mixer = class _Mixer {
4932
5040
  const module2 = getSonareModule();
4933
5041
  return new _Mixer(module2.createMixerFromSceneJson(json, sampleRate, blockSize), blockSize);
4934
5042
  }
4935
- /** Rebuild and compile the routing graph from the current scene topology. */
5043
+ /**
5044
+ * Rebuild and compile the routing graph without resetting its absolute
5045
+ * automation sample position or queued strip automation.
5046
+ */
4936
5047
  compile() {
4937
5048
  this.mixer.compile();
4938
5049
  }
@@ -5091,6 +5202,10 @@ var Mixer = class _Mixer {
5091
5202
  setVcaGroupGainDb(id, gainDb) {
5092
5203
  this.mixer.setVcaGroupGainDb(id, gainDb);
5093
5204
  }
5205
+ /** Replace an existing VCA group's strip membership. */
5206
+ setVcaGroupMembers(id, members) {
5207
+ this.mixer.setVcaGroupMembers(id, members);
5208
+ }
5094
5209
  /** Remove a VCA group by id. */
5095
5210
  removeVcaGroup(id) {
5096
5211
  this.mixer.removeVcaGroup(id);
@@ -5230,6 +5345,10 @@ var Mixer = class _Mixer {
5230
5345
  }
5231
5346
  return this.mixer.meterTap(stripIndex, meterTapCode(tap));
5232
5347
  }
5348
+ /** Read the post-insert meter for a compiled bus, including master. */
5349
+ busMeter(busId) {
5350
+ return this.mixer.busMeter(busId);
5351
+ }
5233
5352
  /**
5234
5353
  * Schedule sample-accurate fader automation on a strip.
5235
5354
  *
@@ -5330,6 +5449,61 @@ var Mixer = class _Mixer {
5330
5449
  };
5331
5450
 
5332
5451
  // src/realtime_voice_changer.ts
5452
+ function isFlatVoiceChangerPod(config) {
5453
+ return typeof config === "object" && config !== null && "retuneSemitones" in config;
5454
+ }
5455
+ function flatVoiceChangerPodToNested(pod) {
5456
+ return {
5457
+ inputGainDb: pod.inputGainDb,
5458
+ outputGainDb: pod.outputGainDb,
5459
+ wetMix: pod.wetMix,
5460
+ retune: { semitones: pod.retuneSemitones, mix: pod.retuneMix, grainSize: pod.retuneGrainSize },
5461
+ formant: {
5462
+ factor: pod.formantFactor,
5463
+ amount: pod.formantAmount,
5464
+ body: pod.formantBody,
5465
+ brightness: pod.formantBrightness,
5466
+ nasal: pod.formantNasal
5467
+ },
5468
+ eq: {
5469
+ highpassHz: pod.eqHighpassHz,
5470
+ bodyDb: pod.eqBodyDb,
5471
+ presenceDb: pod.eqPresenceDb,
5472
+ airDb: pod.eqAirDb
5473
+ },
5474
+ gate: {
5475
+ thresholdDb: pod.gateThresholdDb,
5476
+ attackMs: pod.gateAttackMs,
5477
+ releaseMs: pod.gateReleaseMs,
5478
+ rangeDb: pod.gateRangeDb
5479
+ },
5480
+ compressor: {
5481
+ thresholdDb: pod.compressorThresholdDb,
5482
+ ratio: pod.compressorRatio,
5483
+ attackMs: pod.compressorAttackMs,
5484
+ releaseMs: pod.compressorReleaseMs,
5485
+ makeupGainDb: pod.compressorMakeupGainDb
5486
+ },
5487
+ deesser: {
5488
+ frequencyHz: pod.deesserFrequencyHz,
5489
+ thresholdDb: pod.deesserThresholdDb,
5490
+ ratio: pod.deesserRatio,
5491
+ rangeDb: pod.deesserRangeDb
5492
+ },
5493
+ reverb: {
5494
+ mix: pod.reverbMix,
5495
+ timeMs: pod.reverbTimeMs,
5496
+ damping: pod.reverbDamping,
5497
+ seed: pod.reverbSeed
5498
+ },
5499
+ limiter: {
5500
+ ceilingDb: pod.limiterCeilingDb,
5501
+ releaseMs: pod.limiterReleaseMs,
5502
+ enableIspLimiter: pod.limiterEnableIspLimiter,
5503
+ ispCeilingDbtp: pod.limiterIspCeilingDbtp
5504
+ }
5505
+ };
5506
+ }
5333
5507
  var RealtimeVoiceChanger = class {
5334
5508
  constructor(config = "neutral-monitor") {
5335
5509
  const module2 = getSonareModule();
@@ -5342,7 +5516,8 @@ var RealtimeVoiceChanger = class {
5342
5516
  this.changer.reset();
5343
5517
  }
5344
5518
  setConfig(config) {
5345
- this.changer.setConfig(config);
5519
+ const resolved = isFlatVoiceChangerPod(config) ? flatVoiceChangerPodToNested(config) : config;
5520
+ this.changer.setConfig(resolved);
5346
5521
  }
5347
5522
  configJson() {
5348
5523
  return this.changer.configJson();
@@ -5533,7 +5708,12 @@ var EQ_PHASE_MODES = {
5533
5708
  var StreamingMasteringChain = class {
5534
5709
  constructor(config) {
5535
5710
  const module2 = getSonareModule();
5536
- this.chain = module2.createStreamingMasteringChain(config);
5711
+ const { loudnessStaticGainDb, loudnessStaticGainPeakDb, ...chainConfig } = config;
5712
+ this.chain = module2.createStreamingMasteringChain({
5713
+ __flatParams: flattenChainConfig(chainConfig),
5714
+ loudnessStaticGainDb,
5715
+ loudnessStaticGainPeakDb
5716
+ });
5537
5717
  }
5538
5718
  /**
5539
5719
  * Initialize processors for the given sample rate and block layout.
@@ -6169,6 +6349,7 @@ export {
6169
6349
  meteringPhaseScope,
6170
6350
  meteringPhaseScopeDecimated,
6171
6351
  meteringRmsDb,
6352
+ meteringSilenceRatio,
6172
6353
  meteringSpectrum,
6173
6354
  meteringSpectrumFrame,
6174
6355
  meteringStereoCorrelation,