@banou/ripple 0.0.34 → 0.0.36

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 (51) hide show
  1. package/README.md +8 -0
  2. package/ar-CS4Yzfxw-ntfoSy6g.js +52 -0
  3. package/assets/worker-C5z2LwD4.js +23 -0
  4. package/cs-CxBZ1aTx-DJlDhThf.js +52 -0
  5. package/de-BByk96M5.js +529 -0
  6. package/de-DphEdq0O-Cx5KYfi3.js +52 -0
  7. package/el-BVTNfAWK-DR4z_l3g.js +52 -0
  8. package/es-419-BSBCtqRj.js +529 -0
  9. package/es-419-D6gsvnn8-DPOi-mTp.js +52 -0
  10. package/fkn.json +1 -1
  11. package/fr-BqAzAJX0-tO4ZnrRx.js +52 -0
  12. package/fr-DTVt1r9F.js +529 -0
  13. package/he-DbFw1CdO-BQBjAJw3.js +52 -0
  14. package/hi-rBroV7g8-BKMVjsFT.js +52 -0
  15. package/hu-m-QKaaxF-BMZqq-7P.js +52 -0
  16. package/id-BYpApdK9-Dkgrchef.js +52 -0
  17. package/index.js +17569 -11367
  18. package/it-DFIqX3Zv.js +529 -0
  19. package/it-NTcUh3Gr-lPnPd57N.js +52 -0
  20. package/ja-BNOeE4ac.js +529 -0
  21. package/ja-DSmX_bxj-CtZfcziw.js +52 -0
  22. package/ko-AySLianc.js +529 -0
  23. package/ko-DyPzWasj-BzWTky6S.js +52 -0
  24. package/libav-jspi.wasm +0 -0
  25. package/libav-worker.js +74 -129
  26. package/libav.wasm +0 -0
  27. package/ms-8pQbqM0D-CqJOEt8R.js +52 -0
  28. package/nl-q7xvc3xW-wNb_UuNc.js +52 -0
  29. package/package.json +1 -1
  30. package/pl-C6Ow4ra5-DnFaIeqF.js +52 -0
  31. package/pl-CbVJGC-B.js +529 -0
  32. package/pt-BR-BcfbDHsH.js +529 -0
  33. package/pt-BR-wudK3giz-CNnIslbU.js +52 -0
  34. package/remote-CbOGAlJZ.js +229 -0
  35. package/ro-DBrd08Df-DVgJ76rr.js +52 -0
  36. package/ru-BuCRhXdv.js +529 -0
  37. package/ru-D5kxBkik-Co3SZGx7.js +52 -0
  38. package/sv-DJC3tXfW-CtNN1hTP.js +52 -0
  39. package/sw.js +2 -2
  40. package/th-BNzU_FYo-R9JeWVkr.js +52 -0
  41. package/tr-CW7R7N9k-CKbYpVyF.js +52 -0
  42. package/tr-C_v8NzIK.js +529 -0
  43. package/uk-C66esN2W-BVOzQwRO.js +52 -0
  44. package/vi-oOaDECwu-P7-ObqFb.js +52 -0
  45. package/volume-normalizer-worklet.js +40 -26
  46. package/zh-Hans-D_1CA1C3.js +529 -0
  47. package/zh-Hans-IhL1Pl78-BSKvaC2F.js +52 -0
  48. package/zh-Hant-C5YyFjEc-Dghd5nFF.js +52 -0
  49. package/zh-Hant-CQCQPnti.js +529 -0
  50. package/assets/worker-kaROsro7.js +0 -23
  51. package/remote-DvdN47iT.js +0 -209
@@ -440,8 +440,9 @@ export const audioSamplesOf = (fragment, track) => {
440
440
  /**
441
441
  * The analysis: 1 ms bins of whatever audio it is given, the normal level learned from them, and the cut for the
442
442
  * blocks around the playhead. `toWorklet` takes `{ block, gain, finger }` (10 s of bins from `block * 10` s, NaN where
443
- * the cut or the audio is not known) and `{ drop }` for each block past the 60 sent nearest the playhead, which is all
444
- * the worklet keeps; `toPage` takes the coverage reports the page schedules reads on.
443
+ * the cut or the audio is not known), `{ drop }` for each block past the 60 sent nearest the playhead, which is all
444
+ * the worklet keeps, and `{ end }`, the bin the file's audio ends at, once a run reaches it; `toPage` takes the
445
+ * coverage reports the page schedules reads on.
445
446
  * Separate from the worker's messages so a test can drive it with decoded audio.
446
447
  */
447
448
  export const createAnalysis = ({ toWorklet, toPage = () => {} }) => {
@@ -556,8 +557,6 @@ export const createAnalysis = ({ toWorklet, toPage = () => {} }) => {
556
557
  finger[bin - start] = Math.log(chunk.finger[bin % BLOCK_BINS] + 1e-10)
557
558
  if (bin < known) gain[bin - start] = curve[bin - from]
558
559
  }
559
- // the last cut holds past the end of the file, where playback's audio can run on by its offset
560
- if (b >= endBin && b > start && b < end) gain.fill(gain[b - 1 - start], b - start)
561
560
  }
562
561
  return { gain, finger }
563
562
  }
