import { useRef, useState, useEffect, useCallback } from 'react' import { useStore } from '@nanostores/react' import { mappingMode } from '../stores/mappingMode' interface KnobProps { label: string value: number min: number max: number step: number unit?: string onChange: (value: number) => void formatValue?: (id: string, value: number) => string valueId?: string size?: number paramId?: string onMapClick?: (paramId: string, activeLFO: number) => void mappedLFOs?: number[] } export function Knob({ label, value, min, max, step, unit, onChange, formatValue, valueId, size = 48, paramId, onMapClick, mappedLFOs = [] }: KnobProps) { const [isDragging, setIsDragging] = useState(false) const startYRef = useRef(0) const startValueRef = useRef(0) const mappingModeState = useStore(mappingMode) const displayValue = formatValue && valueId ? formatValue(valueId, value) : `${value}${unit || ''}` const isInMappingMode = mappingModeState.isActive && paramId const hasMappings = mappedLFOs.length > 0 const normalizedValue = (value - min) / (max - min) const angle = -225 + normalizedValue * 270 const handleMouseDown = (e: React.MouseEvent) => { if (isInMappingMode && paramId && mappingModeState.activeLFO !== null && onMapClick) { onMapClick(paramId, mappingModeState.activeLFO) e.preventDefault() return } setIsDragging(true) startYRef.current = e.clientY startValueRef.current = value e.preventDefault() } const handleMouseMove = useCallback((e: MouseEvent) => { if (!isDragging) return const deltaY = startYRef.current - e.clientY const range = max - min const sensitivity = range / 200 const newValue = Math.max(min, Math.min(max, startValueRef.current + deltaY * sensitivity)) const steppedValue = Math.round(newValue / step) * step onChange(steppedValue) }, [isDragging, max, min, step, onChange]) const handleMouseUp = useCallback(() => { setIsDragging(false) }, []) useEffect(() => { if (isDragging) { window.addEventListener('mousemove', handleMouseMove) window.addEventListener('mouseup', handleMouseUp) return () => { window.removeEventListener('mousemove', handleMouseMove) window.removeEventListener('mouseup', handleMouseUp) } } }, [isDragging, handleMouseMove, handleMouseUp]) return (
{hasMappings && ( )}
{isDragging ? displayValue : label.toUpperCase()}
) }