Skip to content

Commit d742264

Browse files
committed
Optimize waveform rendering and timeline preview updates
Improves waveform rendering in ClipTrack by adjusting sample stepping and using requestAnimationFrame for smoother updates. Timeline preview time updates are now throttled to reduce unnecessary state changes and improve performance during mouse movement.
1 parent 4886f65 commit d742264

File tree

2 files changed

+58
-27
lines changed

2 files changed

+58
-27
lines changed

apps/desktop/src/routes/editor/Timeline/ClipTrack.tsx

Lines changed: 46 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@ function WaveformCanvas(props: {
5353
const { project } = useEditorContext();
5454

5555
let canvas: HTMLCanvasElement | undefined;
56+
let rafId: number | null = null;
5657
const { width } = useSegmentContext();
5758
const { secsPerPixel } = useTimelineContext();
5859

@@ -65,11 +66,22 @@ function WaveformCanvas(props: {
6566
) => {
6667
const maxAmplitude = h;
6768

68-
// yellow please
6969
ctx.fillStyle = color;
7070
ctx.beginPath();
7171

7272
const step = 0.05 / secsPerPixel();
73+
const samplesPerSecond = 10;
74+
75+
const startTime = props.segment.start;
76+
const endTime = props.segment.end;
77+
78+
const pixelsPerSecond = 1 / secsPerPixel();
79+
const samplesPerPixel = samplesPerSecond / pixelsPerSecond;
80+
81+
let sampleStep = 0.1;
82+
if (samplesPerPixel < 0.5) {
83+
sampleStep = Math.max(0.1, Math.ceil(1 / samplesPerPixel) * 0.1);
84+
}
7385

7486
ctx.moveTo(0, h);
7587

@@ -78,35 +90,39 @@ function WaveformCanvas(props: {
7890
return 1.0 - Math.max(ww + gain, -60) / -60;
7991
};
8092

93+
let prevX = 0;
94+
let prevY = h;
95+
8196
for (
82-
let segmentTime = props.segment.start;
83-
segmentTime <= props.segment.end + 0.1;
84-
segmentTime += 0.1
97+
let segmentTime = startTime;
98+
segmentTime <= endTime + 0.1;
99+
segmentTime += sampleStep
85100
) {
86-
const index = Math.floor(segmentTime * 10);
87-
const xTime = index / 10;
101+
const index = Math.floor(segmentTime * samplesPerSecond);
102+
if (index < 0 || index >= waveform.length) continue;
103+
104+
const xTime = index / samplesPerSecond;
88105

89106
const currentDb =
90107
typeof waveform[index] === "number" ? waveform[index] : -60;
91108
const amplitude = norm(currentDb) * maxAmplitude;
92109

93-
const x = (xTime - props.segment.start) / secsPerPixel();
110+
const x = (xTime - startTime) / secsPerPixel();
94111
const y = h - amplitude;
95112

96-
const prevX = (xTime - 0.1 - props.segment.start) / secsPerPixel();
97-
const prevDb =
98-
typeof waveform[index - 1] === "number" ? waveform[index - 1] : -60;
99-
const prevAmplitude = norm(prevDb) * maxAmplitude;
100-
const prevY = h - prevAmplitude;
101-
102-
const cpX1 = prevX + step / 2;
103-
const cpX2 = x - step / 2;
113+
if (prevX !== x) {
114+
const cpX1 = prevX + step / 2;
115+
const cpX2 = x - step / 2;
104116

105-
ctx.bezierCurveTo(cpX1, prevY, cpX2, y, x, y);
117+
ctx.bezierCurveTo(cpX1, prevY, cpX2, y, x, y);
118+
119+
prevX = x;
120+
prevY = y;
121+
}
106122
}
107123

108124
ctx.lineTo(
109-
(props.segment.end + 0.3 - props.segment.start) / secsPerPixel(),
125+
(endTime + 0.3 - startTime) / secsPerPixel(),
110126
h,
111127
);
112128

@@ -146,14 +162,25 @@ function WaveformCanvas(props: {
146162
}
147163

148164
createEffect(() => {
149-
renderWaveforms();
165+
// track reactive deps
166+
void width();
167+
void secsPerPixel();
168+
void project.audio.micVolumeDb;
169+
void project.audio.systemVolumeDb;
170+
if (rafId !== null) cancelAnimationFrame(rafId);
171+
rafId = requestAnimationFrame(renderWaveforms);
172+
});
173+
174+
onCleanup(() => {
175+
if (rafId !== null) cancelAnimationFrame(rafId);
150176
});
151177

152178
return (
153179
<canvas
154180
ref={(el) => {
155181
canvas = el;
156-
renderWaveforms();
182+
if (rafId !== null) cancelAnimationFrame(rafId);
183+
rafId = requestAnimationFrame(renderWaveforms);
157184
}}
158185
class="absolute inset-0 w-full h-full pointer-events-none"
159186
height={52}

apps/desktop/src/routes/editor/Timeline/index.tsx

Lines changed: 12 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { createElementBounds } from "@solid-primitives/bounds";
22
import { createEventListener } from "@solid-primitives/event-listener";
3+
import { throttle } from "@solid-primitives/scheduled";
34
import { platform } from "@tauri-apps/plugin-os";
45
import { cx } from "cva";
56
import { batch, createRoot, createSignal, For, onMount, Show } from "solid-js";
@@ -38,6 +39,10 @@ export function Timeline() {
3839

3940
const secsPerPixel = () => transform().zoom / (timelineBounds.width ?? 1);
4041

42+
const setPreviewTimeThrottled = throttle((time: number) => {
43+
setEditorState("previewTime", time);
44+
}, 16);
45+
4146
onMount(() => {
4247
if (!project.timeline) {
4348
const resume = projectHistory.pause();
@@ -194,14 +199,13 @@ export function Timeline() {
194199
});
195200
});
196201
}}
197-
onMouseMove={(e) => {
198-
const { left } = timelineBounds;
199-
if (editorState.playing) return;
200-
setEditorState(
201-
"previewTime",
202-
transform().position + secsPerPixel() * (e.clientX - left!),
203-
);
204-
}}
202+
onMouseMove={(e) => {
203+
const { left } = timelineBounds;
204+
if (editorState.playing || left == null) return;
205+
setPreviewTimeThrottled(
206+
transform().position + secsPerPixel() * (e.clientX - left),
207+
);
208+
}}
205209
onMouseEnter={() => setEditorState("timeline", "hoveredTrack", null)}
206210
onMouseLeave={() => {
207211
setEditorState("previewTime", null);

0 commit comments

Comments
 (0)