feat: implement 100ms snapping for sound placement and enhance zoom controls in Sequencer
This commit is contained in:
@@ -30,7 +30,7 @@ interface SequencerState {
|
||||
|
||||
const INITIAL_DURATION = 30000 // 30 seconds in milliseconds
|
||||
const INITIAL_ZOOM = 40 // 40 pixels per second
|
||||
const MIN_ZOOM = 10
|
||||
const MIN_ZOOM = 5
|
||||
const MAX_ZOOM = 200
|
||||
|
||||
export function SequencerPage() {
|
||||
@@ -124,12 +124,13 @@ export function SequencerPage() {
|
||||
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])
|
||||
// Helper function to snap time to 100ms intervals
|
||||
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(() => {
|
||||
@@ -146,9 +147,9 @@ export function SequencerPage() {
|
||||
currentMousePos.y <= rect.bottom
|
||||
) {
|
||||
const rawX = currentMousePos.x - rect.left
|
||||
// Apply adaptive snapping to the drag over position
|
||||
// Apply 100ms snapping to the drag over position
|
||||
const rawTimeSeconds = rawX / state.zoom
|
||||
const snappedTimeSeconds = snapToGrid(rawTimeSeconds, state.zoom, state.duration)
|
||||
const snappedTimeSeconds = snapToGrid(rawTimeSeconds)
|
||||
const snappedX = snappedTimeSeconds * state.zoom
|
||||
setDragOverInfo({ trackId: track.id, x: Math.max(0, snappedX) })
|
||||
return
|
||||
@@ -160,7 +161,7 @@ export function SequencerPage() {
|
||||
} else {
|
||||
setDragOverInfo(null)
|
||||
}
|
||||
}, [draggedItem, currentMousePos, state.tracks, state.zoom, state.duration, snapToGrid])
|
||||
}, [draggedItem, currentMousePos, state.tracks, state.zoom, snapToGrid])
|
||||
|
||||
const handleDragEnd = useCallback((event: DragEndEvent) => {
|
||||
const { active, over } = event
|
||||
@@ -304,22 +305,47 @@ export function SequencerPage() {
|
||||
}))
|
||||
}
|
||||
|
||||
const handlePlay = () => {
|
||||
setState(prev => ({ ...prev, isPlaying: !prev.isPlaying }))
|
||||
}
|
||||
// const handlePlay = () => {
|
||||
// setState(prev => ({ ...prev, isPlaying: !prev.isPlaying }))
|
||||
// }
|
||||
|
||||
const handleStop = () => {
|
||||
setState(prev => ({ ...prev, isPlaying: false, currentTime: 0 }))
|
||||
}
|
||||
// const handleStop = () => {
|
||||
// setState(prev => ({ ...prev, isPlaying: false, currentTime: 0 }))
|
||||
// }
|
||||
|
||||
const handleReset = () => {
|
||||
setState(prev => ({ ...prev, currentTime: 0 }))
|
||||
}
|
||||
// const handleReset = () => {
|
||||
// setState(prev => ({ ...prev, currentTime: 0 }))
|
||||
// }
|
||||
|
||||
const handleZoomChange = (value: number) => {
|
||||
setState(prev => ({ ...prev, zoom: value }))
|
||||
}
|
||||
|
||||
// Handle mouse wheel zoom with position centering
|
||||
const handleZoomChangeWithPosition = (newZoom: number, mouseX?: number) => {
|
||||
if (mouseX !== undefined && sequencerCanvasRef.current) {
|
||||
const oldZoom = state.zoom
|
||||
const currentScrollLeft = sequencerCanvasRef.current.scrollLeft
|
||||
|
||||
// Calculate the time position that the mouse is pointing at
|
||||
const timeAtMouse = (currentScrollLeft + mouseX) / oldZoom
|
||||
|
||||
// Calculate what the new scroll position should be to keep the same time under the mouse
|
||||
const newScrollLeft = timeAtMouse * newZoom - mouseX
|
||||
|
||||
setState(prev => ({ ...prev, zoom: newZoom }))
|
||||
|
||||
// Apply the new scroll position after the zoom change
|
||||
requestAnimationFrame(() => {
|
||||
if (sequencerCanvasRef.current) {
|
||||
sequencerCanvasRef.current.scrollLeft = Math.max(0, newScrollLeft)
|
||||
}
|
||||
})
|
||||
} else {
|
||||
setState(prev => ({ ...prev, zoom: newZoom }))
|
||||
}
|
||||
}
|
||||
|
||||
const handleDurationChange = (duration: number) => {
|
||||
setState(prev => ({ ...prev, duration }))
|
||||
}
|
||||
@@ -425,6 +451,9 @@ export function SequencerPage() {
|
||||
dragOverInfo={dragOverInfo}
|
||||
onRemoveSound={handleRemoveSound}
|
||||
timeIntervals={getTimeIntervals(state.zoom, state.duration)}
|
||||
onZoomChange={handleZoomChangeWithPosition}
|
||||
minZoom={MIN_ZOOM}
|
||||
maxZoom={MAX_ZOOM}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user