264 lines
6.2 KiB
JavaScript
264 lines
6.2 KiB
JavaScript
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();
|
|
}
|
|
}
|
|
|