mirror of
https://github.com/captbaritone/webamp.git
synced 2026-07-22 17:47:16 +00:00
This is partially an attempt to allow me to focus on the Visualizer component so I can convert it to a functional component, but also an attempt to iteratively type this code
109 lines
3.4 KiB
TypeScript
109 lines
3.4 KiB
TypeScript
export const PIXEL_DENSITY = 2;
|
|
export const NUM_BARS = 20;
|
|
export const BAR_WIDTH = 3 * PIXEL_DENSITY;
|
|
export const BAR_PEAK_DROP_RATE = 0.01;
|
|
export const GRADIENT_COLOR_COUNT = 16;
|
|
export const PEAK_COLOR_INDEX = 23;
|
|
|
|
// Pre-render the background grid
|
|
export function preRenderBg(
|
|
width: number,
|
|
height: number,
|
|
bgColor: string,
|
|
fgColor: string,
|
|
windowShade: boolean
|
|
): HTMLCanvasElement {
|
|
// Off-screen canvas for pre-rendering the background
|
|
const bgCanvas = document.createElement("canvas");
|
|
bgCanvas.width = width;
|
|
bgCanvas.height = height;
|
|
const distance = 2 * PIXEL_DENSITY;
|
|
|
|
const bgCanvasCtx = bgCanvas.getContext("2d")!;
|
|
bgCanvasCtx.fillStyle = bgColor;
|
|
bgCanvasCtx.fillRect(0, 0, width, height);
|
|
if (!windowShade) {
|
|
bgCanvasCtx.fillStyle = fgColor;
|
|
for (let x = 0; x < width; x += distance) {
|
|
for (let y = PIXEL_DENSITY; y < height; y += distance) {
|
|
bgCanvasCtx.fillRect(x, y, PIXEL_DENSITY, PIXEL_DENSITY);
|
|
}
|
|
}
|
|
}
|
|
return bgCanvas;
|
|
}
|
|
|
|
export function preRenderBar(
|
|
height: number,
|
|
colors: string[],
|
|
renderHeight: number
|
|
): HTMLCanvasElement {
|
|
/**
|
|
* The order of the colours is commented in the file: the fist two colours
|
|
* define the background and dots (check it to see what are the dots), the
|
|
* next 16 colours are the analyzer's colours from top to bottom, the next
|
|
* 5 colours are the oscilloscope's ones, from center to top/bottom, the
|
|
* last colour is for the analyzer's peak markers.
|
|
*/
|
|
|
|
// Off-screen canvas for pre-rendering a single bar gradient
|
|
const barCanvas = document.createElement("canvas");
|
|
barCanvas.width = BAR_WIDTH;
|
|
barCanvas.height = height;
|
|
|
|
const offset = 2; // The first two colors are for the background;
|
|
const gradientColors = colors.slice(offset, offset + GRADIENT_COLOR_COUNT);
|
|
|
|
const barCanvasCtx = barCanvas.getContext("2d")!;
|
|
const multiplier = GRADIENT_COLOR_COUNT / renderHeight;
|
|
// In shade mode, the five colors are, from top to bottom:
|
|
// 214, 102, 0 -- 3
|
|
// 222, 165, 24 -- 6
|
|
// 148, 222, 33 -- 9
|
|
// 57, 181, 16 -- 12
|
|
// 24, 132, 8 -- 15
|
|
// TODO: This could probably be improved by iterating backwards
|
|
for (let i = 0; i < renderHeight; i++) {
|
|
const colorIndex = GRADIENT_COLOR_COUNT - 1 - Math.floor(i * multiplier);
|
|
barCanvasCtx.fillStyle = gradientColors[colorIndex];
|
|
const y = height - i * PIXEL_DENSITY;
|
|
barCanvasCtx.fillRect(0, y, BAR_WIDTH, PIXEL_DENSITY);
|
|
}
|
|
return barCanvas;
|
|
}
|
|
|
|
// Return the average value in a slice of dataArray
|
|
export function sliceAverage(
|
|
dataArray: Uint8Array,
|
|
sliceWidth: number,
|
|
sliceNumber: number
|
|
) {
|
|
const start = sliceWidth * sliceNumber;
|
|
const end = start + sliceWidth;
|
|
let sum = 0;
|
|
for (let i = start; i < end; i++) {
|
|
sum += dataArray[i];
|
|
}
|
|
return sum / sliceWidth;
|
|
}
|
|
|
|
export function octaveBucketsForBufferLength(bufferLength: number): number[] {
|
|
const octaveBuckets = new Array(NUM_BARS).fill(0);
|
|
const minHz = 200;
|
|
const maxHz = 22050;
|
|
const octaveStep = Math.pow(maxHz / minHz, 1 / NUM_BARS);
|
|
|
|
octaveBuckets[0] = 0;
|
|
octaveBuckets[1] = minHz;
|
|
for (let i = 2; i < NUM_BARS - 1; i++) {
|
|
octaveBuckets[i] = octaveBuckets[i - 1] * octaveStep;
|
|
}
|
|
octaveBuckets[NUM_BARS - 1] = maxHz;
|
|
|
|
for (let i = 0; i < NUM_BARS; i++) {
|
|
const octaveIdx = Math.floor((octaveBuckets[i] / maxHz) * bufferLength);
|
|
octaveBuckets[i] = octaveIdx;
|
|
}
|
|
|
|
return octaveBuckets;
|
|
}
|