Rework website with bulma instead of bootstrap #6
|
@ -7,12 +7,10 @@ import { musicPlayer } from "./musicPlayerSlice";
|
||||||
function MusicPlayer() {
|
function MusicPlayer() {
|
||||||
return (
|
return (
|
||||||
<div className="is-flex-grow-1 is-flex is-flex-direction-column">
|
<div className="is-flex-grow-1 is-flex is-flex-direction-column">
|
||||||
<div className="is-flex-grow-1 is-overflow-hidden">
|
<Visualizer
|
||||||
<Visualizer
|
audioContext={musicPlayer.audioContext}
|
||||||
audioContext={musicPlayer.audioContext}
|
audioNode={musicPlayer.audioNode}
|
||||||
audioNode={musicPlayer.audioNode}
|
/>
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div className="is-flex-grow-0">
|
<div className="is-flex-grow-0">
|
||||||
<Controls />
|
<Controls />
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -11,11 +11,14 @@ class RendererError extends Error {}
|
||||||
|
|
||||||
class Renderer {
|
class Renderer {
|
||||||
private canvas: HTMLCanvasElement;
|
private canvas: HTMLCanvasElement;
|
||||||
|
private overlay: HTMLSpanElement;
|
||||||
|
|
||||||
private analyser: AnalyserNode;
|
private analyser: AnalyserNode;
|
||||||
private analyserData: Uint8Array;
|
private analyserData: Uint8Array;
|
||||||
|
|
||||||
private time: number;
|
private lastFrameTime: number;
|
||||||
|
private dTime: number;
|
||||||
|
|
||||||
private buffers: {
|
private buffers: {
|
||||||
indices?: WebGLBuffer;
|
indices?: WebGLBuffer;
|
||||||
positions?: WebGLBuffer;
|
positions?: WebGLBuffer;
|
||||||
|
@ -26,7 +29,8 @@ class Renderer {
|
||||||
constructor(
|
constructor(
|
||||||
context: AudioContext,
|
context: AudioContext,
|
||||||
node: AudioNode,
|
node: AudioNode,
|
||||||
canvas: HTMLCanvasElement
|
canvas: HTMLCanvasElement,
|
||||||
|
overlay: HTMLSpanElement
|
||||||
) {
|
) {
|
||||||
const analyser = context.createAnalyser();
|
const analyser = context.createAnalyser();
|
||||||
analyser.fftSize = 2048;
|
analyser.fftSize = 2048;
|
||||||
|
@ -34,10 +38,12 @@ class Renderer {
|
||||||
node.connect(analyser);
|
node.connect(analyser);
|
||||||
|
|
||||||
this.canvas = canvas;
|
this.canvas = canvas;
|
||||||
|
this.overlay = overlay;
|
||||||
this.analyser = analyser;
|
this.analyser = analyser;
|
||||||
this.analyserData = new Uint8Array(analyser.frequencyBinCount);
|
this.analyserData = new Uint8Array(analyser.frequencyBinCount);
|
||||||
|
|
||||||
this.time = 0;
|
this.lastFrameTime = 0;
|
||||||
|
this.dTime = 0;
|
||||||
this.buffers = {};
|
this.buffers = {};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -150,7 +156,7 @@ class Renderer {
|
||||||
mat4.rotateY(
|
mat4.rotateY(
|
||||||
modelViewMatrix,
|
modelViewMatrix,
|
||||||
modelViewMatrix,
|
modelViewMatrix,
|
||||||
this.time * ROTATION_SPEED
|
this.lastFrameTime * ROTATION_SPEED
|
||||||
);
|
);
|
||||||
mat4.translate(modelViewMatrix, modelViewMatrix, [-1.0, 0.0, 0.0]);
|
mat4.translate(modelViewMatrix, modelViewMatrix, [-1.0, 0.0, 0.0]);
|
||||||
gl.uniformMatrix4fv(
|
gl.uniformMatrix4fv(
|
||||||
|
@ -222,6 +228,8 @@ class Renderer {
|
||||||
gl.vertexAttribDivisor(shader.getAttribute("aHeight"), 1);
|
gl.vertexAttribDivisor(shader.getAttribute("aHeight"), 1);
|
||||||
gl.enableVertexAttribArray(shader.getAttribute("aHeight"));
|
gl.enableVertexAttribArray(shader.getAttribute("aHeight"));
|
||||||
|
|
||||||
|
const cpuTime = Math.round(performance.now() - this.lastFrameTime);
|
||||||
|
|
||||||
gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
|
gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
|
||||||
gl.drawElementsInstanced(
|
gl.drawElementsInstanced(
|
||||||
gl.TRIANGLES,
|
gl.TRIANGLES,
|
||||||
|
@ -231,8 +239,16 @@ class Renderer {
|
||||||
this.analyser.frequencyBinCount
|
this.analyser.frequencyBinCount
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const gpuTime = Math.round(performance.now() - this.lastFrameTime);
|
||||||
|
|
||||||
|
this.overlay.innerText = `${Math.round(
|
||||||
|
this.dTime
|
||||||
|
)}ms (${cpuTime}ms / ${gpuTime}ms)`;
|
||||||
|
|
||||||
requestAnimationFrame((time) => {
|
requestAnimationFrame((time) => {
|
||||||
this.time = time;
|
this.dTime = time - this.lastFrameTime;
|
||||||
|
this.lastFrameTime = time;
|
||||||
|
|
||||||
this.drawScene(gl, shader);
|
this.drawScene(gl, shader);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,19 +11,37 @@ function Visualizer({
|
||||||
}) {
|
}) {
|
||||||
const [renderError, setRenderError] = useState<JSX.Element | null>(null);
|
const [renderError, setRenderError] = useState<JSX.Element | null>(null);
|
||||||
|
|
||||||
const canvas = useCallback(
|
const visualizer = useCallback(
|
||||||
(canvas: HTMLCanvasElement | null) => {
|
(visualizer: HTMLDivElement | null) => {
|
||||||
// If we're rendering an error message, we won't be
|
// If we're rendering an error message, we won't be
|
||||||
// setting a canvas.
|
// setting up the visualizer.
|
||||||
//
|
//
|
||||||
// Also, nonintuitively, renderError will be null here on
|
// Also, nonintuitively, renderError will be null here on
|
||||||
// subsequent iterations, so we can't rely on it to
|
// subsequent iterations, so we can't rely on it to
|
||||||
// identify errors.
|
// identify errors.
|
||||||
if (canvas === null) {
|
if (visualizer === null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const renderer = new Renderer(audioContext, audioNode, canvas);
|
const canvas = visualizer.children[0];
|
||||||
|
const overlay = visualizer.children[1];
|
||||||
|
|
||||||
|
if (
|
||||||
|
!(canvas instanceof HTMLCanvasElement) ||
|
||||||
|
!(overlay instanceof HTMLSpanElement)
|
||||||
|
) {
|
||||||
|
throw new Error(
|
||||||
|
"react did not create our visualizer div correctly"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const renderer = new Renderer(
|
||||||
|
audioContext,
|
||||||
|
audioNode,
|
||||||
|
canvas,
|
||||||
|
overlay
|
||||||
|
);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
renderer.initializeScene();
|
renderer.initializeScene();
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
@ -78,7 +96,18 @@ function Visualizer({
|
||||||
);
|
);
|
||||||
|
|
||||||
if (renderError === null) {
|
if (renderError === null) {
|
||||||
return <canvas ref={canvas} style={{ display: "block" }}></canvas>;
|
return (
|
||||||
|
<div
|
||||||
|
ref={visualizer}
|
||||||
|
className="is-flex-grow-1 is-clipped is-relative"
|
||||||
|
>
|
||||||
|
<canvas style={{ display: "block" }}></canvas>
|
||||||
|
<span
|
||||||
|
className="is-bottom-left"
|
||||||
|
style={{ display: "relative" }}
|
||||||
|
></span>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
} else {
|
} else {
|
||||||
return renderError;
|
return renderError;
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,6 +3,12 @@ $fa-font-path: "npm:@fortawesome/fontawesome-free/webfonts";
|
||||||
@import "~/node_modules/@fortawesome/fontawesome-free/scss/fontawesome";
|
@import "~/node_modules/@fortawesome/fontawesome-free/scss/fontawesome";
|
||||||
@import "~/node_modules/@fortawesome/fontawesome-free/scss/solid";
|
@import "~/node_modules/@fortawesome/fontawesome-free/scss/solid";
|
||||||
|
|
||||||
.is-overflow-hidden {
|
.is-relative {
|
||||||
overflow: hidden !important;
|
position: relative !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.is-bottom-left {
|
||||||
|
bottom: 0;
|
||||||
|
left: 0;
|
||||||
|
position: absolute !important;
|
||||||
}
|
}
|
||||||
|
|
Reference in a new issue