import { TrackControls } from '@/components/sequencer/TrackControls' import { TimelineControls } from '@/components/sequencer/TimelineControls' import { SoundLibrary } from '@/components/sequencer/SoundLibrary' import { SequencerCanvas } from '@/components/sequencer/SequencerCanvas' import { DndContext, type DragEndEvent, type DragStartEvent, PointerSensor, useSensors, useSensor } from '@dnd-kit/core' import { useState, useRef, useCallback, useEffect } from 'react' export interface Track { id: string name: string sounds: PlacedSound[] } export interface PlacedSound { id: string soundId: number name: string duration: number // in seconds startTime: number // in seconds trackId: string } interface SequencerState { tracks: Track[] duration: number zoom: number currentTime: number isPlaying: boolean } const INITIAL_DURATION = 30 // 30 seconds const INITIAL_ZOOM = 40 // 40 pixels per second const MIN_ZOOM = 10 const MAX_ZOOM = 200 export function SequencerPage() { const sensors = useSensors( useSensor(PointerSensor, { activationConstraint: { distance: 8, }, }) ) const [state, setState] = useState({ tracks: [ { id: 'track-1', name: 'Track 1', sounds: [], }, ], duration: INITIAL_DURATION, zoom: INITIAL_ZOOM, currentTime: 0, isPlaying: false, }) const [draggedItem, setDraggedItem] = useState(null) const [dragOverInfo, setDragOverInfo] = useState<{trackId: string, x: number} | null>(null) const [currentMousePos, setCurrentMousePos] = useState<{x: number, y: number} | null>(null) const trackControlsRef = useRef(null) const sequencerCanvasRef = useRef(null) const handleDragStart = useCallback((event: DragStartEvent) => { setDraggedItem(event.active.data.current) // Start tracking mouse position globally const handleMouseMove = (e: MouseEvent) => { setCurrentMousePos({ x: e.clientX, y: e.clientY }) } document.addEventListener('mousemove', handleMouseMove) // Store cleanup function ;(window as any).dragMouseCleanup = () => { document.removeEventListener('mousemove', handleMouseMove) } }, []) // Update drag over info based on current mouse position and over target useEffect(() => { if (draggedItem && currentMousePos && (draggedItem.type === 'sound' || draggedItem.type === 'placed-sound')) { // Find which track the mouse is currently over for (const track of state.tracks) { const trackElement = document.getElementById(`track-${track.id}`) if (trackElement) { const rect = trackElement.getBoundingClientRect() if ( currentMousePos.x >= rect.left && currentMousePos.x <= rect.right && currentMousePos.y >= rect.top && currentMousePos.y <= rect.bottom ) { const x = currentMousePos.x - rect.left setDragOverInfo({ trackId: track.id, x: Math.max(0, x) }) return } } } // Mouse is not over any track setDragOverInfo(null) } else { setDragOverInfo(null) } }, [draggedItem, currentMousePos, state.tracks]) const handleDragEnd = useCallback((event: DragEndEvent) => { const { active, over } = event const dragData = active.data.current const overData = over?.data.current // Handle sound drop from library to track if (dragData?.type === 'sound' && overData?.type === 'track') { // Use precise drop position if available let startTime = 0 if (dragOverInfo && dragOverInfo.trackId === overData.trackId) { startTime = Math.max(0, dragOverInfo.x / state.zoom) } const newPlacedSound: PlacedSound = { id: `placed-${Date.now()}-${Math.random()}`, soundId: dragData.sound.id, name: dragData.sound.name || dragData.sound.filename, duration: dragData.sound.duration / 1000, // Convert from ms to seconds startTime, trackId: overData.trackId, } setState(prev => ({ ...prev, tracks: prev.tracks.map(track => track.id === overData.trackId ? { ...track, sounds: [...track.sounds, newPlacedSound] } : track ), })) } // Handle moving placed sounds within tracks if (dragData?.type === 'placed-sound' && overData?.type === 'track') { console.log('Moving placed sound:', dragData, 'to track:', overData.trackId) // Use precise drop position if available let startTime = dragData.startTime || 0 if (dragOverInfo && dragOverInfo.trackId === overData.trackId) { startTime = Math.max(0, dragOverInfo.x / state.zoom) } const sourceTrackId = dragData.trackId const targetTrackId = overData.trackId console.log('Source track:', sourceTrackId, 'Target track:', targetTrackId, 'New start time:', startTime) setState(prev => ({ ...prev, tracks: prev.tracks.map(track => { if (track.id === sourceTrackId && sourceTrackId === targetTrackId) { // Moving within the same track - just update position console.log('Moving within same track') const updatedSound: PlacedSound = { ...dragData, startTime, trackId: targetTrackId, } return { ...track, sounds: track.sounds.map(s => s.id === dragData.id ? updatedSound : s ), } } else if (track.id === sourceTrackId) { // Remove from source track (different track move) console.log('Removing sound from source track:', track.id, 'sounds before:', track.sounds.length) const filtered = track.sounds.filter(s => s.id !== dragData.id) console.log('Sounds after removal:', filtered.length) return { ...track, sounds: filtered, } } else if (track.id === targetTrackId) { // Add to target track (different track move) console.log('Adding sound to target track:', track.id, 'sounds before:', track.sounds.length) const updatedSound: PlacedSound = { ...dragData, startTime, trackId: targetTrackId, } console.log('Updated sound:', updatedSound) const newSounds = [...track.sounds, updatedSound] console.log('Sounds after addition:', newSounds.length) return { ...track, sounds: newSounds, } } return track }), })) } // Clear state setDraggedItem(null) setDragOverInfo(null) setCurrentMousePos(null) // Clean up mouse tracking if ((window as any).dragMouseCleanup) { (window as any).dragMouseCleanup() delete (window as any).dragMouseCleanup } }, [dragOverInfo, state.zoom]) const handleAddTrack = () => { const newTrackNumber = state.tracks.length + 1 const newTrack: Track = { id: `track-${Date.now()}`, name: `Track ${newTrackNumber}`, sounds: [], } setState(prev => ({ ...prev, tracks: [...prev.tracks, newTrack] })) } const handleRemoveTrack = (trackId: string) => { setState(prev => ({ ...prev, tracks: prev.tracks.filter(track => track.id !== trackId), })) } const handleUpdateTrackName = (trackId: string, name: string) => { setState(prev => ({ ...prev, tracks: prev.tracks.map(track => track.id === trackId ? { ...track, name } : track ), })) } const handlePlay = () => { setState(prev => ({ ...prev, isPlaying: !prev.isPlaying })) } const handleStop = () => { setState(prev => ({ ...prev, isPlaying: false, currentTime: 0 })) } const handleReset = () => { setState(prev => ({ ...prev, currentTime: 0 })) } const handleZoomChange = (value: number) => { setState(prev => ({ ...prev, zoom: value })) } const handleDurationChange = (duration: number) => { setState(prev => ({ ...prev, duration })) } const handleVerticalScroll = useCallback(() => { if (trackControlsRef.current && sequencerCanvasRef.current) { const canvasScrollTop = sequencerCanvasRef.current.scrollTop if (Math.abs(trackControlsRef.current.scrollTop - canvasScrollTop) > 1) { trackControlsRef.current.scrollTop = canvasScrollTop } } }, []) // Simple playhead animation useEffect(() => { if (state.isPlaying) { const interval = setInterval(() => { setState(prev => { const newTime = prev.currentTime + 0.1 if (newTime >= prev.duration) { return { ...prev, currentTime: prev.duration, isPlaying: false } } return { ...prev, currentTime: newTime } }) }, 100) return () => clearInterval(interval) } }, [state.isPlaying, state.duration]) return (
{/* Simple Header */}
Home / Sequencer
{/* Main Content */}
{/* Left Sidebar - Sound Library */}
{/* Center Content - Tracks */}
{/* Timeline Controls */}
{/* Track Area */}
{/* Track Controls */}
{/* Sequencer Canvas */}
) }