feat: convert duration and startTime to milliseconds in Sequencer components for consistency
This commit is contained in:
@@ -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>
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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 }
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user