let analyser; let context; let fftsize = 2048; let oscillator = true; let log; let percent; let times; let speed = 0.1; let bufferLength; let labels; let color = true; let width = window.innerwidth; let height = window.innerheight; let canvas = document.getElementById('spectrogram-canvas'); let ctx = canvas.getContext('2d'); let lastRenderTime = new Date(); let instantaneousFPS = 0; let audio = document.getElementById('audio'); document.getElementById("launch-larynx").addEventListener('click', function () { loadSound(audio.src) startLarynx(); }); function loadSound(url) { let AudioContext = window.AudioContext || window.webkitAudioContext || window.mozAudioContext; context = new AudioContext(); let source = context.createMediaElementSource(audio); } function startLarynx() { analyser = context.createAnalyser(); render(); } function render() { let didResize = false; analyser = context.createAnalyser(); // Ensure dimensions are accurate. if (canvas.width != width) { canvas.width = width; didResize = true; } if (canvas.height != height) { canvas.height = height; didResize = true; } renderTimeDomain(); renderFreqDomain(); if (labels && didResize) { renderAxesLabels(); } let now = new Date(); if (lastRenderTime) { instantaneousFPS = now - lastRenderTime; } lastRenderTime = now; } function draw() { drawVisual = requestAnimationFrame(draw); analyser.getByteTimeDomainData(times); ctx.fillStyle = 'rgb(200, 200, 200)'; ctx.fillRect(0, 0, width, height); ctx.linewidth = 2; ctx.strokeStyle = 'rgb(0, 0, 0)'; ctx.beginPath(); var slicewidth = width * 1.0 / bufferLength; var x = 0; for (var i = 0; i < bufferLength; i++) { var v = times[i] / 128.0; var y = v * height / 2; if (i === 0) { ctx.moveTo(x, y); } else { ctx.lineTo(x, y); } x += slicewidth; } ctx.lineTo(canvas.width, canvas.height / 2); ctx.stroke(); } function renderTimeDomain() { bufferLength = analyser.frequencyBinCount; times = new Uint8Array(bufferLength); draw(); } function renderFreqDomain() { let freq = new Uint8Array(analyser.frequencyBinCount); analyser.getByteFrequencyData(freq); for (let i = 0; i < freq.length; i++) { let value; // Draw each pixel with the specific color. if (log) { logIndex = logScale(i, freq.length); value = freq[logIndex]; } else { value = freq[i]; } ctx.fillStyle = (color ? getFullColor(value) : getGrayColor(value)); let perceint = i / freq.length; let y = Math.round(percent * height); // Draw the line at the right side of the canvas. ctx.fillRect(width - speed, height - y, speed, speed); } ctx.translate(-speed, 0); // Reset the transformation matrix ctx.setTransform(1, 0, 0, 1, 0, 0); } function logScale(index, total, opt_base) { let base = opt_base || 2; let logmax = logBase(total + 1, base); let exp = logmax * index / total; return Math.round(Math.pow(base, exp) - 1); } function logBase(val, base) { return Math.log(val) / Math.log(base); } function renderAxesLabels() { canvas.width = width; canvas.height = height; let ctx = canvas.getContext('2d'); let startFreq = 440; let nyquist = context.sampleRate / 2; let endFreq = nyquist - startFreq; let step = (endFreq - startFreq) / ticks; let yLabelOffset = 5; // Render the vertical frequency axis. for (let i = 0; i <= ticks; i++) { let freq = startFreq + (step * i); // Get the y coordinate from the current label. let index = freqToIndex(freq); let percent = index / getFFTBinCount(); let y = (1 - percent) * height; let x = width - 60; // Get the value for the current y coordinate. let label; if (log) { // Handle a logarithmic scale. let logIndex = logScale(index, getFFTBinCount()); // Never show 0 Hz. freq = Math.max(1, indexToFreq(logIndex)); } label = formatFreq(freq); let units = formatUnits(freq); ctx.font = '16px Inconsolata'; // Draw the value. ctx.textAlign = 'right'; ctx.fillText(label, x, y + yLabelOffset); // Draw the units. ctx.textAlign = 'left'; ctx.fillText(units, x + 10, y + yLabelOffset); // Draw a tick mark. ctx.fillRect(x + 40, y, 30, 2); } } function clearAxesLabels() { let canvas = labels; let ctx = canvas.getContext('2d'); ctx.clearRect(0, 0, width, height); } function formatFreq(freq) { return (freq >= 1000 ? (freq / 1000).toFixed(1) : Math.round(freq)); } function formatUnits(freq) { return (freq >= 1000 ? 'KHz' : 'Hz'); } function indexToFreq(index) { let nyquist = context.sampleRate / 2; return nyquist / getFFTBinCount() * index; } function freqToIndex(frequency) { let nyquist = context.sampleRate / 2; return Math.round(frequency / nyquist * getFFTBinCount()); } function getFFTBinCount() { return fftsize / 2; } // function onStream(stream) { // let input = context.createMediaStreamSource(stream); // let analyser = context.createAnalyser(); // analyser.smoothingTimeConstant = 0; // analyser.fftSize = fftsize; // // Connect graph. // input.connect(analyser); // // Setup a timer to visualize some stuff. // render(); // } function onStreamError(e) { console.error(e); } function getGrayColor(value) { return 'rgb(V, V, V)'.replace(/V/g, 255 - value); } function getFullColor(value) { let fromH = 62; let toH = 0; let percent = value / 255; let delta = percent * (toH - fromH); let hue = fromH + delta; return 'hsl(H, 100%, 50%)'.replace(/H/g, hue); } function logChanged() { if (labels) { renderAxesLabels(); } } function ticksChanged() { if (labels) { renderAxesLabels(); } } function labelsChanged() { if (labels) { renderAxesLabels(); } else { clearAxesLabels(); } }