UNPKG

@uppy/audio

Version:

Uppy plugin that records audio using the device’s microphone.

135 lines (106 loc) 3.55 kB
// biome-ignore lint/complexity/noBannedTypes: ... function isFunction(v: any): v is Function { return typeof v === 'function' } function result<T>(v: T | (() => T)): T { return isFunction(v) ? v() : v } type MaybeFunction<T> = T | (() => T) interface AudioOscilloscopeOptions { canvas?: { width?: number height?: number } canvasContext?: { width?: MaybeFunction<number> height?: MaybeFunction<number> lineWidth?: MaybeFunction<number> fillStyle?: MaybeFunction<string> strokeStyle?: MaybeFunction<string> } onDrawFrame?: (oscilloscope: AudioOscilloscope) => void } /* Audio Oscilloscope https://github.com/miguelmota/audio-oscilloscope */ export default class AudioOscilloscope { private canvas: HTMLCanvasElement private canvasContext: CanvasRenderingContext2D private width: number private height: number private analyser: null | AnalyserNode private bufferLength: number private dataArray?: Uint8Array private onDrawFrame: (oscilloscope: AudioOscilloscope) => void private streamSource?: MediaStreamAudioSourceNode private audioContext?: BaseAudioContext public source?: AudioBufferSourceNode constructor( canvas: HTMLCanvasElement, options: AudioOscilloscopeOptions = {}, ) { const canvasOptions = options.canvas || ({} as NonNullable<AudioOscilloscopeOptions['canvas']>) const canvasContextOptions = options.canvasContext || ({} as NonNullable<AudioOscilloscopeOptions['canvasContext']>) this.analyser = null this.bufferLength = 0 this.canvas = canvas this.width = result(canvasOptions.width) || this.canvas.width this.height = result(canvasOptions.height) || this.canvas.height this.canvas.width = this.width this.canvas.height = this.height this.canvasContext = this.canvas.getContext('2d')! this.canvasContext.fillStyle = result(canvasContextOptions.fillStyle) || 'rgb(255, 255, 255)' this.canvasContext.strokeStyle = result(canvasContextOptions.strokeStyle) || 'rgb(0, 0, 0)' this.canvasContext.lineWidth = result(canvasContextOptions.lineWidth) || 1 this.onDrawFrame = isFunction(options.onDrawFrame) ? options.onDrawFrame : () => {} } addSource(streamSource: MediaStreamAudioSourceNode): void { this.streamSource = streamSource this.audioContext = this.streamSource.context this.analyser = this.audioContext.createAnalyser() this.analyser.fftSize = 2048 this.bufferLength = this.analyser.frequencyBinCount this.source = this.audioContext.createBufferSource() this.dataArray = new Uint8Array(this.bufferLength) this.analyser.getByteTimeDomainData(this.dataArray) this.streamSource.connect(this.analyser) } draw(): void { const { analyser, dataArray, bufferLength } = this const ctx = this.canvasContext const w = this.width const h = this.height if (analyser) { analyser.getByteTimeDomainData(dataArray!) } ctx.fillRect(0, 0, w, h) ctx.beginPath() const sliceWidth = (w * 1.0) / bufferLength let x = 0 if (!bufferLength) { ctx.moveTo(0, this.height / 2) } for (let i = 0; i < bufferLength; i++) { const v = dataArray![i] / 128.0 const y = v * (h / 2) if (i === 0) { ctx.moveTo(x, y) } else { ctx.lineTo(x, y) } x += sliceWidth } ctx.lineTo(w, h / 2) ctx.stroke() this.onDrawFrame(this) requestAnimationFrame(this.#draw) } #draw = () => this.draw() }