UNPKG

wave-visualizer

Version:

Audio visualizer library for javascript

365 lines (285 loc) 9.86 kB
'use strict'; function fromElement(element_id, canvas_id, options) { const globalAccessKey = [options.globalAccessKey || '$wave']; const initGlobalObject = (elementId) => { window[globalAccessKey] = window[globalAccessKey] || {}; window[globalAccessKey][elementId] = window[globalAccessKey][elementId] || {}; }; const getGlobal = options['getGlobal'] || function (elementId, accessKey) { initGlobalObject(elementId); return window[globalAccessKey][elementId][accessKey]; }; const setGlobal = options['setGlobal'] || function (elementId, accessKey, value) { let returnValue = getGlobal(elementId); if (!returnValue) { window[globalAccessKey][elementId][accessKey] = window[globalAccessKey][elementId][accessKey] || value; returnValue = window[globalAccessKey][elementId][accessKey]; } return returnValue; }; const waveContext = this; let element = document.getElementById(element_id); if (!element) return; element.crossOrigin = 'anonymous'; function run() { //user gesture has happened this.activated = true; //track current wave for canvas this.activeCanvas = this.activeCanvas || {}; this.activeCanvas[canvas_id] = JSON.stringify(options); //track elements used so multiple elements use the same data this.activeElements[element_id] = this.activeElements[element_id] || {}; if (this.activeElements[element_id].count) this.activeElements[element_id].count += 1; else this.activeElements[element_id].count = 1; const currentCount = this.activeElements[element_id].count; const audioCtx = setGlobal(element.id, 'audioCtx', new AudioContext()); const analyser = setGlobal( element.id, 'analyser', audioCtx.createAnalyser() ); let source = getGlobal(element.id, 'source'); if (source) { if (source.mediaElement !== element) { source = audioCtx.createMediaElementSource(element); } } else { source = audioCtx.createMediaElementSource(element); } setGlobal(element.id, 'source', source); //beep test for ios const oscillator = audioCtx.createOscillator(); oscillator.frequency.value = 1; oscillator.connect(audioCtx.destination); oscillator.start(0); oscillator.stop(0); source.connect(analyser); source.connect(audioCtx.destination); analyser.fftsize = 32768; const bufferLength = analyser.frequencyBinCount; const data = new Uint8Array(bufferLength); let frameCount = 1; function renderFrame() { //only run one wave visual per canvas if (JSON.stringify(options) !== this.activeCanvas[canvas_id]) { return; } //if the element or canvas go out of scope, stop animation if ( !document.getElementById(element_id) || !document.getElementById(canvas_id) ) return; requestAnimationFrame(renderFrame); frameCount++; //check if this element is the last to be called if (!(currentCount < this.activeElements[element_id].count)) { analyser.getByteFrequencyData(data); this.activeElements[element_id].data = data; } this.visualize( this.activeElements[element_id].data, canvas_id, options, frameCount ); } renderFrame = renderFrame.bind(this); renderFrame(); } const create = () => { //remove all events ['touchstart', 'touchmove', 'touchend', 'mouseup', 'click', 'play'].forEach( (event) => { element.removeEventListener(event, create, { once: true }); } ); run.call(waveContext); }; if (this.activated || options['skipUserEventsWatcher']) { run.call(waveContext); } else { //wait for a valid user gesture document.body.addEventListener('touchstart', create, { once: true }); document.body.addEventListener('touchmove', create, { once: true }); document.body.addEventListener('touchend', create, { once: true }); document.body.addEventListener('mouseup', create, { once: true }); document.body.addEventListener('click', create, { once: true }); element.addEventListener('play', create, { once: true }); } } function fromFile(file, options = {}) { //options if (!options.stroke) options.stroke = 10; let audio = new Audio(); audio.src = file; let audioCtx = new AudioContext(); let analyser = audioCtx.createAnalyser(); let source = audioCtx.createMediaElementSource(audio); source.connect(analyser); analyser.fftSize = 64; let bufferLength = analyser.frequencyBinCount; let file_data; let temp_data = new Uint8Array(bufferLength); let getWave; let fdi = 0; let self = this; audio.addEventListener('loadedmetadata', async function () { while (audio.duration === Infinity) { await new Promise(r => setTimeout(r, 1000)); audio.currentTime = 10000000 * Math.random(); } audio.currentTime = 0; audio.play(); }); audio.onplay = function () { let findSize = (size) => { for (let range = 1; range <= 40; range++) { let power = 2 ** range; if (size <= power) return power; } }; let d = audio.duration; audio.playbackRate = 16; d = d / audio.playbackRate; let drawRate = 20; //ms let size = ((d / (drawRate / 1000)) * (analyser.fftSize / 2)); size = findSize(size); file_data = new Uint8Array(size); getWave = setInterval(function () { analyser.getByteFrequencyData(temp_data); for (let data in temp_data) { data = temp_data[data]; file_data[fdi] = data; fdi++; } }, drawRate); }; audio.onended = function () { if (audio.currentTime === audio.duration && file_data !== undefined) { clearInterval(getWave); let canvas = document.createElement("canvas"); canvas.height = window.innerHeight; canvas.width = window.innerWidth; self.visualize(file_data, canvas, options); let image = canvas.toDataURL("image/jpg"); self.onFileLoad(image); canvas.remove(); } }; } function fromStream(stream, canvas_id, options = {}) { this.current_stream.id = canvas_id; this.current_stream.options = options; let audioCtx, analyser, source; if (!this.sources[stream.toString()]) { audioCtx = options.context || new AudioContext(); analyser = audioCtx.createAnalyser(); source = audioCtx.createMediaStreamSource(stream); source.connect(analyser); this.sources[stream.toString()] = { "audioCtx": audioCtx, "analyser": analyser, "source": source }; } else { cancelAnimationFrame(this.sources[stream.toString()].animation); audioCtx = this.sources[stream.toString()].audioCtx; analyser = this.sources[stream.toString()].analyser; source = this.sources[stream.toString()].source; } analyser.fftsize = 32768; let bufferLength = analyser.frequencyBinCount; this.current_stream.data = new Uint8Array(bufferLength); let self = this; function renderFrame() { self.current_stream.animation = requestAnimationFrame(self.current_stream.loop); self.sources[stream.toString()].animation = self.current_stream.animation; analyser.getByteFrequencyData(self.current_stream.data); self.visualize(self.current_stream.data, self.current_stream.id, self.current_stream.options); } this.current_stream.loop = renderFrame; renderFrame(); } function stopStream() { cancelAnimationFrame(this.current_stream.animation); } function playStream() { this.current_stream.loop(); } var fromStream$1 = { fromStream, stopStream, playStream }; var drawDualbarsBlocks = (functionContext) => { let { data, options, ctx, h, w } = functionContext; let percent = h / 255; let width = w / 50; let skip = true; for (let point = 0; point <= 50; point++) { let p = data[point]; //get value p *= percent; let x = width * point; if (skip) { ctx.rect(x, h / 2 + p / 2, width, -p); skip = false; } else { skip = true; } } if (options.colors[1]) { ctx.fillStyle = options.colors[1]; ctx.fill(); } ctx.stroke(); }; //options:type,colors,stroke function visualize(data, canvasId, options = {}, frame) { //make a clone of options options = { ...options }; //options if (!options.stroke) options.stroke = 1; if (!options.colors) options.colors = ['#ff9234', '#ff9234']; let canvas = document.getElementById(canvasId); if (!canvas) return; let ctx = canvas.getContext('2d'); let h = canvas.height; let w = canvas.width; ctx.strokeStyle = options.colors[0]; ctx.lineWidth = options.stroke; const functionContext = { data, options, ctx, h, w, Helper: this.Helper, canvasId, }; ctx.clearRect(0, 0, w, h); ctx.beginPath(); drawDualbarsBlocks(functionContext); } function Wave() { this.current_stream = {}; this.sources = {}; this.onFileLoad = null; this.activeElements = {}; this.activated = false; window.AudioContext = window.AudioContext || window.webkitAudioContext; } Wave.prototype = { fromElement, fromFile, ...fromStream$1, visualize, }; module.exports = Wave;