UNPKG

apphouse

Version:

Component library for React that uses observable state management and theme-able components.

281 lines (247 loc) 7.79 kB
import { action, computed, makeObservable, observable } from "mobx"; import { audioLog } from "./AudioLog"; const BRIGHT_RED = "#ff0000"; const LIME_GREEN = "#45fc1e"; const DEFAULT_WAVE_COLOR = BRIGHT_RED; const FFTSIZE = 2048; export default class Visualizer { barWidth: number; canvas?: HTMLCanvasElement; height: number; on: boolean; width: number; constructor(canvas?: HTMLCanvasElement) { this.canvas = canvas; this.width = canvas !== null ? (canvas ? canvas.width : 0) : 0; this.height = canvas !== null ? (canvas ? canvas.height : 0) : 0; this.barWidth = 0; this.on = false; makeObservable(this, { barWidth: observable, currentBarWidth: computed, height: observable, on: observable, turnOff: action, visualizeFreqBar: action, visualizeOsciloscope: action, visualizeAudioMeter: action, width: observable, turnOn: action, }); } get currentBarWidth() { return this.barWidth; } get visualizer() { return this.canvas; } setCanvas = (canvas: HTMLCanvasElement) => { this.canvas = canvas; this.width = canvas.width; this.height = canvas.height; }; visualizeFreqBar = ( analyzerNode: AnalyserNode, fftsize?: number, color?: string, ) => { this.turnOn(); const barColor = color || LIME_GREEN; analyzerNode.fftSize = fftsize || FFTSIZE; const draw = () => { if (this.on === true && this.canvas) { requestAnimationFrame(draw); const bufferLength = analyzerNode.frequencyBinCount; const dataArray = new Uint8Array(bufferLength); analyzerNode.getByteFrequencyData(dataArray); this.barWidth = this.width / bufferLength; // console.log("canvas", this.barWidth); const canvasContext = this.canvas.getContext("2d"); if (canvasContext) { canvasContext.clearRect(0, 0, this.width, this.height); dataArray.forEach((item, index) => { const y = ((item / 255) * this.height) / 2; const x = this.barWidth * index; canvasContext.fillStyle = barColor; canvasContext.fillRect( x, this.height - y, this.barWidth, y, ); }); } else { console.warn( "No canvas context available to draw visualizer", ); } } else { console.warn( "No canvas element available to draw visualizer", ); } }; draw(); }; visualizeAudioMeter = ( analyzerNode: AnalyserNode, color?: string, ) => { this.turnOn(); analyzerNode.fftSize = FFTSIZE; let meterColor = color || LIME_GREEN; const maxStep = this.width; if (this.canvas) { const ctx = this.canvas.getContext("2d"); if (ctx !== null) { const draw = () => { if (this.on === true) { requestAnimationFrame(draw); ctx.clearRect(0, 0, this.width, this.height); const bufferLength = analyzerNode.frequencyBinCount; const data = new Uint8Array(bufferLength); analyzerNode.getByteFrequencyData(data); const step = Math.ceil(data.length / this.width); const amp = this.height / 2; for (let i = 0; i < this.width; i++) { let neg = 0; let pos = 0; let max = Math.min(step, maxStep); for (let j = 0; j < max; j++) { const val = data[i * step + j]; if (val < 0) { neg += val; } else { pos += val; } } neg = neg / max; pos = pos / max; const displacementX = i; const height = amp * (pos - neg); const amplitude = amp - pos * amp; const barWidth = 1; if (displacementX < 150) { ctx.fillStyle = "green"; } else { ctx.fillStyle = meterColor; } ctx.fillRect( displacementX, amplitude, barWidth, height, ); } } else { console.warn( "No canvas context available to draw visualizer", ); } }; draw(); } } }; visualizeWinamp = (analyser: AnalyserNode) => { this.turnOn(); analyser.minDecibels = -90; analyser.maxDecibels = -10; analyser.fftSize = 256; var bufferLength = analyser.frequencyBinCount; var dataArray = new Uint8Array(bufferLength); if (this.canvas) { const ctx = this.canvas.getContext("2d"); if (ctx) { ctx.clearRect(0, 0, this.width, this.height); const draw = () => { requestAnimationFrame(draw); if (this.on) { analyser.getByteFrequencyData(dataArray); ctx.fillStyle = "rgb(0, 0, 0)"; ctx.fillRect(0, 0, this.width, this.height); var barWidth = (this.width / bufferLength) * 2.5 - 1; var barHeight; var x = 0; for (var i = 0; i < bufferLength; i++) { barHeight = dataArray[i]; ctx.fillStyle = "rgb(" + (barHeight + 100) + ",50,50)"; ctx.fillRect( x, this.height - barHeight / 2, barWidth, barHeight / 2, ); x += barWidth; } } }; draw(); } } }; visualizeOsciloscope = ( analyzerNode: AnalyserNode, color?: string, ) => { this.turnOn(); let waveColor: string = color || DEFAULT_WAVE_COLOR; analyzerNode.fftSize = FFTSIZE; var bufferLength = analyzerNode.frequencyBinCount; var dataArray = new Uint8Array(bufferLength); analyzerNode.getByteTimeDomainData(dataArray); // We now have the audio data for that moment in time captured in our array, // and can proceed to visualize it however we like, if (this.canvas) { const ctx = this.canvas.getContext("2d"); if (ctx !== null) { ctx.clearRect(0, 0, this.width, this.height); const draw = () => { if (this.on === true) { requestAnimationFrame(draw); analyzerNode.getByteTimeDomainData(dataArray); ctx.fillStyle = "rgb(1, 1, 1)"; ctx.fillRect(0, 0, this.width, this.height); ctx.lineWidth = 2; ctx.strokeStyle = `rgb(${hexToRGB(waveColor)})`; ctx.beginPath(); let sliceWidth = (this.width * 1.0) / bufferLength; let x = 0; for (let i = 0; i < bufferLength; i++) { let v = dataArray[i] / 128.0; let y = (v * this.height) / 2; if (i === 0) { ctx.moveTo(x, y); } else { ctx.lineTo(x, y); } x += sliceWidth; } ctx.lineTo(this.width, this.height / 2); ctx.stroke(); } }; draw(); } } }; turnOn = () => { this.on = true; audioLog.logVizualizer("On"); }; turnOff = () => { this.on = false; audioLog.logVizualizer("Off"); }; } export const hexToRGB = (hex: string): string => { if (typeof hex !== "string") { throw new TypeError("Expected a string"); } let hexColor = hex.replace(/^#/, ""); if (hexColor.length === 3) { hexColor = hex[0] + hex[0] + hex[1] + hex[1] + hex[2] + hex[2]; } const num = parseInt(hexColor, 16); return `${num >> 16}, ${(num >> 8) & 255}, ${num & 255}`; };