Skip to content

Commit 2d20e42

Browse files
feat(studio): shrink + lower keyframe diamonds under the beat strip
When a clip's track shows the beat-dot strip (the top band), its keyframe diamonds and connecting lines render at 45% size and centered in the region below the band, so they don't collide with the dots. Full size and vertically centered otherwise. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Co-authored-by: Miguel Ángel <miguel07alm@protonmail.com>
1 parent 2f655e4 commit 2d20e42

3 files changed

Lines changed: 22 additions & 7 deletions

File tree

packages/studio/src/player/components/BeatStrip.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import { moveBeatCompositionTime, deleteBeatAtCompositionTime } from "../../util
33
import { usePlayerStore } from "../store/playerStore";
44
import { CLIP_Y } from "./timelineLayout";
55

6-
const BEAT_BAND_H = 14; // dark band height at top of track
6+
export const BEAT_BAND_H = 14; // dark band height at top of track
77
const BEAT_HIT_W = 12; // grab width per beat (px)
88

99
/** Hide both layers when beats are packed tighter than this (px) — too dense to read. */

packages/studio/src/player/components/TimelineCanvas.tsx

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -217,6 +217,14 @@ export const TimelineCanvas = memo(function TimelineCanvas({
217217
const ts = trackStyles.get(trackNum) ?? getTrackStyle("");
218218
const isPendingTrack =
219219
draggedClip?.started === true && !trackOrder.includes(trackNum) && els.length === 0;
220+
// The beat-dot strip occupies the top of this track's lane (active track,
221+
// or the music track when nothing is selected). When shown, keyframe
222+
// diamonds shrink + drop to the bottom half so they don't collide with it.
223+
const beatStripOnTrack =
224+
(beatAnalysis?.beatTimes?.length ?? 0) >= 2 &&
225+
(selectedElementId
226+
? els.some((e) => (e.key ?? e.id) === selectedElementId)
227+
: els.some(isMusicTrack));
220228
return (
221229
<div
222230
key={trackNum}
@@ -260,9 +268,7 @@ export const TimelineCanvas = memo(function TimelineCanvas({
260268
/>
261269
{/* Beat dots on the active track (the one holding the selection),
262270
falling back to the music track when nothing is selected. */}
263-
{(selectedElementId
264-
? els.some((e) => (e.key ?? e.id) === selectedElementId)
265-
: els.some(isMusicTrack)) && (
271+
{beatStripOnTrack && (
266272
<BeatStrip
267273
beatTimes={beatAnalysis?.beatTimes}
268274
beatStrengths={beatAnalysis?.beatStrengths}
@@ -428,6 +434,7 @@ export const TimelineCanvas = memo(function TimelineCanvas({
428434
keyframesData={keyframeCache.get(elementKey)!}
429435
clipWidthPx={Math.max(previewElement.duration * pps, 4)}
430436
clipHeightPx={TRACK_H - 2 * CLIP_Y}
437+
beatsActive={beatStripOnTrack}
431438
accentColor={clipStyle.accent}
432439
isSelected={isSelected}
433440
currentPercentage={

packages/studio/src/player/components/TimelineClipDiamonds.tsx

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { memo, useEffect, useRef, useState } from "react";
2+
import { BEAT_BAND_H } from "./BeatStrip";
23

34
interface KeyframeEntry {
45
percentage: number;
@@ -17,6 +18,9 @@ interface TimelineClipDiamondsProps {
1718
keyframesData: KeyframeCacheEntry;
1819
clipWidthPx: number;
1920
clipHeightPx: number;
21+
/** Beat-dot strip is shown on this track → shrink diamonds + drop them into
22+
* the bottom half so they clear the strip at the top. */
23+
beatsActive?: boolean;
2024
accentColor: string;
2125
isSelected: boolean;
2226
currentPercentage: number;
@@ -41,6 +45,7 @@ export const TimelineClipDiamonds = memo(function TimelineClipDiamonds({
4145
keyframesData,
4246
clipWidthPx,
4347
clipHeightPx,
48+
beatsActive,
4449
accentColor,
4550
isSelected,
4651
currentPercentage,
@@ -98,8 +103,11 @@ export const TimelineClipDiamonds = memo(function TimelineClipDiamonds({
98103

99104
if (clipWidthPx < 20) return null;
100105

101-
const diamondSize = Math.round(clipHeightPx * DIAMOND_RATIO);
106+
// When the beat strip occupies the top band, shrink the diamonds and center
107+
// them in the remaining bottom region so they don't collide with it.
108+
const diamondSize = Math.round(clipHeightPx * (beatsActive ? 0.45 : DIAMOND_RATIO));
102109
const half = diamondSize / 2;
110+
const centerY = beatsActive ? BEAT_BAND_H + (clipHeightPx - BEAT_BAND_H) / 2 : clipHeightPx / 2;
103111
const sorted = keyframesData.keyframes.slice().sort((a, b) => a.percentage - b.percentage);
104112
const baseColor = isSelected ? accentColor : "#a3a3a3";
105113
const baseOpacity = isSelected ? 0.4 : 0.25;
@@ -185,7 +193,7 @@ export const TimelineClipDiamonds = memo(function TimelineClipDiamonds({
185193
className="absolute"
186194
style={{
187195
left: x1,
188-
top: "50%",
196+
top: centerY,
189197
width: x2 - x1,
190198
height: 2,
191199
transform: "translateY(-1px)",
@@ -211,7 +219,7 @@ export const TimelineClipDiamonds = memo(function TimelineClipDiamonds({
211219
className="absolute"
212220
style={{
213221
left: leftPx,
214-
top: "50%",
222+
top: centerY,
215223
transform: "translateY(-50%)",
216224
width: diamondSize,
217225
height: diamondSize,

0 commit comments

Comments
 (0)