diff --git a/src/components/sequencer/SequencerCanvas.tsx b/src/components/sequencer/SequencerCanvas.tsx
index 8b4f5fc..d623489 100644
--- a/src/components/sequencer/SequencerCanvas.tsx
+++ b/src/components/sequencer/SequencerCanvas.tsx
@@ -157,26 +157,38 @@ function TrackRow({ track, duration, zoom, isPlaying, currentTime, draggedItem,
{/* Precise drag preview */}
- {draggedItem && dragOverInfo && dragOverInfo.trackId === track.id && (
-
-
- {draggedItem.type === 'sound'
- ? (draggedItem.sound.name || draggedItem.sound.filename)
- : draggedItem.name
- }
+ {draggedItem && dragOverInfo && dragOverInfo.trackId === track.id && (() => {
+ const soundDuration = draggedItem.type === 'sound'
+ ? draggedItem.sound.duration / 1000 // Convert ms to seconds
+ : draggedItem.duration
+ const startTime = dragOverInfo.x / zoom
+ const endTime = startTime + soundDuration
+ const isValidPosition = startTime >= 0 && endTime <= duration
+
+ return (
+
+
+ {draggedItem.type === 'sound'
+ ? (draggedItem.sound.name || draggedItem.sound.filename)
+ : draggedItem.name
+ }
+ {!isValidPosition && ' (Invalid)'}
+
-
- )}
+ )
+ })()}
{/* Playhead */}
{isPlaying && (
diff --git a/src/components/sequencer/SoundLibrary.tsx b/src/components/sequencer/SoundLibrary.tsx
index a22d014..051b88a 100644
--- a/src/components/sequencer/SoundLibrary.tsx
+++ b/src/components/sequencer/SoundLibrary.tsx
@@ -21,6 +21,8 @@ import {
} from 'lucide-react'
import { useCallback, useEffect, useState } from 'react'
import { toast } from 'sonner'
+import { formatDuration } from '@/utils/format-duration'
+import { formatSize } from '@/utils/format-size'
interface DraggableSoundProps {
sound: Sound
@@ -43,18 +45,6 @@ function DraggableSound({ sound }: DraggableSoundProps) {
// Don't apply transform to prevent layout shift - DragOverlay handles the visual feedback
const style = undefined
- const formatDuration = (ms: number): string => {
- const seconds = Math.floor(ms / 1000)
- const mins = Math.floor(seconds / 60)
- const secs = seconds % 60
- return `${mins}:${secs.toString().padStart(2, '0')}`
- }
-
- const formatFileSize = (bytes: number): string => {
- const mb = bytes / (1024 * 1024)
- return `${mb.toFixed(1)}MB`
- }
-
return (
{formatDuration(sound.duration)}
•
- {formatFileSize(sound.size)}
+ {formatSize(sound.size)}
{sound.play_count > 0 && (
<>
•
diff --git a/src/pages/SequencerPage.tsx b/src/pages/SequencerPage.tsx
index f5674ea..1c3f1c8 100644
--- a/src/pages/SequencerPage.tsx
+++ b/src/pages/SequencerPage.tsx
@@ -119,85 +119,89 @@ export function SequencerPage() {
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,
- }
+ const soundDuration = dragData.sound.duration / 1000 // Convert from ms to seconds
+
+ // Restrict placement to within track duration
+ const maxStartTime = Math.max(0, state.duration - soundDuration)
+ startTime = Math.min(startTime, maxStartTime)
- setState(prev => ({
- ...prev,
- tracks: prev.tracks.map(track =>
- track.id === overData.trackId
- ? { ...track, sounds: [...track.sounds, newPlacedSound] }
- : track
- ),
- }))
+ // Only proceed if the sound can fit within the track
+ if (startTime >= 0 && startTime + soundDuration <= state.duration) {
+ const newPlacedSound: PlacedSound = {
+ id: `placed-${Date.now()}-${Math.random()}`,
+ soundId: dragData.sound.id,
+ name: dragData.sound.name || dragData.sound.filename,
+ duration: soundDuration,
+ 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)
+ // Restrict placement to within track duration
+ const maxStartTime = Math.max(0, state.duration - dragData.duration)
+ startTime = Math.min(startTime, maxStartTime)
- 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,
+ // Only proceed if the sound can fit within the track
+ if (startTime >= 0 && startTime + dragData.duration <= state.duration) {
+ const sourceTrackId = dragData.trackId
+ const targetTrackId = overData.trackId
+
+ setState(prev => ({
+ ...prev,
+ tracks: prev.tracks.map(track => {
+ if (track.id === sourceTrackId && sourceTrackId === targetTrackId) {
+ // Moving within the same track - just update position
+ 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)
+ return {
+ ...track,
+ sounds: track.sounds.filter(s => s.id !== dragData.id),
+ }
+ } else if (track.id === targetTrackId) {
+ // Add to target track (different track move)
+ const updatedSound: PlacedSound = {
+ ...dragData,
+ startTime,
+ trackId: targetTrackId,
+ }
+ return {
+ ...track,
+ sounds: [...track.sounds, updatedSound],
+ }
}
- 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
- }),
- }))
+ return track
+ }),
+ }))
+ }
}
// Clear state