@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
package/dist/index.js CHANGED
@@ -8,6 +8,7 @@ var ErrorCode = /* @__PURE__ */ ((ErrorCode2) => {
8
8
  ErrorCode2[ErrorCode2["OutOfMemory"] = 5] = "OutOfMemory";
9
9
  ErrorCode2[ErrorCode2["NotSupported"] = 6] = "NotSupported";
10
10
  ErrorCode2[ErrorCode2["InvalidState"] = 7] = "InvalidState";
11
+ ErrorCode2[ErrorCode2["Cancelled"] = 8] = "Cancelled";
11
12
  ErrorCode2[ErrorCode2["Unknown"] = 99] = "Unknown";
12
13
  return ErrorCode2;
13
14
  })(ErrorCode || {});
@@ -200,6 +201,10 @@ function assertSampleRate(fnName, sampleRate) {
200
201
  );
201
202
  }
202
203
  }
204
+ function validateAudioBuffer(samples, sampleRate) {
205
+ assertSamples("Audio.fromBuffer", samples, true);
206
+ assertSampleRate("Audio.fromBuffer", sampleRate);
207
+ }
203
208
  function assertNonNegativeInteger(fnName, value, argName) {
204
209
  if (!Number.isInteger(value) || value < 0) {
205
210
  throw new RangeError(`${fnName}: ${argName} must be a non-negative integer`);
@@ -322,7 +327,7 @@ function noteStretch(samples, sampleRate = 22050, options = {}) {
322
327
  request.samples,
323
328
  request.sampleRate ?? 22050,
324
329
  request.onsetSample ?? 0,
325
- request.offsetSample ?? 0,
330
+ request.offsetSample ?? request.samples.length,
326
331
  request.stretchRatio ?? 1
327
332
  );
328
333
  }
@@ -333,7 +338,7 @@ function noteMove(samples, sampleRate = 22050, options = {}) {
333
338
  request.samples,
334
339
  request.sampleRate ?? 22050,
335
340
  request.onsetSample ?? 0,
336
- request.offsetSample ?? 0,
341
+ request.offsetSample ?? request.samples.length,
337
342
  request.targetOnsetSample ?? 0
338
343
  );
339
344
  }
@@ -401,26 +406,12 @@ function flattenChainConfig(config) {
401
406
  out[path] = value;
402
407
  } else if (value !== null && typeof value === "object") {
403
408
  walk(value, path);
409
+ } else if (value !== void 0) {
410
+ throw new TypeError(`Mastering override '${path}' must be a number or boolean.`);
404
411
  }
405
412
  }
406
413
  };
407
414
  walk(config, "");
408
- const aliases = {
409
- "repair.denoise": "repair.denoise.enabled",
410
- "repair.nFft": "repair.denoise.nFft",
411
- "repair.hopLength": "repair.denoise.hopLength",
412
- "repair.ddAlpha": "repair.denoise.ddAlpha",
413
- "repair.gainFloor": "repair.denoise.gainFloor",
414
- "eq.tiltDb": "eq.tilt.tiltDb",
415
- "eq.pivotHz": "eq.tilt.pivotHz"
416
- };
417
- for (const [legacy, canonical] of Object.entries(aliases)) {
418
- const value = out[legacy];
419
- if (value !== void 0) {
420
- out[canonical] = value;
421
- delete out[legacy];
422
- }
423
- }
424
415
  return out;
425
416
  }
426
417
 
@@ -458,12 +449,14 @@ function masterAudioStereoRequest(requestOrLeft, right, sampleRate, preset, over
458
449
  }
