feat: complete Phase 9.3 - automation recording with write/touch/latch modes
Implemented comprehensive automation recording system for volume, pan, and effect parameters: - Added automation recording modes: - Write: Records continuously during playback when values change - Touch: Records only while control is being touched/moved - Latch: Records from first touch until playback stops - Implemented value change detection (0.001 threshold) to prevent infinite loops - Fixed React setState-in-render errors by: - Using queueMicrotask() to defer state updates - Moving lane creation logic to useEffect - Properly memoizing touch handlers with useMemo - Added proper value ranges for effect parameters: - Frequency: 20-20000 Hz - Q: 0.1-20 - Gain: -40-40 dB - Enhanced automation lane auto-creation with parameter-specific ranges - Added touch callbacks to all parameter controls (volume, pan, effects) - Implemented throttling (100ms) to avoid excessive automation points Technical improvements: - Used tracksRef and onRecordAutomationRef to ensure latest values in animation loops - Added proper cleanup on playback stop - Optimized recording to only trigger when values actually change 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -13,6 +13,8 @@ export interface CircularKnobProps {
|
||||
className?: string;
|
||||
label?: string;
|
||||
formatValue?: (value: number) => string;
|
||||
onTouchStart?: () => void;
|
||||
onTouchEnd?: () => void;
|
||||
}
|
||||
|
||||
export function CircularKnob({
|
||||
@@ -25,6 +27,8 @@ export function CircularKnob({
|
||||
className,
|
||||
label,
|
||||
formatValue,
|
||||
onTouchStart,
|
||||
onTouchEnd,
|
||||
}: CircularKnobProps) {
|
||||
const knobRef = React.useRef<HTMLDivElement>(null);
|
||||
const [isDragging, setIsDragging] = React.useState(false);
|
||||
@@ -68,8 +72,9 @@ export function CircularKnob({
|
||||
y: e.clientY,
|
||||
value,
|
||||
};
|
||||
onTouchStart?.();
|
||||
},
|
||||
[value]
|
||||
[value, onTouchStart]
|
||||
);
|
||||
|
||||
const handleMouseMove = React.useCallback(
|
||||
@@ -83,7 +88,8 @@ export function CircularKnob({
|
||||
|
||||
const handleMouseUp = React.useCallback(() => {
|
||||
setIsDragging(false);
|
||||
}, []);
|
||||
onTouchEnd?.();
|
||||
}, [onTouchEnd]);
|
||||
|
||||
React.useEffect(() => {
|
||||
if (isDragging) {
|
||||
|
||||
@@ -13,6 +13,8 @@ export interface SliderProps
|
||||
step?: number;
|
||||
label?: string;
|
||||
showValue?: boolean;
|
||||
onTouchStart?: () => void;
|
||||
onTouchEnd?: () => void;
|
||||
}
|
||||
|
||||
const Slider = React.forwardRef<HTMLInputElement, SliderProps>(
|
||||
@@ -28,6 +30,8 @@ const Slider = React.forwardRef<HTMLInputElement, SliderProps>(
|
||||
label,
|
||||
showValue = false,
|
||||
disabled,
|
||||
onTouchStart,
|
||||
onTouchEnd,
|
||||
...props
|
||||
},
|
||||
ref
|
||||
@@ -41,6 +45,21 @@ const Slider = React.forwardRef<HTMLInputElement, SliderProps>(
|
||||
onValueChange?.([numValue]);
|
||||
};
|
||||
|
||||
const handleMouseDown = () => {
|
||||
onTouchStart?.();
|
||||
};
|
||||
|
||||
const handleMouseUp = () => {
|
||||
onTouchEnd?.();
|
||||
};
|
||||
|
||||
React.useEffect(() => {
|
||||
if (onTouchEnd) {
|
||||
window.addEventListener('mouseup', handleMouseUp);
|
||||
return () => window.removeEventListener('mouseup', handleMouseUp);
|
||||
}
|
||||
}, [onTouchEnd]);
|
||||
|
||||
return (
|
||||
<div className={cn('w-full', className)}>
|
||||
{(label || showValue) && (
|
||||
@@ -63,6 +82,7 @@ const Slider = React.forwardRef<HTMLInputElement, SliderProps>(
|
||||
step={step}
|
||||
value={currentValue}
|
||||
onChange={handleChange}
|
||||
onMouseDown={handleMouseDown}
|
||||
disabled={disabled}
|
||||
className={cn(
|
||||
'w-full h-2 bg-secondary rounded-lg appearance-none cursor-pointer',
|
||||
|
||||
@@ -12,6 +12,8 @@ export interface VerticalFaderProps {
|
||||
step?: number;
|
||||
className?: string;
|
||||
showDb?: boolean;
|
||||
onTouchStart?: () => void;
|
||||
onTouchEnd?: () => void;
|
||||
}
|
||||
|
||||
export function VerticalFader({
|
||||
@@ -23,6 +25,8 @@ export function VerticalFader({
|
||||
step = 0.01,
|
||||
className,
|
||||
showDb = true,
|
||||
onTouchStart,
|
||||
onTouchEnd,
|
||||
}: VerticalFaderProps) {
|
||||
const trackRef = React.useRef<HTMLDivElement>(null);
|
||||
const [isDragging, setIsDragging] = React.useState(false);
|
||||
@@ -58,8 +62,9 @@ export function VerticalFader({
|
||||
e.preventDefault();
|
||||
setIsDragging(true);
|
||||
updateValue(e.clientY);
|
||||
onTouchStart?.();
|
||||
},
|
||||
[updateValue]
|
||||
[updateValue, onTouchStart]
|
||||
);
|
||||
|
||||
const handleMouseMove = React.useCallback(
|
||||
@@ -73,7 +78,8 @@ export function VerticalFader({
|
||||
|
||||
const handleMouseUp = React.useCallback(() => {
|
||||
setIsDragging(false);
|
||||
}, []);
|
||||
onTouchEnd?.();
|
||||
}, [onTouchEnd]);
|
||||
|
||||
React.useEffect(() => {
|
||||
if (isDragging) {
|
||||
|
||||
Reference in New Issue
Block a user