Skip to content

Commit

Permalink
fix playhead width
Browse files Browse the repository at this point in the history
  • Loading branch information
jbilcke-hf committed Sep 5, 2024
1 parent f304ad7 commit ef82923
Show file tree
Hide file tree
Showing 18 changed files with 162 additions and 65 deletions.
47 changes: 47 additions & 0 deletions packages/app/src/components/core/timeline/Slider.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import * as React from 'react'
import * as SliderPrimitive from '@radix-ui/react-slider'
import { cn } from '@/lib/utils'

const Slider = React.forwardRef<
React.ElementRef<typeof SliderPrimitive.Root>,
React.ComponentPropsWithoutRef<typeof SliderPrimitive.Root> & {
trackClass?: string
rangeClass?: string
thumbClass?: string
}
>(({ className, trackClass, rangeClass, thumbClass, ...props }, ref) => (
<SliderPrimitive.Root
ref={ref}
className={cn(
'relative flex touch-none select-none items-center',
className
)}
{...props}
>
<SliderPrimitive.Track
className={cn(
`relative grow overflow-hidden rounded border border-neutral-900 bg-neutral-100 dark:bg-neutral-700/70`,
`h-1.5 w-full`,
trackClass
)}
>
<SliderPrimitive.Range
className={cn(
`absolute bg-neutral-900/80 dark:bg-neutral-50/40`,
`h-full rounded-none`,
rangeClass
)}
/>
</SliderPrimitive.Track>
<SliderPrimitive.Thumb
className={cn(
`block border border-neutral-400 bg-neutral-400 shadow-lg ring-offset-white transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-neutral-950 focus-visible:ring-offset-1 disabled:pointer-events-none disabled:opacity-50 dark:border-neutral-400 dark:bg-neutral-400 dark:ring-offset-neutral-400 dark:focus-visible:ring-neutral-400`,
`h-3 w-3 rounded-full`,
thumbClass
)}
/>
</SliderPrimitive.Root>
))
Slider.displayName = 'Slider'

export { Slider }
37 changes: 37 additions & 0 deletions packages/app/src/components/core/timeline/TimelineZoom.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import * as React from 'react'
import { BiSolidZoomIn, BiSolidZoomOut } from 'react-icons/bi'
import { useTimeline } from '@aitube/timeline'

import { cn } from '@/lib/utils'

import { Slider } from './Slider'
import { useDebounceFn } from '@/lib/hooks'

export function TimelineZoom() {
const setHorizontalZoomLevel = useTimeline((s) => s.setHorizontalZoomLevel)
const minHorizontalZoomLevel = useTimeline((s) => s.minHorizontalZoomLevel)
const maxHorizontalZoomLevel = useTimeline((s) => s.maxHorizontalZoomLevel)

const onValueChange = useDebounceFn((values: number[]) => {
setHorizontalZoomLevel(values[0])
}, 250)

return (
<div
className={cn(
`flex w-40 flex-row items-center justify-center`,
`space-x-1.5 text-xl text-white/40`
)}
>
<BiSolidZoomIn className="" />
<Slider
className={cn('w-full')}
orientation="horizontal"
min={minHorizontalZoomLevel}
max={maxHorizontalZoomLevel}
onValueChange={onValueChange}
/>
<BiSolidZoomOut className="" />
</div>
)
}
6 changes: 3 additions & 3 deletions packages/app/src/components/core/waveform/useWaveform.ts
Original file line number Diff line number Diff line change
Expand Up @@ -73,14 +73,14 @@ export function useWaveform({

const cachedImage = cache[cacheKey]

console.log(`redraw() for cache key "${cacheKey}"`)
// console.log(`redraw() for cache key "${cacheKey}"`)
if (canvasRenderingContext) {
if (cachedImage) {
console.log('redraw() we have a cached image:', cachedImage)
// console.log('redraw() we have a cached image:', cachedImage)
// If we have cached data
canvasRenderingContext.putImageData(cachedImage, 0, 0)
} else {
console.log('redraw() no cached image')
// console.log('redraw() no cached image')
const middle = mode === WaveformRenderingMode.MONO ? height : height / 2
const channelData = audioBuffer.getChannelData(0)
const step = Math.ceil(channelData.length / (width * zoom))
Expand Down
2 changes: 1 addition & 1 deletion packages/app/src/components/monitor/VUMeter/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -130,4 +130,4 @@ export function VUMeter({ className = 'w-24 h-full' }) {
</div>
</div>
)
}
}
2 changes: 1 addition & 1 deletion packages/app/src/components/monitor/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ export function Monitor() {
`flex h-full flex-col items-center justify-between overflow-hidden`
)}
>
<VUMeter className={cn(`mt-12 ml-1 mr-2 h-[80%] w-8`)} />
<VUMeter className={cn(`ml-1 mr-2 mt-12 h-[80%] w-8`)} />
</div>
</div>
)
Expand Down
2 changes: 2 additions & 0 deletions packages/app/src/components/toolbars/bottom-bar/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { Metrics } from './metrics'
import { APP_REVISION } from '@/lib/core/constants'
import { Tasks } from './tasks'
import { useTimeline } from '@aitube/timeline'
import { TimelineZoom } from '@/components/core/timeline/TimelineZoom'

