Replace the 5-Band-EQ with the Parametric EQ and remove it (#395)
The Parametric EQ does all the 5-Band-EQ did and more, so the editor keeps only one EQ. The old node was used in one project only, with nothing linked to its band inputs. - "Garden of Sacrifices" was converted once on the project file, not by a load migration (#137). Its 5 nodes are Parametric EQs now with the same filters: low shelf 80 Hz, peaks at 250 Hz, 1 kHz and 4 kHz with Q 1, high shelf 12 kHz. A band at 0 became no band. - Two values below -24 dB were held to -24, as the Parametric EQ goes no further. Both are in the asset "Delay" (high -60, low -36). The user will rework them by ear. - The write went through the API's own Store (lock, revision 20181 to 20182), so a tab with the old revision gets a 409 instead of overwriting it. - Removed with the node: its stage kind "filter" and the type FxFilter, which nothing else made, and the player's branch for it. A Pan is now the last branch of fxNodes, a Flanger that of stagesOf. - Tests that took the 5-Band-EQ as "some effect" use the Pan or the Parametric EQ now. The test for a parameter feeding a band now feeds the cutoff of a Filter, which had no such test yet. - A project that still names the type, such as an old snapshot of "Garden of Sacrifices", loads without those nodes. Unknown types were always left out on load. Tests: - Unit (stein2): soundfx 67, instrument 36, envelope 13, sound-library 127, all green - Counter-check: with the cutoff not taken from the parameter, only the new Filter test goes red - The converted project loads and compiles: Delay, Vocals and Drums play their bands (3+1, 4 and 5) - e2e 34 (atx): ALL PASSED, 136 checks, the palette check now looks for the Parametric EQ - Gate (atx): CHECK EXIT 0, 288 files, 4417 tests Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
cb3183227b
commit
8c53ce0cbe
7 files changed
+93
-129
No files matched your search
+14
-64
@@ -20,21 +20,17 @@ import { eqBandsOf, eqStagesOf, hasGain, isEqBandHeard, MAX_EQ_BANDS, type PassB
|
||||
|
||||
export { SOUND_FX_INPUT, SOUND_FX_OUTPUT };
|
||||
|
||||
/** The filter shapes the EQ uses – Web Audio's names, so the player hands them on as they are. */
|
||||
export type FxFilter = 'lowshelf' | 'peaking' | 'highshelf';
|
||||
|
||||
/** The shapes of a Filter (#335, #318), Web Audio's names. */
|
||||
export type FxPassFilter =
|
||||
'lowpass' | 'highpass' | 'bandpass' | 'notch' | 'lowshelf' | 'highshelf' | 'peaking' | 'allpass';
|
||||
|
||||
/**
|
||||
* One step a voice passes on its way out: a volume, one band of an equalizer, a filter, the volume an
|
||||
* One step a voice passes on its way out: a volume, a filter or a band of an EQ, the volume an
|
||||
* Envelope gives a note (#335), or an echo (#327). An echo's `damping` is already the cutoff of the
|
||||
* low-pass in its feedback, in Hz. A `mod` is an Envelope moving that number while the note plays.
|
||||
*/
|
||||
export type FxStage =
|
||||
| { kind: 'gain'; gain: number; mod?: FxMod }
|
||||
| { kind: 'filter'; filter: FxFilter; frequency: number; gain: number; q: number; mod?: FxMod }
|
||||
| FxPass
|
||||
| { kind: 'envelope'; envelope: FxEnvelope }
|
||||
| FxDelay
|
||||
@@ -298,7 +294,6 @@ export interface FxAmount {
|
||||
|
||||
export type FxStagePlan =
|
||||
| { kind: 'gain'; db: FxAmount }
|
||||
| { kind: 'filter'; filter: FxFilter; frequency: number; gain: FxAmount; q: number }
|
||||
| { kind: 'pass'; filter: FxPassFilter; stages: number; cutoff: FxAmount; q: FxAmount; gain: FxAmount }
|
||||
| { kind: 'envelope'; envelope: FxEnvelopePlan }
|
||||
| {
|
||||
@@ -588,43 +583,6 @@ export const SoundFxPitch: SoundFxNodeDef = effect(
|
||||
'own',
|
||||
);
|
||||
|
||||
/** The five bands of the EQ: a shelf at either end and three peaks between them. */
|
||||
const EQ_BANDS: readonly { key: string; label: string; filter: FxFilter; frequency: number }[] = [
|
||||
{ key: 'low', label: '80 Hz', filter: 'lowshelf', frequency: 80 },
|
||||
{ key: 'lowMid', label: '250 Hz', filter: 'peaking', frequency: 250 },
|
||||
{ key: 'mid', label: '1 kHz', filter: 'peaking', frequency: 1000 },
|
||||
{ key: 'highMid', label: '4 kHz', filter: 'peaking', frequency: 4000 },
|
||||
{ key: 'high', label: '12 kHz', filter: 'highshelf', frequency: 12000 },
|
||||
];
|
||||
|
||||
/** About an octave and a half wide: neighbouring peaks overlap, so no frequency falls between them. */
|
||||
const PEAK_Q = 1;
|
||||
const EQ_ROWS = EQ_BANDS.map((band) => numberRow(band.key, band.label, -60, 24, 0.5));
|
||||
|
||||
export const SoundFxEq: SoundFxNodeDef = effect(
|
||||
{
|
||||
type: 'SoundFxEq',
|
||||
label: '5-Band-EQ',
|
||||
category: 'sound',
|
||||
description:
|
||||
'A five-band equalizer: raises or lowers the lows (80 Hz), low mids (250 Hz), mids (1 kHz), high mids (4 kHz) and highs (12 kHz), each in decibels. A band at 0 does nothing.' +
|
||||
BLENDS,
|
||||
inputs: [audioIn, ...EQ_ROWS.map(followingIn)],
|
||||
outputs: [audioOut],
|
||||
params: [],
|
||||
emit: (sounds, amounts) => {
|
||||
const stages: FxStagePlan[] = [];
|
||||
EQ_BANDS.forEach((band, i) => {
|
||||
const gain = held(amounts[band.key], EQ_ROWS[i]!);
|
||||
if (!isNone(gain))
|
||||
stages.push({ kind: 'filter', filter: band.filter, frequency: band.frequency, gain, q: PEAK_Q });
|
||||
});
|
||||
return withStages(arriving(sounds), stages);
|
||||
},
|
||||
},
|
||||
'blend',
|
||||
);
|
||||
|
||||
export const SoundFxParametricEq: SoundFxNodeDef = effect(
|
||||
{
|
||||
type: 'SoundFxParametricEq',
|
||||
@@ -1326,7 +1284,6 @@ export const SOUND_FX_NODES: readonly SoundFxNodeDef[] = [
|
||||
SoundFxOutput,
|
||||
SoundFxGain,
|
||||
SoundFxPitch,
|
||||
SoundFxEq,
|
||||
SoundFxParametricEq,
|
||||
SoundFxFilter,
|
||||
SoundFxMix,
|
||||
@@ -1542,26 +1499,19 @@ function stagesOf(plan: readonly FxStagePlan[], params: FxParams, tempo: number)
|
||||
stages.push({ kind: 'pan', pan, ...(mod && { mod }) });
|
||||
continue;
|
||||
}
|
||||
if (stage.kind === 'flanger') {
|
||||
const offsetMod = modOf(stage.offset, params);
|
||||
const mixMod = modOf(stage.mix, params);
|
||||
stages.push({
|
||||
kind: 'flanger',
|
||||
offset: amountOf(stage.offset, params),
|
||||
sweep: amountOf(stage.sweep, params),
|
||||
rate: amountOf(stage.rate, params),
|
||||
depth: amountOf(stage.depth, params),
|
||||
feedback: amountOf(stage.feedback, params),
|
||||
mix: amountOf(stage.mix, params),
|
||||
...(offsetMod && { offsetMod }),
|
||||
...(mixMod && { mixMod }),
|
||||
});
|
||||
continue;
|
||||
}
|
||||
const gain = amountOf(stage.gain, params);
|
||||
const mod = modOf(stage.gain, params);
|
||||
if (gain !== 0 || stage.gain.param !== undefined || mod)
|
||||
stages.push({ ...stage, gain, ...(mod && { mod }) });
|
||||
const offsetMod = modOf(stage.offset, params);
|
||||
const mixMod = modOf(stage.mix, params);
|
||||
stages.push({
|
||||
kind: 'flanger',
|
||||
offset: amountOf(stage.offset, params),
|
||||
sweep: amountOf(stage.sweep, params),
|
||||
rate: amountOf(stage.rate, params),
|
||||
depth: amountOf(stage.depth, params),
|
||||
feedback: amountOf(stage.feedback, params),
|
||||
mix: amountOf(stage.mix, params),
|
||||
...(offsetMod && { offsetMod }),
|
||||
...(mixMod && { mixMod }),
|
||||
});
|
||||
}
|
||||
return stages;
|
||||
}
|
||||
|
||||
+6
-19
@@ -133,26 +133,13 @@ export function stageNodes(
|
||||
{ name: 'release', value: stage.release, even: 'ratio' },
|
||||
{ name: 'autoGain', value: stage.autoGain, even: 'value' },
|
||||
]);
|
||||
if (stage.kind === 'pan') {
|
||||
const panner = context.createStereoPanner();
|
||||
panner.pan.value = stage.mod ? 0 : stage.pan;
|
||||
if (stage.mod) follow?.(stage.mod, panner.pan, false);
|
||||
return {
|
||||
input: panner,
|
||||
output: panner,
|
||||
knobs: [{ param: stage.mod ? null : panner.pan, target: stage.pan, even: 'value' }],
|
||||
};
|
||||
}
|
||||
const filter = context.createBiquadFilter();
|
||||
filter.type = stage.filter;
|
||||
filter.frequency.value = stage.frequency;
|
||||
filter.gain.value = stage.mod ? 0 : stage.gain;
|
||||
filter.Q.value = stage.q;
|
||||
if (stage.mod) follow?.(stage.mod, filter.gain, false);
|
||||
const panner = context.createStereoPanner();
|
||||
panner.pan.value = stage.mod ? 0 : stage.pan;
|
||||
if (stage.mod) follow?.(stage.mod, panner.pan, false);
|
||||
return {
|
||||
input: filter,
|
||||
output: filter,
|
||||
knobs: [{ param: stage.mod ? null : filter.gain, target: stage.gain, even: 'value' }],
|
||||
input: panner,
|
||||
output: panner,
|
||||
knobs: [{ param: stage.mod ? null : panner.pan, target: stage.pan, even: 'value' }],
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
Reference in new issue
Block a user