fix: timeline zoom and waveform rendering improvements

- Fix timeline width calculation to always fill viewport at minimum
- Fix waveform sampling to match timeline width calculation exactly
- Fix infinite scroll loop by removing circular callback
- Ensure scrollbars appear correctly when zooming in
- Use consistent PIXELS_PER_SECOND_BASE = 5 across all components

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
2025-11-20 10:34:31 +01:00
parent 477a444c78
commit 1855988b83
3 changed files with 20 additions and 9 deletions

View File

@@ -42,10 +42,16 @@ export function TimeScale({
// Calculate total timeline width (match waveform calculation)
// Uses 5 pixels per second as base scale, multiplied by zoom
// Always ensure minimum width is at least viewport width for full coverage
const PIXELS_PER_SECOND_BASE = 5;
const totalWidth = React.useMemo(() => {
return duration * zoom * PIXELS_PER_SECOND_BASE;
}, [duration, zoom]);
if (zoom >= 1) {
const calculatedWidth = duration * zoom * PIXELS_PER_SECOND_BASE;
// Ensure it's at least viewport width so timeline always fills
return Math.max(calculatedWidth, viewportWidth);
}
return viewportWidth;
}, [duration, zoom, viewportWidth]);
// Update viewport width on resize
React.useEffect(() => {

View File

@@ -329,7 +329,17 @@ export function Track({
const buffer = track.audioBuffer;
const channelData = buffer.getChannelData(0);
const samplesPerPixel = Math.floor(buffer.length / (width * zoom));
// Calculate samples per pixel based on the total width
// Must match the timeline calculation exactly
const PIXELS_PER_SECOND_BASE = 5;
let totalWidth;
if (zoom >= 1) {
const calculatedWidth = duration * zoom * PIXELS_PER_SECOND_BASE;
totalWidth = Math.max(calculatedWidth, width);
} else {
totalWidth = width;
}
const samplesPerPixel = buffer.length / totalWidth;
// Draw waveform
ctx.fillStyle = track.color;

View File

@@ -170,12 +170,7 @@ export function TrackList({
});
setSyncingScroll(false);
// Also call the external callback if provided
if (onTimeScaleScroll) {
onTimeScaleScroll();
}
}, [syncingScroll, onTimeScaleScroll]);
}, [syncingScroll]);
// Expose the scroll handler via ref so AudioEditor can call it
React.useEffect(() => {