Spaces:
Sleeping
Sleeping
| /** | |
| * WaveformVisualizer - handles canvas waveform animation for user and agent audio | |
| */ | |
| export class WaveformVisualizer { | |
| constructor(userCanvas, agentCanvas) { | |
| this.userCanvas = userCanvas; | |
| this.agentCanvas = agentCanvas; | |
| this.userCtx = userCanvas.getContext('2d'); | |
| this.agentCtx = agentCanvas.getContext('2d'); | |
| this.animationId = null; | |
| this.isUserSpeaking = false; | |
| this.isAgentSpeaking = false; | |
| this._initCanvases(); | |
| } | |
| _initCanvases() { | |
| this.userCanvas.width = 350; | |
| this.userCanvas.height = 60; | |
| this.agentCanvas.width = 350; | |
| this.agentCanvas.height = 60; | |
| this._clear(this.userCtx, this.userCanvas); | |
| this._clear(this.agentCtx, this.agentCanvas); | |
| } | |
| _clear(ctx, canvas) { | |
| ctx.fillStyle = '#000'; | |
| ctx.fillRect(0, 0, canvas.width, canvas.height); | |
| } | |
| _draw(ctx, canvas, isActive, color, waveformData) { | |
| this._clear(ctx, canvas); | |
| if (!isActive) return; | |
| ctx.strokeStyle = color; | |
| ctx.lineWidth = 2; | |
| ctx.beginPath(); | |
| const centerY = canvas.height / 2; | |
| if (waveformData && waveformData.length > 0) { | |
| const sliceWidth = canvas.width / waveformData.length; | |
| let x = 0; | |
| for (let i = 0; i < waveformData.length; i++) { | |
| const v = waveformData[i] / 128.0; | |
| const y = (v * canvas.height) / 2; | |
| if (i === 0) ctx.moveTo(x, y); | |
| else ctx.lineTo(x, y); | |
| x += sliceWidth; | |
| } | |
| } else { | |
| const frequency = Date.now() * 0.01; | |
| for (let x = 0; x < canvas.width; x++) { | |
| const y = centerY | |
| + Math.sin((x * 0.02) + frequency) * 12 | |
| + Math.sin((x * 0.05) + frequency * 0.7) * 6 | |
| + Math.sin((x * 0.03) + frequency * 1.3) * 4 | |
| + (Math.random() - 0.5) * 3; | |
| if (x === 0) ctx.moveTo(x, y); | |
| else ctx.lineTo(x, y); | |
| } | |
| } | |
| ctx.stroke(); | |
| } | |
| /** | |
| * Start animation loop. | |
| * @param {Function} getUserWaveformData - returns Uint8Array or null | |
| * @param {Function} getAgentWaveformData - returns Uint8Array or null | |
| */ | |
| start(getUserWaveformData, getAgentWaveformData) { | |
| const animate = () => { | |
| this._draw(this.userCtx, this.userCanvas, this.isUserSpeaking, '#2196f3', getUserWaveformData()); | |
| this._draw(this.agentCtx, this.agentCanvas, this.isAgentSpeaking, '#9c27b0', getAgentWaveformData()); | |
| this.animationId = requestAnimationFrame(animate); | |
| }; | |
| animate(); | |
| } | |
| stop() { | |
| if (this.animationId) { | |
| cancelAnimationFrame(this.animationId); | |
| this.animationId = null; | |
| } | |
| } | |
| startUserSpeaking() { | |
| this.isUserSpeaking = true; | |
| } | |
| stopUserSpeaking() { | |
| this.isUserSpeaking = false; | |
| } | |
| startAgentSpeaking() { | |
| this.isAgentSpeaking = true; | |
| } | |
| stopAgentSpeaking() { | |
| this.isAgentSpeaking = false; | |
| } | |
| } | |