Files
suaveplan/packages/ui/react/react-audio/src/hooks/use-parameter/use-parameter.ts
2026-05-27 23:47:53 +01:00

88 lines
2.6 KiB
TypeScript

import type {
AudioFxPlugin,
ParameterCurve,
} from "@suaveplan/audio-fx-plugin-base";
import { useCallback, useSyncExternalStore } from "react";
import {
REACT_AUDIO_CODES,
ReactAudioError,
} from "../../error-codes/error-codes.js";
import { useProviderStateOrThrow } from "../use-provider-state.js";
export interface UseParameterResult {
readonly value: number;
set(value: number): void;
curve(curve: ParameterCurve): void;
scheduleAt(value: number, transportTimeSec: number): void;
}
/**
* Reads, writes, curves, and schedules a single plugin parameter.
*
* - `value` is sampled at the provider's `sampleRateHz` (default 60),
* matching the per-block parameter automation cadence to the UI's
* frame budget.
* - `set` calls `plugin.setParameter`.
* - `curve` calls `plugin.setParameterCurve`.
* - `scheduleAt` calls `plugin.setParameterAt`.
*
* Throws `REACT_AUDIO.UNKNOWN_PARAMETER` synchronously on first
* render when the supplied `paramId` is not present in the plugin's
* `parameterIndex`.
*/
export function useParameter(
plugin: AudioFxPlugin,
paramId: string
): UseParameterResult {
const { sampleRateHz } = useProviderStateOrThrow();
if (!plugin.parameterIndex.has(paramId)) {
throw new ReactAudioError(
REACT_AUDIO_CODES.UNKNOWN_PARAMETER,
"react-audio.unknown-parameter",
{
context: {
paramId,
pluginId: plugin.metadata.id,
available: Array.from(plugin.parameterIndex.keys()),
},
}
);
}
const subscribe = useCallback(
(listener: () => void) => {
const interval = Math.max(1, Math.floor(1000 / sampleRateHz));
const id = setInterval(() => {
listener();
}, interval);
return () => {
clearInterval(id);
};
},
[sampleRateHz]
);
const getSnapshot = useCallback(
() => plugin.getParameter(paramId),
[plugin, paramId]
);
const value = useSyncExternalStore(subscribe, getSnapshot, getSnapshot);
const set = useCallback<UseParameterResult["set"]>(
(v) => plugin.setParameter(paramId, v),
[plugin, paramId]
);
const curve = useCallback<UseParameterResult["curve"]>(
(c) => plugin.setParameterCurve(paramId, c),
[plugin, paramId]
);
const scheduleAt = useCallback<UseParameterResult["scheduleAt"]>(
(v, t) => plugin.setParameterAt(paramId, v, t),
[plugin, paramId]
);
return { value, set, curve, scheduleAt };
}