feat: add waveform generation and timeline display

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-09-21 11:12:36 -04:00
parent c96b0cedc9
commit e05e83f62f
8 changed files with 185 additions and 11 deletions

View File

@@ -1,5 +1,7 @@
import { drawClips } from './clipRenderer';
import type { Clip } from '$lib/stores/clips.svelte';
import { drawWaveform } from './waveformRenderer';
import type { ThumbnailSpritesheet } from '$lib/bindings/mediaAnalysis';
export interface TimelineState {
visibleStart: number;
@@ -32,7 +34,9 @@ export function drawTimeline(
duration: number,
clips: Clip[] = [],
selectedClipId: string | null = null,
pendingInPoint: number | null = null
pendingInPoint: number | null = null,
waveformPeaks: number[] = [],
_thumbnailSpritesheets: ThumbnailSpritesheet[] = []
): void {
const { width, height } = state;
ctx.clearRect(0, 0, width, height);
@@ -43,7 +47,12 @@ export function drawTimeline(
drawTimeTicks(ctx, state, duration);
drawPlaceholderLane(ctx, state, 0, THUMB_LANE_HEIGHT, 'Thumbnails');
drawPlaceholderLane(ctx, state, THUMB_LANE_HEIGHT, WAVEFORM_LANE_HEIGHT, 'Waveform');
if (waveformPeaks.length > 0) {
drawWaveform(ctx, state, waveformPeaks, duration, THUMB_LANE_HEIGHT, WAVEFORM_LANE_HEIGHT);
} else {
drawPlaceholderLane(ctx, state, THUMB_LANE_HEIGHT, WAVEFORM_LANE_HEIGHT, 'Waveform');
}
drawClips(ctx, state, clips, selectedClipId, pendingInPoint);

View File

@@ -0,0 +1,44 @@
import type { TimelineState } from './renderer';
export function drawWaveform(
ctx: CanvasRenderingContext2D,
state: TimelineState,
peaks: number[],
duration: number,
y: number,
height: number
): void {
if (peaks.length === 0 || duration <= 0) return;
const { width, visibleStart, visibleEnd } = state;
const visibleRange = visibleEnd - visibleStart;
ctx.fillStyle = '#89b4fa44';
ctx.strokeStyle = '#89b4fa88';
ctx.lineWidth = 1;
ctx.beginPath();
ctx.moveTo(0, y + height);
for (let px = 0; px < width; px++) {
const startSample = Math.floor(
((visibleStart + ((px - 0.5) / width) * visibleRange) / duration) * peaks.length
);
const endSample = Math.floor(
((visibleStart + ((px + 0.5) / width) * visibleRange) / duration) * peaks.length
);
let maxPeak = 0;
for (let i = Math.max(0, startSample); i <= Math.min(endSample, peaks.length - 1); i++) {
maxPeak = Math.max(maxPeak, peaks[i]);
}
const barHeight = maxPeak * height;
ctx.lineTo(px, y + height - barHeight);
}
ctx.lineTo(width, y + height);
ctx.closePath();
ctx.fill();
ctx.stroke();
}