Ваш код рендеринга крайне неэффективен, потому что он рендерит 44100 пикселей за каждую секунду звука. Желательно визуализировать не более ширины области просмотра с уменьшенным набором данных.
Диапазон выборки на пиксель, необходимый для соответствия сигнала в области просмотра, можно рассчитать с помощью audioDurationSeconds * samplerate / viewPortWidthPx. Таким образом, для области просмотра 1000 пикселей и аудиофайла в 2 секунды с частотой дискретизации 44100 выборок на пиксель = (2 * 44100) / 1000 = ~ 88.
Для каждого пикселя на экране вы берете минимальное и максимальное значение из этого диапазона выборки, вы используете эти данные для рисования формы волны.
Вот пример алгоритма, который делает это, но позволяет вам давать выборки на пиксель в качестве аргумента, а также позицию прокрутки, чтобы обеспечить виртуальную прокрутку и масштабирование. Он включает параметр разрешения, который вы можете настроить для повышения производительности, он указывает, сколько сэмплов должно быть взято на пиксельный диапазон сэмплов:
Рисование масштабируемой временной шкалы формы звукового сигнала в Javascript
Метод рисования там похож на ваш, чтобы сгладить его, вам нужно использовать lineTo вместо fillRect. На самом деле это различие не должно быть таким огромным, я думаю, вы можете забыть установить атрибуты width и height холст. Установка этого в css вызывает размытое рисование, вам нужно установить атрибуты.
let drawWaveform = function(canvas, drawData, width, height) {
let ctx = canvas.getContext('2d');
let drawHeight = height / 2;
// clear canvas incase there is already something drawn
ctx.clearRect(0, 0, width, height);
ctx.beginPath();
ctx.moveTo(0, drawHeight);
for(let i = 0; i < width; i++) {
// transform data points to pixel height and move to centre
let minPixel = drawData[i][0] * drawHeigth + drawHeight;
ctx.lineTo(i, minPixel);
}
ctx.lineTo(width, drawHeight);
ctx.moveTo(0, drawHeight);
for(let i = 0; i < width; i++) {
// transform data points to pixel height and move to centre
let maxPixel = drawData[i][1] * drawHeigth + drawHeight;
ctx.lineTo(i, maxPixel);
}
ctx.lineTo(width, drawHeight);
ctx.closePath();
ctx.fill(); // can do ctx.stroke() for an outline of the waveform
}