Files
suaveplan/packages/media/audio-fx/audio-fx-plugin-spessasynth/src/parameters/parameters.ts
2026-05-27 23:47:53 +01:00

150 lines
4.0 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import type { PluginParameter } from "@suaveplan/audio-fx-plugin-base/base";
import {
SPESSASYNTH_CODES,
SpessaSynthError,
} from "../error-codes/error-codes.js";
export const CHANNEL_COUNT = 16;
export const MAX_CHANNEL_INDEX = CHANNEL_COUNT - 1;
/** Per-channel parameter kind. */
export type ChannelParamKind =
| "volume"
| "pan"
| "program"
| "reverbSend"
| "chorusSend";
/** Closed enum of per-channel kinds in declaration order. */
export const CHANNEL_PARAM_KINDS: ReadonlyArray<ChannelParamKind> = [
"volume",
"pan",
"program",
"reverbSend",
"chorusSend",
];
/** Build a deterministic parameter id from a channel + kind. */
export function channelParameterId(
channel: number,
kind: ChannelParamKind
): string {
return `channel/${channel}/${kind}`;
}
/** Master-gain parameter id. */
export const MASTER_GAIN_ID = "master/gain";
/**
* Build the full 16-channel × 5-kind + master parameter declaration
* the `AudioFxPlugin` constructor consumes. Every parameter is
* `automatable: true` so the host chain can drive it.
*/
export function buildParameterDeclarations(): ReadonlyArray<PluginParameter> {
const out: PluginParameter[] = [];
for (let ch = 0; ch < CHANNEL_COUNT; ch++) {
out.push({
id: channelParameterId(ch, "volume"),
name: `Ch ${ch + 1} Volume`,
value: 1,
min: 0,
max: 1,
default: 1,
unit: "ratio",
automatable: true,
});
out.push({
id: channelParameterId(ch, "pan"),
name: `Ch ${ch + 1} Pan`,
value: 0,
min: -1,
max: 1,
default: 0,
unit: "ratio",
automatable: true,
});
out.push({
id: channelParameterId(ch, "program"),
name: `Ch ${ch + 1} Program`,
value: 0,
min: 0,
max: 127,
default: 0,
unit: "enum",
automatable: false,
});
out.push({
id: channelParameterId(ch, "reverbSend"),
name: `Ch ${ch + 1} Reverb Send`,
value: 0,
min: 0,
max: 1,
default: 0,
unit: "ratio",
automatable: true,
});
out.push({
id: channelParameterId(ch, "chorusSend"),
name: `Ch ${ch + 1} Chorus Send`,
value: 0,
min: 0,
max: 1,
default: 0,
unit: "ratio",
automatable: true,
});
}
out.push({
id: MASTER_GAIN_ID,
name: "Master Gain",
value: 1,
min: 0,
max: 4,
default: 1,
unit: "ratio",
automatable: true,
});
return out;
}
/**
* Parse a `channel/<n>/<kind>` parameter id into its components. Returns
* `undefined` for the master id or any unrecognised pattern; the caller
* decides whether that's an error.
*/
export function parseChannelParameterId(
id: string
): { channel: number; kind: ChannelParamKind } | undefined {
if (!id.startsWith("channel/")) return undefined;
const rest = id.slice("channel/".length);
const slash = rest.indexOf("/");
if (slash < 0) return undefined;
const channelStr = rest.slice(0, slash);
const kind = rest.slice(slash + 1) as ChannelParamKind;
const channel = Number.parseInt(channelStr, 10);
if (
!Number.isInteger(channel) ||
channel < 0 ||
channel > MAX_CHANNEL_INDEX
) {
return undefined;
}
if (!CHANNEL_PARAM_KINDS.includes(kind)) return undefined;
return { channel, kind };
}
/** Throws `SPESSASYNTH.INVALID_CHANNEL` when channel is out of range. */
export function assertChannel(channel: number): void {
if (
!Number.isInteger(channel) ||
channel < 0 ||
channel > MAX_CHANNEL_INDEX
) {
throw new SpessaSynthError(
SPESSASYNTH_CODES.INVALID_CHANNEL,
"spessasynth.invalid-channel",
{ context: { channel } }
);
}
}