diff --git a/src/components/sequencer/SequencerCanvas.tsx b/src/components/sequencer/SequencerCanvas.tsx
index 824ec91..77f2385 100644
--- a/src/components/sequencer/SequencerCanvas.tsx
+++ b/src/components/sequencer/SequencerCanvas.tsx
@@ -125,6 +125,52 @@ function TrackRow({ track, duration, zoom, isPlaying, currentTime, draggedItem,
onRemoveSound(soundId, track.id)
}
+ // Calculate logical time intervals based on zoom level (same as main component)
+ const getTimeIntervals = (zoom: number, duration: number) => {
+ const durationSeconds = duration / 1000
+ const minorIntervals: number[] = []
+ const majorIntervals: number[] = []
+
+ // Define logical interval progressions
+ const intervalSets = [
+ { minor: 1, major: 5 }, // 1s minor, 5s major (very zoomed in)
+ { minor: 5, major: 30 }, // 5s minor, 30s major
+ { minor: 10, major: 60 }, // 10s minor, 1min major
+ { minor: 30, major: 300 }, // 30s minor, 5min major
+ { minor: 60, major: 600 }, // 1min minor, 10min major
+ { minor: 300, major: 1800 }, // 5min minor, 30min major
+ { minor: 600, major: 3600 } // 10min minor, 1h major
+ ]
+
+ // Find appropriate interval set based on zoom level
+ // We want major intervals to be roughly 100-200px apart
+ const targetMajorSpacing = 150
+
+ let selectedIntervals = intervalSets[intervalSets.length - 1] // fallback to largest
+ for (const intervals of intervalSets) {
+ if (intervals.major * zoom >= targetMajorSpacing) {
+ selectedIntervals = intervals
+ break
+ }
+ }
+
+ // Generate minor intervals (every interval)
+ for (let i = 0; i * selectedIntervals.minor <= durationSeconds; i++) {
+ const time = i * selectedIntervals.minor
+ minorIntervals.push(time)
+ }
+
+ // Generate major intervals (at major boundaries)
+ for (let i = 0; i * selectedIntervals.major <= durationSeconds; i++) {
+ const time = i * selectedIntervals.major
+ majorIntervals.push(time)
+ }
+
+ return { minorIntervals, majorIntervals, minorInterval: selectedIntervals.minor, majorInterval: selectedIntervals.major }
+ }
+
+ const { minorIntervals, majorIntervals } = getTimeIntervals(zoom, duration)
+
return (
{/* Grid lines for time markers */}
- {Array.from({ length: Math.ceil(duration / 1000) + 1 }).map((_, i) => (
+ {/* Minor grid lines */}
+ {minorIntervals.map((time) => (
))}
- {/* Major grid lines every 10 seconds */}
- {Array.from({ length: Math.floor(duration / 10000) + 1 }).map((_, i) => (
+ {/* Major grid lines */}
+ {majorIntervals.map((time) => (
))}
@@ -228,7 +275,6 @@ export const SequencerCanvas = forwardRef
(
}, ref) => {
const totalWidth = (duration / 1000) * zoom // Convert ms to seconds for zoom calculation
const timelineRef = useRef(null)
- const containerRef = useRef(null)
// Add a fallback droppable for the entire canvas area
const { setNodeRef: setCanvasDropRef } = useDroppable({
@@ -238,6 +284,53 @@ export const SequencerCanvas = forwardRef(
},
})
+ // Calculate logical time intervals based on zoom level
+ const getTimeIntervals = (zoom: number, duration: number) => {
+ const durationSeconds = duration / 1000
+ const minorIntervals: number[] = []
+ const majorIntervals: number[] = []
+
+ // Define logical interval progressions
+ const intervalSets = [
+ { minor: 0.1, major: 1 }, // 0.1s minor, 1s major (mega zoomed in)
+ { minor: 1, major: 5 }, // 1s minor, 5s major (very zoomed in)
+ { minor: 5, major: 30 }, // 5s minor, 30s major
+ { minor: 10, major: 60 }, // 10s minor, 1min major
+ { minor: 30, major: 300 }, // 30s minor, 5min major
+ { minor: 60, major: 600 }, // 1min minor, 10min major
+ { minor: 300, major: 1800 }, // 5min minor, 30min major
+ { minor: 600, major: 3600 } // 10min minor, 1h major
+ ]
+
+ // Find appropriate interval set based on zoom level
+ // We want major intervals to be roughly 100-200px apart
+ const targetMajorSpacing = 150
+
+ let selectedIntervals = intervalSets[intervalSets.length - 1] // fallback to largest
+ for (const intervals of intervalSets) {
+ if (intervals.major * zoom >= targetMajorSpacing) {
+ selectedIntervals = intervals
+ break
+ }
+ }
+
+ // Generate minor intervals (every interval)
+ for (let i = 0; i * selectedIntervals.minor <= durationSeconds; i++) {
+ const time = i * selectedIntervals.minor
+ minorIntervals.push(time)
+ }
+
+ // Generate major intervals (at major boundaries)
+ for (let i = 0; i * selectedIntervals.major <= durationSeconds; i++) {
+ const time = i * selectedIntervals.major
+ majorIntervals.push(time)
+ }
+
+ return { minorIntervals, majorIntervals, minorInterval: selectedIntervals.minor, majorInterval: selectedIntervals.major }
+ }
+
+ const { minorIntervals, majorIntervals } = getTimeIntervals(zoom, duration)
+
const handleTracksScroll = (e: React.UIEvent) => {
// Sync timeline horizontal scroll with tracks
if (timelineRef.current) {
@@ -264,23 +357,39 @@ export const SequencerCanvas = forwardRef(
}}
>
- {Array.from({ length: Math.ceil(duration / 1000) + 1 }).map((_, i) => (
-
- {/* Time markers */}
- {i % 5 === 0 && (
- <>
-
-
- {Math.floor(i / 60)}:{(i % 60).toString().padStart(2, '0')}
-
- >
- )}
- {i % 5 !== 0 && (
-
- )}
+ {/* Minor time markers */}
+ {minorIntervals.map((time) => (
+
))}
+ {/* Major time markers with labels */}
+ {majorIntervals.map((time) => {
+ const formatTime = (seconds: number): string => {
+ if (seconds < 60) {
+ // For times under 1 minute, show seconds with decimal places if needed
+ return seconds < 10 && seconds % 1 !== 0
+ ? seconds.toFixed(1) + 's'
+ : Math.floor(seconds) + 's'
+ } else {
+ // For times over 1 minute, show MM:SS format
+ const mins = Math.floor(seconds / 60)
+ const secs = Math.floor(seconds % 60)
+ return `${mins}:${secs.toString().padStart(2, '0')}`
+ }
+ }
+
+ return (
+
+
+
+ {formatTime(time)}
+
+
+ )
+ })}
+
{/* Playhead in ruler */}
{isPlaying && (