127 lines
4.3 KiB
TypeScript
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
|
|
}
|
|
}
|