Files
audio-ui/lib/hooks/useAudioWorker.ts
Sebastian Krüger 119c8c2942 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>
2025-11-20 08:25:33 +01:00

139 lines
3.5 KiB
TypeScript

'use client';
import { useRef, useEffect, useCallback } from 'react';
import type { WorkerMessage, WorkerResponse } from '@/lib/workers/audio.worker';
/**
* Hook to use the audio Web Worker for heavy computations
* Automatically manages worker lifecycle and message passing
*/
export function useAudioWorker() {
const workerRef = useRef<Worker | null>(null);
const callbacksRef = useRef<Map<string, (result: any, error?: string) => void>>(new Map());
const messageIdRef = useRef(0);
// Initialize worker
useEffect(() => {
// Create worker from the audio worker file
workerRef.current = new Worker(
new URL('../workers/audio.worker.ts', import.meta.url),
{ type: 'module' }
);
// Handle messages from worker
workerRef.current.onmessage = (event: MessageEvent<WorkerResponse>) => {
const { id, result, error } = event.data;
const callback = callbacksRef.current.get(id);
if (callback) {
callback(result, error);
callbacksRef.current.delete(id);
}
};
// Cleanup on unmount
return () => {
if (workerRef.current) {
workerRef.current.terminate();
workerRef.current = null;
}
callbacksRef.current.clear();
};
}, []);
// Send message to worker
const sendMessage = useCallback(
<T = any>(type: WorkerMessage['type'], payload: any): Promise<T> => {
return new Promise((resolve, reject) => {
if (!workerRef.current) {
reject(new Error('Worker not initialized'));
return;
}
const id = `msg-${++messageIdRef.current}`;
const message: WorkerMessage = { id, type, payload };
callbacksRef.current.set(id, (result, error) => {
if (error) {
reject(new Error(error));
} else {
resolve(result);
}
});
workerRef.current.postMessage(message);
});
},
[]
);
// API methods
const generatePeaks = useCallback(
async (channelData: Float32Array, width: number): Promise<Float32Array> => {
const result = await sendMessage<Float32Array>('generatePeaks', {
channelData,
width,
});
return new Float32Array(result);
},
[sendMessage]
);
const generateMinMaxPeaks = useCallback(
async (
channelData: Float32Array,
width: number
): Promise<{ min: Float32Array; max: Float32Array }> => {
const result = await sendMessage<{ min: Float32Array; max: Float32Array }>(
'generateMinMaxPeaks',
{ channelData, width }
);
return {
min: new Float32Array(result.min),
max: new Float32Array(result.max),
};
},
[sendMessage]
);
const normalizePeaks = useCallback(
async (peaks: Float32Array, targetMax: number = 1): Promise<Float32Array> => {
const result = await sendMessage<Float32Array>('normalizePeaks', {
peaks,
targetMax,
});
return new Float32Array(result);
},
[sendMessage]
);
const analyzeAudio = useCallback(
async (
channelData: Float32Array
): Promise<{
peak: number;
rms: number;
crestFactor: number;
dynamicRange: number;
}> => {
return sendMessage('analyzeAudio', { channelData });
},
[sendMessage]
);
const findPeak = useCallback(
async (channelData: Float32Array): Promise<number> => {
return sendMessage<number>('findPeak', { channelData });
},
[sendMessage]
);
return {
generatePeaks,
generateMinMaxPeaks,
normalizePeaks,
analyzeAudio,
findPeak,
};
}