/** * String-by-string tuning guide component. * Shows all strings with visual status indicators. */ import type { Tuning } from '../../domain/tunings'; interface StringGuideProps { tuning: Tuning; detectedFrequency: number | null; } export function StringGuide({ tuning, detectedFrequency }: StringGuideProps) { // Find the closest string to the detected frequency const getStringStatus = (stringFreq: number) => { if (!detectedFrequency) { return { color: 'bg-gray-400/30', isClosest: false, diff: 0 }; } const cents = 1200 * Math.log2(detectedFrequency / stringFreq); const absCents = Math.abs(cents); // Determine if this is the closest string const isClosest = tuning.strings.every(s => Math.abs(detectedFrequency - stringFreq) <= Math.abs(detectedFrequency - s.frequency) ); if (!isClosest) { return { color: 'bg-gray-400/30', isClosest: false, diff: cents }; } // Color based on tuning accuracy let color = 'bg-gray-400/30'; if (absCents <= 5) { color = 'bg-green-500/80'; } else if (absCents <= 15) { color = 'bg-yellow-500/80'; } else if (absCents <= 30) { color = 'bg-orange-500/80'; } else { color = 'bg-red-500/80'; } return { color, isClosest: true, diff: cents }; }; return (
{tuning.strings.map((string, index) => { const status = getStringStatus(string.frequency); return (
{/* String name */}
{string.name}
{/* Visual string line */}
{/* Status indicator */} {status.isClosest && (
)}
{/* Frequency */}
{string.frequency.toFixed(0)} Hz
{/* Cents indicator */} {status.isClosest && detectedFrequency && (
{status.diff > 0 ? '+' : ''}{status.diff.toFixed(0)}ยข
)}
); })}
); }