49 lines
1.6 KiB
TypeScript
49 lines
1.6 KiB
TypeScript
/**
|
|
* Defines an audio file's waveform, the bars the player seeks on: how many
|
|
* peaks it has, and how they are taken from the samples. The server makes an
|
|
* uploaded file's from its decoded audio (`api/lib/ffmpeg.ts`), and the
|
|
* dump form a local file's in the browser (`src/utils/player/waveform.ts`),
|
|
* both with {@link peaksOf}, so the two draw alike.
|
|
*
|
|
* @module
|
|
*/
|
|
|
|
/** How many peaks, and so bars, a waveform has. */
|
|
export const WAVEFORM_PEAKS = 150;
|
|
|
|
/**
|
|
* Returned by `GET /api/dumps/:dumpId/waveform` for an audio file dump, to the
|
|
* player's waveform.
|
|
*/
|
|
export interface DumpWaveform {
|
|
/** {@link WAVEFORM_PEAKS} heights from 0 to 1, as {@link peaksOf} gives. */
|
|
peaks: number[];
|
|
}
|
|
|
|
/**
|
|
* Returns `n` peaks of `samples`, each the loudest of its slice, scaled so the
|
|
* loudest of all is 1 and rounded to three places: a quiet recording fills the
|
|
* waveform as a loud one does. `samples` may be signed, as audio is, or
|
|
* already peaks of smaller slices. Silence gives zeros.
|
|
*/
|
|
export function peaksOf(
|
|
samples: ArrayLike<number>,
|
|
n: number = WAVEFORM_PEAKS,
|
|
): number[] {
|
|
const peaks: number[] = [];
|
|
for (let i = 0; i < n; i++) {
|
|
// Slices cover every sample; with fewer samples than peaks, some share.
|
|
const start = Math.floor((i * samples.length) / n);
|
|
const end = Math.max(start + 1, Math.floor(((i + 1) * samples.length) / n));
|
|
let peak = 0;
|
|
for (let j = start; j < Math.min(end, samples.length); j++) {
|
|
peak = Math.max(peak, Math.abs(samples[j]));
|
|
}
|
|
peaks.push(peak);
|
|
}
|
|
const loudest = Math.max(...peaks);
|
|
return peaks.map((p) =>
|
|
loudest > 0 ? Math.round((p / loudest) * 1000) / 1000 : 0
|
|
);
|
|
}
|