|
import { AudioPipe } from "./audio.js"; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
export class AudioPipeVisualizer extends AudioPipe { |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
constructor(options = {}) { |
|
super(options); |
|
this.canvas = options.canvas || null; |
|
this.lineWidth = options.lineWidth || 1; |
|
this.fftSize = options.fftSize || 2048; |
|
this.fillStyle = options.fillStyle || "#FFFFFF"; |
|
this.strokeStyle = options.strokeStyle || "#000000"; |
|
this.waveformNoiseLevel = options.waveformNoiseLevel || 0; |
|
this.fftBuffer = new Uint8Array(this.fftSize); |
|
this.startDrawing(); |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
waveformStyles() { |
|
let strokeStyle = this.strokeStyle; |
|
let lineWidth = this.lineWidth; |
|
if (!Array.isArray(strokeStyle)) { |
|
strokeStyle = [strokeStyle]; |
|
} |
|
if (!Array.isArray(lineWidth)) { |
|
lineWidth = [lineWidth]; |
|
} |
|
let numStrokes = Math.max(strokeStyle.length, lineWidth.length); |
|
let styles = []; |
|
for (let i = 0; i < numStrokes; i++) { |
|
styles.push({ |
|
strokeStyle: strokeStyle[Math.min(i, strokeStyle.length - 1)], |
|
lineWidth: lineWidth[Math.min(i, lineWidth.length - 1)] |
|
}); |
|
} |
|
return styles; |
|
} |
|
|
|
push(data, sampleRate = 44100) { |
|
let audioBuffer = super.push(data, sampleRate); |
|
audioBuffer.connect(this.analyser); |
|
return audioBuffer; |
|
} |
|
|
|
|
|
|
|
|
|
initialize() { |
|
super.initialize(); |
|
|
|
this.analyser = this.audioContext.createAnalyser(); |
|
this.analyser.fftSize = this.fftSize; |
|
this.analyser.connect(this.audioContext.destination); |
|
} |
|
|
|
|
|
|
|
|
|
|
|
startDrawing() { |
|
const context = this.canvas.getContext("2d"); |
|
const sliceWidth = this.canvas.width / this.fftSize; |
|
context.fillStyle = this.fillStyle; |
|
const draw = () => { |
|
|
|
this.analyser !== undefined && this.analyser.getByteTimeDomainData(this.fftBuffer); |
|
|
|
context.fillRect(0, 0, this.canvas.width, this.canvas.height); |
|
|
|
let x = 0; |
|
context.beginPath(); |
|
for (let i = 0; i < this.fftSize; i++) { |
|
let v = this.initialized ? this.fftBuffer[i] / 128.0 : 1; |
|
|
|
let distance = Math.abs(v - 1.0); |
|
v = 1.0 + (distance * this.volume * Math.sign(v - 1.0)); |
|
if (this.waveformNoiseLevel > 0) { |
|
v += (Math.random() - 0.5) * this.waveformNoiseLevel * 2 * Math.sin(i / this.fftSize * Math.PI) * this.volume; |
|
} |
|
const y = v * this.canvas.height / 2; |
|
if (i === 0) { |
|
context.moveTo(x, y); |
|
} else { |
|
context.lineTo(x, y); |
|
} |
|
|
|
x += sliceWidth; |
|
} |
|
|
|
context.lineTo(this.canvas.width, this.canvas.height / 2); |
|
|
|
for (let style of this.waveformStyles()) { |
|
context.strokeStyle = style.strokeStyle; |
|
context.lineWidth = style.lineWidth; |
|
context.stroke(); |
|
} |
|
|
|
requestAnimationFrame(draw); |
|
}; |
|
|
|
requestAnimationFrame(draw); |
|
} |
|
}; |
|
|