music video renderer web app deftoku.digi.rip
audio visualizer webapp
1

Configure Feed

Select the types of activity you want to include in your feed.

visualizers/ascii: added ascii bar visualizer

- implemented new text box paramater
- paramaters styles are now able to be set within the visualizer itself

digi.rip (Jun 21, 2026, 12:01 PM EDT) c88d5861 8df464a0

+127
+23
src/controller.ts
··· 11 11 12 12 // visualizers 13 13 import { 14 + category as asciiBarsCategory, 15 + draw as asciiBars, 16 + params as asciiBarsParams, 17 + preview as asciiBarsPreview, 18 + } from "./visualizers/ascii/bars.ts"; 19 + import { 14 20 category as barsCategory, 15 21 draw as bars, 16 22 params as barsParams, ··· 53 59 max?: number; 54 60 step?: number; 55 61 label: string; 62 + inputStyle?: Partial<CSSStyleDeclaration>; 56 63 }>; 57 64 category: string; 58 65 default?: boolean; 59 66 }; 60 67 61 68 const visualizers: Record<string, VisualizerDef> = { 69 + asciiBars: { 70 + draw: asciiBars, 71 + params: asciiBarsParams, 72 + preview: asciiBarsPreview, 73 + category: asciiBarsCategory, 74 + }, 62 75 bars: { 63 76 draw: bars, 64 77 params: barsParams, ··· 205 218 input.value = String(p.default); 206 219 container.appendChild(label); 207 220 container.appendChild(input); 221 + } else if (p.type === "text") { 222 + const input = document.createElement("input"); 223 + input.type = "text"; 224 + input.id = `param-${p.key}`; 225 + input.maxLength = 1; 226 + input.value = String(p.default); 227 + if (p.inputStyle) Object.assign(input.style, p.inputStyle); 228 + container.appendChild(label); 229 + container.appendChild(input); 208 230 } 209 231 } 210 232 } ··· 216 238 const input = document.querySelector<HTMLInputElement>(`#param-${p.key}`); 217 239 if (p.type === "number") opts[p.key] = Number(input?.value ?? p.default); 218 240 if (p.type === "color") opts[p.key] = input?.value ?? p.default; 241 + if (p.type === "text") opts[p.key] = input?.value ?? p.default; 219 242 } 220 243 return opts; 221 244 }
+104
src/visualizers/ascii/bars.ts
··· 1 + export const category = "ascii"; 2 + 3 + export const params = [ 4 + { 5 + key: "char", 6 + type: "text", 7 + default: "#", 8 + label: "char", 9 + inputStyle: { width: "1ch" }, 10 + }, 11 + { 12 + key: "rowCount", 13 + type: "number", 14 + default: 16, 15 + min: 4, 16 + max: 64, 17 + label: "rows", 18 + }, 19 + { 20 + key: "colCount", 21 + type: "number", 22 + default: 8, 23 + min: 2, 24 + max: 64, 25 + label: "cols", 26 + }, 27 + { key: "barColor", type: "color", default: "#ffffff", label: "fg" }, 28 + { key: "bgColor", type: "color", default: "#000000", label: "bg" }, 29 + ]; 30 + 31 + export function draw( 32 + canvas: HTMLCanvasElement, 33 + freq: Uint8Array, 34 + options: Record<string, string | number>, 35 + ) { 36 + const ctx = canvas.getContext("2d"); 37 + if (!ctx) return; 38 + 39 + ctx.fillStyle = String(options.bgColor ?? "#000000"); 40 + ctx.fillRect(0, 0, canvas.width, canvas.height); 41 + 42 + const rowCount = Number(options.rowCount ?? 8); 43 + const colCount = Number(options.colCount ?? 8); 44 + const barColor = String(options.barColor ?? "#ffffff"); 45 + const ch = String(options.char ?? "#").charAt(0); 46 + const cellW = canvas.width / rowCount; 47 + const cellH = canvas.height / colCount; 48 + 49 + ctx.fillStyle = barColor; 50 + ctx.font = `${cellH}px monospace`; 51 + ctx.textAlign = "center"; 52 + ctx.textBaseline = "middle"; 53 + 54 + const textWidth = ctx.measureText(ch).width; 55 + const scale = Math.min(cellW / (textWidth || 1), 1); 56 + 57 + for (let i = 0; i < rowCount; i++) { 58 + const freqIndex = Math.floor((i / rowCount) * freq.length); 59 + const freqAmpl = freq[freqIndex] / 255; 60 + const filledCells = Math.round(freqAmpl * colCount); 61 + 62 + for (let j = 0; j < filledCells; j++) { 63 + const cx = i * cellW + cellW / 2; 64 + const cy = canvas.height - j * cellH - cellH / 2; 65 + ctx.save(); 66 + ctx.translate(cx, cy); 67 + ctx.scale(scale, scale); 68 + ctx.fillText(ch, 0, 0); 69 + ctx.restore(); 70 + } 71 + } 72 + } 73 + 74 + export function preview( 75 + canvas: HTMLCanvasElement, 76 + _freq: Uint8Array, 77 + getOptions: () => Record<string, string | number>, 78 + fps: number, 79 + ) { 80 + let phase = 0; 81 + const fixedDt = 1 / fps; 82 + let nextFrame = performance.now() + fixedDt * 1000; 83 + let raf = 0; 84 + 85 + function loop(now: number) { 86 + if (now >= nextFrame) { 87 + nextFrame += fixedDt * 1000; 88 + if (nextFrame < now) nextFrame = now + fixedDt * 1000; 89 + phase += 3 * fixedDt; 90 + 91 + const size = 1024; 92 + const freq = new Uint8Array(size); 93 + for (let i = 0; i < size; i++) { 94 + const t = (i / size) * Math.PI * 2 + phase; 95 + freq[i] = Math.floor((Math.sin(t) * 0.5 + 0.5) * 255); 96 + } 97 + draw(canvas, freq, getOptions()); 98 + } 99 + raf = requestAnimationFrame(loop); 100 + } 101 + 102 + raf = requestAnimationFrame(loop); 103 + return () => cancelAnimationFrame(raf); 104 + }