ACloudCenter Claude Fable 5 commited on
Commit
197d8c5
·
1 Parent(s): 05a8f68

Stride the waveform peak scan so hours-long takes stay instant

Browse files

Visiting every sample meant ~475M iterations for a 5.5-hour take,
locking the UI for seconds. Sample ~1k points per bucket instead —
statistically identical for an RMS envelope — with the stride grid kept
aligned across slice boundaries. Measured: 82MB in 70ms, so a ~950MB
record take lands under a second, with the loudness envelope and silent
gaps still clearly resolved.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

Files changed (1) hide show
  1. static/app.js +9 -3
static/app.js CHANGED
@@ -1283,14 +1283,20 @@ async function peaksFromWavBlob(blob, buckets) {
1283
  const frames = Math.max(1, totalSamples / channels);
1284
  const sums = new Float64Array(buckets);
1285
  const counts = new Float64Array(buckets);
 
 
 
 
 
1286
  const CHUNK = 1 << 23; // 8MB slices keep memory flat regardless of take length
1287
  let sampleIndex = 0;
1288
  for (let pos = dataStart; pos < dataStart + dataSize; pos += CHUNK) {
1289
  const buf = await blob.slice(pos, Math.min(pos + CHUNK, dataStart + dataSize)).arrayBuffer();
1290
  const int16 = new Int16Array(buf, 0, Math.floor(buf.byteLength / 2));
1291
- for (let i = 0; i < int16.length; i += 1) {
1292
- const frame = Math.floor((sampleIndex + i) / channels);
1293
- const b = Math.min(buckets - 1, Math.floor((frame / frames) * buckets));
 
1294
  const v = int16[i] / 32768;
1295
  sums[b] += v * v;
1296
  counts[b] += 1;
 
1283
  const frames = Math.max(1, totalSamples / channels);
1284
  const sums = new Float64Array(buckets);
1285
  const counts = new Float64Array(buckets);
1286
+ // A 5.5-hour take is ~475M samples; visiting each one would lock the UI for
1287
+ // seconds. Sampling ~1k points per bucket is statistically identical for an
1288
+ // RMS envelope, so stride past the rest and stay well under a second.
1289
+ const TARGET_PER_BUCKET = 1000;
1290
+ const stride = Math.max(1, Math.floor(frames / (buckets * TARGET_PER_BUCKET))) * channels;
1291
  const CHUNK = 1 << 23; // 8MB slices keep memory flat regardless of take length
1292
  let sampleIndex = 0;
1293
  for (let pos = dataStart; pos < dataStart + dataSize; pos += CHUNK) {
1294
  const buf = await blob.slice(pos, Math.min(pos + CHUNK, dataStart + dataSize)).arrayBuffer();
1295
  const int16 = new Int16Array(buf, 0, Math.floor(buf.byteLength / 2));
1296
+ // Keep the stride grid aligned across slice boundaries.
1297
+ const first = (stride - (sampleIndex % stride)) % stride;
1298
+ for (let i = first; i < int16.length; i += stride) {
1299
+ const b = Math.min(buckets - 1, Math.floor(((sampleIndex + i) / channels / frames) * buckets));
1300
  const v = int16[i] / 32768;
1301
  sums[b] += v * v;
1302
  counts[b] += 1;