feat: convert duration and startTime to milliseconds in Sequencer components for consistency

This commit is contained in:
JSC
2025-09-03 20:22:59 +02:00
parent 1ba6f23999
commit 37c932fe75
3 changed files with 47 additions and 44 deletions

View File

@@ -54,8 +54,8 @@ function PlacedSoundItem({ sound, zoom, trackId, onRemove }: PlacedSoundItemProp
transform: `translate3d(${transform.x}px, ${transform.y}px, 0)`, transform: `translate3d(${transform.x}px, ${transform.y}px, 0)`,
} : undefined } : undefined
const width = sound.duration * zoom const width = (sound.duration / 1000) * zoom // Convert ms to seconds for zoom calculation
const left = sound.startTime * zoom const left = (sound.startTime / 1000) * zoom // Convert ms to seconds for zoom calculation
const formatTime = (seconds: number): string => { const formatTime = (seconds: number): string => {
const mins = Math.floor(seconds / 60) const mins = Math.floor(seconds / 60)
@@ -84,7 +84,7 @@ function PlacedSoundItem({ sound, zoom, trackId, onRemove }: PlacedSoundItemProp
group transition-colors group transition-colors
${isDragging ? 'opacity-50 z-10' : 'z-0'} ${isDragging ? 'opacity-50 z-10' : 'z-0'}
`} `}
title={`${sound.name} (${formatTime(sound.duration)})`} title={`${sound.name} (${formatTime(sound.duration / 1000)})`}
> >
<div className="flex items-center gap-1 min-w-0 flex-1"> <div className="flex items-center gap-1 min-w-0 flex-1">
<Volume2 className="h-3 w-3 flex-shrink-0 text-primary" /> <Volume2 className="h-3 w-3 flex-shrink-0 text-primary" />
@@ -110,8 +110,8 @@ function PlacedSoundItem({ sound, zoom, trackId, onRemove }: PlacedSoundItemProp
} }
function TrackRow({ track, duration, zoom, isPlaying, currentTime, draggedItem, dragOverInfo, onRemoveSound }: TrackRowProps) { function TrackRow({ track, duration, zoom, isPlaying, currentTime, draggedItem, dragOverInfo, onRemoveSound }: TrackRowProps) {
const totalWidth = duration * zoom const totalWidth = (duration / 1000) * zoom // Convert ms to seconds for zoom calculation
const playheadPosition = currentTime * zoom const playheadPosition = (currentTime / 1000) * zoom // Convert ms to seconds for zoom calculation
const { isOver, setNodeRef: setDropRef } = useDroppable({ const { isOver, setNodeRef: setDropRef } = useDroppable({
id: `track-${track.id}`, id: `track-${track.id}`,
@@ -139,7 +139,7 @@ function TrackRow({ track, duration, zoom, isPlaying, currentTime, draggedItem,
> >
{/* Grid lines for time markers */} {/* Grid lines for time markers */}
<div className="absolute inset-0 pointer-events-none"> <div className="absolute inset-0 pointer-events-none">
{Array.from({ length: Math.ceil(duration) + 1 }).map((_, i) => ( {Array.from({ length: Math.ceil(duration / 1000) + 1 }).map((_, i) => (
<div <div
key={i} key={i}
className="absolute top-0 bottom-0 w-px bg-border/30" className="absolute top-0 bottom-0 w-px bg-border/30"
@@ -147,7 +147,7 @@ function TrackRow({ track, duration, zoom, isPlaying, currentTime, draggedItem,
/> />
))} ))}
{/* Major grid lines every 10 seconds */} {/* Major grid lines every 10 seconds */}
{Array.from({ length: Math.floor(duration / 10) + 1 }).map((_, i) => ( {Array.from({ length: Math.floor(duration / 10000) + 1 }).map((_, i) => (
<div <div
key={`major-${i}`} key={`major-${i}`}
className="absolute top-0 bottom-0 w-px bg-border/60" className="absolute top-0 bottom-0 w-px bg-border/60"
@@ -158,12 +158,14 @@ function TrackRow({ track, duration, zoom, isPlaying, currentTime, draggedItem,
{/* Precise drag preview */} {/* Precise drag preview */}
{draggedItem && dragOverInfo && dragOverInfo.trackId === track.id && (() => { {draggedItem && dragOverInfo && dragOverInfo.trackId === track.id && (() => {
const soundDuration = draggedItem.type === 'sound' const soundDurationMs = draggedItem.type === 'sound'
? draggedItem.sound.duration / 1000 // Convert ms to seconds ? draggedItem.sound.duration // Already in ms
: draggedItem.duration : draggedItem.duration // Already in ms
const startTime = dragOverInfo.x / zoom const soundDurationSeconds = soundDurationMs / 1000
const endTime = startTime + soundDuration const startTimeSeconds = dragOverInfo.x / zoom
const isValidPosition = startTime >= 0 && endTime <= duration const endTimeSeconds = startTimeSeconds + soundDurationSeconds
const durationSeconds = duration / 1000
const isValidPosition = startTimeSeconds >= 0 && endTimeSeconds <= durationSeconds
return ( return (
<div <div
@@ -174,7 +176,7 @@ function TrackRow({ track, duration, zoom, isPlaying, currentTime, draggedItem,
}`} }`}
style={{ style={{
left: `${Math.max(0, dragOverInfo.x)}px`, left: `${Math.max(0, dragOverInfo.x)}px`,
width: `${Math.max(60, soundDuration * zoom)}px`, width: `${Math.max(60, soundDurationSeconds * zoom)}px`,
}} }}
> >
<div className={`text-xs truncate font-medium ${ <div className={`text-xs truncate font-medium ${
@@ -224,7 +226,7 @@ export const SequencerCanvas = forwardRef<HTMLDivElement, SequencerCanvasProps>(
dragOverInfo, dragOverInfo,
onRemoveSound, onRemoveSound,
}, ref) => { }, ref) => {
const totalWidth = duration * zoom const totalWidth = (duration / 1000) * zoom // Convert ms to seconds for zoom calculation
const timelineRef = useRef<HTMLDivElement>(null) const timelineRef = useRef<HTMLDivElement>(null)
const containerRef = useRef<HTMLDivElement>(null) const containerRef = useRef<HTMLDivElement>(null)
@@ -262,7 +264,7 @@ export const SequencerCanvas = forwardRef<HTMLDivElement, SequencerCanvasProps>(
}} }}
> >
<div className="relative h-full" style={{ width: `${totalWidth}px` }}> <div className="relative h-full" style={{ width: `${totalWidth}px` }}>
{Array.from({ length: Math.ceil(duration) + 1 }).map((_, i) => ( {Array.from({ length: Math.ceil(duration / 1000) + 1 }).map((_, i) => (
<div key={i} className="absolute top-0 bottom-0" style={{ left: `${i * zoom}px` }}> <div key={i} className="absolute top-0 bottom-0" style={{ left: `${i * zoom}px` }}>
{/* Time markers */} {/* Time markers */}
{i % 5 === 0 && ( {i % 5 === 0 && (
@@ -283,7 +285,7 @@ export const SequencerCanvas = forwardRef<HTMLDivElement, SequencerCanvasProps>(
{isPlaying && ( {isPlaying && (
<div <div
className="absolute top-0 bottom-0 w-0.5 bg-red-500 z-30" className="absolute top-0 bottom-0 w-0.5 bg-red-500 z-30"
style={{ left: `${currentTime * zoom}px` }} style={{ left: `${(currentTime / 1000) * zoom}px` }}
/> />
)} )}
</div> </div>

View File

@@ -6,9 +6,9 @@ import { Minus, Plus, ZoomIn, ZoomOut } from 'lucide-react'
import { useState } from 'react' import { useState } from 'react'
interface TimelineControlsProps { interface TimelineControlsProps {
duration: number duration: number // in milliseconds
zoom: number zoom: number
onDurationChange: (duration: number) => void onDurationChange: (duration: number) => void // expects milliseconds
onZoomChange: (zoom: number) => void onZoomChange: (zoom: number) => void
minZoom: number minZoom: number
maxZoom: number maxZoom: number
@@ -22,33 +22,34 @@ export function TimelineControls({
minZoom, minZoom,
maxZoom, maxZoom,
}: TimelineControlsProps) { }: TimelineControlsProps) {
const [durationInput, setDurationInput] = useState(duration.toString()) const durationInSeconds = duration / 1000
const [durationInput, setDurationInput] = useState(durationInSeconds.toString())
const handleDurationInputChange = (value: string) => { const handleDurationInputChange = (value: string) => {
setDurationInput(value) setDurationInput(value)
const numValue = parseFloat(value) const numValue = parseFloat(value)
if (!isNaN(numValue) && numValue > 0 && numValue <= 600) { // Max 10 minutes if (!isNaN(numValue) && numValue > 0 && numValue <= 600) { // Max 10 minutes
onDurationChange(numValue) onDurationChange(numValue * 1000) // Convert to milliseconds
} }
} }
const handleDurationInputBlur = () => { const handleDurationInputBlur = () => {
const numValue = parseFloat(durationInput) const numValue = parseFloat(durationInput)
if (isNaN(numValue) || numValue <= 0) { if (isNaN(numValue) || numValue <= 0) {
setDurationInput(duration.toString()) setDurationInput(durationInSeconds.toString())
} }
} }
const increaseDuration = () => { const increaseDuration = () => {
const newDuration = Math.min(600, duration + 10) const newDurationSeconds = Math.min(600, durationInSeconds + 10)
onDurationChange(newDuration) onDurationChange(newDurationSeconds * 1000) // Convert to milliseconds
setDurationInput(newDuration.toString()) setDurationInput(newDurationSeconds.toString())
} }
const decreaseDuration = () => { const decreaseDuration = () => {
const newDuration = Math.max(10, duration - 10) const newDurationSeconds = Math.max(5, durationInSeconds - 10)
onDurationChange(newDuration) onDurationChange(newDurationSeconds * 1000) // Convert to milliseconds
setDurationInput(newDuration.toString()) setDurationInput(newDurationSeconds.toString())
} }
const increaseZoom = () => { const increaseZoom = () => {
@@ -102,7 +103,7 @@ export function TimelineControls({
</Button> </Button>
</div> </div>
<span className="text-sm text-muted-foreground"> <span className="text-sm text-muted-foreground">
seconds ({formatTime(duration)}) seconds ({formatTime(duration / 1000)})
</span> </span>
</div> </div>
@@ -148,7 +149,7 @@ export function TimelineControls({
{/* Timeline Info */} {/* Timeline Info */}
<div className="flex items-center gap-4 ml-auto text-sm text-muted-foreground"> <div className="flex items-center gap-4 ml-auto text-sm text-muted-foreground">
<div> <div>
Total width: {Math.round(duration * zoom)}px Total width: {Math.round((duration / 1000) * zoom)}px
</div> </div>
</div> </div>
</div> </div>

View File

@@ -15,20 +15,20 @@ export interface PlacedSound {
id: string id: string
soundId: number soundId: number
name: string name: string
duration: number // in seconds duration: number // in milliseconds
startTime: number // in seconds startTime: number // in milliseconds
trackId: string trackId: string
} }
interface SequencerState { interface SequencerState {
tracks: Track[] tracks: Track[]
duration: number duration: number // in milliseconds
zoom: number zoom: number // pixels per second
currentTime: number currentTime: number // in milliseconds
isPlaying: boolean isPlaying: boolean
} }
const INITIAL_DURATION = 30 // 30 seconds const INITIAL_DURATION = 30000 // 30 seconds in milliseconds
const INITIAL_ZOOM = 40 // 40 pixels per second const INITIAL_ZOOM = 40 // 40 pixels per second
const MIN_ZOOM = 10 const MIN_ZOOM = 10
const MAX_ZOOM = 200 const MAX_ZOOM = 200
@@ -113,15 +113,15 @@ export function SequencerPage() {
// Handle sound drop from library to track // Handle sound drop from library to track
if (dragData?.type === 'sound' && overData?.type === 'track') { if (dragData?.type === 'sound' && overData?.type === 'track') {
// Use precise drop position if available // Use precise drop position if available (convert from pixels to milliseconds)
let startTime = 0 let startTime = 0
if (dragOverInfo && dragOverInfo.trackId === overData.trackId) { if (dragOverInfo && dragOverInfo.trackId === overData.trackId) {
startTime = Math.max(0, dragOverInfo.x / state.zoom) startTime = Math.max(0, (dragOverInfo.x / state.zoom) * 1000) // Convert seconds to milliseconds
} }
const soundDuration = dragData.sound.duration / 1000 // Convert from ms to seconds const soundDuration = dragData.sound.duration // Already in milliseconds
// Restrict placement to within track duration // Restrict placement to within track duration (all in milliseconds)
const maxStartTime = Math.max(0, state.duration - soundDuration) const maxStartTime = Math.max(0, state.duration - soundDuration)
startTime = Math.min(startTime, maxStartTime) startTime = Math.min(startTime, maxStartTime)
@@ -149,13 +149,13 @@ export function SequencerPage() {
// Handle moving placed sounds within tracks // Handle moving placed sounds within tracks
if (dragData?.type === 'placed-sound' && overData?.type === 'track') { if (dragData?.type === 'placed-sound' && overData?.type === 'track') {
// Use precise drop position if available // Use precise drop position if available (convert from pixels to milliseconds)
let startTime = dragData.startTime || 0 let startTime = dragData.startTime || 0
if (dragOverInfo && dragOverInfo.trackId === overData.trackId) { if (dragOverInfo && dragOverInfo.trackId === overData.trackId) {
startTime = Math.max(0, dragOverInfo.x / state.zoom) startTime = Math.max(0, (dragOverInfo.x / state.zoom) * 1000) // Convert seconds to milliseconds
} }
// Restrict placement to within track duration // Restrict placement to within track duration (all in milliseconds)
const maxStartTime = Math.max(0, state.duration - dragData.duration) const maxStartTime = Math.max(0, state.duration - dragData.duration)
startTime = Math.min(startTime, maxStartTime) startTime = Math.min(startTime, maxStartTime)
@@ -287,7 +287,7 @@ export function SequencerPage() {
if (state.isPlaying) { if (state.isPlaying) {
const interval = setInterval(() => { const interval = setInterval(() => {
setState(prev => { setState(prev => {
const newTime = prev.currentTime + 0.1 const newTime = prev.currentTime + 100 // Add 100ms every 100ms
if (newTime >= prev.duration) { if (newTime >= prev.duration) {
return { ...prev, currentTime: prev.duration, isPlaying: false } return { ...prev, currentTime: prev.duration, isPlaying: false }
} }