fix waveform not being the right length.

This commit is contained in:
Alan Trebugeais
2026-05-10 23:46:39 +02:00
parent ccbeb5fa1d
commit b702f43ed5
3 changed files with 40 additions and 20 deletions
@@ -74,22 +74,36 @@ export class WaveformGenerator {
const arrayBuffer = await response.arrayBuffer();
const decoded = await this.audioContext.decodeAudioData(arrayBuffer);
const adaptivePeakCount = Math.max(
peakCount,
Math.floor(decoded.duration * 500)
);
const channels: Float32Array[] = [];
for (let i = 0; i < decoded.numberOfChannels; i++) {
// We slice to transfer the underlying buffer to the worker
channels.push(decoded.getChannelData(i).slice());
}
const peaks = await this.computePeaksWithWorker(channels, peakCount);
const peaks = await this.computePeaksWithWorker(channels, adaptivePeakCount);
// Robust Normalization: Use 99.5th percentile to avoid being squashed by a single loud spike/pop
let max = 0;
for (let i = 0; i < peaks.length; i++) {
if (peaks[i] > max) max = peaks[i];
const sortedPeaks = [...peaks].sort((a, b) => a - b);
const percentileIndex = Math.floor(sortedPeaks.length * 0.995);
const robustMax = sortedPeaks[percentileIndex] || 0;
// Fallback to absolute max if the percentile is zero (very quiet file)
if (robustMax === 0) {
for (let i = 0; i < peaks.length; i++) {
if (peaks[i] > max) max = peaks[i];
}
} else {
max = robustMax;
}
if (max > 0) {
for (let i = 0; i < peaks.length; i++) {
peaks[i] /= max;
peaks[i] = Math.min(1.0, peaks[i] / max);
}
}
@@ -24,12 +24,17 @@ workerScope.onmessage = (e: MessageEvent<WaveformWorkerRequest>) => {
const firstChannel = channels[0];
const result = new Float32Array(samples);
const total = firstChannel.length;
const blockSize = total / samples;
for (let i = 0; i < samples; i++) {
const start = Math.floor((i * total) / samples);
const end = Math.floor(((i + 1) * total) / samples);
const start = Math.floor(i * blockSize);
const end = Math.min(total, Math.floor((i + 1) * blockSize));
let max = 0;
for (let j = start; j < end; j++) {
// Ensure we check at least one sample even if blockSize < 1
const actualEnd = Math.max(start + 1, end);
for (let j = start; j < actualEnd && j < total; j++) {
for (let c = 0; c < channels.length; c++) {
const val = Math.abs(channels[c][j]);
if (val > max) max = val;
@@ -75,31 +75,32 @@ function AudioWaveformComponent({
const { peaks: peakData, durationMs } = peaks;
if (durationMs <= 0 || peakData.length === 0) return;
const rawVisibleStartMs = segmentStartMs ?? range.start;
const rawVisibleEndMs = segmentEndMs ?? range.end;
const msPerBin = durationMs / peakData.length;
const visibleStartMs =
msPerBin > 0 ? Math.round(rawVisibleStartMs / msPerBin) * msPerBin : rawVisibleStartMs;
const visibleEndMs =
msPerBin > 0 ? Math.round(rawVisibleEndMs / msPerBin) * msPerBin : rawVisibleEndMs;
// Use raw values for smooth zooming/panning (no snapping)
const visibleStartMs = segmentStartMs ?? range.start;
const visibleEndMs = segmentEndMs ?? range.end;
const visibleDurationMs = visibleEndMs - visibleStartMs;
if (visibleDurationMs <= 0) return;
const midY = height / 2;
ctx.beginPath();
for (let px = 0; px < width; px++) {
const t = visibleStartMs + (px / width) * visibleDurationMs;
const exactIndex = Math.max(
0,
Math.min(peakData.length - 1, (t / durationMs) * (peakData.length - 1)),
);
// If the timeline time is beyond the actual audio duration, we draw nothing (flat line)
if (t < 0 || t > durationMs) continue;
const exactIndex = (t / durationMs) * (peakData.length - 1);
const leftIndex = Math.floor(exactIndex);
const rightIndex = Math.min(peakData.length - 1, leftIndex + 1);
const mix = exactIndex - leftIndex;
let amplitude = peakData[leftIndex] * (1 - mix) + peakData[rightIndex] * mix;
if (normalize) amplitude = Math.sqrt(Math.max(0, amplitude));
amplitude = Math.max(0, Math.min(1, amplitude * gain));
const barHeight = amplitude * midY * 0.85;
ctx.moveTo(px, midY - barHeight);