feat: enhance time snapping and interval calculation for improved sound placement in Sequencer
This commit is contained in:
@@ -79,14 +79,58 @@ export function SequencerPage() {
|
||||
}
|
||||
}, [])
|
||||
|
||||
// Helper function to snap time to 100ms intervals with improved precision
|
||||
const snapToGrid = useCallback((timeInSeconds: number): number => {
|
||||
const snapIntervalMs = 100 // 100ms snap interval
|
||||
const timeInMs = Math.max(0, timeInSeconds * 1000) // Ensure non-negative
|
||||
const snappedMs = Math.round(timeInMs / snapIntervalMs) * snapIntervalMs
|
||||
return Math.max(0, snappedMs / 1000) // Convert back to seconds, ensure non-negative
|
||||
// Calculate logical time intervals based on zoom level (shared with SequencerCanvas)
|
||||
const getTimeIntervals = useCallback((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 }
|
||||
}, [])
|
||||
|
||||
// Helper function to snap time to the current minor interval
|
||||
const snapToGrid = useCallback((timeInSeconds: number, zoom: number, duration: number): number => {
|
||||
const { minorInterval } = getTimeIntervals(zoom, duration)
|
||||
const snappedTime = Math.round(timeInSeconds / minorInterval) * minorInterval
|
||||
return Math.max(0, snappedTime) // Ensure non-negative
|
||||
}, [getTimeIntervals])
|
||||
|
||||
// Update drag over info based on current mouse position and over target
|
||||
useEffect(() => {
|
||||
if (draggedItem && currentMousePos && (draggedItem.type === 'sound' || draggedItem.type === 'placed-sound')) {
|
||||
@@ -102,9 +146,9 @@ export function SequencerPage() {
|
||||
currentMousePos.y <= rect.bottom
|
||||
) {
|
||||
const rawX = currentMousePos.x - rect.left
|
||||
// Apply snapping to the drag over position for consistency
|
||||
// Apply adaptive snapping to the drag over position
|
||||
const rawTimeSeconds = rawX / state.zoom
|
||||
const snappedTimeSeconds = snapToGrid(rawTimeSeconds)
|
||||
const snappedTimeSeconds = snapToGrid(rawTimeSeconds, state.zoom, state.duration)
|
||||
const snappedX = snappedTimeSeconds * state.zoom
|
||||
setDragOverInfo({ trackId: track.id, x: Math.max(0, snappedX) })
|
||||
return
|
||||
@@ -116,7 +160,7 @@ export function SequencerPage() {
|
||||
} else {
|
||||
setDragOverInfo(null)
|
||||
}
|
||||
}, [draggedItem, currentMousePos, state.tracks, state.zoom, snapToGrid])
|
||||
}, [draggedItem, currentMousePos, state.tracks, state.zoom, state.duration, snapToGrid])
|
||||
|
||||
const handleDragEnd = useCallback((event: DragEndEvent) => {
|
||||
const { active, over } = event
|
||||
@@ -380,6 +424,7 @@ export function SequencerPage() {
|
||||
draggedItem={draggedItem}
|
||||
dragOverInfo={dragOverInfo}
|
||||
onRemoveSound={handleRemoveSound}
|
||||
timeIntervals={getTimeIntervals(state.zoom, state.duration)}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user