@@ -616,7 +615,10 @@ export const createAnalysis = ({ toWorklet, toPage = () => {} }) => {
616
615
  endRun: (finished) => {
617
616
  if (!run) return
618
617
  run.binner?.flush()
619
- if (finished && run.last >= 0) endBin = run.last + 1
618
+ if (finished && run.last >= 0) {
619
+ endBin = run.last + 1
620
+ toWorklet({ end: endBin })
621
+ }
620
622
  run = null
621
623
  schedule()
622
624
  },
@@ -644,10 +646,11 @@ export const createAnalysis = ({ toWorklet, toPage = () => {} }) => {
644
646
  *
645
647
  * { type: 'port', port } the worklet's end of a MessageChannel, for the blocks
646
648
  * { type: 'init', data, codec } the init segment and the codec string MediaSource was given
647
- * { type: 'run', run, pts, start } a run of fragments starts, `pts` added to their times as the SourceBuffer's
648
- * timestampOffset adds it for playback; `start` for the one from the start,
649
- * and `join: true` for one whose first fragments went by unseen, which starts
650
- * at its first sample
649
+ * { type: 'run', run, pts, from, start }
650
+ * a run of fragments starts at `from` s, `pts` added to their times as the
651
+ * SourceBuffer's timestampOffset adds it for playback; `start` for the one
652
+ * from the start, and `join: true` for one whose first fragments went by
653
+ * unseen, which starts at its first sample
651
654
  * { type: 'fragment', run, data } answered { type: 'ack', run, spans, end, normal } once its audio is queued
652
655
  * for decoding: the analysed seconds, the file's end once known, and the
653
656
  * level learned so far in LUFS
@@ -682,10 +685,10 @@ const serve = (scope) => {
682
685
  })
683
686
  /*
684
687
  * Each output is placed by its own chunk's timestamp, never the one AudioData carries: Chrome 155 stamps outputs by
685
- * counting samples from the first chunk, and libav's first audio sample in a run lasts until the video's first frame
686
- * is due (62 ms more than its own length in Matroska and mp4 with B-frames, 82 ms in FLAC), which MediaSource plays
687
- * as a gap; counted, what follows came out that much early, too far for the first match to find (HOR-498 build,
688
- * 2026-10-17).
688
+ * counting samples from the first chunk, so past a gap in the timestamps, which MediaSource plays as a gap, what
689
+ * follows comes out early by the gap. Before 0.9.0 libav-wasm's first audio sample of a run lasted until the video's
690
+ * first frame, 62 ms more than its own length in Matroska and mp4 with B-frames and 82 ms in FLAC, too far for the
691
+ * first match to find (HOR-498 build, 2026-10-17).
689
692
  */
690
693
  let stamps = []
691
694
  const open = (id) => {
@@ -722,7 +725,7 @@ const serve = (scope) => {
722
725
  pts = data.pts
723
726
  open(runId)
724
727
  joining = !!data.join
725
- if (!joining) analysis.startRun(runId, pts, data.start)
728
+ if (!joining) analysis.startRun(runId, data.from, data.start)
726
729
  } else if (data.type === 'fragment') {
727
730
  if (data.run !== runId || decoder.state !== 'configured') return
728
731
  const samples = audioSamplesOf(data.data, track)
@@ -781,13 +784,14 @@ const HANDOVER_SECONDS = 0.05
781
784
  const CROSSFADE_SECONDS = 0.05
782
785
  /*
783
786
  * How far either way the live audio is searched for in the analysed audio, in ms: first, and after a seek, against
784
- * where the last match put it. Played from the start, the fixtures matched 21 to 60 ms off, and FLAC past 60 ms (HOR-498
785
- * build, 2026-10-17), as long as libav's first audio sample stretches to the video's first frame, which B-frames set.
787
+ * where the last match put it. Played from the start, the fixtures matched 21 to 60 ms off, and FLAC past 60 ms, while
788
+ * libav-wasm stretched each run's first audio sample to the video's first frame (HOR-498 build, 2026-10-17); with each
789
+ * track at its own time (0.9.0), within 3.4 ms (2026-10-30).
786
790
  */
787
791
  const LOCK_RANGE_MS = 250
788
792
  const RELOCK_RANGE_MS = 30
789
793
  // how far from the last match a seek may move the audio, which the deepest cut in doubt covers, in ms: FLAC moved 82 ms
790
- // across the first remuxer seek, from the stream's start, where MediaSource plays libav's first sample short
794
+ // across the first remuxer seek while libav-wasm stretched a run's first audio sample, and at most 3 ms since 0.9.0
791
795
  const DOUBT_MS = 100
792
796
  // how far from the best offset another must come close for the match to be in question, in ms
793
797
  const CLEAR_MS = 60
@@ -805,14 +809,15 @@ const SCORES_PER_QUANTUM = 40_000
805
809
  * context.currentTime read in one task names the media time of the audio reaching a worklet that same render frame,
806
810
  * to the sample for a 48 kHz file and within about 2 ms for a 44.1 kHz one, except for the odd pair off by up to
807
811
  * 13 ms; so a jump of more than 25 ms re-anchors the timeline, and the median of the next five pairs settles it. Where
808
- * playback's audio sits against the analysed audio changes with each run of the remuxer behind it: -18 to +44 ms on
809
- * the fixtures, the same file 44 ms apart before and after a seek (HOR-498 build, 2026-10-17). So the offset is found
810
- * from the audio itself: the live fingerprint against the analysed one, every 250 ms at normal speed, to a fraction of
811
- * a millisecond. The same match catches the rare stall (once, 60 ms of silence came in mid-file and the audio after it
812
- * ran 11 ms ahead of the clock). Nothing is cut ahead until the first match, which also proves the blocks belong to
813
- * this audio; after a seek, until the next one, each quantum gets the deepest cut within the doubt, never less than
814
- * the cut asks, as it does away from normal speed (Chrome's time stretching moves the audio up to about 30 ms either
815
- * way).
812
+ * playback's audio sits against the analysed audio changed with each run of the remuxer behind it while libav-wasm
813
+ * moved each run by its own amount: -18 to +44 ms on the fixtures, the same file 44 ms apart before and after a seek
814
+ * (HOR-498 build, 2026-10-17); with each track at its own time, -3.4 to +1.2 ms (0.9.0, 2026-10-30). So the offset
815
+ * is found from the audio itself: the live fingerprint against the analysed one, every 250 ms at normal speed, to a
816
+ * fraction of a millisecond. The same match catches the rare stall (once, 60 ms of silence came in mid-file and the
817
+ * audio after it ran 11 ms ahead of the clock). Nothing is cut ahead until the first match, which also proves the
818
+ * blocks belong to this audio; after a seek, until the next one, each quantum gets the deepest cut within the doubt,
819
+ * never less than the cut asks, as it does away from normal speed (Chrome's time stretching moves the audio up to
820
+ * about 30 ms either way).
816
821
  */
817
822
  const defineProcessor = () => class VolumeNormalizer extends AudioWorkletProcessor {
818
823
  constructor({ processorOptions = {} } = {}) {
@@ -867,6 +872,8 @@ const defineProcessor = () => class VolumeNormalizer extends AudioWorkletProcess
867
872
  this.shortTime = 0
868
873
 
869
874
  this.blocks = new Map()
875
+ // the bin the file's audio ends at; the last cut holds past it, where playback's audio can run on
876
+ this.end = Infinity
870
877
  this.analysis = null
871
878
  this.fingerFilter = fingerprintFilter(sampleRate)
872
879
  this.fingerState = []
@@ -909,6 +916,7 @@ const defineProcessor = () => class VolumeNormalizer extends AudioWorkletProcess
909
916
  if (data?.analysis !== undefined) {
910
917
  this.analysis?.close()
911
918
  this.blocks.clear()
919
+ this.end = Infinity
912
920
  this.search = null
913
921
  this.analysis = data.analysis
914
922
  if (this.analysis) this.analysis.onmessage = ({ data: block }) => this.receive(block)
@@ -930,6 +938,7 @@ const defineProcessor = () => class VolumeNormalizer extends AudioWorkletProcess
930
938
  receive(message) {
931
939
  if (typeof message?.drop === 'number') this.blocks.delete(message.drop)
932
940
  else if (typeof message?.block === 'number') this.blocks.set(message.block, message)
941
+ else if (typeof message?.end === 'number') this.end = message.end
933
942
  this.lastBlock = undefined
934
943
  }
935
944
 
@@ -937,7 +946,11 @@ const defineProcessor = () => class VolumeNormalizer extends AudioWorkletProcess
937
946
  const frame = ctx * sampleRate
938
947
  const model = this.model
939
948
  if (paused) {
940
- this.model = { paused: true, media: model && !model.paused ? this.mediaAt(frame) : media }
949
+ // More than 25 ms from where the clock had it, the media is where the pair says: a seek moved it, its audio
950
+ // already playing (10 to 27 ms after the move in Chrome 155, where the main thread once took 52 ms to say so,
951
+ // 2026-10-30), or a pause was told late. The seek's reset, posted on the same event, is what puts it in doubt.
952
+ const held = model && !model.paused ? this.mediaAt(frame) : media
953
+ this.model = { paused: true, media: Math.abs(media - held) <= 0.025 ? held : media }
941
954
  this.liveCount = 0
942
955
  return
943
956
  }
@@ -973,6 +986,7 @@ const defineProcessor = () => class VolumeNormalizer extends AudioWorkletProcess
973
986
  }
974
987
 
975
988
  gainBin(i) {
989
+ if (i >= this.end) i = this.end - 1
976
990
  const block = this.blockOf(i)
977
991
  return block.gain ? block.gain[i - block.block * BLOCK_BINS] : NaN
978
992
  }