Implement basic zoom in/out

This commit is contained in:
Rob Watson 2021-09-11 12:58:43 +02:00
parent 06e4b7f550
commit faf818e4ae
1 changed files with 54 additions and 23 deletions

View File

@ -11,12 +11,20 @@ type AudioFile = {
sampleRate: number; sampleRate: number;
}; };
type ZoomSettings = {
startFrame: number;
endFrame: number;
};
const defaultZoomSettings: ZoomSettings = { startFrame: 0, endFrame: 0 };
export const Waveform: React.FC<WaveformProps> = ({ export const Waveform: React.FC<WaveformProps> = ({
audioContext, audioContext,
}: WaveformProps) => { }: WaveformProps) => {
const [audioFile, setAudioFile] = useState<AudioFile | null>(null); const [audioFile, setAudioFile] = useState<AudioFile | null>(null);
const [currentTime, setCurrentTime] = useState(0); const [currentTime, setCurrentTime] = useState(0);
const [audio, setAudio] = useState(new Audio()); const [audio, setAudio] = useState(new Audio());
const [zoomSettings, setZoomSettings] = useState(defaultZoomSettings);
const waveformCanvasRef = useRef<HTMLCanvasElement>(null); const waveformCanvasRef = useRef<HTMLCanvasElement>(null);
const hudCanvasRef = useRef<HTMLCanvasElement>(null); const hudCanvasRef = useRef<HTMLCanvasElement>(null);
@ -41,13 +49,13 @@ export const Waveform: React.FC<WaveformProps> = ({
return Math.floor((x / canvasLogicalWidth) * audioFile.frames); return Math.floor((x / canvasLogicalWidth) * audioFile.frames);
}; };
const canvasXToSecs = (x: number): number => { // const canvasXToSecs = (x: number): number => {
if (audioFile == null) { // if (audioFile == null) {
return 0; // return 0;
} // }
const duration = audioFile.frames / audioFile.sampleRate; // const duration = audioFile.frames / audioFile.sampleRate;
return (canvasXToFrame(x) / audioFile.frames) * duration; // return (canvasXToFrame(x) / audioFile.frames) * duration;
}; // };
const secsToCanvasX = (canvasWidth: number, secs: number): number => { const secsToCanvasX = (canvasWidth: number, secs: number): number => {
if (audioFile == null) { if (audioFile == null) {
@ -92,10 +100,11 @@ export const Waveform: React.FC<WaveformProps> = ({
}; };
setAudioFile(audioFile); setAudioFile(audioFile);
setZoomSettings({ startFrame: 0, endFrame: audioFile.frames });
})(); })();
}, [audioContext]); }, [audioContext]);
// render waveform to canvas when audioData is updated: // render waveform to canvas when zoom settings are updated:
useEffect(() => { useEffect(() => {
(async function () { (async function () {
if (audioFile == null) { if (audioFile == null) {
@ -116,18 +125,21 @@ export const Waveform: React.FC<WaveformProps> = ({
return; return;
} }
ctx.strokeStyle = '#00aa00'; let endFrame = zoomSettings.endFrame;
ctx.fillStyle = 'black'; if (endFrame <= zoomSettings.startFrame) {
ctx.fillRect(0, 0, canvas.width, canvas.height); endFrame = audioFile.frames;
}
const resp = await fetch( const resp = await fetch(
`http://localhost:8888/api/peaks?video_id=${videoID}&start=0&end=${Math.round( `http://localhost:8888/api/peaks?video_id=${videoID}&start=${zoomSettings.startFrame}&end=${endFrame}&bins=${canvas.width}`
audioFile.frames
)}&bins=${canvas.width}`
); );
const peaks = await resp.json(); const peaks = await resp.json();
console.log('respBody from peaks =', peaks); console.log('respBody from peaks =', peaks);
ctx.strokeStyle = '#00aa00';
ctx.fillStyle = 'black';
ctx.fillRect(0, 0, canvas.width, canvas.height);
const numChannels = peaks.length; const numChannels = peaks.length;
const chanHeight = canvas.height / numChannels; const chanHeight = canvas.height / numChannels;
for (let c = 0; c < numChannels; c++) { for (let c = 0; c < numChannels; c++) {
@ -144,7 +156,7 @@ export const Waveform: React.FC<WaveformProps> = ({
} }
} }
})(); })();
}, [audioFile]); }, [zoomSettings]);
// redraw HUD // redraw HUD
useEffect(() => { useEffect(() => {
@ -163,6 +175,10 @@ export const Waveform: React.FC<WaveformProps> = ({
ctx.clearRect(0, 0, canvas.width, canvas.height); ctx.clearRect(0, 0, canvas.width, canvas.height);
if (audioFile == null) {
return;
}
const x = secsToCanvasX(canvas.width, currentTime); const x = secsToCanvasX(canvas.width, currentTime);
ctx.strokeStyle = 'red'; ctx.strokeStyle = 'red';
@ -180,14 +196,8 @@ export const Waveform: React.FC<WaveformProps> = ({
console.log('mousemove, x =', canvasX, 'frame =', canvasXToFrame(canvasX)); console.log('mousemove, x =', canvasX, 'frame =', canvasXToFrame(canvasX));
}; };
const handleMouseDown = (evt: MouseEvent<HTMLCanvasElement>) => { const handleMouseDown = () => {
if (audioFile == null) { return null;
return;
}
const canvasX = mouseEventToCanvasX(evt);
audio.currentTime = canvasXToSecs(canvasX);
console.log('currentTime now', canvasXToSecs(canvasX));
}; };
const handleMouseUp = () => { const handleMouseUp = () => {
@ -207,11 +217,29 @@ export const Waveform: React.FC<WaveformProps> = ({
}; };
const handleZoomIn = () => { const handleZoomIn = () => {
if (audioFile == null) {
return;
}
console.log('zoom in'); console.log('zoom in');
const diff = zoomSettings.endFrame - zoomSettings.startFrame;
const endFrame = zoomSettings.startFrame + Math.floor(diff / 2);
const settings = { ...zoomSettings, endFrame: endFrame };
setZoomSettings(settings);
}; };
const handleZoomOut = () => { const handleZoomOut = () => {
if (audioFile == null) {
return;
}
console.log('zoom out'); console.log('zoom out');
const diff = zoomSettings.endFrame - zoomSettings.startFrame;
const newDiff = diff * 2;
const endFrame = Math.min(
zoomSettings.endFrame + newDiff,
audioFile.frames
);
const settings = { ...zoomSettings, endFrame: endFrame };
setZoomSettings(settings);
}; };
// render component: // render component:
@ -222,6 +250,7 @@ export const Waveform: React.FC<WaveformProps> = ({
position: 'relative', position: 'relative',
margin: '0 auto', margin: '0 auto',
} as React.CSSProperties; } as React.CSSProperties;
const waveformCanvasProps = { const waveformCanvasProps = {
width: '100%', width: '100%',
position: 'absolute', position: 'absolute',
@ -231,6 +260,7 @@ export const Waveform: React.FC<WaveformProps> = ({
bottom: 0, bottom: 0,
zIndex: 0, zIndex: 0,
} as React.CSSProperties; } as React.CSSProperties;
const hudCanvasProps = { const hudCanvasProps = {
width: '100%', width: '100%',
position: 'absolute', position: 'absolute',
@ -240,6 +270,7 @@ export const Waveform: React.FC<WaveformProps> = ({
bottom: 0, bottom: 0,
zIndex: 1, zIndex: 1,
} as React.CSSProperties; } as React.CSSProperties;
const clockTextAreaProps = { color: '#999', width: '400px' }; const clockTextAreaProps = { color: '#999', width: '400px' };
return ( return (