139 lines
3.5 KiB
TypeScript
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,
|
||
|
|
};
|
||
|
|
}
|