459
450
  function masteringChain(samples, sampleRate = 22050, config = {}, onProgress) {
460
451
  const request = samples instanceof Float32Array ? { samples, sampleRate, config, onProgress } : samples;
461
- if (request.onProgress) {
452
+ if (request.onProgress || request.cancel) {
462
453
  return requireModule3().masteringChainWithProgress(
463
454
  request.samples,
464
455
  request.sampleRate ?? 22050,
465
456
  canonicalChainConfig(request.config ?? {}),
466
- request.onProgress
457
+ request.onProgress ?? (() => {
458
+ }),
459
+ request.cancel ?? (() => false)
467
460
  );
468
461
  }
469
462
  return requireModule3().masteringChain(
@@ -477,13 +470,15 @@ function masteringChainStereo(left, right, sampleRate = 22050, config = {}, onPr
477
470
  if (request.left.length !== request.right.length) {
478
471
  throw new Error("Stereo channel lengths must match.");
479
472
  }
480
- if (request.onProgress) {
473
+ if (request.onProgress || request.cancel) {
481
474
  return requireModule3().masteringChainStereoWithProgress(
482
475
  request.left,
483
476
  request.right,
484
477
  request.sampleRate ?? 22050,
485
478
  canonicalChainConfig(request.config ?? {}),
486
- request.onProgress
479
+ request.onProgress ?? (() => {
480
+ }),
481
+ request.cancel ?? (() => false)
487
482
  );
488
483
  }
489
484
  return requireModule3().masteringChainStereo(
@@ -502,7 +497,8 @@ function masteringChainWithProgress(samples, sampleRate = 22050, config = {}, on
502
497
  request.samples,
503
498
  request.sampleRate ?? 22050,
504
499
  canonicalChainConfig(request.config ?? {}),
505
- request.onProgress
500
+ request.onProgress,
501
+ request.cancel ?? (() => false)
506
502
  );
507
503
  }
508
504
  function masteringChainStereoWithProgress(left, right, sampleRate = 22050, config = {}, onProgress) {
@@ -518,7 +514,8 @@ function masteringChainStereoWithProgress(left, right, sampleRate = 22050, confi
518
514
  request.right,
519
515
  request.sampleRate ?? 22050,
520
516
  canonicalChainConfig(request.config ?? {}),
521
- request.onProgress
517
+ request.onProgress,
518
+ request.cancel ?? (() => false)
522
519
  );
523
520
  }
524
521
  function masteringPresetNames() {
@@ -527,13 +524,15 @@ function masteringPresetNames() {
527
524
  function masterAudio(samples, sampleRate = 22050, presetName = "pop", overrides = {}, onProgress) {
528
525
  const request = masterAudioRequest(samples, sampleRate, presetName, overrides, onProgress);
529
526
  const flat = flattenChainConfig(request.overrides ?? {});
530
- if (request.onProgress) {
527
+ if (request.onProgress || request.cancel) {
531
528
  return requireModule3().masterAudioWithProgress(
532
529
  request.preset ?? "pop",
533
530
  request.samples,
534
531
  request.sampleRate ?? 22050,
535
532
  flat,
536
- request.onProgress
533
+ request.onProgress ?? (() => {
534
+ }),
535
+ request.cancel ?? (() => false)
537
536
  );
538
537
  }
539
538
  return requireModule3().masterAudio(
@@ -556,14 +555,16 @@ function masterAudioStereo(left, right = void 0, sampleRate = 22050, presetName
556
555
  if (request.left.length !== request.right.length) {
557
556
  throw new Error("Stereo channel lengths must match.");
558
557
  }
559
- if (request.onProgress) {
558
+ if (request.onProgress || request.cancel) {
560
559
  return requireModule3().masterAudioStereoWithProgress(
561
560
  request.preset ?? "pop",
562
561
  request.left,
563
562
  request.right,
564
563
  request.sampleRate ?? 22050,
565
564
  flat,
566
- request.onProgress
565
+ request.onProgress ?? (() => {
566
+ }),
567
+ request.cancel ?? (() => false)
567
568
  );
568
569
  }
569
570
  return requireModule3().masterAudioStereo(
@@ -584,7 +585,8 @@ function masterAudioWithProgress(samples, sampleRate = 22050, presetName = "pop"
584
585
  request.samples,
585
586
  request.sampleRate ?? 22050,
586
587
  flattenChainConfig(request.overrides ?? {}),
587
- request.onProgress
588
+ request.onProgress,
589
+ request.cancel ?? (() => false)
588
590
  );
589
591
  }
590
592
  function masterAudioStereoWithProgress(left, right = void 0, sampleRate = 22050, presetName = "pop", overrides = null, onProgress) {
@@ -608,7 +610,8 @@ function masterAudioStereoWithProgress(left, right = void 0, sampleRate = 22050,
608
610
  request.right,
609
611
  request.sampleRate ?? 22050,
610
612
  flattenChainConfig(request.overrides ?? {}),
611
- request.onProgress
613
+ request.onProgress,
614
+ request.cancel ?? (() => false)
612
615
  );
613
616
  }
614
617
 
@@ -771,7 +774,10 @@ function masteringDynamicsCompressor(samples, sampleRate, options = {}) {
771
774
  const request = samples instanceof Float32Array ? { samples, sampleRate, ...options } : samples;
772
775
  assertSamples("masteringDynamicsCompressor", request.samples, request.validate !== false);
773
776
  const detector = typeof request.detector === "string" ? COMPRESSOR_DETECTOR_MAP[request.detector] : request.detector;
774
- const opts = { ...request };
777
+ const opts = Object.assign(
778
+ Object.create(Object.getPrototypeOf(request)),
779
+ request
780
+ );
775
781
  if (detector !== void 0) {
776
782
  opts.detector = detector;
777
783
  }
@@ -860,6 +866,36 @@ function mixStereo(leftChannels, rightChannels, sampleRate = 48e3, options = {})
860
866
  function requireModule8() {
861
867
  return getSonareModule();
862
868
  }
869
+ function tone(frequency = 440, sampleRate = 22050, duration = 1, phase = 0, amplitude = 1) {
870
+ const request = typeof frequency === "number" ? { frequency, sampleRate, duration, phase, amplitude } : frequency;
871
+ return requireModule8().tone(
872
+ request.frequency ?? 440,
873
+ request.sampleRate ?? 22050,
874
+ request.duration ?? 1,
875
+ request.phase ?? 0,
876
+ request.amplitude ?? 1
877
+ );
878
+ }
879
+ function chirp(fmin = 440, fmax = 880, sampleRate = 22050, duration = 1, linear = true) {
880
+ const request = typeof fmin === "number" ? { fmin, fmax, sampleRate, duration, linear } : fmin;
881
+ return requireModule8().chirp(
882
+ request.fmin ?? 440,
883
+ request.fmax ?? 880,
884
+ request.sampleRate ?? 22050,
885
+ request.duration ?? 1,
886
+ request.linear ?? true
887
+ );
888
+ }
889
+ function clicks(times, sampleRate = 22050, length = 0, frequency = 1e3, clickDuration = 0.1) {
890
+ const request = times instanceof Float32Array ? { times, sampleRate, length, frequency, clickDuration } : times;
891
+ return requireModule8().clicks(
892
+ request.times,
893
+ request.sampleRate ?? 22050,
894
+ request.length ?? 0,
895
+ request.frequency ?? 1e3,
896
+ request.clickDuration ?? 0.1
897
+ );
898
+ }
863
899
  function hzToMel(hz) {
864
900
  return requireModule8().hzToMel(hz);
865
901
  }
@@ -956,6 +992,12 @@ function fixFrames(frames, xMin = 0, xMax = -1, pad = true) {
956
992
  }
957
993
  return requireModule8().fixFrames(frames, xMin, xMax, pad);
958
994
  }
995
+ function onsetBacktrack(events, energy) {
996
+ if (!(events instanceof Int32Array)) {
997
+ return onsetBacktrack(events.events, events.energy);
998
+ }
999
+ return requireModule8().onsetBacktrack(events, energy);
1000
+ }
959
1001
  function peakPick(values, preMax, postMax, preAvg, postAvg, delta, wait) {
960
1002
  if (!(values instanceof Float32Array)) {
961
1003
  const r = values;
@@ -980,7 +1022,17 @@ function vectorNormalize(values, normType = 0, threshold = 0) {
980
1022
  function pcen(values, nBins = 0, nFrames = 0, options = {}) {
981
1023
  if (!(values instanceof Float32Array)) {
982
1024
  const r = values;
983
- return pcen(r.values, r.nBins, r.nFrames, r.options);
1025
+ const {
1026
+ values: requestValues,
1027
+ nBins: requestBins,
1028
+ nFrames: requestFrames,
1029
+ options: legacyOptions,
1030
+ ...flatOptions
1031
+ } = r;
1032
+ return pcen(requestValues, requestBins, requestFrames, {
1033
+ ...legacyOptions,
1034
+ ...flatOptions
1035
+ });
984
1036
  }
985
1037
  return requireModule8().pcen(values, nBins, nFrames, options);
986
1038
  }
@@ -990,12 +1042,28 @@ function tonnetz(chromagram, nChroma, nFrames) {
990
1042
  }
991
1043
  return requireModule8().tonnetz(chromagram, nChroma, nFrames);
992
1044
  }
993
- function tempogram(onsetEnvelope2, sampleRate = 22050, hopLength = 512, winLength = 384, mode = "autocorrelation") {
1045
+ function tempogram(onsetEnvelope2, sampleRate = 22050, hopLength = 512, winLength = 384, mode = "autocorrelation", center = true, norm = true) {
994
1046
  if (!(onsetEnvelope2 instanceof Float32Array)) {
995
1047
  const r = onsetEnvelope2;
996
- return tempogram(r.onsetEnvelope, r.sampleRate, r.hopLength, r.winLength, r.mode);
1048
+ return tempogram(
1049
+ r.onsetEnvelope,
1050
+ r.sampleRate,
1051
+ r.hopLength,
1052
+ r.winLength,
1053
+ r.mode,
1054
+ r.center,
1055
+ r.norm
1056
+ );
997
1057
  }
998
- return requireModule8().tempogram(onsetEnvelope2, sampleRate, hopLength, winLength, mode);
1058
+ return requireModule8().tempogram(
1059
+ onsetEnvelope2,
1060
+ sampleRate,
1061
+ hopLength,
1062
+ winLength,
1063
+ mode,
1064
+ center,
1065
+ norm
1066
+ );
999
1067
  }
1000
1068
  function cyclicTempogram(onsetEnvelope2, sampleRate = 22050, hopLength = 512, winLength = 384, bpmMin = 60, nBins = 60) {
1001
1069
  if (!(onsetEnvelope2 instanceof Float32Array)) {
@@ -1056,7 +1124,13 @@ function nnlsChroma(samples, sampleRate = 22050, options = {}) {
1056
1124
  return nnlsChroma(samples.samples, samples.sampleRate, samples);
1057
1125
  }
1058
1126
  validateMusicSamples("nnlsChroma", samples, sampleRate, options);
1059
- return requireModule9().nnlsChroma(samples, sampleRate);
1127
+ return requireModule9().nnlsChroma(
1128
+ samples,
1129
+ sampleRate,
1130
+ options.enableStftBlend ?? true,
1131
+ options.stftBlendWeight ?? 0.55,
1132
+ options.stftBlendNFft ?? 4096
1133
+ );
1060
1134
  }
1061
1135
  function cqt(samples, sampleRate = 22050, hopLength = 512, fmin = 32.70319566257483, nBins = 84, binsPerOctave = 12, options = {}) {
1062
1136
  if (!(samples instanceof Float32Array)) {
@@ -1112,7 +1186,7 @@ function hybridCqt(samples, sampleRate = 22050, hopLength = 512, fmin = 32.70319
1112
1186
  validateFrequencyBounds("hybridCqt", fmin);
1113
1187
  return requireModule9().hybridCqt(samples, sampleRate, hopLength, fmin, nBins, binsPerOctave);
1114
1188
  }
1115
- function vqt(samples, sampleRate = 22050, hopLength = 512, fmin = 32.70319566257483, nBins = 84, binsPerOctave = 12, gamma = 0, options = {}) {
1189
+ function vqt(samples, sampleRate = 22050, hopLength = 512, fmin = 32.70319566257483, nBins = 84, binsPerOctave = 12, gamma = -1, options = {}) {
1116
1190
  if (!(samples instanceof Float32Array)) {
1117
1191
  const request = samples;
1118
1192
  return vqt(
@@ -1130,9 +1204,6 @@ function vqt(samples, sampleRate = 22050, hopLength = 512, fmin = 32.70319566257
1130
1204
  validatePositiveIntegers("vqt", { hopLength, nBins, binsPerOctave });
1131
1205
  validateFrequencyBounds("vqt", fmin);
1132
1206
  assertFiniteScalar("vqt", gamma, "gamma");
1133
- if (gamma < 0) {
1134
- throw new RangeError("vqt: gamma must be non-negative");
1135
- }
1136
1207
  return requireModule9().vqt(samples, sampleRate, hopLength, fmin, nBins, binsPerOctave, gamma);
1137
1208
  }
1138
1209
  function validateCqtInverse(fnName, magnitude, nBins, nFrames, sampleRate, hopLength, fmin, binsPerOctave, nIter, options) {
@@ -1188,7 +1259,7 @@ function cqtToAudio(magnitude, nBins = 0, nFrames = 0, sampleRate = 22050, hopLe
1188
1259
  nIter
1189
1260
  );
1190
1261
  }
1191
- function vqtToAudio(magnitude, nBins = 0, nFrames = 0, sampleRate = 22050, hopLength = 512, fmin = 32.70319566257483, binsPerOctave = 12, gamma = 0, nIter = 32, options = {}) {
1262
+ function vqtToAudio(magnitude, nBins = 0, nFrames = 0, sampleRate = 22050, hopLength = 512, fmin = 32.70319566257483, binsPerOctave = 12, gamma = -1, nIter = 32, options = {}) {
1192
1263
  if (!(magnitude instanceof Float32Array)) {
1193
1264
  const request = magnitude;
1194
1265
  return vqtToAudio(
@@ -1217,9 +1288,6 @@ function vqtToAudio(magnitude, nBins = 0, nFrames = 0, sampleRate = 22050, hopLe
1217
1288
  options
1218
1289
  );
1219
1290
  assertFiniteScalar("vqtToAudio", gamma, "gamma");
1220
- if (gamma < 0) {
1221
- throw new RangeError("vqtToAudio: gamma must be non-negative");
1222
- }
1223
1291
  return requireModule9().vqtToAudio(
1224
1292
  magnitude,
1225
1293
  nBins,
@@ -1243,8 +1311,8 @@ function analyzeSections(samples, sampleRate = 22050, options = {}) {
1243
1311
  hopLength: options.hopLength ?? 512
1244
1312
  });
1245
1313
  assertFiniteScalar("analyzeSections", options.minSectionSec ?? 4, "minSectionSec");
1246
- if ((options.minSectionSec ?? 4) <= 0) {
1247
- throw new RangeError("analyzeSections: minSectionSec must be positive");
1314
+ if ((options.minSectionSec ?? 4) < 0) {
1315
+ throw new RangeError("analyzeSections: minSectionSec must be non-negative");
1248
1316
  }
1249
1317
  const sections = requireModule9().analyzeSections(
1250
1318
  samples,
@@ -1329,7 +1397,7 @@ function onsetStrengthMulti(samples, sampleRate = 22050, nFft = 2048, hopLength
1329
1397
  validatePositiveIntegers("onsetStrengthMulti", { nFft, hopLength, nMels, nBands });
1330
1398
  return requireModule9().onsetStrengthMulti(samples, sampleRate, nFft, hopLength, nMels, nBands);
1331
1399
  }
1332
- function fourierTempogram(onsetEnvelope2, sampleRate = 22050, hopLength = 512, winLength = 384, options = {}) {
1400
+ function fourierTempogram(onsetEnvelope2, sampleRate = 22050, hopLength = 512, winLength = 384, center = true, norm = true, options = {}) {
1333
1401
  if (!(onsetEnvelope2 instanceof Float32Array)) {
1334
1402
  const request = onsetEnvelope2;
1335
1403
  return fourierTempogram(
@@ -1337,13 +1405,22 @@ function fourierTempogram(onsetEnvelope2, sampleRate = 22050, hopLength = 512, w
1337
1405
  request.sampleRate,
1338
1406
  request.hopLength,
1339
1407
  request.winLength,
1408
+ request.center,
1409
+ request.norm,
1340
1410
  request
1341
1411
  );
1342
1412
  }
1343
1413
  assertSampleRate("fourierTempogram", sampleRate);
1344
1414
  assertSamples("fourierTempogram", onsetEnvelope2, options.validate !== false, "onsetEnvelope");
1345
1415
  validatePositiveIntegers("fourierTempogram", { hopLength, winLength });
1346
- return requireModule9().fourierTempogram(onsetEnvelope2, sampleRate, hopLength, winLength);
1416
+ return requireModule9().fourierTempogram(
1417
+ onsetEnvelope2,
1418
+ sampleRate,
1419
+ hopLength,
1420
+ winLength,
1421
+ center,
1422
+ norm
1423
+ );
1347
1424
  }
1348
1425
  function tempogramRatio(tempogramData, winLength = 384, sampleRate = 22050, hopLength = 512, factors, options = {}) {
1349
1426
  if (!(tempogramData instanceof Float32Array)) {
@@ -1394,7 +1471,22 @@ function shortTermLufs(samples, sampleRate = 22050, options = {}) {
1394
1471
  function requireModule10() {
1395
1472
  return getSonareModule();
1396
1473
  }
1397
- function pitchYin(samples, sampleRate = 22050, frameLength = 2048, hopLength = 512, fmin = 65, fmax = 2093, threshold = 0.3, fillNa = false) {
1474
+ function piptrack(samples, sampleRate = 22050, nFft = 2048, hopLength = 512, fmin = 150, fmax = 4e3, threshold = 0.1) {
1475
+ if (!(samples instanceof Float32Array)) {
1476
+ const request = samples;
1477
+ return piptrack(
1478
+ request.samples,
1479
+ request.sampleRate,
1480
+ request.nFft,
1481
+ request.hopLength,
1482
+ request.fmin,
1483
+ request.fmax,
1484
+ request.threshold
1485
+ );
1486
+ }
1487
+ return requireModule10().piptrack(samples, sampleRate, nFft, hopLength, fmin, fmax, threshold);
1488
+ }
1489
+ function pitchYin(samples, sampleRate = 22050, frameLength = 2048, hopLength = 512, fmin = 65, fmax = 2093, threshold = 0.1, fillNa = false) {
1398
1490
  if (!(samples instanceof Float32Array)) {
1399
1491
  const request = samples;
1400
1492
  return pitchYin(
@@ -1419,7 +1511,7 @@ function pitchYin(samples, sampleRate = 22050, frameLength = 2048, hopLength = 5
1419
1511
  fillNa
1420
1512
  );
1421
1513
  }
1422
- function pitchPyin(samples, sampleRate = 22050, frameLength = 2048, hopLength = 512, fmin = 65, fmax = 2093, threshold = 0.3, fillNa = false) {
1514
+ function pitchPyin(samples, sampleRate = 22050, frameLength = 2048, hopLength = 512, fmin = 65, fmax = 2093, threshold = 0.1, fillNa = false) {
1423
1515
  if (!(samples instanceof Float32Array)) {
1424
1516
  const request = samples;
1425
1517
  return pitchPyin(
@@ -1444,6 +1536,13 @@ function pitchPyin(samples, sampleRate = 22050, frameLength = 2048, hopLength =
1444
1536
  fillNa
1445
1537
  );
1446
1538
  }
1539
+ function noteSegments(request) {
1540
+ return requireModule10().noteSegments(request.f0Hz, request.voicedProb, request.frameRate, {
1541
+ segmentationThresholdCents: request.segmentationThresholdCents,
1542
+ minNoteMs: request.minNoteMs,
1543
+ referenceHz: request.referenceHz
1544
+ });
1545
+ }
1447
1546
 
1448
1547
  // src/feature_resample.ts
1449
1548
  function requireModule11() {
@@ -1460,6 +1559,15 @@ function resample(samples, srcSr, targetSr) {
1460
1559
  function requireModule12() {
1461
1560
  return getSonareModule();
1462
1561
  }
1562
+ function validateSegmentMatrix(fnName, data, rows, cols, dataName) {
1563
+ assertPositiveInteger(fnName, rows, "rows");
1564
+ assertPositiveInteger(fnName, cols, "cols");
1565
+ assertSamples(fnName, data, true, dataName);
1566
+ const expected = rows * cols;
1567
+ if (!Number.isSafeInteger(expected) || data.length !== expected) {
1568
+ throw new RangeError(`${fnName}: ${dataName} length must equal rows * cols`);
1569
+ }
1570
+ }
1463
1571
  function spectralCentroid(samples, sampleRate = 22050, nFft = 2048, hopLength = 512) {
1464
1572
  if (!(samples instanceof Float32Array)) {
1465
1573
  return spectralCentroid(samples.samples, samples.sampleRate, samples.nFft, samples.hopLength);
@@ -1575,10 +1683,10 @@ function remix(samples, intervals, sampleRate = 22050, alignZeros = false) {
1575
1683
  const intervalsI32 = intervals instanceof Int32Array ? intervals : Int32Array.from(intervals, (v) => Math.trunc(v));
1576
1684
  return requireModule12().remix(samples, intervalsI32, sampleRate, alignZeros);
1577
1685
  }
1578
- function phaseVocoder(samples, rate = 1, sampleRate = 22050, nFft = 2048, hopLength = 512) {
1686
+ function phaseVocoder(samples, sampleRate = 22050, rate = 1, nFft = 2048, hopLength = 512) {
1579
1687
  if (!(samples instanceof Float32Array)) {
1580
1688
  const r = samples;
1581
- return phaseVocoder(r.samples, r.rate, r.sampleRate, r.nFft, r.hopLength);
1689
+ return phaseVocoder(r.samples, r.sampleRate ?? 22050, r.rate, r.nFft, r.hopLength);
1582
1690
  }
1583
1691
  return requireModule12().phaseVocoder(samples, sampleRate, rate, nFft, hopLength);
1584
1692
  }
@@ -1604,11 +1712,17 @@ function ebur128LoudnessRange(samples, sampleRate = 22050) {
1604
1712
  }
1605
1713
  return requireModule12().ebur128LoudnessRange(samples, sampleRate);
1606
1714
  }
1607
- function spectralBandwidth(samples, sampleRate = 22050, nFft = 2048, hopLength = 512) {
1715
+ function spectralBandwidth(samples, sampleRate = 22050, nFft = 2048, hopLength = 512, p = 2) {
1608
1716
  if (!(samples instanceof Float32Array)) {
1609
- return spectralBandwidth(samples.samples, samples.sampleRate, samples.nFft, samples.hopLength);
1717
+ return spectralBandwidth(
1718
+ samples.samples,
1719
+ samples.sampleRate,
1720
+ samples.nFft,
1721
+ samples.hopLength,
1722
+ samples.p
1723
+ );
1610
1724
  }
1611
- return requireModule12().spectralBandwidth(samples, sampleRate, nFft, hopLength);
1725
+ return requireModule12().spectralBandwidth(samples, sampleRate, nFft, hopLength, p);
1612
1726
  }
1613
1727
  function spectralRolloff(samples, sampleRate = 22050, nFft = 2048, hopLength = 512, rollPercent = 0.85) {
1614
1728
  if (!(samples instanceof Float32Array)) {
@@ -1628,6 +1742,18 @@ function spectralFlatness(samples, sampleRate = 22050, nFft = 2048, hopLength =
1628
1742
  }
1629
1743
  return requireModule12().spectralFlatness(samples, sampleRate, nFft, hopLength);
1630
1744
  }
1745
+ function spectralFlux(samples, sampleRate = 22050, nFft = 2048, hopLength = 512, lag = 1) {
1746
+ if (!(samples instanceof Float32Array)) {
1747
+ return spectralFlux(
1748
+ samples.samples,
1749
+ samples.sampleRate,
1750
+ samples.nFft,
1751
+ samples.hopLength,
1752
+ samples.lag
1753
+ );
1754
+ }
1755
+ return requireModule12().spectralFlux(samples, sampleRate, nFft, hopLength, lag);
1756
+ }
1631
1757
  function zeroCrossingRate(samples, sampleRate = 22050, frameLength = 2048, hopLength = 512) {
1632
1758
  if (!(samples instanceof Float32Array)) {
1633
1759
  return zeroCrossingRate(
@@ -1645,6 +1771,107 @@ function rmsEnergy(samples, sampleRate = 22050, frameLength = 2048, hopLength =
1645
1771
  }
1646
1772
  return requireModule12().rmsEnergy(samples, sampleRate, frameLength, hopLength);
1647
1773
  }
1774
+ function segmentCrossSimilarity(request) {
1775
+ validateSegmentMatrix("segmentCrossSimilarity", request.x, request.xRows, request.xCols, "x");
1776
+ validateSegmentMatrix("segmentCrossSimilarity", request.y, request.yRows, request.yCols, "y");
1777
+ if (request.xRows !== request.yRows) {
1778
+ throw new RangeError("segmentCrossSimilarity: feature dimensions must match");
1779
+ }
1780
+ assertNonNegativeInteger("segmentCrossSimilarity", request.k ?? 0, "k");
1781
+ return requireModule12().segmentCrossSimilarity(
1782
+ request.x,
1783
+ request.xRows,
1784
+ request.xCols,
1785
+ request.y,
1786
+ request.yRows,
1787
+ request.yCols,
1788
+ request.k ?? 0,
1789
+ request.metric ?? "cosine",
1790
+ request.mode ?? "connectivity"
1791
+ );
1792
+ }
1793
+ function segmentRecurrenceMatrix(request) {
1794
+ validateSegmentMatrix(
1795
+ "segmentRecurrenceMatrix",
1796
+ request.data,
1797
+ request.rows,
1798
+ request.cols,
1799
+ "data"
1800
+ );
1801
+ assertNonNegativeInteger("segmentRecurrenceMatrix", request.k ?? 0, "k");
1802
+ assertNonNegativeInteger("segmentRecurrenceMatrix", request.width ?? 1, "width");
1803
+ return requireModule12().segmentRecurrenceMatrix(
1804
+ request.data,
1805
+ request.rows,
1806
+ request.cols,
1807
+ request.k ?? 0,
1808
+ request.width ?? 1,
1809
+ request.sym ?? false,
1810
+ request.metric ?? "euclidean",
1811
+ request.mode ?? "connectivity"
1812
+ );
1813
+ }
1814
+ function segmentRecurrenceToLag(request) {
1815
+ validateSegmentMatrix(
1816
+ "segmentRecurrenceToLag",
1817
+ request.recurrence,
1818
+ request.n,
1819
+ request.n,
1820
+ "recurrence"
1821
+ );
1822
+ return requireModule12().segmentRecurrenceToLag(
1823
+ request.recurrence,
1824
+ request.n,
1825
+ request.pad ?? false
1826
+ );
1827
+ }
1828
+ function segmentLagToRecurrence(request) {
1829
+ validateSegmentMatrix("segmentLagToRecurrence", request.lag, request.rows, request.lags, "lag");
1830
+ return requireModule12().segmentLagToRecurrence(request.lag, request.rows, request.lags);
1831
+ }
1832
+ function segmentSubsegment(request) {
1833
+ validateSegmentMatrix("segmentSubsegment", request.data, request.rows, request.cols, "data");
1834
+ assertPositiveInteger("segmentSubsegment", request.nSegments ?? 4, "nSegments");
1835
+ return requireModule12().segmentSubsegment(
1836
+ request.data,
1837
+ request.rows,
1838
+ request.cols,
1839
+ request.boundaries,
1840
+ request.nSegments ?? 4
1841
+ );
1842
+ }
1843
+ function segmentAgglomerative(request) {
1844
+ validateSegmentMatrix("segmentAgglomerative", request.data, request.rows, request.cols, "data");
1845
+ assertPositiveInteger("segmentAgglomerative", request.k, "k");
1846
+ return requireModule12().segmentAgglomerative(
1847
+ request.data,
1848
+ request.rows,
1849
+ request.cols,
1850
+ request.k,
1851
+ request.linkage ?? "average"
1852
+ );
1853
+ }
1854
+ function segmentPathEnhance(request) {
1855
+ validateSegmentMatrix(
1856
+ "segmentPathEnhance",
1857
+ request.recurrence,
1858
+ request.n,
1859
+ request.n,
1860
+ "recurrence"
1861
+ );
1862
+ assertPositiveInteger("segmentPathEnhance", request.win, "win");
1863
+ assertPositiveInteger("segmentPathEnhance", request.maxRatio ?? 2, "maxRatio");
1864
+ assertNonNegativeInteger("segmentPathEnhance", request.minRatio ?? 0, "minRatio");
1865
+ assertPositiveInteger("segmentPathEnhance", request.nFilters ?? 7, "nFilters");
1866
+ return requireModule12().segmentPathEnhance(
1867
+ request.recurrence,
1868
+ request.n,
1869
+ request.win,
1870
+ request.maxRatio ?? 2,
1871
+ request.minRatio ?? 0,
1872
+ request.nFilters ?? 7
1873
+ );
1874
+ }
1648
1875
 
1649
1876
  // src/feature_spectrogram.ts
1650
1877
  function requireModule13() {
@@ -1708,7 +1935,7 @@ function stftDb(samples, sampleRate = 22050, nFft = 2048, hopLength = 512, optio
1708
1935
  validatePositiveIntegers2("stftDb", { nFft, hopLength });
1709
1936
  return requireModule13().stftDb(samples, sampleRate, nFft, hopLength);
1710
1937
  }
1711
- function chromaCens(samples, sampleRate = 22050, hopLength = 512, nChroma = 12, options = {}) {
1938
+ function chromaCens(samples, sampleRate = 22050, hopLength = 512, nChroma = 12, binsPerOctave = 36, options = {}) {
1712
1939
  if (!(samples instanceof Float32Array)) {
1713
1940
  const request = samples;
1714
1941
  return chromaCens(
@@ -1716,14 +1943,18 @@ function chromaCens(samples, sampleRate = 22050, hopLength = 512, nChroma = 12,
1716
1943
  request.sampleRate,
1717
1944
  request.hopLength,
1718
1945
  request.nChroma,
1946
+ request.binsPerOctave,
1719
1947
  request
1720
1948
  );
1721
1949
  }
1722
1950
  validateSpectrogramSamples("chromaCens", samples, sampleRate, options);
1723
- validatePositiveIntegers2("chromaCens", { hopLength, nChroma });
1724
- return requireModule13().chromaCens(samples, sampleRate, hopLength, nChroma);
1951
+ validatePositiveIntegers2("chromaCens", { hopLength, nChroma, binsPerOctave });
1952
+ if (binsPerOctave % nChroma !== 0) {
1953
+ throw new RangeError("chromaCens: binsPerOctave must be a multiple of nChroma");
1954
+ }
1955
+ return requireModule13().chromaCens(samples, sampleRate, hopLength, nChroma, binsPerOctave);
1725
1956
  }
1726
- function chromaCqt(samples, sampleRate = 22050, hopLength = 512, nChroma = 12, options = {}) {
1957
+ function chromaCqt(samples, sampleRate = 22050, hopLength = 512, nChroma = 12, binsPerOctave = 36, options = {}) {
1727
1958
  if (!(samples instanceof Float32Array)) {
1728
1959
  const request = samples;
1729
1960
  return chromaCqt(
@@ -1731,12 +1962,16 @@ function chromaCqt(samples, sampleRate = 22050, hopLength = 512, nChroma = 12, o
1731
1962
  request.sampleRate,
1732
1963
  request.hopLength,
1733
1964
  request.nChroma,
1965
+ request.binsPerOctave,
1734
1966
  request
1735
1967
  );
1736
1968
  }
1737
1969
  validateSpectrogramSamples("chromaCqt", samples, sampleRate, options);
1738
- validatePositiveIntegers2("chromaCqt", { hopLength, nChroma });
1739
- return requireModule13().chromaCqt(samples, sampleRate, hopLength, nChroma);
1970
+ validatePositiveIntegers2("chromaCqt", { hopLength, nChroma, binsPerOctave });
1971
+ if (binsPerOctave % nChroma !== 0) {
1972
+ throw new RangeError("chromaCqt: binsPerOctave must be a multiple of nChroma");
1973
+ }
1974
+ return requireModule13().chromaCqt(samples, sampleRate, hopLength, nChroma, binsPerOctave);
1740
1975
  }
1741
1976
  function bassChroma(samples, sampleRate = 22050, hopLength = 512, nChroma = 12, options = {}) {
1742
1977
  if (!(samples instanceof Float32Array)) {
@@ -1815,6 +2050,53 @@ function mfcc(samples, sampleRate = 22050, nFft = 2048, hopLength = 512, nMels =
1815
2050
  lifter
1816
2051
  );
1817
2052
  }
2053
+ function melDelta(features, nFeatures, nFrames, width = 9) {
2054
+ const request = features instanceof Float32Array ? { features, nFeatures: nFeatures ?? 0, nFrames: nFrames ?? 0, width } : features;
2055
+ assertPositiveInteger("melDelta", request.nFeatures, "nFeatures");
2056
+ assertPositiveInteger("melDelta", request.nFrames, "nFrames");
2057
+ assertPositiveInteger("melDelta", request.width ?? 9, "width");
2058
+ if ((request.width ?? 9) < 3 || (request.width ?? 9) % 2 === 0) {
2059
+ throw new RangeError("melDelta: width must be an odd integer of at least 3");
2060
+ }
2061
+ if (request.features.length !== request.nFeatures * request.nFrames) {
2062
+ throw new RangeError("melDelta: feature matrix length must equal nFeatures * nFrames");
2063
+ }
2064
+ return requireModule13().melDelta(
2065
+ request.features,
2066
+ request.nFeatures,
2067
+ request.nFrames,
2068
+ request.width ?? 9
2069
+ );
2070
+ }
2071
+ function reassignedSpectrogram(samples, sampleRate = 22050, nFft = 2048, hopLength = 512, refPower = 1e-6, fillNan = false) {
2072
+ if (!(samples instanceof Float32Array)) {
2073
+ const request = samples;
2074
+ return reassignedSpectrogram(
2075
+ request.samples,
2076
+ request.sampleRate,
2077
+ request.nFft,
2078
+ request.hopLength,
2079
+ request.refPower,
2080
+ request.fillNan
2081
+ );
2082
+ }
2083
+ assertSamples("reassignedSpectrogram", samples, true);
2084
+ assertSampleRate("reassignedSpectrogram", sampleRate);
2085
+ assertPositiveInteger("reassignedSpectrogram", nFft, "nFft");
2086
+ assertPositiveInteger("reassignedSpectrogram", hopLength, "hopLength");
2087
+ assertFiniteScalar("reassignedSpectrogram", refPower, "refPower");
2088
+ if (refPower < 0) {
2089
+ throw new RangeError("reassignedSpectrogram: refPower must be non-negative");
2090
+ }
2091
+ return requireModule13().reassignedSpectrogram(
2092
+ samples,
2093
+ sampleRate,
2094
+ nFft,
2095
+ hopLength,
2096
+ refPower,
2097
+ fillNan
2098
+ );
2099
+ }
1818
2100
  function melToStft(melPower, nMels = 0, nFrames = 0, sampleRate = 22050, nFft = 2048, fmin = 0, fmax = 0, htk = false, options = {}) {
1819
2101
  if (!(melPower instanceof Float32Array)) {
1820
2102
  const request = melPower;
@@ -1870,7 +2152,36 @@ function melToAudio(melPower, nMels = 0, nFrames = 0, sampleRate = 22050, nFft =
1870
2152
  htk
1871
2153
  );
1872
2154
  }
1873
- function mfccToMel(mfccCoefficients, nMfcc = 0, nFrames = 0, nMels = 128, options = {}) {
2155
+ function griffinLim(magnitude, nBins = 0, nFrames = 0, sampleRate = 22050, nFft = 2048, hopLength = 512, nIter = 32, momentum = 0.99, options = {}) {
2156
+ if (!(magnitude instanceof Float32Array)) {
2157
+ const request = magnitude;
2158
+ return griffinLim(
2159
+ request.magnitude,
2160
+ request.nBins,
2161
+ request.nFrames,
2162
+ request.sampleRate,
2163
+ request.nFft,
2164
+ request.hopLength,
2165
+ request.nIter,
2166
+ request.momentum,
2167
+ request
2168
+ );
2169
+ }
2170
+ assertSampleRate("griffinLim", sampleRate);
2171
+ validateMatrix("griffinLim", magnitude, nBins, nFrames, "magnitude", "nBins", options);
2172
+ validatePositiveIntegers2("griffinLim", { nFft, hopLength, nIter });
2173
+ return requireModule13().griffinLim(
2174
+ magnitude,
2175
+ nBins,
2176
+ nFrames,
2177
+ sampleRate,
2178
+ nFft,
2179
+ hopLength,
2180
+ nIter,
2181
+ momentum
2182
+ );
2183
+ }
2184
+ function mfccToMel(mfccCoefficients, nMfcc = 0, nFrames = 0, nMels = 128, lifter = 0, options = {}) {
1874
2185
  if (!(mfccCoefficients instanceof Float32Array)) {
1875
2186
  const request = mfccCoefficients;
1876
2187
  return mfccToMel(
@@ -1878,6 +2189,7 @@ function mfccToMel(mfccCoefficients, nMfcc = 0, nFrames = 0, nMels = 128, option
1878
2189
  request.nMfcc,
1879
2190
  request.nFrames,
1880
2191
  request.nMels,
2192
+ request.lifter,
1881
2193
  request
1882
2194
  );
1883
2195
  }
@@ -1891,9 +2203,9 @@ function mfccToMel(mfccCoefficients, nMfcc = 0, nFrames = 0, nMels = 128, option
1891
2203
  options
1892
2204
  );
1893
2205
  validatePositiveIntegers2("mfccToMel", { nMels });
1894
- return requireModule13().mfccToMel(mfccCoefficients, nMfcc, nFrames, nMels);
2206
+ return requireModule13().mfccToMel(mfccCoefficients, nMfcc, nFrames, nMels, lifter);
1895
2207
  }
1896
- function mfccToAudio(mfccCoefficients, nMfcc = 0, nFrames = 0, nMels = 128, sampleRate = 22050, nFft = 2048, hopLength = 512, fmin = 0, fmax = 0, nIter = 32, htk = false, options = {}) {
2208
+ 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 = {}) {
1897
2209
  if (!(mfccCoefficients instanceof Float32Array)) {
1898
2210
  const request = mfccCoefficients;
1899
2211
  return mfccToAudio(
@@ -1908,6 +2220,7 @@ function mfccToAudio(mfccCoefficients, nMfcc = 0, nFrames = 0, nMels = 128, samp
1908
2220
  request.fmax,
1909
2221
  request.nIter,
1910
2222
  request.htk,
2223
+ request.lifter,
1911
2224
  request
1912
2225
  );
1913
2226
  }
@@ -1934,7 +2247,8 @@ function mfccToAudio(mfccCoefficients, nMfcc = 0, nFrames = 0, nMels = 128, samp
1934
2247
  fmin,
1935
2248
  fmax,
1936
2249
  nIter,
1937
- htk
2250
+ htk,
2251
+ lifter
1938
2252
  );
1939
2253
  }
1940
2254
  function chroma(samples, sampleRate = 22050, nFft = 2048, hopLength = 512, options = {}) {
@@ -2011,6 +2325,23 @@ var SectionType = {
2011
2325
  };
2012
2326
 
2013
2327
  // src/analysis_helpers.ts
2328
+ var PITCH_CLASS_NAMES = [
2329
+ "C",
2330
+ "C#",
2331
+ "D",
2332
+ "D#",
2333
+ "E",
2334
+ "F",
2335
+ "F#",
2336
+ "G",
2337
+ "G#",
2338
+ "A",
2339
+ "A#",
2340
+ "B"
2341
+ ];
2342
+ function pitchClassName(value) {
2343
+ return PITCH_CLASS_NAMES[value] ?? "C";
2344
+ }
2014
2345
  function convertKeyCandidate(wasm) {
2015
2346
  return {
2016
2347
  key: {
@@ -2081,6 +2412,8 @@ function convertChordAnalysisResult(wasm) {
2081
2412
  chords: wasm.chords.map((c) => ({
2082
2413
  root: c.root,
2083
2414
  bass: c.bass,
2415
+ rootName: pitchClassName(c.root),
2416
+ bassName: pitchClassName(c.bass),
2084
2417
  quality: c.quality,
2085
2418
  start: c.start,
2086
2419
  end: c.end,
@@ -2106,6 +2439,11 @@ function convertAnalysisResult(wasm) {
2106
2439
  return {
2107
2440
  bpm: wasm.bpm,
2108
2441
  bpmConfidence: wasm.bpmConfidence,
2442
+ bpmCandidates: wasm.bpmCandidates.map((candidate) => ({
2443
+ value: candidate.value,
2444
+ confidence: candidate.confidence,
2445
+ relation: candidate.relation
2446
+ })),
2109
2447
  key: {
2110
2448
  root: wasm.key.root,
2111
2449
  mode: wasm.key.mode,
@@ -2114,11 +2452,14 @@ function convertAnalysisResult(wasm) {
2114
2452
  shortName: wasm.key.shortName
2115
2453
  },
2116
2454
  timeSignature: wasm.timeSignature,
2455
+ timeSignatureCandidates: wasm.timeSignatureCandidates,
2117
2456
  beatTimes,
2118
2457
  beats: wasm.beats,
2119
2458
  chords: wasm.chords.map((c) => ({
2120
2459
  root: c.root,
2121
2460
  bass: c.bass,
2461
+ rootName: pitchClassName(c.root),
2462
+ bassName: pitchClassName(c.bass),
2122
2463
  quality: c.quality,
2123
2464
  start: c.start,
2124
2465
  end: c.end,
@@ -2202,7 +2543,7 @@ function detectKeyCandidates(samples, sampleRate = 22050, options = {}) {
2202
2543
  function detectOnsets(samples, sampleRate = 22050, options = {}) {
2203
2544
  const request = samples instanceof Float32Array ? { samples, sampleRate, ...options } : samples;
2204
2545
  validateAnalysisInput("detectOnsets", request.samples, request.sampleRate ?? 22050, request);
2205
- return requireModule14().detectOnsets(request.samples, request.sampleRate ?? 22050);
2546
+ return requireModule14().detectOnsets(request.samples, request.sampleRate ?? 22050, request);
2206
2547
  }
2207
2548
  function detectBeats(samples, sampleRate = 22050, options = {}) {
2208
2549
  const request = samples instanceof Float32Array ? { samples, sampleRate, ...options } : samples;
@@ -2267,7 +2608,7 @@ function chordFunctionalAnalysis(samples, keyRoot, keyMode, sampleRate = 22050,
2267
2608
  function analyze(samples, sampleRate = 22050, options = {}) {
2268
2609
  const request = samples instanceof Float32Array ? { samples, sampleRate, ...options } : samples;
2269
2610
  validateAnalysisInput("analyze", request.samples, request.sampleRate ?? 22050, request);
2270
- const result = requireModule14().analyze(request.samples, request.sampleRate ?? 22050);
2611
+ const result = requireModule14().analyze(request.samples, request.sampleRate ?? 22050, request);
2271
2612
  return convertAnalysisResult(result);
2272
2613
  }
2273
2614
  function analyzeImpulseResponse(samples, sampleRate = 48e3, nOctaveBands = 6) {
@@ -2334,7 +2675,9 @@ function analyzeWithProgress(samples, sampleRate = 22050, onProgress) {
2334
2675
  const result = requireModule14().analyzeWithProgress(
2335
2676
  request.samples,
2336
2677
  request.sampleRate ?? 22050,
2337
- request.onProgress
2678
+ request.onProgress ?? (() => {
2679
+ }),
2680
+ request.cancel ?? (() => false)
2338
2681
  );
2339
2682
  return convertAnalysisResult(result);
2340
2683
  }
@@ -2441,7 +2784,8 @@ var Audio = class _Audio {
2441
2784
  * Node/Python surfaces).
2442
2785
  */
2443
2786
  static fromBuffer(samples, sampleRate = 48e3) {
2444
- return new _Audio(samples, sampleRate);
2787
+ validateAudioBuffer(samples, sampleRate);
2788
+ return new _Audio(samples.slice(), sampleRate);
2445
2789
  }
2446
2790
  /**
2447
2791
  * Create an Audio instance by decoding audio bytes in memory.
@@ -2640,7 +2984,7 @@ var Audio = class _Audio {
2640
2984
  rmsEnergy(frameLength = 2048, hopLength = 512) {
2641
2985
  return rmsEnergy(this._samples, this._sampleRate, frameLength, hopLength);
2642
2986
  }
2643
- pitchYin(frameLength = 2048, hopLength = 512, fmin = 65, fmax = 2093, threshold = 0.3, fillNa = false) {
2987
+ pitchYin(frameLength = 2048, hopLength = 512, fmin = 65, fmax = 2093, threshold = 0.1, fillNa = false) {
2644
2988
  return pitchYin(
2645
2989
  this._samples,
2646
2990
  this._sampleRate,
@@ -2652,7 +2996,7 @@ var Audio = class _Audio {
2652
2996
  fillNa
2653
2997
  );
2654
2998
  }
2655
- pitchPyin(frameLength = 2048, hopLength = 512, fmin = 65, fmax = 2093, threshold = 0.3, fillNa = false) {
2999
+ pitchPyin(frameLength = 2048, hopLength = 512, fmin = 65, fmax = 2093, threshold = 0.1, fillNa = false) {
2656
3000
  return pitchPyin(
2657
3001
  this._samples,
2658
3002
  this._sampleRate,
@@ -2757,7 +3101,12 @@ self.onmessage = async (event) => {
2757
3101
  `;
2758
3102
  function createOpfsClipPageWorker() {
2759
3103
  const blob = new Blob([opfsClipPageWorkerSource], { type: "text/javascript" });
2760
- return new Worker(URL.createObjectURL(blob));
3104
+ const url = URL.createObjectURL(blob);
3105
+ try {
3106
+ return new Worker(url);
3107
+ } finally {
3108
+ URL.revokeObjectURL(url);
3109
+ }
2761
3110
  }
2762
3111
  function createOpfsClipPageProvider(engine, options) {
2763
3112
  if (options.numChannels <= 0 || options.numSamples <= 0 || options.pageFrames <= 0) {
@@ -2797,6 +3146,7 @@ function createOpfsClipPageProvider(engine, options) {
2797
3146
  }
2798
3147
  try {
2799
3148
  provider.supply(response.pageIndex, channels);
3149
+ options.onPageSupplied?.(response.pageIndex, channels);
2800
3150
  } catch {
2801
3151
  entry.resolve(false);
2802
3152
  return;
@@ -2844,6 +3194,13 @@ function createOpfsClipPageProvider(engine, options) {
2844
3194
  supplyRequest(request) {
2845
3195
  return supplyPage(Math.floor(request.sample / options.pageFrames));
2846
3196
  },
3197
+ clearPage(pageIndex) {
3198
+ if (closed) {
3199
+ return;
3200
+ }
3201
+ provider.clear(pageIndex);
3202
+ options.onPageCleared?.(pageIndex);
3203
+ },
2847
3204
  close() {
2848
3205
  if (closed) {
2849
3206
  return;
@@ -2855,6 +3212,7 @@ function createOpfsClipPageProvider(engine, options) {
2855
3212
  }
2856
3213
  pending.clear();
2857
3214
  provider.destroy();
3215
+ options.onClose?.();
2858
3216
  if (ownsWorker) {
2859
3217
  worker.terminate();
2860
3218
  }
@@ -2932,7 +3290,10 @@ var ClipPageStreamer = class {
2932
3290
  if (!state) {
2933
3291
  continue;
2934
3292
  }
2935
- const page = Math.floor(request.sample / state.source.pageFrames);
3293
+ const page = request.pageIndex !== void 0 ? request.pageIndex : Math.floor((request.sample ?? Number.NaN) / state.source.pageFrames);
3294
+ if (!Number.isInteger(page) || page < 0 || page > state.lastPage) {
3295
+ continue;
3296
+ }
2936
3297
  frontiers.set(request.clipId, page);
2937
3298
  }
2938
3299
  const fetches = [];
@@ -2967,7 +3328,7 @@ var ClipPageStreamer = class {
2967
3328
  const high = Math.min(state.lastPage, frontier + this.readAheadPages);
2968
3329
  for (const page of state.resident.keys()) {
2969
3330
  if (page < low || page > high) {
2970
- state.source.binding.provider.clear(page);
3331
+ this.clearPage(state, page);
2971
3332
  state.resident.delete(page);
2972
3333
  }
2973
3334
  }
@@ -2982,7 +3343,7 @@ var ClipPageStreamer = class {
2982
3343
  state.source.binding.supplyPage(pageIndex).then(
2983
3344
  (ok) => {
2984
3345
  if (state.generation !== generation) {
2985
- state.source.binding.provider.clear(pageIndex);
3346
+ this.clearPage(state, pageIndex);
2986
3347
  } else if (!ok && state.resident.get(pageIndex) === generation) {
2987
3348
  state.resident.delete(pageIndex);
2988
3349
  }
@@ -3003,12 +3364,28 @@ var ClipPageStreamer = class {
3003
3364
  state.generation += 1;
3004
3365
  state.lastFrontier = null;
3005
3366
  for (const page of state.resident.keys()) {
3006
- state.source.binding.provider.clear(page);
3367
+ this.clearPage(state, page);
3007
3368
  }
3008
3369
  state.resident.clear();
3009
3370
  }
3371
+ clearPage(state, pageIndex) {
3372
+ if (state.source.binding.clearPage) {
3373
+ state.source.binding.clearPage(pageIndex);
3374
+ } else {
3375
+ state.source.binding.provider.clear(pageIndex);
3376
+ }
3377
+ }
3010
3378
  };
3011
- async function attachOpfsClipStream(streamer, engine, options) {
3379
+ async function attachOpfsClipStream(streamerOrEngine, engineOrOptions, maybeOptions) {
3380
+ if (!(streamerOrEngine instanceof ClipPageStreamer)) {
3381
+ return streamerOrEngine.attachOpfsClipStream(engineOrOptions);
3382
+ }
3383
+ const streamer = streamerOrEngine;
3384
+ const engine = engineOrOptions;
3385
+ const options = maybeOptions;
3386
+ if (!options) {
3387
+ throw new Error("attachOpfsClipStream requires options.");
3388
+ }
3012
3389
  const { clipId, primePages = 1, ...providerOptions } = options;
3013
3390
  const binding = createOpfsClipPageProvider(engine, providerOptions);
3014
3391
  const lastPage = Math.ceil(providerOptions.numSamples / providerOptions.pageFrames) - 1;
@@ -3064,7 +3441,11 @@ async function bindMicrophoneInput(context, engine, options = {}) {
3064
3441
  function assertOversampleFactor(fnName, factor) {
3065
3442
  const normalized = factor === 0 ? 4 : factor;
3066
3443
  if (!Number.isInteger(normalized) || normalized < 1 || normalized > 16 || (normalized & normalized - 1) !== 0) {
3067
- throw new RangeError(`${fnName}: oversampleFactor must be 0 or a power of two from 1 to 16`);
3444
+ throw new SonareError(
3445
+ 4 /* InvalidParameter */,
3446
+ "InvalidParameter",
3447
+ `${fnName}: oversampleFactor must be 0 or a power of two from 1 to 16`
3448
+ );
3068
3449
  }
3069
3450
  }
3070
3451
  function requireModule15() {
@@ -3080,6 +3461,17 @@ function meteringRmsDb(samples, sampleRate = 22050, options = {}) {
3080
3461
  assertSamples("meteringRmsDb", request.samples, request.validate !== false);
3081
3462
  return requireModule15().meteringRmsDb(request.samples, request.sampleRate ?? 22050);
3082
3463
  }
3464
+ function meteringSilenceRatio(samples, sampleRate = 22050, thresholdDb = -45, frameLength = 1024, hopLength = 256, options = {}) {
3465
+ const request = samples instanceof Float32Array ? { samples, sampleRate, thresholdDb, frameLength, hopLength, ...options } : samples;
3466
+ assertSamples("meteringSilenceRatio", request.samples, request.validate !== false);
3467
+ return requireModule15().meteringSilenceRatio(
3468
+ request.samples,
3469
+ request.sampleRate ?? 22050,
3470
+ request.thresholdDb ?? -45,
3471
+ request.frameLength ?? 1024,
3472
+ request.hopLength ?? 256
3473
+ );
3474
+ }
3083
3475
  function meteringCrestFactorDb(samples, sampleRate = 22050, options = {}) {
3084
3476
  const request = samples instanceof Float32Array ? { samples, sampleRate, ...options } : samples;
3085
3477
  assertSamples("meteringCrestFactorDb", request.samples, request.validate !== false);
@@ -3100,11 +3492,15 @@ function meteringTruePeakDb(samples, sampleRate = 22050, oversampleFactor = 4, o
3100
3492
  function meteringDetectClipping(samples, sampleRate = 22050, options = {}) {
3101
3493
  const request = samples instanceof Float32Array ? { samples, sampleRate, ...options } : samples;
3102
3494
  assertSamples("meteringDetectClipping", request.samples, request.validate !== false);
3495
+ const minRegionSamples = request.minRegionSamples ?? 1;
3496
+ if (!Number.isInteger(minRegionSamples) || minRegionSamples < 0) {
3497
+ throw new RangeError("meteringDetectClipping: minRegionSamples must be a non-negative integer");
3498
+ }
3103
3499
  return requireModule15().meteringDetectClipping(
3104
3500
  request.samples,
3105
3501
  request.sampleRate ?? 22050,
3106
3502
  request.threshold ?? 0.999,
3107
- request.minRegionSamples ?? 1
3503
+ minRegionSamples
3108
3504
  );
3109
3505
  }
3110
3506
  function meteringDynamicRange(samples, sampleRate = 22050, options = {}) {
@@ -3229,6 +3625,61 @@ function waveformPeakPyramid(samples, channels, options = {}) {
3229
3625
  return requireModule15().waveformPeakPyramid(request.samples, request.channels, levels);
3230
3626
  }
3231
3627
 
3628
+ // src/codes.ts
3629
+ function resolveEnumOrdinal(value, values, enumName) {
3630
+ if (typeof value === "number") {
3631
+ if (!Number.isSafeInteger(value) || !Object.values(values).includes(value)) {
3632
+ throw new RangeError(`Invalid ${enumName}: ${String(value)}`);
3633
+ }
3634
+ return value;
3635
+ }
3636
+ if (typeof value === "string") {
3637
+ const ordinal = values[value];
3638
+ if (ordinal !== void 0) {
3639
+ return ordinal;
3640
+ }
3641
+ }
3642
+ throw new RangeError(`Invalid ${enumName}: ${String(value)}`);
3643
+ }
3644
+ var AUTOMATION_CURVE_VALUES = {
3645
+ linear: 0,
3646
+ exponential: 1,
3647
+ hold: 2,
3648
+ "s-curve": 3
3649
+ };
3650
+ var PAN_LAW_VALUES = {
3651
+ const3dB: 0,
3652
+ "const4.5dB": 1,
3653
+ const6dB: 2,
3654
+ linear0dB: 3
3655
+ };
3656
+ var PAN_MODE_VALUES = {
3657
+ balance: 0,
3658
+ pan: 0,
3659
+ stereopan: 1,
3660
+ "stereo-pan": 1,
3661
+ dualpan: 2,
3662
+ "dual-pan": 2
3663
+ };
3664
+ var METER_TAP_VALUES = { preFader: 0, postFader: 1 };
3665
+ var SEND_TIMING_VALUES = { postFader: 0, preFader: 1 };
3666
+ function automationCurveCode(curve) {
3667
+ return resolveEnumOrdinal(curve, AUTOMATION_CURVE_VALUES, "automation curve");
3668
+ }
3669
+ function panLawCode(panLaw) {
3670
+ return resolveEnumOrdinal(panLaw, PAN_LAW_VALUES, "pan law");
3671
+ }
3672
+ function panModeCode(panMode) {
3673
+ const normalized = typeof panMode === "string" ? panMode.replace(/_/g, "-").toLowerCase() : panMode;
3674
+ return resolveEnumOrdinal(normalized, PAN_MODE_VALUES, "pan mode");
3675
+ }
3676
+ function meterTapCode(tap) {
3677
+ return resolveEnumOrdinal(tap, METER_TAP_VALUES, "meter tap");
3678
+ }
3679
+ function sendTimingCode(timing) {
3680
+ return resolveEnumOrdinal(timing, SEND_TIMING_VALUES, "send timing");
3681
+ }
3682
+
3232
3683
  // src/project_internal.ts
3233
3684
  function projectModule() {
3234
3685
  const candidate = getSonareModule();
@@ -3295,37 +3746,17 @@ function assertProjectMidiEvents(fnName, events) {
3295
3746
  });
3296
3747
  }
3297
3748
  function projectTrackKindValue(kind) {
3298
- if (kind === void 0 || kind === "audio") {
3299
- return 0;
3300
- }
3301
- if (kind === "midi") {
3302
- return 1;
3303
- }
3304
- if (kind === "aux") {
3305
- return 2;
3306
- }
3307
- return kind;
3749
+ return resolveEnumOrdinal(kind ?? "audio", { audio: 0, midi: 1, aux: 2 }, "project track kind");
3308
3750
  }
3309
3751
  function projectWarpModeValue(mode) {
3310
- if (mode === void 0 || mode === "off") {
3311
- return 0;
3312
- }
3313
- if (mode === "repitch") {
3314
- return 1;
3315
- }
3316
- if (mode === "tempo-sync") {
3317
- return 2;
3318
- }
3319
- return mode;
3752
+ return resolveEnumOrdinal(
3753
+ mode ?? "off",
3754
+ { off: 0, repitch: 1, "tempo-sync": 2 },
3755
+ "project warp mode"
3756
+ );
3320
3757
  }
3321
3758
  function projectLoopModeValue(mode) {
3322
- if (mode === void 0 || mode === "off") {
3323
- return 0;
3324
- }
3325
- if (mode === "loop") {
3326
- return 1;
3327
- }
3328
- return mode;
3759
+ return resolveEnumOrdinal(mode ?? "off", { off: 0, loop: 1 }, "project loop mode");
3329
3760
  }
3330
3761
 
3331
3762
  // src/project_class.ts
@@ -3498,6 +3929,16 @@ var Project = class _Project {
3498
3929
  addClip(desc) {
3499
3930
  return this.native.addClip(desc);
3500
3931
  }
3932
+ /** Import host-separated PCM through the normal audio-track/clip path. */
3933
+ importExternalStems(request) {
3934
+ return this.native.importExternalStems({
3935
+ ...request,
3936
+ stems: request.stems.map((stem) => ({
3937
+ ...stem,
3938
+ layout: stem.layout === "mono" ? 1 : stem.layout === "stereo" ? 2 : stem.layout
3939
+ }))
3940
+ });
3941
+ }
3501
3942
  /** Split captured loop-recording audio into takes and add one clip. */
3502
3943
  addLoopRecordingTakes(desc) {
3503
3944
  return this.native.addLoopRecordingTakes(desc);
@@ -3542,7 +3983,11 @@ var Project = class _Project {
3542
3983
  * Route a track's MIDI to host-instrument `destinationId` (0 = default). The
3543
3984
  * compiler stamps every MIDI clip on the track with this id so the engine
3544
3985
  * dispatches its events to the instrument registered for that destination.
3545
- * Routes through an undoable edit command.
3986
+ * Routes through an undoable edit command. Builtin, NativeSynth, and SF2
3987
+ * instruments retain source-track provenance inside a shared destination
3988
+ * voice pool. With only zero-latency instruments bound, live lanes and
3989
+ * channel-strip bounces remain aligned. Configure one live lane per source
3990
+ * track that needs strip processing.
3546
3991
  */
3547
3992
  setTrackMidiDestination(trackId, destinationId) {
3548
3993
  this.native.setTrackMidiDestination(trackId, destinationId);
@@ -3587,7 +4032,11 @@ var Project = class _Project {
3587
4032
  assertProjectMidiEvents("Project.setMidiEvents", events);
3588
4033
  this.native.setMidiEvents(clipId, events);
3589
4034
  }
3590
- /** Import an in-memory SMF buffer; returns the first added clip id. */
4035
+ /**
4036
+ * Import an in-memory SMF buffer; returns the first added clip id.
4037
+ * Malformed or partially truncated tracks are rejected instead of installing
4038
+ * a silently shortened clip.
4039
+ */
3591
4040
  importSmf(data) {
3592
4041
  return this.native.importSmf(data);
3593
4042
  }
@@ -3624,7 +4073,10 @@ var Project = class _Project {
3624
4073
  setProgramOnChannel(clipId, group, channel, program, bank = -1) {
3625
4074
  this.native.setProgramOnChannel(clipId, group, channel, program, bank);
3626
4075
  }
3627
- /** Destructively bake a MIDI-FX chain into a clip's stored MIDI events. */
4076
+ /**
4077
+ * Destructively bake a MIDI-FX chain into all stored events. Large clips are
4078
+ * drained without truncation; failure leaves the original clip unchanged.
4079
+ */
3628
4080
  bakeMidiFx(clipId, configJson) {
3629
4081
  this.native.bakeMidiFx(clipId, configJson);
3630
4082
  }
@@ -3634,9 +4086,9 @@ var Project = class _Project {
3634
4086
  }
3635
4087
  /**
3636
4088
  * Pre-flight check for hanging / unmatched notes in a MIDI clip: reports
3637
- * whether every note-on has a matching note-off (FIFO per channel+note).
3638
- * Useful before bouncing to catch a stuck note. Throws if `clipId` is unknown
3639
- * or not a MIDI clip.
4089
+ * whether every note-on in the exported half-open playback window has a
4090
+ * matching note-off (FIFO per group+channel+note). Useful before bouncing to
4091
+ * catch a stuck note. Throws if `clipId` is unknown or not a MIDI clip.
3640
4092
  */
3641
4093
  validateMidiNotes(clipId) {
3642
4094
  return this.native.validateMidiNotes(clipId);
@@ -3772,6 +4224,14 @@ var Project = class _Project {
3772
4224
  setClipFade(clipId, fadeIn = {}, fadeOut = {}) {
3773
4225
  this.native.setClipFade(clipId, fadeIn, fadeOut);
3774
4226
  }
4227
+ /** Audio source ids that need decoded PCM after deserialization. */
4228
+ unresolvedAudioSourceIds() {
4229
+ return this.native.unresolvedAudioSourceIds();
4230
+ }
4231
+ /** Register decoded interleaved PCM for an existing audio source (undoable). */
4232
+ setSourceAudio(sourceId, audio, channels, sampleRate) {
4233
+ this.native.setSourceAudio(sourceId, audio, channels, sampleRate);
4234
+ }
3775
4235
  /** Replace a clip's take list and active take id (undoable). */
3776
4236
  setClipTakes(clipId, takes, activeTakeId = 0) {
3777
4237
  this.native.setClipTakes(clipId, takes, activeTakeId);
@@ -3813,23 +4273,23 @@ var Project = class _Project {
3813
4273
  setTrackRoute(trackId, channelStripRef, outputTarget) {
3814
4274
  this.native.setTrackRoute(trackId, channelStripRef ?? "", outputTarget ?? "");
3815
4275
  }
3816
- /** Append an automation lane to a track; returns the lane index (undoable). */
4276
+ /** Append an automation lane; returns its stable target parameter id (undoable). */
3817
4277
  addAutomationLane(trackId, desc) {
3818
4278
  return this.native.addAutomationLane(trackId, {
3819
4279
  targetParamId: desc.targetParamId,
3820
4280
  points: desc.points
3821
4281
  });
3822
4282
  }
3823
- /** Replace an existing automation lane in place (undoable). */
3824
- editAutomationLane(trackId, laneIndex, desc) {
3825
- this.native.editAutomationLane(trackId, laneIndex, {
4283
+ /** Replace the lane identified by its stable target parameter id (undoable). */
4284
+ editAutomationLane(trackId, targetParamId, desc) {
4285
+ this.native.editAutomationLane(trackId, targetParamId, {
3826
4286
  targetParamId: desc.targetParamId,
3827
4287
  points: desc.points
3828
4288
  });
3829
4289
  }
3830
- /** Remove an automation lane from a track (undoable). */
3831
- removeAutomationLane(trackId, laneIndex) {
3832
- this.native.removeAutomationLane(trackId, laneIndex);
4290
+ /** Remove the lane identified by its stable target parameter id (undoable). */
4291
+ removeAutomationLane(trackId, targetParamId) {
4292
+ this.native.removeAutomationLane(trackId, targetParamId);
3833
4293
  }
3834
4294
  /** Replace the project's key annotation stream (undoable). */
3835
4295
  annotateKeys(keys) {
@@ -3893,6 +4353,18 @@ var Project = class _Project {
3893
4353
  markerByIndex(index) {
3894
4354
  return this.native.markerByIndex(index);
3895
4355
  }
4356
+ /** Read a stored project track by 0-based index. */
4357
+ trackByIndex(index) {
4358
+ return this.native.trackByIndex(index);
4359
+ }
4360
+ /** Read a stored project clip by 0-based index. */
4361
+ clipByIndex(index) {
4362
+ return this.native.clipByIndex(index);
4363
+ }
4364
+ /** Read a stored project source by 0-based index. */
4365
+ sourceByIndex(index) {
4366
+ return this.native.sourceByIndex(index);
4367
+ }
3896
4368
  /** Number of markers in the project. */
3897
4369
  markerCount() {
3898
4370
  return this.native.markerCount();
@@ -3966,6 +4438,7 @@ var MarkerKind = {
3966
4438
  cuePoint: 3,
3967
4439
  keySignature: 4
3968
4440
  };
4441
+ var BUILTIN_SYNTH_WAVEFORMS = ["sine", "saw", "sawtooth", "square", "triangle"];
3969
4442
  var SYNTH_ENGINE_MODES = [
3970
4443
  "default",
3971
4444
  "subtractive",
@@ -4028,82 +4501,6 @@ var SYNTH_MOD_DESTINATIONS = [
4028
4501
  "pan-units"
4029
4502
  ];
4030
4503
 
4031
- // src/codes.ts
4032
- function automationCurveCode(curve) {
4033
- switch (curve) {
4034
- case "linear":
4035
- return 0;
4036
- case "exponential":
4037
- return 1;
4038
- case "hold":
4039
- return 2;
4040
- case "s-curve":
4041
- return 3;
4042
- default:
4043
- throw new Error(`Invalid automation curve: ${curve}`);
4044
- }
4045
- }
4046
- function panLawCode(panLaw) {
4047
- if (typeof panLaw === "number") {
4048
- return panLaw;
4049
- }
4050
- switch (panLaw) {
4051
- case "const3dB":
4052
- return 0;
4053
- case "const4.5dB":
4054
- return 1;
4055
- case "const6dB":
4056
- return 2;
4057
- case "linear0dB":
4058
- return 3;
4059
- default:
4060
- throw new Error(`Invalid pan law: ${panLaw}`);
4061
- }
4062
- }
4063
- function panModeCode(panMode) {
4064
- if (typeof panMode === "number") {
4065
- return panMode;
4066
- }
4067
- switch (panMode) {
4068
- case "balance":
4069
- return 0;
4070
- case "stereoPan":
4071
- case "stereo-pan":
4072
- return 1;
4073
- case "dualPan":
4074
- case "dual-pan":
4075
- return 2;
4076
- default:
4077
- throw new Error(`Invalid pan mode: ${panMode}`);
4078
- }
4079
- }
4080
- function meterTapCode(tap) {
4081
- if (typeof tap === "number") {
4082
- return tap;
4083
- }
4084
- switch (tap) {
4085
- case "preFader":
4086
- return 0;
4087
- case "postFader":
4088
- return 1;
4089
- default:
4090
- throw new Error(`Invalid meter tap: ${tap}`);
4091
- }
4092
- }
4093
- function sendTimingCode(timing) {
4094
- if (typeof timing === "number") {
4095
- return timing;
4096
- }
4097
- switch (timing) {
4098
- case "postFader":
4099
- return 0;
4100
- case "preFader":
4101
- return 1;
4102
- default:
4103
- throw new Error(`Invalid send timing: ${timing}`);
4104
- }
4105
- }
4106
-
4107
4504
  // src/realtime_engine.ts
4108
4505
  var EXPECTED_ENGINE_ABI_VERSION = 3;
4109
4506
  function engineCapabilities() {
@@ -4122,23 +4519,30 @@ function engineCapabilities() {
4122
4519
  };
4123
4520
  }
4124
4521
  var RealtimeEngine = class {
4125
- constructor(sampleRate = 48e3, maxBlockSize = 128, commandCapacity = 1024, telemetryCapacity = 1024) {
4522
+ constructor(sampleRate = 48e3, maxBlockSize = 128, commandCapacity = 1024, telemetryCapacity = 1024, maxChannels = 64) {
4126
4523
  const module2 = getSonareModule();
4127
- const capabilities = engineCapabilities();
4128
- if (!capabilities.abiCompatible) {
4524
+ const capabilities2 = engineCapabilities();
4525
+ if (!capabilities2.abiCompatible) {
4129
4526
  throw new Error(
4130
- `Engine ABI mismatch: wasm=${capabilities.engineAbiVersion}, expected=${capabilities.expectedEngineAbiVersion}`
4527
+ `Engine ABI mismatch: wasm=${capabilities2.engineAbiVersion}, expected=${capabilities2.expectedEngineAbiVersion}`
4131
4528
  );
4132
4529
  }
4133
4530
  this.native = new module2.RealtimeEngine(
4134
4531
  sampleRate,
4135
4532
  maxBlockSize,
4136
4533
  commandCapacity,
4137
- telemetryCapacity
4534
+ telemetryCapacity,
4535
+ maxChannels
4138
4536
  );
4139
4537
  }
4140
- prepare(sampleRate, maxBlockSize, commandCapacity = 1024, telemetryCapacity = 1024) {
4141
- this.native.prepare(sampleRate, maxBlockSize, commandCapacity, telemetryCapacity);
4538
+ prepare(sampleRate, maxBlockSize, commandCapacity = 1024, telemetryCapacity = 1024, maxChannels = 64) {
4539
+ this.native.prepareWithChannels(
4540
+ sampleRate,
4541
+ maxBlockSize,
4542
+ commandCapacity,
4543
+ telemetryCapacity,
4544
+ maxChannels
4545
+ );
4142
4546
  }
4143
4547
  /** Queue a sample-accurate parameter change (engine kSetParam). */
4144
4548
  setParameter(paramId, value, renderFrame = -1) {
@@ -4218,6 +4622,10 @@ var RealtimeEngine = class {
4218
4622
  options.maxValue ?? 1
4219
4623
  );
4220
4624
  }
4625
+ /** Bind a 7/14-bit CC, RPN, or NRPN descriptor to a live parameter. */
4626
+ bindMidiCcBinding(binding) {
4627
+ this.native.bindMidiCcBinding(binding);
4628
+ }
4221
4629
  clearMidiCcBindings() {
4222
4630
  this.native.clearMidiCcBindings();
4223
4631
  }
@@ -4260,6 +4668,9 @@ var RealtimeEngine = class {
4260
4668
  externalMidiDroppedCount() {
4261
4669
  return this.native.externalMidiDroppedCount();
4262
4670
  }
4671
+ externalMidiPendingCount() {
4672
+ return this.native.externalMidiPendingCount();
4673
+ }
4263
4674
  /**
4264
4675
  * Drain queued external-MIDI events, already lowered to MIDI 1.0 byte
4265
4676
  * messages ready to write to a Web MIDI output port. Call once per audio
@@ -4270,6 +4681,25 @@ var RealtimeEngine = class {
4270
4681
  drainExternalMidi(maxRecords = 1024) {
4271
4682
  return this.native.drainExternalMidi(maxRecords);
4272
4683
  }
4684
+ /** Scalar, allocation-free external-MIDI drain for AudioWorklet SAB output. */
4685
+ popExternalMidiToScratch() {
4686
+ return this.native.popExternalMidiToScratch();
4687
+ }
4688
+ externalMidiScratchDestinationId() {
4689
+ return this.native.externalMidiScratchDestinationId();
4690
+ }
4691
+ externalMidiScratchRenderFrame() {
4692
+ return this.native.externalMidiScratchRenderFrame();
4693
+ }
4694
+ externalMidiScratchByteWord() {
4695
+ return this.native.externalMidiScratchByteWord();
4696
+ }
4697
+ externalMidiScratchByteCount() {
4698
+ return this.native.externalMidiScratchByteCount();
4699
+ }
4700
+ consumeExternalMidiScratch() {
4701
+ this.native.consumeExternalMidiScratch();
4702
+ }
4273
4703
  pushMidiInputNoteOn(group, channel, note, velocity, portTimeSamples = 0) {
4274
4704
  this.native.pushMidiInputNoteOn(group, channel, note, velocity, portTimeSamples);
4275
4705
  }
@@ -4294,6 +4724,10 @@ var RealtimeEngine = class {
4294
4724
  pushMidiCc(destinationId, group, channel, controller, value, renderFrame = -1) {
4295
4725
  this.native.pushMidiCc(destinationId, group, channel, controller, value, renderFrame);
4296
4726
  }
4727
+ /** Queue one immediate MIDI 1.0 channel-voice UMP word for a destination. */
4728
+ pushMidiUmp(destinationId, word0, renderFrame = -1) {
4729
+ this.native.pushMidiUmp(destinationId, word0, renderFrame);
4730
+ }
4297
4731
  /**
4298
4732
  * Queue an immediate (live) MIDI SysEx frame to a MIDI destination. `data` is
4299
4733
  * the full message including the leading 0xF0 and trailing 0xF7 (1..512
@@ -4339,9 +4773,14 @@ var RealtimeEngine = class {
4339
4773
  settleParameters() {
4340
4774
  this.native.settleParameters();
4341
4775
  }
4776
+ /** Drains queued commands on an offline/control-only engine immediately. */
4777
+ flushControlCommands() {
4778
+ this.native.flushControlCommands();
4779
+ }
4342
4780
  seekPpq(ppq, renderFrame = -1) {
4343
4781
  this.native.seekPpq(ppq, renderFrame);
4344
4782
  }
4783
+ /** Set a finite tempo in the range (0, 100000] BPM. */
4345
4784
  setTempo(bpm) {
4346
4785
  this.native.setTempo(bpm);
4347
4786
  }
@@ -4396,6 +4835,7 @@ var RealtimeEngine = class {
4396
4835
  setLoopFromMarkers(startMarkerId, endMarkerId) {
4397
4836
  this.native.setLoopFromMarkers(startMarkerId, endMarkerId);
4398
4837
  }
4838
+ /** Set a metronome config; click lengths are limited to one second. */
4399
4839
  setMetronome(config) {
4400
4840
  this.native.setMetronome(config);
4401
4841
  }
@@ -4591,6 +5031,23 @@ var RealtimeEngine = class {
4591
5031
  popClipPageRequest() {
4592
5032
  return this.native.popClipPageRequest();
4593
5033
  }
5034
+ /**
5035
+ * Moves one native request into the binding's persistent scalar scratch.
5036
+ * This avoids creating an embind JS object in AudioWorklet process().
5037
+ */
5038
+ popClipPageRequestToScratch() {
5039
+ return this.native.popClipPageRequestToScratch();
5040
+ }
5041
+ clipPageRequestScratchClipId() {
5042
+ return this.native.clipPageRequestScratchClipId();
5043
+ }
5044
+ clipPageRequestScratchSample() {
5045
+ return this.native.clipPageRequestScratchSample();
5046
+ }
5047
+ /** Cumulative page misses dropped because the native bounded request queue was full. */
5048
+ clipPageRequestOverflowCount() {
5049
+ return this.native.clipPageRequestOverflowCount();
5050
+ }
4594
5051
  setCaptureBuffer(numChannels, capacityFrames) {
4595
5052
  this.native.setCaptureBuffer(numChannels, capacityFrames);
4596
5053
  }
@@ -4603,6 +5060,7 @@ var RealtimeEngine = class {
4603
5060
  setCaptureSource(source) {
4604
5061
  this.native.setCaptureSource(source);
4605
5062
  }
5063
+ /** Positive values delay capture relative to the punch window. */
4606
5064
  setRecordOffsetSamples(offsetSamples) {
4607
5065
  this.native.setRecordOffsetSamples(offsetSamples);
4608
5066
  }
@@ -4618,6 +5076,10 @@ var RealtimeEngine = class {
4618
5076
  capturedAudio() {
4619
5077
  return this.native.capturedAudio();
4620
5078
  }
5079
+ /**
5080
+ * Renders in place, adding engine output to `channels`. Zero each plane first
5081
+ * when it contains no upstream input.
5082
+ */
4621
5083
  process(channels) {
4622
5084
  return this.native.process(channels);
4623
5085
  }
@@ -4640,6 +5102,7 @@ var RealtimeEngine = class {
4640
5102
  }
4641
5103
  /**
4642
5104
  * Runs the engine in place over the prepared per-channel scratch buffers.
5105
+ * Zero each active span first when it contains no upstream input.
4643
5106
  * Allocation-free: safe to call on the AudioWorklet render thread after
4644
5107
  * `prepareChannels`.
4645
5108
  */
@@ -4661,6 +5124,42 @@ var RealtimeEngine = class {
4661
5124
  drainTelemetry(maxRecords = 1024) {
4662
5125
  return this.native.drainTelemetry(maxRecords);
4663
5126
  }
5127
+ popTelemetryToScratch() {
5128
+ return this.native.popTelemetryToScratch();
5129
+ }
5130
+ telemetryScratchType() {
5131
+ return this.native.telemetryScratchType();
5132
+ }
5133
+ telemetryScratchError() {
5134
+ return this.native.telemetryScratchError();
5135
+ }
5136
+ telemetryScratchRenderFrame() {
5137
+ return Number(this.native.telemetryScratchRenderFrame());
5138
+ }
5139
+ telemetryScratchTimelineSample() {
5140
+ return Number(this.native.telemetryScratchTimelineSample());
5141
+ }
5142
+ telemetryScratchAudibleTimelineSample() {
5143
+ return Number(this.native.telemetryScratchAudibleTimelineSample());
5144
+ }
5145
+ telemetryScratchGraphLatencySamplesQ8() {
5146
+ return this.native.telemetryScratchGraphLatencySamplesQ8();
5147
+ }
5148
+ telemetryScratchValue() {
5149
+ return this.native.telemetryScratchValue();
5150
+ }
5151
+ popMeterTelemetryToScratch() {
5152
+ return this.native.popMeterTelemetryToScratch();
5153
+ }
5154
+ meterScratchTargetId() {
5155
+ return this.native.meterScratchTargetId();
5156
+ }
5157
+ meterScratchRenderFrame() {
5158
+ return Number(this.native.meterScratchRenderFrame());
5159
+ }
5160
+ meterScratchValue(field) {
5161
+ return this.native.meterScratchValue(field);
5162
+ }
4664
5163
  drainMeterTelemetry(maxRecords = 1024) {
4665
5164
  return this.native.drainMeterTelemetry(maxRecords);
4666
5165
  }
@@ -4688,6 +5187,30 @@ var RealtimeEngine = class {
4688
5187
  drainScopeTelemetry(maxRecords = 1024) {
4689
5188
  return this.native.drainScopeTelemetry(maxRecords);
4690
5189
  }
5190
+ popScopeTelemetryToScratch() {
5191
+ return this.native.popScopeTelemetryToScratch();
5192
+ }
5193
+ scopeScratchTargetId() {
5194
+ return this.native.scopeScratchTargetId();
5195
+ }
5196
+ scopeScratchRenderFrame() {
5197
+ return Number(this.native.scopeScratchRenderFrame());
5198
+ }
5199
+ scopeScratchBandCount() {
5200
+ return this.native.scopeScratchBandCount();
5201
+ }
5202
+ scopeScratchBand(index) {
5203
+ return this.native.scopeScratchBand(index);
5204
+ }
5205
+ scopeScratchPointCount() {
5206
+ return this.native.scopeScratchPointCount();
5207
+ }
5208
+ scopeScratchPointLeft(index) {
5209
+ return this.native.scopeScratchPointLeft(index);
5210
+ }
5211
+ scopeScratchPointRight(index) {
5212
+ return this.native.scopeScratchPointRight(index);
5213
+ }
4691
5214
  destroy() {
4692
5215
  this.native.delete();
4693
5216
  }
@@ -4980,7 +5503,10 @@ var Mixer = class _Mixer {
4980
5503
  const module2 = getSonareModule();
4981
5504
  return new _Mixer(module2.createMixerFromSceneJson(json, sampleRate, blockSize), blockSize);
4982
5505
  }
4983
- /** Rebuild and compile the routing graph from the current scene topology. */
5506
+ /**
5507
+ * Rebuild and compile the routing graph without resetting its absolute
5508
+ * automation sample position or queued strip automation.
5509
+ */
4984
5510
  compile() {
4985
5511
  this.mixer.compile();
4986
5512
  }
@@ -5139,6 +5665,10 @@ var Mixer = class _Mixer {
5139
5665
  setVcaGroupGainDb(id, gainDb) {
5140
5666
  this.mixer.setVcaGroupGainDb(id, gainDb);
5141
5667
  }
5668
+ /** Replace an existing VCA group's strip membership. */
5669
+ setVcaGroupMembers(id, members) {
5670
+ this.mixer.setVcaGroupMembers(id, members);
5671
+ }
5142
5672
  /** Remove a VCA group by id. */
5143
5673
  removeVcaGroup(id) {
5144
5674
  this.mixer.removeVcaGroup(id);
@@ -5278,6 +5808,10 @@ var Mixer = class _Mixer {
5278
5808
  }
5279
5809
  return this.mixer.meterTap(stripIndex, meterTapCode(tap));
5280
5810
  }
5811
+ /** Read the post-insert meter for a compiled bus, including master. */
5812
+ busMeter(busId) {
5813
+ return this.mixer.busMeter(busId);
5814
+ }
5281
5815
  /**
5282
5816
  * Schedule sample-accurate fader automation on a strip.
5283
5817
  *
@@ -5378,65 +5912,18 @@ var Mixer = class _Mixer {
5378
5912
  };
5379
5913
 
5380
5914
  // src/realtime_voice_changer.ts
5381
- function isFlatVoiceChangerPod(config) {
5382
- return typeof config === "object" && config !== null && "retuneSemitones" in config;
5383
- }
5384
- function flatVoiceChangerPodToNested(pod) {
5385
- return {
5386
- inputGainDb: pod.inputGainDb,
5387
- outputGainDb: pod.outputGainDb,
5388
- wetMix: pod.wetMix,
5389
- retune: { semitones: pod.retuneSemitones, mix: pod.retuneMix, grainSize: pod.retuneGrainSize },
5390
- formant: {
5391
- factor: pod.formantFactor,
5392
- amount: pod.formantAmount,
5393
- body: pod.formantBody,
5394
- brightness: pod.formantBrightness,
5395
- nasal: pod.formantNasal
5396
- },
5397
- eq: {
5398
- highpassHz: pod.eqHighpassHz,
5399
- bodyDb: pod.eqBodyDb,
5400
- presenceDb: pod.eqPresenceDb,
5401
- airDb: pod.eqAirDb
5402
- },
5403
- gate: {
5404
- thresholdDb: pod.gateThresholdDb,
5405
- attackMs: pod.gateAttackMs,
5406
- releaseMs: pod.gateReleaseMs,
5407
- rangeDb: pod.gateRangeDb
5408
- },
5409
- compressor: {
5410
- thresholdDb: pod.compressorThresholdDb,
5411
- ratio: pod.compressorRatio,
5412
- attackMs: pod.compressorAttackMs,
5413
- releaseMs: pod.compressorReleaseMs,
5414
- makeupGainDb: pod.compressorMakeupGainDb
5415
- },
5416
- deesser: {
5417
- frequencyHz: pod.deesserFrequencyHz,
5418
- thresholdDb: pod.deesserThresholdDb,
5419
- ratio: pod.deesserRatio,
5420
- rangeDb: pod.deesserRangeDb
5421
- },
5422
- reverb: {
5423
- mix: pod.reverbMix,
5424
- timeMs: pod.reverbTimeMs,
5425
- damping: pod.reverbDamping,
5426
- seed: pod.reverbSeed
5427
- },
5428
- limiter: {
5429
- ceilingDb: pod.limiterCeilingDb,
5430
- releaseMs: pod.limiterReleaseMs,
5431
- enableIspLimiter: pod.limiterEnableIspLimiter,
5432
- ispCeilingDbtp: pod.limiterIspCeilingDbtp
5433
- }
5434
- };
5435
- }
5436
5915
  var RealtimeVoiceChanger = class {
5437
- constructor(config = "neutral-monitor") {
5916
+ /**
5917
+ * Creates a voice changer. Supplying `sampleRate` prepares it immediately,
5918
+ * matching the Node and Python constructors; omitting it preserves the
5919
+ * explicit {@link prepare} lifecycle for callers that configure later.
5920
+ */
5921
+ constructor(config = "neutral-monitor", sampleRate, maxBlockSize = 128, channels = 1) {
5438
5922
  const module2 = getSonareModule();
5439
5923
  this.changer = module2.createRealtimeVoiceChanger(config);
5924
+ if (sampleRate !== void 0) {
5925
+ this.changer.prepare(sampleRate, maxBlockSize, channels);
5926
+ }
5440
5927
  }
5441
5928
  prepare(sampleRate, maxBlockSize = 128, channels = 1) {
5442
5929
  this.changer.prepare(sampleRate, maxBlockSize, channels);
@@ -5445,8 +5932,15 @@ var RealtimeVoiceChanger = class {
5445
5932
  this.changer.reset();
5446
5933
  }
5447
5934
  setConfig(config) {
5448
- const resolved = isFlatVoiceChangerPod(config) ? flatVoiceChangerPodToNested(config) : config;
5449
- this.changer.setConfig(resolved);
5935
+ this.changer.setConfig(config);
5936
+ }
5937
+ /**
5938
+ * Apply a flat, pre-normalized config without JSON serialization. Intended
5939
+ * for AudioWorklet control messages whose sender prepared the POD on the
5940
+ * main thread.
5941
+ */
5942
+ setPodConfig(config) {
5943
+ this.changer.setPodConfig(config);
5450
5944
  }
5451
5945
  configJson() {
5452
5946
  return this.changer.configJson();
@@ -5454,6 +5948,13 @@ var RealtimeVoiceChanger = class {
5454
5948
  latencySamples() {
5455
5949
  return this.changer.latencySamples();
5456
5950
  }
5951
+ /**
5952
+ * Monotonically increases whenever {@link prepare} can replace the native
5953
+ * scratch buffers. Cached WASM heap views must be reacquired after it changes.
5954
+ */
5955
+ bufferGeneration() {
5956
+ return this.changer.bufferGeneration();
5957
+ }
5457
5958
  processMono(samples) {
5458
5959
  return this.changer.processMono(samples);
5459
5960
  }
@@ -5530,10 +6031,12 @@ var RealtimeVoiceChanger = class {
5530
6031
  createRealtimeMonoBuffer(numSamples) {
5531
6032
  let input = this.getMonoInputBuffer(numSamples);
5532
6033
  let output = this.getMonoOutputBuffer(numSamples);
6034
+ let generation = this.bufferGeneration();
5533
6035
  const reacquireIfDetached = () => {
5534
- if (input.byteLength === 0 || output.byteLength === 0) {
6036
+ if (generation !== this.bufferGeneration() || input.byteLength === 0 || output.byteLength === 0) {
5535
6037
  input = this.getMonoInputBuffer(numSamples);
5536
6038
  output = this.getMonoOutputBuffer(numSamples);
6039
+ generation = this.bufferGeneration();
5537
6040
  }
5538
6041
  };
5539
6042
  return {
@@ -5555,10 +6058,12 @@ var RealtimeVoiceChanger = class {
5555
6058
  createRealtimeInterleavedBuffer(numFrames, numChannels) {
5556
6059
  let input = this.getInterleavedInputBuffer(numFrames, numChannels);
5557
6060
  let output = this.getInterleavedOutputBuffer(numFrames, numChannels);
6061
+ let generation = this.bufferGeneration();
5558
6062
  const reacquireIfDetached = () => {
5559
- if (input.byteLength === 0 || output.byteLength === 0) {
6063
+ if (generation !== this.bufferGeneration() || input.byteLength === 0 || output.byteLength === 0) {
5560
6064
  input = this.getInterleavedInputBuffer(numFrames, numChannels);
5561
6065
  output = this.getInterleavedOutputBuffer(numFrames, numChannels);
6066
+ generation = this.bufferGeneration();
5562
6067
  }
5563
6068
  };
5564
6069
  return {
@@ -5585,15 +6090,17 @@ var RealtimeVoiceChanger = class {
5585
6090
  */
5586
6091
  createRealtimePlanarBuffer(numFrames, numChannels) {
5587
6092
  let channels = [];
6093
+ let generation = this.bufferGeneration();
5588
6094
  const acquire = () => {
5589
6095
  channels = [];
5590
6096
  for (let ch = 0; ch < numChannels; ch++) {
5591
6097
  channels.push(this.getPlanarChannelBuffer(ch, numFrames));
5592
6098
  }
6099
+ generation = this.bufferGeneration();
5593
6100
  };
5594
6101
  acquire();
5595
6102
  const reacquireIfDetached = () => {
5596
- if ((channels[0]?.byteLength ?? 0) === 0) {
6103
+ if (generation !== this.bufferGeneration() || (channels[0]?.byteLength ?? 0) === 0) {
5597
6104
  acquire();
5598
6105
  }
5599
6106
  };
@@ -5637,7 +6144,12 @@ var EQ_PHASE_MODES = {
5637
6144
  var StreamingMasteringChain = class {
5638
6145
  constructor(config) {
5639
6146
  const module2 = getSonareModule();
5640
- this.chain = module2.createStreamingMasteringChain(config);
6147
+ const { loudnessStaticGainDb, loudnessStaticGainPeakDb, ...chainConfig } = config;
6148
+ this.chain = module2.createStreamingMasteringChain({
6149
+ __flatParams: flattenChainConfig(chainConfig),
6150
+ loudnessStaticGainDb,
6151
+ loudnessStaticGainPeakDb
6152
+ });
5641
6153
  }
5642
6154
  /**
5643
6155
  * Initialize processors for the given sample rate and block layout.
@@ -5664,6 +6176,19 @@ var StreamingMasteringChain = class {
5664
6176
  }
5665
6177
  return this.chain.processStereo(left, right);
5666
6178
  }
6179
+ /**
6180
+ * Emit delayed audio and finite processor tails after the final mono block.
6181
+ * Call until this returns an empty array. The initial `latencySamples()`
6182
+ * samples of the concatenated stream are delayed and should be discarded for
6183
+ * time-aligned output.
6184
+ */
6185
+ flushMono() {
6186
+ return this.chain.flushMono();
6187
+ }
6188
+ /** Stereo counterpart of {@link flushMono}. */
6189
+ flushStereo() {
6190
+ return this.chain.flushStereo();
6191
+ }
5667
6192
  /** Reset all processor state without rebuilding. */
5668
6193
  reset() {
5669
6194
  this.chain.reset();
@@ -6055,6 +6580,214 @@ function iterInputs(access) {
6055
6580
  return access.inputs instanceof Map ? access.inputs.entries() : access.inputs;
6056
6581
  }
6057
6582
 
6583
+ // src/worker_client.ts
6584
+ var OfflineWorkerTask = class {
6585
+ constructor(result, cancelRequest) {
6586
+ this.result = result;
6587
+ this.cancelRequest = cancelRequest;
6588
+ }
6589
+ cancel() {
6590
+ this.cancelRequest();
6591
+ }
6592
+ // biome-ignore lint/suspicious/noThenProperty: this intentionally implements PromiseLike so callers can await a task and cancel it.
6593
+ then(onfulfilled, onrejected) {
6594
+ return this.result.then(onfulfilled, onrejected);
6595
+ }
6596
+ catch(onrejected) {
6597
+ return this.result.catch(onrejected);
6598
+ }
6599
+ finally(onfinally) {
6600
+ return this.result.finally(onfinally);
6601
+ }
6602
+ };
6603
+ function cloneForWorker(value, copy, transfers, transferred = /* @__PURE__ */ new Set()) {
6604
+ if (value instanceof Float32Array) {
6605
+ const samples = copy ? value.slice() : value;
6606
+ const buffer = samples.buffer;
6607
+ if (!(buffer instanceof ArrayBuffer)) {
6608
+ throw new TypeError(
6609
+ "OfflineWorkerClient only transfers Float32Array values backed by ArrayBuffer"
6610
+ );
6611
+ }
6612
+ if (!transferred.has(buffer)) {
6613
+ transferred.add(buffer);
6614
+ transfers.push(buffer);
6615
+ }
6616
+ return samples;
6617
+ }
6618
+ if (Array.isArray(value)) {
6619
+ return value.map((item) => cloneForWorker(item, copy, transfers, transferred));
6620
+ }
6621
+ if (value !== null && typeof value === "object") {
6622
+ return Object.fromEntries(
6623
+ Object.entries(value).map(([key, item]) => [
6624
+ key,
6625
+ cloneForWorker(item, copy, transfers, transferred)
6626
+ ])
6627
+ );
6628
+ }
6629
+ return value;
6630
+ }
6631
+ function cancellationFlag() {
6632
+ if (typeof SharedArrayBuffer === "undefined") {
6633
+ return void 0;
6634
+ }
6635
+ return new Int32Array(new SharedArrayBuffer(Int32Array.BYTES_PER_ELEMENT));
6636
+ }
6637
+ function workerError(message) {
6638
+ if (message.error.code !== void 0) {
6639
+ return new SonareError(
6640
+ message.error.code,
6641
+ message.error.codeName ?? "Unknown",
6642
+ message.error.message
6643
+ );
6644
+ }
6645
+ const error = new Error(message.error.message);
6646
+ error.name = message.error.name;
6647
+ return error;
6648
+ }
6649
+ var OfflineWorkerClient = class {
6650
+ constructor(options = {}) {
6651
+ this.pending = /* @__PURE__ */ new Map();
6652
+ this.nextId = 1;
6653
+ this.closed = false;
6654
+ this.usesEventTarget = false;
6655
+ this.onMessage = (event) => {
6656
+ const message = event.data;
6657
+ const pending = this.pending.get(message.id);
6658
+ if (!pending) {
6659
+ return;
6660
+ }
6661
+ if (message.type === "sonare:offline-progress") {
6662
+ pending.onProgress?.({ progress: message.progress, stage: message.stage });
6663
+ return;
6664
+ }
6665
+ this.pending.delete(message.id);
6666
+ if (message.type === "sonare:offline-result") {
6667
+ pending.resolve(message.result);
6668
+ return;
6669
+ }
6670
+ pending.reject(workerError(message));
6671
+ };
6672
+ this.onError = (event) => {
6673
+ const error = new Error(event.message || "Offline Worker failed");
6674
+ for (const { reject } of this.pending.values()) {
6675
+ reject(error);
6676
+ }
6677
+ this.pending.clear();
6678
+ };
6679
+ this.onNodeMessage = (data) => {
6680
+ this.onMessage({ data });
6681
+ };
6682
+ this.onNodeError = (error) => {
6683
+ this.onError(
6684
+ error instanceof Error ? { message: error.message } : { message: String(error) }
6685
+ );
6686
+ };
6687
+ this.ownsWorker = options.worker === void 0 || options.terminateWorkerOnDispose === true;
6688
+ if (options.worker) {
6689
+ this.worker = options.worker;
6690
+ } else {
6691
+ if (!options.workerFactory && typeof Worker === "undefined") {
6692
+ throw new Error("OfflineWorkerClient requires a browser Worker implementation");
6693
+ }
6694
+ const url = options.workerUrl === void 0 ? new URL("./worker.js", import.meta.url) : new URL(options.workerUrl, import.meta.url);
6695
+ this.worker = options.workerFactory?.(url) ?? new Worker(url, { type: "module", name: "sonare-offline" });
6696
+ }
6697
+ if (this.worker.addEventListener) {
6698
+ this.usesEventTarget = true;
6699
+ this.worker.addEventListener("message", this.onMessage);
6700
+ this.worker.addEventListener("error", this.onError);
6701
+ } else if (this.worker.on) {
6702
+ this.worker.on("message", this.onNodeMessage);
6703
+ this.worker.on("error", this.onNodeError);
6704
+ } else {
6705
+ throw new TypeError("OfflineWorkerClient requires Worker event listeners");
6706
+ }
6707
+ }
6708
+ /** Dispatch full music analysis to the Worker. */
6709
+ analyze(request, options) {
6710
+ return this.call("analyze", request, options);
6711
+ }
6712
+ /** Dispatch BPM detection to the Worker. */
6713
+ detectBpm(request, options) {
6714
+ return this.call("detectBpm", request, options);
6715
+ }
6716
+ /** Dispatch key detection to the Worker. */
6717
+ detectKey(request, options) {
6718
+ return this.call("detectKey", request, options);
6719
+ }
6720
+ /** Dispatch chord detection to the Worker. */
6721
+ detectChords(request, options) {
6722
+ return this.call("detectChords", request, options);
6723
+ }
6724
+ /** Dispatch mono preset mastering to the Worker. */
6725
+ masterAudio(request, options) {
6726
+ return this.call("masterAudio", request, options);
6727
+ }
6728
+ /** Dispatch stereo preset mastering to the Worker. */
6729
+ masterAudioStereo(request, options) {
6730
+ return this.call("masterAudioStereo", request, options);
6731
+ }
6732
+ /** Stop accepting calls, reject outstanding work, and release the Worker if owned. */
6733
+ dispose() {
6734
+ if (this.closed) {
6735
+ return;
6736
+ }
6737
+ this.closed = true;
6738
+ if (this.usesEventTarget) {
6739
+ this.worker.removeEventListener?.("message", this.onMessage);
6740
+ this.worker.removeEventListener?.("error", this.onError);
6741
+ } else {
6742
+ this.worker.off?.("message", this.onNodeMessage);
6743
+ this.worker.off?.("error", this.onNodeError);
6744
+ }
6745
+ for (const { reject } of this.pending.values()) {
6746
+ reject(new Error("OfflineWorkerClient was disposed"));
6747
+ }
6748
+ this.pending.clear();
6749
+ if (this.ownsWorker) {
6750
+ this.worker.terminate();
6751
+ }
6752
+ }
6753
+ call(operation, request, options = {}) {
6754
+ if (this.closed) {
6755
+ throw new Error("OfflineWorkerClient was disposed");
6756
+ }
6757
+ const id = this.nextId++;
6758
+ const transfers = [];
6759
+ const preparedRequest = cloneForWorker(request, options.copy === true, transfers);
6760
+ const cancelFlag = cancellationFlag();
6761
+ const result = new Promise((resolve, reject) => {
6762
+ this.pending.set(id, {
6763
+ resolve: (value) => resolve(value),
6764
+ reject,
6765
+ onProgress: options.onProgress,
6766
+ cancelFlag
6767
+ });
6768
+ });
6769
+ this.worker.postMessage(
6770
+ {
6771
+ type: "sonare:offline-run",
6772
+ id,
6773
+ operation,
6774
+ request: preparedRequest,
6775
+ ...cancelFlag ? { cancelBuffer: cancelFlag.buffer } : {}
6776
+ },
6777
+ transfers
6778
+ );
6779
+ return new OfflineWorkerTask(result, () => {
6780
+ if (!this.pending.has(id)) {
6781
+ return;
6782
+ }
6783
+ if (cancelFlag) {
6784
+ Atomics.store(cancelFlag, 0, 1);
6785
+ }
6786
+ this.worker.postMessage({ type: "sonare:offline-cancel", id });
6787
+ });
6788
+ }
6789
+ };
6790
+
6058
6791
  // src/index.ts
6059
6792
  var module = null;
6060
6793
  var initPromise = null;
@@ -6086,6 +6819,18 @@ function version() {
6086
6819
  }
6087
6820
  return module.version();
6088
6821
  }
6822
+ function capabilities() {
6823
+ if (!module) {
6824
+ throw new Error("Module not initialized. Call init() first.");
6825
+ }
6826
+ return module.capabilities();
6827
+ }
6828
+ function capabilityCatalog() {
6829
+ if (!module) {
6830
+ throw new Error("Module not initialized. Call init() first.");
6831
+ }
6832
+ return JSON.parse(module.capabilityCatalog());
6833
+ }
6089
6834
  function abiVersion() {
6090
6835
  if (!module) {
6091
6836
  throw new Error("Module not initialized. Call init() first.");
@@ -6114,6 +6859,9 @@ var VOICE_PRESET_ORDINALS = [
6114
6859
  ];
6115
6860
  function resolveVoicePresetOrdinal(preset) {
6116
6861
  if (typeof preset === "number") {
6862
+ if (!Number.isSafeInteger(preset) || preset < 0 || preset >= VOICE_PRESET_ORDINALS.length) {
6863
+ throw new RangeError(`Unknown voice-character preset ordinal: ${String(preset)}`);
6864
+ }
6117
6865
  return preset;
6118
6866
  }
6119
6867
  const ordinal = VOICE_PRESET_ORDINALS.indexOf(preset);
@@ -6136,7 +6884,9 @@ function realtimeVoiceChangerPresetConfig(preset) {
6136
6884
  }
6137
6885
  export {
6138
6886
  Audio,
6887
+ BUILTIN_SYNTH_WAVEFORMS,
6139
6888
  ChordQuality,
6889
+ ClipPageProvider,
6140
6890
  ClipPageStreamer,
6141
6891
  EXPECTED_ENGINE_ABI_VERSION,
6142
6892
  EXPECTED_PROJECT_ABI_VERSION,
@@ -6145,6 +6895,8 @@ export {
6145
6895
  MarkerKind,
6146
6896
  Mixer,
6147
6897
  Mode,
6898
+ OfflineWorkerClient,
6899
+ OfflineWorkerTask,
6148
6900
  PitchClass as Pitch,
6149
6901
  PitchClass,
6150
6902
  Project,
@@ -6178,10 +6930,14 @@ export {
6178
6930
  bassChroma,
6179
6931
  bindMicrophoneInput,
6180
6932
  bindWebMidi,
6933
+ capabilities,
6934
+ capabilityCatalog,
6935
+ chirp,
6181
6936
  chordFunctionalAnalysis,
6182
6937
  chroma,
6183
6938
  chromaCens,
6184
6939
  chromaCqt,
6940
+ clicks,
6185
6941
  cqt,
6186
6942
  cqtToAudio,
6187
6943
  createOpfsClipPageProvider,
@@ -6211,6 +6967,7 @@ export {
6211
6967
  frameSignal,
6212
6968
  framesToSamples,
6213
6969
  framesToTime,
6970
+ griffinLim,
6214
6971
  harmonic,
6215
6972
  hasFfmpegSupport,
6216
6973
  hpss,
@@ -6261,6 +7018,7 @@ export {
6261
7018
  masteringStereoAnalysisNames,
6262
7019
  masteringStereoAnalyze,
6263
7020
  masteringStreamingPreview,
7021
+ melDelta,
6264
7022
  melSpectrogram,
6265
7023
  melToAudio,
6266
7024
  melToHz,
@@ -6273,6 +7031,7 @@ export {
6273
7031
  meteringPhaseScope,
6274
7032
  meteringPhaseScopeDecimated,
6275
7033
  meteringRmsDb,
7034
+ meteringSilenceRatio,
6276
7035
  meteringSpectrum,
6277
7036
  meteringSpectrumFrame,
6278
7037
  meteringStereoCorrelation,
@@ -6292,8 +7051,10 @@ export {
6292
7051
  nnlsChroma,
6293
7052
  normalize,
6294
7053
  noteMove,
7054
+ noteSegments,
6295
7055
  noteStretch,
6296
7056
  noteToHz,
7057
+ onsetBacktrack,
6297
7058
  onsetEnvelope,
6298
7059
  onsetStrengthMulti,
6299
7060
  opfsClipPageWorkerSource,
@@ -6302,6 +7063,7 @@ export {
6302
7063
  peakPick,
6303
7064
  percussive,
6304
7065
  phaseVocoder,
7066
+ piptrack,
6305
7067
  pitchCorrectTimevarying,
6306
7068
  pitchCorrectToMidi,
6307
7069
  pitchCorrectToMidiTimevarying,
@@ -6318,6 +7080,7 @@ export {
6318
7080
  realtimeVoiceChangerPresetConfig,
6319
7081
  realtimeVoiceChangerPresetJson,
6320
7082
  realtimeVoiceChangerPresetNames,
7083
+ reassignedSpectrogram,
6321
7084
  remix,
6322
7085
  resample,
6323
7086
  rmsEnergy,
@@ -6326,12 +7089,20 @@ export {
6326
7089
  scaleCorrectionSemitones,
6327
7090
  scalePitchClassEnabled,
6328
7091
  scaleQuantizeMidi,
7092
+ segmentAgglomerative,
7093
+ segmentCrossSimilarity,
7094
+ segmentLagToRecurrence,
7095
+ segmentPathEnhance,
7096
+ segmentRecurrenceMatrix,
7097
+ segmentRecurrenceToLag,
7098
+ segmentSubsegment,
6329
7099
  shortTermLufs,
6330
7100
  spectralBandwidth,
6331
7101
  spectralCentroid,
6332
7102
  spectralContrast,
6333
7103
  spectralEdit,
6334
7104
  spectralFlatness,
7105
+ spectralFlux,
6335
7106
  spectralRolloff,
6336
7107
  splitSilence,
6337
7108
  stft,
@@ -6345,6 +7116,7 @@ export {
6345
7116
  tempogramRatio,
6346
7117
  timeStretch,
6347
7118
  timeToFrames,
7119
+ tone,
6348
7120
  tonnetz,
6349
7121
  trim,
6350
7122
  trimSilence,