/** * Microphone button rendered on the canvas header for boards whose def * declares builtInMicrophone (XIAO ESP32S3 Sense, M5Stack Cardputer ADV). * * Click -> asks for mic permission -> streams the computer's microphone into * the board's emulated PDM/I2S mic through the bridge's setMicrophoneSource * seam. While off, the bridge feeds its built-in 440 Hz test tone, so unlike * the camera there is NO auto-start on run: sketches read a live level either * way, and grabbing the user's microphone should take an explicit click. * * Visual states mirror CameraToggle: * idle grey mic icon * requesting orange (pulsing) — browser permission prompt visible * streaming green + live level meter * denied red, click-to-retry * error red, hover for message */ import React from 'react'; import { useMicrophoneStream } from '../../hooks/useMicrophoneStream'; interface MicrophoneToggleProps { boardId: string | null; } export const MicrophoneToggle: React.FC = ({ boardId }) => { const { status, errorMessage, level, start, stop } = useMicrophoneStream(); const handleClick = () => { if (!boardId) return; if (status === 'streaming') { stop(); } else { void start(boardId); } }; const isOn = status === 'streaming'; const pct = Math.round(level * 100); const tooltip = status === 'streaming' ? `Streaming your microphone (level ${pct}%) — click to stop (falls back to the test tone)` : status === 'requesting' ? 'Asking for microphone permission…' : status === 'denied' ? 'Permission denied. Click to retry.' : status === 'error' ? `Microphone error: ${errorMessage ?? 'unknown'}` : 'Use your microphone as the board mic (off = 440 Hz test tone)'; const color = status === 'streaming' ? '#3fb950' : status === 'requesting' ? '#f0883e' : status === 'denied' || status === 'error' ? '#f85149' : '#ccc'; return ( ); };