import { useEffect, useState } from 'react' import type { MorphPlayer } from '../morph' type Props = { active: boolean busy: boolean cycleHz: number setCycleHz: (hz: number) => void onStart: () => void onStop: () => void player: MorphPlayer | null error: string | null } const LOG_MIN = -2 // 0.01 Hz const LOG_MAX = 2 // 100 Hz const BOX_MIN = 0.001 const BOX_MAX = 1000 /** Round to three significant figures so slider positions give tidy numbers. */ const sig3 = (x: number) => Number(x.toPrecision(3)) const formatPeriod = (t: number) => { if (t >= 100) return `${t.toFixed(0)} s` if (t >= 10) return `${t.toFixed(1)} s` if (t >= 1) return `${t.toFixed(2)} s` return `${Number((t * 1000).toPrecision(3))} ms` } /** Controls for the continuous tone whose phases cycle between A and B. */ export function CyclePanel({ active, busy, cycleHz, setCycleHz, onStart, onStop, player, error }: Props) { const [pos, setPos] = useState(0) useEffect(() => { if (!active || !player) { setPos(0) return } return player.subscribe(setPos) }, [active, player]) const period = 1 / cycleHz return (

Continuous tone cycling between A and B

A steady tone whose harmonic phases glide smoothly from A's values to B's and back, following a raised cosine at the rate below. The amplitudes never change, so any change you hear is due to phase alone. Phase, amplitude, f0, and rate edits take effect while it plays.

{active ? ( ) : ( )}
A
B
{error &&
{error}
}
) }