WIP: Re-implement music visualizer
This commit is contained in:
parent
3788e377d9
commit
ced10cc09f
15 changed files with 615 additions and 4 deletions
197
src/music/features/visualizer/Renderer.ts
Normal file
197
src/music/features/visualizer/Renderer.ts
Normal file
|
|
@ -0,0 +1,197 @@
|
|||
import { Shader } from "./Shader";
|
||||
import { mat4 } from "gl-matrix";
|
||||
|
||||
import { Cube } from "./cube";
|
||||
import vertexSource from "./vertices.glsl";
|
||||
import fragmentSource from "./fragments.glsl";
|
||||
|
||||
class RendererError extends Error {}
|
||||
|
||||
class Renderer {
|
||||
private canvas: HTMLCanvasElement;
|
||||
|
||||
private analyser: AnalyserNode;
|
||||
private analyserData: Float32Array;
|
||||
|
||||
private time: number;
|
||||
private buffers: {
|
||||
indices?: WebGLBuffer;
|
||||
positions?: WebGLBuffer;
|
||||
normals?: WebGLBuffer;
|
||||
};
|
||||
|
||||
constructor(
|
||||
context: AudioContext,
|
||||
node: AudioNode,
|
||||
canvas: HTMLCanvasElement
|
||||
) {
|
||||
const analyser = context.createAnalyser();
|
||||
analyser.fftSize = 2048;
|
||||
analyser.smoothingTimeConstant = 0.8;
|
||||
node.connect(analyser);
|
||||
|
||||
this.canvas = canvas;
|
||||
this.analyser = analyser;
|
||||
this.analyserData = new Float32Array(analyser.frequencyBinCount);
|
||||
|
||||
this.time = 0;
|
||||
this.buffers = { indices: undefined, positions: undefined };
|
||||
}
|
||||
|
||||
resize() {
|
||||
this.canvas.width = this.canvas.parentElement!.clientWidth;
|
||||
this.canvas.height = this.canvas.parentElement!.clientHeight;
|
||||
}
|
||||
|
||||
initializeScene() {
|
||||
this.resize();
|
||||
|
||||
const gl = this.canvas.getContext("webgl2");
|
||||
if (gl === null) {
|
||||
throw new RendererError("WebGL (2) is unsupported on this browser");
|
||||
}
|
||||
|
||||
const shader = Shader.builder(gl)
|
||||
.addShader(vertexSource, gl.VERTEX_SHADER)
|
||||
.addShader(fragmentSource, gl.FRAGMENT_SHADER)
|
||||
.addAttribute("aVertexPosition")
|
||||
.addAttribute("aVertexNormal")
|
||||
.addUniforms("uProjectionMatrix")
|
||||
.addUniforms("uModelViewMatrix")
|
||||
.addUniforms("uNormalMatrix")
|
||||
.build();
|
||||
|
||||
this.initGL(gl, shader);
|
||||
this.initBuffers(gl);
|
||||
this.drawScene(gl, shader);
|
||||
}
|
||||
|
||||
initBuffers(gl: WebGLRenderingContext) {
|
||||
// Position buffer
|
||||
const positionBuffer = gl.createBuffer();
|
||||
|
||||
if (positionBuffer === null) {
|
||||
throw new Error("could not initialize position buffer");
|
||||
}
|
||||
|
||||
gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer);
|
||||
gl.bufferData(gl.ARRAY_BUFFER, Cube.vertices, gl.STATIC_DRAW);
|
||||
this.buffers.positions = positionBuffer;
|
||||
|
||||
// Index buffer
|
||||
const indexBuffer = gl.createBuffer();
|
||||
|
||||
if (indexBuffer === null) {
|
||||
throw new Error("could not initialize index buffer");
|
||||
}
|
||||
|
||||
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, indexBuffer);
|
||||
gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, Cube.indices, gl.STATIC_DRAW);
|
||||
this.buffers.indices = indexBuffer;
|
||||
|
||||
// Surface normal buffer
|
||||
const normalBuffer = gl.createBuffer();
|
||||
|
||||
if (normalBuffer === null) {
|
||||
throw new Error("could not initialize normal buffer");
|
||||
}
|
||||
|
||||
gl.bindBuffer(gl.ARRAY_BUFFER, normalBuffer);
|
||||
gl.bufferData(gl.ARRAY_BUFFER, Cube.normals, gl.STATIC_DRAW);
|
||||
this.buffers.normals = normalBuffer;
|
||||
}
|
||||
|
||||
initGL(gl: WebGLRenderingContext, shader: Shader) {
|
||||
gl.useProgram(shader.program);
|
||||
gl.clearColor(0.0, 0.0, 0.0, 1.0);
|
||||
gl.clearDepth(1.0);
|
||||
gl.enable(gl.DEPTH_TEST);
|
||||
gl.depthFunc(gl.LEQUAL);
|
||||
}
|
||||
|
||||
updateMatrices(gl: WebGLRenderingContext, shader: Shader) {
|
||||
const projectionMatrix = mat4.create();
|
||||
mat4.perspective(
|
||||
projectionMatrix,
|
||||
(45 * Math.PI) / 180,
|
||||
gl.canvas.clientWidth / gl.canvas.clientHeight,
|
||||
0.1,
|
||||
100.0
|
||||
);
|
||||
gl.uniformMatrix4fv(
|
||||
shader.getUniform("uProjectionMatrix"),
|
||||
false,
|
||||
projectionMatrix
|
||||
);
|
||||
|
||||
const modelViewMatrix = mat4.create();
|
||||
mat4.translate(modelViewMatrix, modelViewMatrix, [0.0, 1.0, -8.0]);
|
||||
mat4.rotateY(modelViewMatrix, modelViewMatrix, this.time / 1000);
|
||||
mat4.rotateX(
|
||||
modelViewMatrix,
|
||||
modelViewMatrix,
|
||||
(this.time / 1000) * 0.7
|
||||
);
|
||||
gl.uniformMatrix4fv(
|
||||
shader.getUniform("uModelViewMatrix"),
|
||||
false,
|
||||
modelViewMatrix
|
||||
);
|
||||
|
||||
const normalMatrix = mat4.create();
|
||||
mat4.invert(normalMatrix, modelViewMatrix);
|
||||
mat4.transpose(normalMatrix, normalMatrix);
|
||||
gl.uniformMatrix4fv(
|
||||
shader.getUniform("uNormalMatrix"),
|
||||
false,
|
||||
normalMatrix
|
||||
);
|
||||
}
|
||||
|
||||
drawScene(gl: WebGL2RenderingContext, shader: Shader) {
|
||||
if (
|
||||
this.buffers.indices === undefined ||
|
||||
this.buffers.positions === undefined ||
|
||||
this.buffers.normals === undefined
|
||||
) {
|
||||
throw new Error("failed to create buffers before rendering");
|
||||
}
|
||||
|
||||
this.updateMatrices(gl, shader);
|
||||
|
||||
// Update cube buffers
|
||||
gl.bindBuffer(gl.ARRAY_BUFFER, this.buffers.positions);
|
||||
gl.vertexAttribPointer(
|
||||
shader.getAttribute("aVertexPosition"),
|
||||
3,
|
||||
gl.FLOAT,
|
||||
false,
|
||||
0,
|
||||
0
|
||||
);
|
||||
gl.enableVertexAttribArray(shader.getAttribute("aVertexPosition"));
|
||||
|
||||
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, this.buffers.indices);
|
||||
|
||||
gl.bindBuffer(gl.ARRAY_BUFFER, this.buffers.normals);
|
||||
gl.vertexAttribPointer(
|
||||
shader.getAttribute("aVertexNormal"),
|
||||
3,
|
||||
gl.FLOAT,
|
||||
false,
|
||||
0,
|
||||
0
|
||||
);
|
||||
gl.enableVertexAttribArray(shader.getAttribute("aVertexNormal"));
|
||||
|
||||
gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
|
||||
gl.drawElementsInstanced(gl.TRIANGLES, 36, gl.UNSIGNED_SHORT, 0, 2);
|
||||
|
||||
requestAnimationFrame((time) => {
|
||||
this.time = time;
|
||||
this.drawScene(gl, shader);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export { Renderer, RendererError };
|
||||
Reference in a new issue