feat: implement time snapping to 100ms intervals for improved sound placement accuracy
This commit is contained in:
@@ -54,8 +54,18 @@ function PlacedSoundItem({ sound, zoom, trackId, onRemove }: PlacedSoundItemProp
|
||||
transform: `translate3d(${transform.x}px, ${transform.y}px, 0)`,
|
||||
} : undefined
|
||||
|
||||
// Helper function to snap time to 100ms intervals
|
||||
const snapToGrid = (timeInSeconds: number): number => {
|
||||
const snapIntervalMs = 100 // 100ms snap interval
|
||||
const timeInMs = timeInSeconds * 1000
|
||||
const snappedMs = Math.round(timeInMs / snapIntervalMs) * snapIntervalMs
|
||||
return snappedMs / 1000 // Convert back to seconds
|
||||
}
|
||||
|
||||
const width = (sound.duration / 1000) * zoom // Convert ms to seconds for zoom calculation
|
||||
const left = (sound.startTime / 1000) * zoom // Convert ms to seconds for zoom calculation
|
||||
// Ensure placed sounds are positioned at snapped locations
|
||||
const snappedStartTime = snapToGrid(sound.startTime / 1000)
|
||||
const left = snappedStartTime * zoom
|
||||
|
||||
const formatTime = (seconds: number): string => {
|
||||
const mins = Math.floor(seconds / 60)
|
||||
@@ -110,7 +120,6 @@ function PlacedSoundItem({ sound, zoom, trackId, onRemove }: PlacedSoundItemProp
|
||||
}
|
||||
|
||||
function TrackRow({ track, duration, zoom, isPlaying, currentTime, draggedItem, dragOverInfo, onRemoveSound }: TrackRowProps) {
|
||||
const totalWidth = (duration / 1000) * zoom // Convert ms to seconds for zoom calculation
|
||||
const playheadPosition = (currentTime / 1000) * zoom // Convert ms to seconds for zoom calculation
|
||||
|
||||
const { isOver, setNodeRef: setDropRef } = useDroppable({
|
||||
@@ -203,12 +212,14 @@ function TrackRow({ track, duration, zoom, isPlaying, currentTime, draggedItem,
|
||||
))}
|
||||
</div>
|
||||
|
||||
{/* Precise drag preview */}
|
||||
{/* Precise drag preview (dragOverInfo.x is already snapped) */}
|
||||
{draggedItem && dragOverInfo && dragOverInfo.trackId === track.id && (() => {
|
||||
const soundDurationMs = draggedItem.type === 'sound'
|
||||
? draggedItem.sound.duration // Already in ms
|
||||
: draggedItem.duration // Already in ms
|
||||
const soundDurationSeconds = soundDurationMs / 1000
|
||||
|
||||
// dragOverInfo.x is already snapped in the parent component
|
||||
const startTimeSeconds = dragOverInfo.x / zoom
|
||||
const endTimeSeconds = startTimeSeconds + soundDurationSeconds
|
||||
const durationSeconds = duration / 1000
|
||||
|
||||
@@ -79,6 +79,14 @@ 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
|
||||
}, [])
|
||||
|
||||
// Update drag over info based on current mouse position and over target
|
||||
useEffect(() => {
|
||||
if (draggedItem && currentMousePos && (draggedItem.type === 'sound' || draggedItem.type === 'placed-sound')) {
|
||||
@@ -93,8 +101,12 @@ export function SequencerPage() {
|
||||
currentMousePos.y >= rect.top &&
|
||||
currentMousePos.y <= rect.bottom
|
||||
) {
|
||||
const x = currentMousePos.x - rect.left
|
||||
setDragOverInfo({ trackId: track.id, x: Math.max(0, x) })
|
||||
const rawX = currentMousePos.x - rect.left
|
||||
// Apply snapping to the drag over position for consistency
|
||||
const rawTimeSeconds = rawX / state.zoom
|
||||
const snappedTimeSeconds = snapToGrid(rawTimeSeconds)
|
||||
const snappedX = snappedTimeSeconds * state.zoom
|
||||
setDragOverInfo({ trackId: track.id, x: Math.max(0, snappedX) })
|
||||
return
|
||||
}
|
||||
}
|
||||
@@ -104,7 +116,7 @@ export function SequencerPage() {
|
||||
} else {
|
||||
setDragOverInfo(null)
|
||||
}
|
||||
}, [draggedItem, currentMousePos, state.tracks])
|
||||
}, [draggedItem, currentMousePos, state.tracks, state.zoom, snapToGrid])
|
||||
|
||||
const handleDragEnd = useCallback((event: DragEndEvent) => {
|
||||
const { active, over } = event
|
||||
@@ -113,10 +125,10 @@ export function SequencerPage() {
|
||||
|
||||
// Handle sound drop from library to track
|
||||
if (dragData?.type === 'sound' && overData?.type === 'track') {
|
||||
// Use precise drop position if available (convert from pixels to milliseconds)
|
||||
// Use precise drop position if available (dragOverInfo.x is already snapped)
|
||||
let startTime = 0
|
||||
if (dragOverInfo && dragOverInfo.trackId === overData.trackId) {
|
||||
startTime = Math.max(0, (dragOverInfo.x / state.zoom) * 1000) // Convert seconds to milliseconds
|
||||
startTime = Math.max(0, (dragOverInfo.x / state.zoom) * 1000) // Convert to milliseconds
|
||||
}
|
||||
|
||||
const soundDuration = dragData.sound.duration // Already in milliseconds
|
||||
@@ -149,10 +161,10 @@ export function SequencerPage() {
|
||||
|
||||
// Handle moving placed sounds within tracks
|
||||
if (dragData?.type === 'placed-sound' && overData?.type === 'track') {
|
||||
// Use precise drop position if available (convert from pixels to milliseconds)
|
||||
// Use precise drop position if available (dragOverInfo.x is already snapped)
|
||||
let startTime = dragData.startTime || 0
|
||||
if (dragOverInfo && dragOverInfo.trackId === overData.trackId) {
|
||||
startTime = Math.max(0, (dragOverInfo.x / state.zoom) * 1000) // Convert seconds to milliseconds
|
||||
startTime = Math.max(0, (dragOverInfo.x / state.zoom) * 1000) // Convert to milliseconds
|
||||
}
|
||||
|
||||
// Restrict placement to within track duration (all in milliseconds)
|
||||
@@ -170,7 +182,10 @@ export function SequencerPage() {
|
||||
if (track.id === sourceTrackId && sourceTrackId === targetTrackId) {
|
||||
// Moving within the same track - just update position
|
||||
const updatedSound: PlacedSound = {
|
||||
...dragData,
|
||||
id: dragData.id,
|
||||
soundId: dragData.soundId,
|
||||
name: dragData.name,
|
||||
duration: dragData.duration,
|
||||
startTime,
|
||||
trackId: targetTrackId,
|
||||
}
|
||||
@@ -189,7 +204,10 @@ export function SequencerPage() {
|
||||
} else if (track.id === targetTrackId) {
|
||||
// Add to target track (different track move)
|
||||
const updatedSound: PlacedSound = {
|
||||
...dragData,
|
||||
id: dragData.id,
|
||||
soundId: dragData.soundId,
|
||||
name: dragData.name,
|
||||
duration: dragData.duration,
|
||||
startTime,
|
||||
trackId: targetTrackId,
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user