osh-js
Version:
OSH javascript Toolkit
144 lines (117 loc) • 4.36 kB
JavaScript
import {isDefined, randomUUID} from "../../../../../utils/Utils";
import AudioVisualizer from "../AudioVisualizer";
class AudioRollingSpectrogramCanvasVisualizer extends AudioVisualizer {
constructor(properties) {
super({
fftSize: 1024,
...properties,
type: 'frequency',
format: 'byte'
});
this.initFrequencySpectrogram(properties);
}
initFrequencySpectrogram(properties) {
let domNode = document.getElementById(properties.container);
const bounds = domNode.getBoundingClientRect();
let canvas = document.createElement("canvas");
canvas.setAttribute("id", randomUUID());
canvas.setAttribute("class", properties.css);
canvas.setAttribute("width", bounds.width);
canvas.setAttribute("height", bounds.height);
domNode.appendChild(canvas);
this.clearThreshold = 250;
this.width = bounds.width;
this.height = 256;
this.x = 0;
this.ctx = canvas.getContext('2d');
this.imageData = this.ctx.getImageData(0, 0, this.width, this.height);
this.buf = new ArrayBuffer(this.imageData.data.length);
this.data32 = new Uint32Array(this.buf);
this.data = this.imageData.data;
this.buf8 = new Uint8ClampedArray(this.buf);
this.animate();
}
animate() {
this.requestId = requestAnimationFrame(() => this.animate());
this.render();
}
render() {
const time = performance.now();
if(time - this.lastTime > this.clearThreshold) {
this.DATA = null;
}
if(isDefined(this.DATA)) {
this.update_geometry();
}
}
update_geometry() {
const colorData = this.colorizeData(this.DATA)
this.addColumn(colorData);
this.imageData.data.set(this.buf8);
this.ctx.putImageData(this.imageData, 0, 0);
}
draw(decodedSample) {
const data = decodedSample[this.properties.type][this.properties.format];
const length = data.length;
this.DATA = new Float32Array(length);
// Reverse the direction, making lower frequencies on the bottom.
for (var i = length - 1; i >= 0; i--) {
this.DATA[i] = (data[length - 1 - i]) / 255.0 ;
}
}
setPixel(x, y, red, green, blue, alpha) {
this.data32[y * this.width + x] =
(alpha << 24) | // alpha
(blue << 16) | // blue
(green << 8) | // green
red; // red
}
addColumn(colorizeData) {
for (let y = 0; y < this.height; y++) {
this.setPixel(this.x, y, colorizeData[4 * y + 0], colorizeData[4 * y + 1], colorizeData[4 * y + 2], colorizeData[4 * y + 3]);
}
this.x++;
this.x %= this.width;
}
color(value) {
const rgb = {R: 0, G: 0, B: 0};
if (0 <= value && value <= 1 / 8) {
rgb.R = 0;
rgb.G = 0;
rgb.B = 4 * value + .5; // .5 - 1 // b = 1/2
} else if (1 / 8 < value && value <= 3 / 8) {
rgb.R = 0;
rgb.G = 4 * value - .5; // 0 - 1 // b = - 1/2
rgb.B = 0;
} else if (3 / 8 < value && value <= 5 / 8) {
rgb.R = 4*value - 1.5; // 0 - 1 // b = - 3/2
rgb.G = 1;
rgb.B = -4 * value + 2.5; // 1 - 0 // b = 5/2
} else if (5 / 8 < value && value <= 7 / 8) {
rgb.R = 1;
rgb.G = -4 * value + 3.5; // 1 - 0 // b = 7/2
rgb.B = 0;
} else if (7 / 8 < value && value <= 1) {
rgb.R = -4*value + 4.5; // 1 - .5 // b = 9/2
rgb.G = 0;
rgb.B = 0;
} else { // should never happen - value > 1
rgb.R = .5;
rgb.G = 0;
rgb.B = 0;
}
return [rgb.R, rgb.G, rgb.B, 1].map((d) => { return parseInt(d * 255, 10)})
}
colorizeData(data) {
const colorData = new Uint8Array( this.properties.fftSize * 2);
for(let i = 0, n = data.length; i < n; i++) {
colorData.set(this.color(data[i]), i * 4);
}
return colorData;
}
onended() {
this.lastTime = performance.now();
}
reset() {}
}
export default AudioRollingSpectrogramCanvasVisualizer;