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:
@@ -2,7 +2,7 @@
|
||||
|
||||
import * as React from 'react';
|
||||
import { cn } from '@/lib/utils/cn';
|
||||
import { generateMinMaxPeaks } from '@/lib/waveform/peaks';
|
||||
import { useAudioWorker } from '@/lib/hooks/useAudioWorker';
|
||||
import type { Selection } from '@/types/selection';
|
||||
|
||||
export interface WaveformProps {
|
||||
@@ -39,6 +39,16 @@ export function Waveform({
|
||||
const [isSelecting, setIsSelecting] = React.useState(false);
|
||||
const [selectionStart, setSelectionStart] = React.useState<number | null>(null);
|
||||
|
||||
// Worker for peak generation
|
||||
const worker = useAudioWorker();
|
||||
|
||||
// Cache peaks to avoid regenerating on every render
|
||||
const [peaksCache, setPeaksCache] = React.useState<{
|
||||
width: number;
|
||||
min: Float32Array;
|
||||
max: Float32Array;
|
||||
} | null>(null);
|
||||
|
||||
// Handle resize
|
||||
React.useEffect(() => {
|
||||
const handleResize = () => {
|
||||
@@ -52,10 +62,35 @@ export function Waveform({
|
||||
return () => window.removeEventListener('resize', handleResize);
|
||||
}, []);
|
||||
|
||||
// Generate peaks in worker when audioBuffer or zoom changes
|
||||
React.useEffect(() => {
|
||||
if (!audioBuffer) {
|
||||
setPeaksCache(null);
|
||||
return;
|
||||
}
|
||||
|
||||
const visibleWidth = Math.floor(width * zoom);
|
||||
|
||||
// Check if we already have peaks for this width
|
||||
if (peaksCache && peaksCache.width === visibleWidth) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Generate peaks in worker
|
||||
const channelData = audioBuffer.getChannelData(0);
|
||||
worker.generateMinMaxPeaks(channelData, visibleWidth).then((peaks) => {
|
||||
setPeaksCache({
|
||||
width: visibleWidth,
|
||||
min: peaks.min,
|
||||
max: peaks.max,
|
||||
});
|
||||
});
|
||||
}, [audioBuffer, width, zoom, worker, peaksCache]);
|
||||
|
||||
// Draw waveform
|
||||
React.useEffect(() => {
|
||||
const canvas = canvasRef.current;
|
||||
if (!canvas || !audioBuffer) return;
|
||||
if (!canvas || !audioBuffer || !peaksCache) return;
|
||||
|
||||
const ctx = canvas.getContext('2d');
|
||||
if (!ctx) return;
|
||||
@@ -75,8 +110,8 @@ export function Waveform({
|
||||
// Calculate visible width based on zoom
|
||||
const visibleWidth = Math.floor(width * zoom);
|
||||
|
||||
// Generate peaks for visible portion
|
||||
const { min, max } = generateMinMaxPeaks(audioBuffer, visibleWidth, 0);
|
||||
// Use cached peaks
|
||||
const { min, max } = peaksCache;
|
||||
|
||||
// Draw waveform
|
||||
const middle = height / 2;
|
||||
@@ -176,7 +211,7 @@ export function Waveform({
|
||||
ctx.lineTo(progressX, height);
|
||||
ctx.stroke();
|
||||
}
|
||||
}, [audioBuffer, width, height, currentTime, duration, zoom, scrollOffset, amplitudeScale, selection]);
|
||||
}, [audioBuffer, width, height, currentTime, duration, zoom, scrollOffset, amplitudeScale, selection, peaksCache]);
|
||||
|
||||
const handleClick = (e: React.MouseEvent<HTMLCanvasElement>) => {
|
||||
if (!onSeek || !duration || isDragging) return;
|
||||
|
||||
Reference in New Issue
Block a user