Files
bruitiste/src/hooks/usePlaybackControl.ts

127 lines
4.3 KiB
TypeScript

import { useState, useRef, useEffect, useCallback } from 'react'
import { useStore } from '@nanostores/react'
import type { TileState } from '../types/tiles'
import type { SynthesisMode } from '../stores/synthesisMode'
import { PlaybackManager } from '../services/PlaybackManager'
import { engineSettings, effectSettings } from '../stores/settings'
import { getSampleRateFromIndex } from '../config/parameters'
import { DEFAULT_VARIABLES, LOOP_DURATION } from '../constants/defaults'
interface UsePlaybackControlProps {
mode: SynthesisMode
onPlaybackPositionUpdate?: (position: number) => void
}
export function usePlaybackControl({ mode, onPlaybackPositionUpdate }: UsePlaybackControlProps) {
const engineValues = useStore(engineSettings)
const effectValues = useStore(effectSettings)
const [playing, setPlaying] = useState<string | null>(null)
const [queued, setQueued] = useState<string | null>(null)
const [playbackPosition, setPlaybackPosition] = useState<number>(0)
const playbackManagerRef = useRef<PlaybackManager | null>(null)
const switchTimerRef = useRef<number | null>(null)
useEffect(() => {
if (playbackManagerRef.current) {
playbackManagerRef.current.setPlaybackPositionCallback((position) => {
setPlaybackPosition(position)
onPlaybackPositionUpdate?.(position)
})
}
}, [onPlaybackPositionUpdate])
const clearSwitchTimer = useCallback(() => {
if (switchTimerRef.current !== null) {
clearTimeout(switchTimerRef.current)
switchTimerRef.current = null
}
}, [])
const startSwitchTimer = useCallback((callback: () => void) => {
clearSwitchTimer()
switchTimerRef.current = window.setTimeout(callback, engineValues.loopCount * 1000)
}, [engineValues.loopCount, clearSwitchTimer])
useEffect(() => {
return () => clearSwitchTimer()
}, [clearSwitchTimer])
const play = useCallback(async (formula: string, id: string, tile?: TileState) => {
const sampleRate = getSampleRateFromIndex(engineValues.sampleRate)
const duration = LOOP_DURATION
if (!playbackManagerRef.current) {
playbackManagerRef.current = new PlaybackManager({ sampleRate, duration })
playbackManagerRef.current.setMode(mode)
} else {
await playbackManagerRef.current.updateOptions({ sampleRate, duration })
}
playbackManagerRef.current.stop()
playbackManagerRef.current.setEffects(effectValues)
playbackManagerRef.current.setVariables(
engineValues.a ?? DEFAULT_VARIABLES.a,
engineValues.b ?? DEFAULT_VARIABLES.b,
engineValues.c ?? DEFAULT_VARIABLES.c,
engineValues.d ?? DEFAULT_VARIABLES.d
)
playbackManagerRef.current.setPitch(engineValues.pitch ?? 1.0)
const fmPatch = mode === 'fm' ? JSON.parse(formula) : null
const lfoRates = fmPatch?.lfoRates || undefined
playbackManagerRef.current.setAlgorithm(engineValues.fmAlgorithm ?? 0, lfoRates)
playbackManagerRef.current.setFeedback(engineValues.fmFeedback ?? 0)
if (tile?.lfoConfigs) {
playbackManagerRef.current.setLFOConfig(0, tile.lfoConfigs.lfo1)
playbackManagerRef.current.setLFOConfig(1, tile.lfoConfigs.lfo2)
playbackManagerRef.current.setLFOConfig(2, tile.lfoConfigs.lfo3)
playbackManagerRef.current.setLFOConfig(3, tile.lfoConfigs.lfo4)
}
await playbackManagerRef.current.play(formula)
setPlaying(id)
setQueued(null)
}, [mode, engineValues, effectValues])
const stop = useCallback(() => {
clearSwitchTimer()
playbackManagerRef.current?.stop()
setPlaying(null)
setQueued(null)
setPlaybackPosition(0)
}, [clearSwitchTimer])
const queue = useCallback((id: string, callback: () => void) => {
setQueued(id)
startSwitchTimer(callback)
}, [startSwitchTimer])
const cancelQueue = useCallback(() => {
clearSwitchTimer()
setQueued(null)
}, [clearSwitchTimer])
const updateMode = useCallback((newMode: SynthesisMode) => {
if (playbackManagerRef.current) {
playbackManagerRef.current.setMode(newMode)
playbackManagerRef.current.setAlgorithm(engineValues.fmAlgorithm ?? 0)
playbackManagerRef.current.setFeedback(engineValues.fmFeedback ?? 0)
}
}, [engineValues.fmAlgorithm, engineValues.fmFeedback])
return {
playing,
queued,
playbackPosition,
playbackManager: playbackManagerRef,
play,
stop,
queue,
cancelQueue,
updateMode
}
}