/** * Relabi waveform display * @module src/relabi/canvas.js; */ export default class RelabiCanvas { /** * Initialize relabi wave renderer */ constructor({ relabi, parent }) { this.relabi = relabi; this.height = 400; this.lastFrame = 0; this.lastAppendTime = 0; this.lastAppendFrame = 0; // Speed of the wave in pixels per second this.speed = 1 / 5; // Attach to the DOM this.parent = parent = document.body; this.canvas = document.createElement("canvas"); this.ctx = this.canvas.getContext("2d"); this.parent.appendChild(this.canvas); // Initialize array this.values = new Array(window.innerWidth).fill(0); this.append([]); // Clear the canvas this.resize(); // Start drawing this.requestAnimationFrame(0); } /** * Draw the next frame */ requestAnimationFrame(frame) { requestAnimationFrame((frame) => { this.requestAnimationFrame(frame); }); this.paint(frame); this.lastFrame = frame; } /** * Handle window resize */ resize() { this.canvas.width = window.innerWidth; this.canvas.height = this.height; } /** * Clear the canvas */ clear() { this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height); } /** * Append values */ append(time, values) { this.lastAppendTime = time; this.lastAppendFrame = this.lastFrame; this.values = this.values.concat(values).slice(-this.canvas.width); } /** * Paint the canvas */ paint(frame) { this.clear(); this.drawRelabiWave(frame); this.drawBounds(); } /** * Draw the bounds */ drawBounds() { const { canvas, ctx } = this; const { width, height } = canvas; // Draw dashed lines for all bounds for (const bound of this.relabi.bounds) { const y = getWaveHeight(bound.level, height); ctx.beginPath(); ctx.setLineDash([10, 5]); ctx.strokeStyle = bound.color || "#888"; ctx.moveTo(0, y); ctx.lineTo(width, y); ctx.stroke(); } } /** * Draw the relabi wave */ drawRelabiWave(frame) { const { canvas, ctx } = this; const { width, height } = canvas; const pointCount = this.values.length; let index = 0; // Start the path ctx.beginPath(); ctx.strokeStyle = "white"; ctx.setLineDash([]); // This is the offset in seconds from the last frame we computed const frameOffset = (frame - this.lastAppendFrame) / 1000; // Make a path connecting all values for (const point of this.values) { if (!point) { index += 1; continue; } const [time, value] = point; // This is the offset of this time from current time // If this is in the future, it will be positive. // Subtracting the frame offset pulls it negative. const timeOffset = this.lastAppendTime + frameOffset - time; const x = width - timeOffset * this.speed * width - 10; if (x < 0) { continue; } const y = getWaveHeight(value, height); if (index === 0) { ctx.moveTo(x, y); } ctx.lineTo(x, y); index += 1; } // Paint the line ctx.stroke(); } } /** * Get the height of the wave at a given value */ const getWaveHeight = (value, height) => ((value + 1) / 2) * height;