/** * The add-effect shelf: Tone, the composite jobs, and the raw registry. * * Split out of `propertyPanelFxSection.tsx`'s `{adding ? (...) : null}` block — * the section owns the audition/chain plumbing this shelf drives, and passes it * straight through. */ import { getAudioFxDef, HF_AUDIO_FX, type HfAudioFxChain, type HfAudioFxGroup, } from "@hyperframes/core/audio-fx"; import { HF_AUDIO_FX_JOBS, HF_AUDIO_FX_JOB_TYPES, type HfAudioFxJob, } from "@hyperframes/core/audio-fx-jobs"; import { EFFECT_COPY } from "@hyperframes/core/audio-fx-copy"; const GROUP_ORDER: HfAudioFxGroup[] = ["filter", "dynamics", "nonlinear", "time"]; const GROUP_LABEL: Record = { filter: "Filters", dynamics: "Dynamics", nonlinear: "Non-linear", time: "Time", }; /** * The add menu, with the jobs standing in for the effect they are made of. * * `peaking` is not offered as itself: picking it is picking a machine and * leaving the real decision — which range — for afterwards. The jobs are that * decision, already made. See `audioFxJobs.ts`. * * Computed once at module scope, not per render: it has no dependency on props * or state, just the static effect registry. */ const GROUPED = GROUP_ORDER.map((g) => ({ group: g, defs: HF_AUDIO_FX.filter((d) => d.group === g && !HF_AUDIO_FX_JOB_TYPES.has(d.id)), jobs: HF_AUDIO_FX_JOBS.filter((job) => getAudioFxDef(job.type)?.group === g), })); export interface FxAddMenuProps { disabled?: boolean; analysing?: boolean; levelled?: boolean; auditioningLevel?: boolean; /** Measure this track and write the levelling lane. Absent when unavailable. */ onLevel?(): void; /** Take the levelling stage and its lane back out. */ onRemoveLevel?(): void; onEq(): void; onJob(job: HfAudioFxJob): void; onEffect(type: string): void; /** The shelf just picked something, or the levelling button did its own close. */ onClose(): void; /** Play a hypothetical chain without committing to it, or `null` to stop. */ audition(make: ((base: HfAudioFxChain) => HfAudioFxChain) | null): void; onAuditionLevel?(on: boolean): void; withJob(base: HfAudioFxChain, job: HfAudioFxJob): HfAudioFxChain; withEffect(base: HfAudioFxChain, type: string): HfAudioFxChain; } /** The shelf `adding` opens: Tone, the named jobs, and the raw effect registry. */ export function FxAddMenu({ disabled, analysing, levelled, auditioningLevel, onLevel, onRemoveLevel, onEq, onJob, onEffect, onClose, audition, onAuditionLevel, withJob, withEffect, }: FxAddMenuProps) { return (
{ audition(null); onAuditionLevel?.(false); }} // The keyboard's version of leaving. Tabbing between two entries fires // this and then the next one's focus, so it reverts and re-auditions. onBlur={() => { audition(null); onAuditionLevel?.(false); }} >
Tone {onLevel ? ( ) : null}
{GROUPED.map(({ group, defs, jobs }) => (
{GROUP_LABEL[group]} {jobs.map((job) => ( ))} {defs.map((d) => ( ))}
))}
); }