feat: implement medium effort features - markers, web workers, and bezier automation
Implemented three major medium effort features to enhance the audio editor: **1. Region Markers System** - Add marker type definitions supporting point markers and regions - Create useMarkers hook for marker state management - Build MarkerTimeline component for visual marker display - Create MarkerDialog component for adding/editing markers - Add keyboard shortcuts: M (add marker), Shift+M (next), Shift+Ctrl+M (previous) - Support marker navigation, editing, and deletion **2. Web Worker for Computations** - Create audio worker for offloading heavy computations - Implement worker functions: generatePeaks, generateMinMaxPeaks, normalizePeaks, analyzeAudio, findPeak - Build useAudioWorker hook for easy worker integration - Integrate worker into Waveform component with peak caching - Significantly improve UI responsiveness during waveform generation **3. Bezier Curve Automation** - Enhance interpolateAutomationValue to support Bezier curves - Implement cubic Bezier interpolation with control handles - Add createSmoothHandles for auto-smooth curve generation - Add generateBezierCurvePoints for smooth curve rendering - Support bezier alongside existing linear and step curves All features are type-safe and integrate seamlessly with the existing codebase. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -127,7 +127,14 @@ export function interpolateAutomationValue(
|
||||
return prevPoint.value;
|
||||
}
|
||||
|
||||
// Linear interpolation
|
||||
// Handle bezier curve
|
||||
if (prevPoint.curve === 'bezier') {
|
||||
const timeDelta = nextPoint.time - prevPoint.time;
|
||||
const t = (time - prevPoint.time) / timeDelta;
|
||||
return interpolateBezier(prevPoint, nextPoint, t);
|
||||
}
|
||||
|
||||
// Linear interpolation (default)
|
||||
const timeDelta = nextPoint.time - prevPoint.time;
|
||||
const valueDelta = nextPoint.value - prevPoint.value;
|
||||
const progress = (time - prevPoint.time) / timeDelta;
|
||||
@@ -139,6 +146,117 @@ export function interpolateAutomationValue(
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Interpolate value using cubic Bezier curve
|
||||
* Uses the control handles from both points to create smooth curves
|
||||
*/
|
||||
function interpolateBezier(
|
||||
p0: AutomationPoint,
|
||||
p1: AutomationPoint,
|
||||
t: number
|
||||
): number {
|
||||
// Default handle positions if not specified
|
||||
// Out handle defaults to 1/3 towards next point
|
||||
// In handle defaults to 1/3 back from current point
|
||||
const timeDelta = p1.time - p0.time;
|
||||
|
||||
// Control point 1 (out handle from p0)
|
||||
const c1x = p0.handleOut?.x ?? timeDelta / 3;
|
||||
const c1y = p0.handleOut?.y ?? 0;
|
||||
|
||||
// Control point 2 (in handle from p1)
|
||||
const c2x = p1.handleIn?.x ?? -timeDelta / 3;
|
||||
const c2y = p1.handleIn?.y ?? 0;
|
||||
|
||||
// Convert handles to absolute positions
|
||||
const cp1Value = p0.value + c1y;
|
||||
const cp2Value = p1.value + c2y;
|
||||
|
||||
// Cubic Bezier formula: B(t) = (1-t)³P₀ + 3(1-t)²tP₁ + 3(1-t)t²P₂ + t³P₃
|
||||
const mt = 1 - t;
|
||||
const mt2 = mt * mt;
|
||||
const mt3 = mt2 * mt;
|
||||
const t2 = t * t;
|
||||
const t3 = t2 * t;
|
||||
|
||||
const value =
|
||||
mt3 * p0.value +
|
||||
3 * mt2 * t * cp1Value +
|
||||
3 * mt * t2 * cp2Value +
|
||||
t3 * p1.value;
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create smooth bezier handles for a point based on surrounding points
|
||||
* This creates an "auto-smooth" effect similar to DAWs
|
||||
*/
|
||||
export function createSmoothHandles(
|
||||
prevPoint: AutomationPoint | null,
|
||||
currentPoint: AutomationPoint,
|
||||
nextPoint: AutomationPoint | null
|
||||
): { handleIn: { x: number; y: number }; handleOut: { x: number; y: number } } {
|
||||
// If no surrounding points, return horizontal handles
|
||||
if (!prevPoint && !nextPoint) {
|
||||
return {
|
||||
handleIn: { x: -0.1, y: 0 },
|
||||
handleOut: { x: 0.1, y: 0 },
|
||||
};
|
||||
}
|
||||
|
||||
// Calculate slope from surrounding points
|
||||
let slope = 0;
|
||||
|
||||
if (prevPoint && nextPoint) {
|
||||
// Use average slope from both neighbors
|
||||
const timeDelta = nextPoint.time - prevPoint.time;
|
||||
const valueDelta = nextPoint.value - prevPoint.value;
|
||||
slope = valueDelta / timeDelta;
|
||||
} else if (nextPoint) {
|
||||
// Only have next point
|
||||
const timeDelta = nextPoint.time - currentPoint.time;
|
||||
const valueDelta = nextPoint.value - currentPoint.value;
|
||||
slope = valueDelta / timeDelta;
|
||||
} else if (prevPoint) {
|
||||
// Only have previous point
|
||||
const timeDelta = currentPoint.time - prevPoint.time;
|
||||
const valueDelta = currentPoint.value - prevPoint.value;
|
||||
slope = valueDelta / timeDelta;
|
||||
}
|
||||
|
||||
// Create handles with 1/3 distance to neighbors
|
||||
const handleDistance = 0.1; // Fixed distance for smooth curves
|
||||
const handleY = slope * handleDistance;
|
||||
|
||||
return {
|
||||
handleIn: { x: -handleDistance, y: -handleY },
|
||||
handleOut: { x: handleDistance, y: handleY },
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate points along a bezier curve for rendering
|
||||
* Returns array of {time, value} points
|
||||
*/
|
||||
export function generateBezierCurvePoints(
|
||||
p0: AutomationPoint,
|
||||
p1: AutomationPoint,
|
||||
numPoints: number = 50
|
||||
): Array<{ time: number; value: number }> {
|
||||
const points: Array<{ time: number; value: number }> = [];
|
||||
const timeDelta = p1.time - p0.time;
|
||||
|
||||
for (let i = 0; i <= numPoints; i++) {
|
||||
const t = i / numPoints;
|
||||
const time = p0.time + t * timeDelta;
|
||||
const value = interpolateBezier(p0, p1, t);
|
||||
points.push({ time, value });
|
||||
}
|
||||
|
||||
return points;
|
||||
}
|
||||
|
||||
/**
|
||||
* Apply automation value to track parameter
|
||||
*/
|
||||
|
||||
Reference in New Issue
Block a user