Before attempting to integrate osciloscope properely. STILL BROKEN NOW
This commit is contained in:
@@ -0,0 +1,246 @@
|
||||
import { useState, useRef, useCallback, useEffect } from 'react';
|
||||
|
||||
interface AudioAnalyzerState {
|
||||
isActive: boolean;
|
||||
error: string | null;
|
||||
source: 'microphone' | 'file' | null;
|
||||
fileName: string | null;
|
||||
isPlaying: boolean;
|
||||
}
|
||||
|
||||
export const useAudioAnalyzer = () => {
|
||||
const [state, setState] = useState<AudioAnalyzerState>({
|
||||
isActive: false,
|
||||
error: null,
|
||||
source: null,
|
||||
fileName: null,
|
||||
isPlaying: false,
|
||||
});
|
||||
|
||||
const audioContextRef = useRef<AudioContext | null>(null);
|
||||
const analyzerLeftRef = useRef<AnalyserNode | null>(null);
|
||||
const analyzerRightRef = useRef<AnalyserNode | null>(null);
|
||||
const sourceRef = useRef<MediaStreamAudioSourceNode | MediaElementAudioSourceNode | null>(null);
|
||||
const splitterRef = useRef<ChannelSplitterNode | null>(null);
|
||||
const streamRef = useRef<MediaStream | null>(null);
|
||||
const analysisGainNodeRef = useRef<GainNode | null>(null);
|
||||
const audioElementRef = useRef<HTMLAudioElement | null>(null);
|
||||
const gainValueRef = useRef<number>(3); // Default higher gain for analysis sensitivity only
|
||||
|
||||
const getTimeDomainData = useCallback(() => {
|
||||
if (!analyzerLeftRef.current) return null;
|
||||
|
||||
const bufferLength = analyzerLeftRef.current.fftSize;
|
||||
const dataArray = new Uint8Array(bufferLength);
|
||||
analyzerLeftRef.current.getByteTimeDomainData(dataArray);
|
||||
|
||||
return dataArray;
|
||||
}, []);
|
||||
|
||||
const getStereoData = useCallback(() => {
|
||||
if (!analyzerLeftRef.current || !analyzerRightRef.current) return null;
|
||||
|
||||
const bufferLength = analyzerLeftRef.current.fftSize;
|
||||
const leftData = new Uint8Array(bufferLength);
|
||||
const rightData = new Uint8Array(bufferLength);
|
||||
|
||||
analyzerLeftRef.current.getByteTimeDomainData(leftData);
|
||||
analyzerRightRef.current.getByteTimeDomainData(rightData);
|
||||
|
||||
return { left: leftData, right: rightData };
|
||||
}, []);
|
||||
|
||||
const setGain = useCallback((value: number) => {
|
||||
gainValueRef.current = value;
|
||||
if (analysisGainNodeRef.current) {
|
||||
analysisGainNodeRef.current.gain.value = value;
|
||||
}
|
||||
}, []);
|
||||
|
||||
const setupAnalyzers = useCallback((audioContext: AudioContext) => {
|
||||
// Create gain node for analysis sensitivity (does NOT affect audio output)
|
||||
analysisGainNodeRef.current = audioContext.createGain();
|
||||
analysisGainNodeRef.current.gain.value = gainValueRef.current;
|
||||
|
||||
// Create channel splitter for stereo
|
||||
splitterRef.current = audioContext.createChannelSplitter(2);
|
||||
|
||||
// Create analyzers for each channel
|
||||
analyzerLeftRef.current = audioContext.createAnalyser();
|
||||
analyzerRightRef.current = audioContext.createAnalyser();
|
||||
|
||||
// Configure analyzers for higher sensitivity
|
||||
const fftSize = 2048;
|
||||
analyzerLeftRef.current.fftSize = fftSize;
|
||||
analyzerRightRef.current.fftSize = fftSize;
|
||||
analyzerLeftRef.current.smoothingTimeConstant = 0.5;
|
||||
analyzerRightRef.current.smoothingTimeConstant = 0.5;
|
||||
analyzerLeftRef.current.minDecibels = -90;
|
||||
analyzerRightRef.current.minDecibels = -90;
|
||||
analyzerLeftRef.current.maxDecibels = -10;
|
||||
analyzerRightRef.current.maxDecibels = -10;
|
||||
}, []);
|
||||
|
||||
const startMicrophone = useCallback(async () => {
|
||||
try {
|
||||
setState(prev => ({ ...prev, isActive: false, error: null }));
|
||||
|
||||
const stream = await navigator.mediaDevices.getUserMedia({
|
||||
audio: {
|
||||
echoCancellation: false,
|
||||
noiseSuppression: false,
|
||||
autoGainControl: false,
|
||||
},
|
||||
});
|
||||
|
||||
streamRef.current = stream;
|
||||
audioContextRef.current = new AudioContext();
|
||||
|
||||
setupAnalyzers(audioContextRef.current);
|
||||
|
||||
// Create source from microphone
|
||||
const micSource = audioContextRef.current.createMediaStreamSource(stream);
|
||||
sourceRef.current = micSource;
|
||||
|
||||
// Connect: source -> analysisGain -> splitter -> analyzers
|
||||
// (microphone doesn't need output, just analysis)
|
||||
micSource.connect(analysisGainNodeRef.current!);
|
||||
analysisGainNodeRef.current!.connect(splitterRef.current!);
|
||||
splitterRef.current!.connect(analyzerLeftRef.current!, 0);
|
||||
splitterRef.current!.connect(analyzerRightRef.current!, 1);
|
||||
|
||||
setState({
|
||||
isActive: true,
|
||||
error: null,
|
||||
source: 'microphone',
|
||||
fileName: null,
|
||||
isPlaying: true
|
||||
});
|
||||
} catch (err) {
|
||||
const message = err instanceof Error ? err.message : 'Failed to access microphone';
|
||||
setState(prev => ({ ...prev, isActive: false, error: message }));
|
||||
}
|
||||
}, [setupAnalyzers]);
|
||||
|
||||
const loadAudioFile = useCallback(async (file: File) => {
|
||||
try {
|
||||
// Stop any existing audio
|
||||
stop();
|
||||
|
||||
setState(prev => ({ ...prev, isActive: false, error: null }));
|
||||
|
||||
// Create audio element
|
||||
const audioElement = new Audio();
|
||||
audioElement.src = URL.createObjectURL(file);
|
||||
audioElement.loop = true;
|
||||
audioElementRef.current = audioElement;
|
||||
|
||||
audioContextRef.current = new AudioContext();
|
||||
setupAnalyzers(audioContextRef.current);
|
||||
|
||||
// Create source from audio element
|
||||
const audioSource = audioContextRef.current.createMediaElementSource(audioElement);
|
||||
sourceRef.current = audioSource;
|
||||
|
||||
// For files: source -> destination (clean audio output)
|
||||
// source -> analysisGain -> splitter -> analyzers (boosted for visualization)
|
||||
audioSource.connect(audioContextRef.current.destination);
|
||||
audioSource.connect(analysisGainNodeRef.current!);
|
||||
analysisGainNodeRef.current!.connect(splitterRef.current!);
|
||||
splitterRef.current!.connect(analyzerLeftRef.current!, 0);
|
||||
splitterRef.current!.connect(analyzerRightRef.current!, 1);
|
||||
|
||||
// Start playing
|
||||
await audioElement.play();
|
||||
|
||||
setState({
|
||||
isActive: true,
|
||||
error: null,
|
||||
source: 'file',
|
||||
fileName: file.name,
|
||||
isPlaying: true
|
||||
});
|
||||
} catch (err) {
|
||||
const message = err instanceof Error ? err.message : 'Failed to load audio file';
|
||||
setState(prev => ({ ...prev, isActive: false, error: message }));
|
||||
}
|
||||
}, [setupAnalyzers]);
|
||||
|
||||
const togglePlayPause = useCallback(() => {
|
||||
if (!audioElementRef.current) return;
|
||||
|
||||
if (audioElementRef.current.paused) {
|
||||
audioElementRef.current.play();
|
||||
setState(prev => ({ ...prev, isPlaying: true }));
|
||||
} else {
|
||||
audioElementRef.current.pause();
|
||||
setState(prev => ({ ...prev, isPlaying: false }));
|
||||
}
|
||||
}, []);
|
||||
|
||||
const stop = useCallback(() => {
|
||||
if (streamRef.current) {
|
||||
streamRef.current.getTracks().forEach(track => track.stop());
|
||||
streamRef.current = null;
|
||||
}
|
||||
|
||||
if (audioElementRef.current) {
|
||||
audioElementRef.current.pause();
|
||||
audioElementRef.current.src = '';
|
||||
audioElementRef.current = null;
|
||||
}
|
||||
|
||||
if (sourceRef.current) {
|
||||
sourceRef.current.disconnect();
|
||||
sourceRef.current = null;
|
||||
}
|
||||
|
||||
if (analysisGainNodeRef.current) {
|
||||
analysisGainNodeRef.current.disconnect();
|
||||
analysisGainNodeRef.current = null;
|
||||
}
|
||||
|
||||
if (splitterRef.current) {
|
||||
splitterRef.current.disconnect();
|
||||
splitterRef.current = null;
|
||||
}
|
||||
|
||||
if (audioContextRef.current) {
|
||||
audioContextRef.current.close();
|
||||
audioContextRef.current = null;
|
||||
}
|
||||
|
||||
analyzerLeftRef.current = null;
|
||||
analyzerRightRef.current = null;
|
||||
|
||||
setState({
|
||||
isActive: false,
|
||||
error: null,
|
||||
source: null,
|
||||
fileName: null,
|
||||
isPlaying: false
|
||||
});
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
return () => {
|
||||
stop();
|
||||
};
|
||||
}, [stop]);
|
||||
|
||||
const getAudioElement = useCallback(() => {
|
||||
return audioElementRef.current;
|
||||
}, []);
|
||||
|
||||
return {
|
||||
...state,
|
||||
startMicrophone,
|
||||
loadAudioFile,
|
||||
togglePlayPause,
|
||||
stop,
|
||||
setGain,
|
||||
getTimeDomainData,
|
||||
getStereoData,
|
||||
getAudioElement,
|
||||
};
|
||||
};
|
||||
Reference in New Issue
Block a user