export function BottomToolbar() {
const theme = useTheme()
Expand Down Expand Up @@ -57,6 +58,7 @@ export function BottomToolbar() {
</div>
</div>
<div className="flex flex-row space-x-6">
<TimelineZoom />
<Tasks />
</div>
</div>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,12 +36,13 @@ export function Tasks() {
<PopoverTrigger
className={cn(
`group transition-all duration-100 ease-out`,
`flex flex-row items-center justify-center pt-[1px]`,
nbRunningBackgroundTasks
? 'opacity-80 hover:opacity-100'
: 'opacity-70 hover:opacity-90'
)}
>
<span className="mr-0.5 text-white/55">
<span className="pr-0.5 text-white/55">
{nbRunningBackgroundTasks || 'no'}
</span>
<span className="text-white/40">pending tasks</span>
Expand Down
2 changes: 1 addition & 1 deletion packages/app/src/lib/core/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
export const HARD_LIMIT_NB_MAX_ASSETS_TO_GENERATE_IN_PARALLEL = 32

export const APP_NAME = 'Clapper.app'
export const APP_REVISION = '20240904+0113'
export const APP_REVISION = '20240905+1543'

export const APP_DOMAIN = 'Clapper.app'
export const APP_LINK = 'https://clapper.app'
Expand Down
1 change: 1 addition & 0 deletions packages/app/src/lib/hooks/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
export { useDebounce } from './useDebounce'
export { useDebounceFn } from './useDebounceFn'
export { useOpenFilePicker } from './useOpenFilePicker'
export { useFullscreenStatus } from './useFullscreenStatus'
export { useQueryStringParams } from './useQueryStringParams'
Expand Down
34 changes: 34 additions & 0 deletions packages/app/src/lib/hooks/useDebounceFn.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import { useRef, useEffect } from 'react'

type Timer = ReturnType<typeof setTimeout>
type SomeFunction = (...args: any[]) => void
/**
*
* @param func The original, non debounced function (You can pass any number of args to it)
* @param delay The delay (in ms) for the function to return
* @returns The debounced function, which will run only if the debounced function has not been called in the last (delay) ms
*/

export function useDebounceFn<Func extends SomeFunction>(
func: Func,
delay = 1000
) {
const timer = useRef<Timer>()

useEffect(() => {
return () => {
if (!timer.current) return
clearTimeout(timer.current)
}
}, [])

const debouncedFunction = ((...args) => {
const newTimer = setTimeout(() => {
func(...args)
}, delay)
clearTimeout(timer.current)
timer.current = newTimer
}) as Func

return debouncedFunction
}
1 change: 0 additions & 1 deletion packages/app/src/services/assistant/useVoiceAssistant.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ import { useEffect } from 'react'

import { useMic } from '../mic/useMic'
import { useAssistant } from './useAssistant'
import { useDebounce } from '@/lib/hooks'

export function useVoiceAssistant() {
const processUserMessage = useAssistant((s) => s.processUserMessage)
Expand Down
1 change: 0 additions & 1 deletion packages/app/src/services/renderer/useRenderLoop.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@ import { useAudio } from '@/services/audio/useAudio'
import { useMonitor } from '../monitor/useMonitor'
import { useEffect, useRef } from 'react'
import { useSettings } from '../settings'
import { set } from 'date-fns'

/**
* Runs a rendering loop
Expand Down
34 changes: 8 additions & 26 deletions packages/timeline/src/components/timeline/Cells.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,54 +5,36 @@ import {
} from "@/hooks"

import { Cell } from "@/components/cells"
import { Suspense } from "react";

export function Cells() {
import { GRID_REFRESH_RATE_IN_MS } from "@/constants/grid";

// refresh rate for the grid (high value == delay before we see the "hidden" cells)
// this should be a fact of the number of segments,
// as this puts a strain on the rendering FPS
//
// another solution can also consist in rendering more hidden cells,
// to avoid having to re-compute
const refreshRateInMs = 50
export function Cells() {

// subscribe to changes in content width; height, as well as the container width
const contentHeight = useTimeline(s => s.contentHeight)
const containerWidth = useTimeline(s => s.containerWidth)

// note: this one is async, so it creates a delay
// we could cheat by detecting the cell width change and apply it
// faster on the current geometries
const { visibleSegments, loadedSegments } = useSegmentLoader({
refreshRateInMs,
const { loadedSegments } = useSegmentLoader({
refreshRateInMs: GRID_REFRESH_RATE_IN_MS,
});


/*
const [props, set] = useSpring(() => ({
pos: [0, 0, 0],
scale: [1, 1, 1],
rotation: [0, 0, 0],
config: { mass: 10, tension: 1000, friction: 300, precision: 0.00001 }
}))
*/

// console.log(`re-rendering <Cells> (${visibleSegments.length} strictly visible, ${loadedSegments.length} loaded in total)`)
console.log(`re-rendering <Cells> (${loadedSegments.length} loaded segments)`)

return (
<group position={[
0,
// height/2 is to shift the group above, to make it centered
// cellHeight/2 is to also take into account the height of a cell
// (baseCellHeight / 2) - (baseCellHeight / 2),
contentHeight / 2,
-5
]}>
{loadedSegments.map((s) =>
<Suspense key={s.id} fallback={<></>}><Cell
<Cell
key={s.id}
segment={s}
/>
</Suspense>
)}
</group>
);
Expand Down
22 changes: 15 additions & 7 deletions packages/timeline/src/components/timeline/Cursor.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,10 @@ import { useAnimationFrame, useTimeline } from "@/hooks"
import { useCursorGeometry } from "@/hooks/useCursorGeometry"
import { leftBarTrackScaleWidth } from "@/constants/themes"

const CURSOR_WIDTH_IN_PX = 2

const SPEED_RESOLUTION = 50 // px/sec
const MAX_SPEED = 200
const GRADIENT_EXPONENT = 3

export function Cursor() {
Expand Down Expand Up @@ -36,20 +38,22 @@ export function Cursor() {
const granularSpeed = Math.floor(speed / SPEED_RESOLUTION) * SPEED_RESOLUTION

if (granularSpeed !== lastSpeedRef.current) {
const maxSpeed = 200
const visibility = Math.min(granularSpeed / maxSpeed, 1)
const visibility = Math.min(granularSpeed / MAX_SPEED, 1)

lineMatRefs.current.forEach((mat, idx) => {
const t = idx / (lineMatRefs.current.length - 1)
const opacity = visibility * Math.pow(t, GRADIENT_EXPONENT)
const isLast = idx >= (lineMatRefs.current.length - 2)
mat.opacity = isLast ? 1.0 : opacity
const isAlwaysVisible = idx >= (lineMatRefs.current.length - CURSOR_WIDTH_IN_PX)
mat.opacity = isAlwaysVisible ? 1.0 : opacity
})

lastSpeedRef.current = granularSpeed
}

timelineCursor.scale.x = direction
// note: we need to avoid having a scale of 0
if (direction !== 0) {
timelineCursor.scale.x = direction
}
}

lastPositionRef.current = currentPosition
Expand All @@ -64,7 +68,7 @@ export function Cursor() {
{cursorGeometries.map((lineGeometry, idx) => {
const t = idx / (cursorGeometries.length - 1)
const opacity = Math.pow(t, GRADIENT_EXPONENT)
const isLast = idx >= (cursorGeometries.length - 2)
const isAlwaysVisible = idx >= (cursorGeometries.length - CURSOR_WIDTH_IN_PX)
return (
<line
// @ts-ignore-line
Expand All @@ -74,8 +78,12 @@ export function Cursor() {
ref={(ref) => { if (ref) lineMatRefs.current[idx] = ref }}
attach="material"
color={theme.playbackCursor.lineColor}

// note: in WebGL lines larger than 1 are not guaranteed to be
// supported by the browser, so let's assume it is always 1px
linewidth={1}
opacity={isLast ? 1.0 : opacity}

opacity={isAlwaysVisible ? 1.0 : opacity}
transparent
/>
</line>
Expand Down
21 changes: 0 additions & 21 deletions packages/timeline/src/components/timeline/CursorWeird.tsx

This file was deleted.

2 changes: 1 addition & 1 deletion packages/timeline/src/components/timeline/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
export { Cursor } from "./CursorWeird"
export { Cursor } from "./Cursor"
export { Timeline } from "./Timeline"
export { TopBarTimeScale } from "./TopBarTimeScale"
export { LeftBarTrackScale } from "./LeftBarTrackScale"
Expand Down
8 changes: 8 additions & 0 deletions packages/timeline/src/constants/grid.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,3 +11,11 @@ export const PROMPT_STEP_HEIGHT_IN_PX = 48
export const PREVIEW_STEP_HEIGHT_IN_PX = 3 * PROMPT_STEP_HEIGHT_IN_PX

export const NB_MAX_SHOTS = ((8 * 60 * 60) / 2) // 6 hours converted to seconds, and divided by 2 (a shot is about 2 sec)

// refresh rate for the grid (high value == delay before we see the "hidden" cells)
// this should be a factor of the number of segments,
// as this puts a strain on the rendering FPS
//
// another solution can also consist in rendering more hidden cells,
// to avoid having to re-compute
export const GRID_REFRESH_RATE_IN_MS = 25
2 changes: 1 addition & 1 deletion packages/timeline/src/constants/themes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { ClapTimelineTheme } from "@/types"
import { ClapSegmentCategoryColors } from "@/types/theme"

export const leftBarTrackScaleWidth = 120
export const topBarTimeScaleHeight = 40
export const topBarTimeScaleHeight = 34

export const baseClapSegmentCategoryColors: ClapSegmentCategoryColors = {
[ClapSegmentCategory.SPLAT]: { hue: 347, saturation: 30, lightness: 78.6 },
Expand Down

0 comments on commit ef82923

Please sign in to comment.