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
@@ -198,21 +198,21 @@ describe('an Envelope in an instrument', () => {
|
||||
});
|
||||
});
|
||||
|
||||
it('moves a Pitch on top of the one the way has, a Gain in its factor, a band and a Mix', () => {
|
||||
it('moves a Pitch on top of the one the way has, a Gain in its factor, a Pan and a Mix', () => {
|
||||
const f = makeInstrument();
|
||||
const envelope = f.edit.addNode('InstrumentEnvelope', { from: -12, to: 0 });
|
||||
const pitch = f.edit.addNode('SoundFxPitch');
|
||||
const gain = f.edit.addNode('SoundFxGain');
|
||||
const eq = f.edit.addNode('SoundFxEq');
|
||||
const pan = f.edit.addNode('SoundFxPan');
|
||||
const mix = f.edit.addNode('SoundFxMix');
|
||||
f.edit.wire([
|
||||
[f.sample, 'out', pitch, 'in'],
|
||||
[envelope, 'value', pitch, 'semitones'],
|
||||
[pitch, 'out', gain, 'in'],
|
||||
[envelope, 'value', gain, 'gain'],
|
||||
[gain, 'out', eq, 'in'],
|
||||
[envelope, 'value', eq, 'mid'],
|
||||
[eq, 'out', mix, 'a'],
|
||||
[gain, 'out', pan, 'in'],
|
||||
[envelope, 'value', pan, 'pan'],
|
||||
[pan, 'out', mix, 'a'],
|
||||
[f.sample, 'out', mix, 'b'],
|
||||
[envelope, 'value', mix, 'mix'],
|
||||
[mix, 'out', f.output, 'in'],
|
||||
@@ -227,7 +227,8 @@ describe('an Envelope in an instrument', () => {
|
||||
expect(chain.a.pitchMods).toMatchObject([{ from: -12, to: 0 }]);
|
||||
expect(chain.a.stages).toMatchObject([
|
||||
{ kind: 'gain', mod: { from: 10 ** (-12 / 20), to: 1 } },
|
||||
{ kind: 'filter', frequency: 1000, mod: { from: -12, to: 0 } },
|
||||
// The Pan rests at "from" as well, held to -1..1.
|
||||
{ kind: 'pan', mod: { from: -1, to: 0 } },
|
||||
]);
|
||||
});
|
||||
|
||||
|
||||
@@ -307,7 +307,7 @@ describe('compiling a note', () => {
|
||||
f.instrument.graph.edges = f.instrument.graph.edges.filter((e) => e.to.node !== output.id);
|
||||
const byVelocity = f.edit.addNode('SoundFxGain');
|
||||
const byParam = f.edit.addNode('SoundFxGain');
|
||||
const byNote = f.edit.addNode('SoundFxEq');
|
||||
const byNote = f.edit.addNode('SoundFxPan');
|
||||
f.stack.execute(
|
||||
new ConnectCommand(
|
||||
f.instrument.id,
|
||||
@@ -320,21 +320,21 @@ describe('compiling a note', () => {
|
||||
[byVelocity, 'out', byParam, 'in'],
|
||||
[params, 'level', byParam, 'gain'],
|
||||
[byParam, 'out', byNote, 'in'],
|
||||
[notes, 'note', byNote, 'high'],
|
||||
[notes, 'note', byNote, 'pan'],
|
||||
[byNote, 'out', output, 'in'],
|
||||
]);
|
||||
const plan = compile(f, 62).layers[0]!;
|
||||
expect(plan.stages).toEqual([
|
||||
{ kind: 'gain', db: { value: 1, param: VELOCITY_PARAM, min: -120, max: 24 } },
|
||||
{ kind: 'gain', db: { value: -3, param: 'level', min: -120, max: 24 } },
|
||||
// The note is a number by now: 62, held to the band's row.
|
||||
{ kind: 'filter', filter: 'highshelf', frequency: 12000, gain: { value: 24 }, q: 1 },
|
||||
// The note is a number by now: 62, held to the Pan's row.
|
||||
{ kind: 'pan', pan: { value: 1 } },
|
||||
]);
|
||||
const chain = fxChainOf(plan, { [VELOCITY_PARAM]: -6, level: -9 });
|
||||
expect(chain.stages.map((s) => ('gain' in s ? s.gain : 0))).toEqual([
|
||||
expect(chain.stages.map((s) => (s.kind === 'pan' ? s.pan : 'gain' in s ? s.gain : 0))).toEqual([
|
||||
10 ** (-6 / 20),
|
||||
10 ** (-9 / 20),
|
||||
24,
|
||||
1,
|
||||
]);
|
||||
});
|
||||
|
||||
|
||||
+39
-28
@@ -80,6 +80,15 @@ function line(effects: { type: string; params?: PropertyBag }[]) {
|
||||
return { ...f, input, output };
|
||||
}
|
||||
|
||||
/** A shelf of a Parametric EQ as its node keeps it. */
|
||||
const shelf = (type: 'lowshelf' | 'highshelf', frequency: number, gain: number) => ({
|
||||
type,
|
||||
frequency,
|
||||
gain,
|
||||
q: 1,
|
||||
slope: 12,
|
||||
});
|
||||
|
||||
describe('the sound FX asset', () => {
|
||||
it('is not placeable, starts with an empty graph and is what the fx picker offers', () => {
|
||||
expect(isPlaceableType('soundfx')).toBe(false);
|
||||
@@ -128,7 +137,6 @@ describe('the sound FX asset', () => {
|
||||
'SoundFxOutput',
|
||||
'SoundFxGain',
|
||||
'SoundFxPitch',
|
||||
'SoundFxEq',
|
||||
'SoundFxParametricEq',
|
||||
'SoundFxFilter',
|
||||
'SoundFxMix',
|
||||
@@ -149,7 +157,6 @@ describe('the sound FX asset', () => {
|
||||
expect(ids()).toEqual([
|
||||
'SoundFxGain',
|
||||
'SoundFxPitch',
|
||||
'SoundFxEq',
|
||||
'SoundFxParametricEq',
|
||||
'SoundFxFilter',
|
||||
'SoundFxMix',
|
||||
@@ -194,24 +201,28 @@ describe('compiling a sound FX graph', () => {
|
||||
const f = line([
|
||||
{ type: 'SoundFxGain', params: { gain: -6 } },
|
||||
{ type: 'SoundFxPitch', params: { semitones: 12 } },
|
||||
{ type: 'SoundFxEq', params: { low: 6, high: -3 } },
|
||||
{
|
||||
type: 'SoundFxParametricEq',
|
||||
params: { bands: [shelf('lowshelf', 80, 6), shelf('highshelf', 12000, -3)] },
|
||||
},
|
||||
{ type: 'SoundFxPitch', params: { semitones: -5 } },
|
||||
]);
|
||||
const build = compileSoundFx(f.fx.graph);
|
||||
expect(build.effects).toBe(4);
|
||||
const chain = fxChainOf(build.compiled!);
|
||||
expect(pitchOf(chain)).toBe(7);
|
||||
expect(chain.stages).toHaveLength(3);
|
||||
expect(chain.stages[0]!.kind).toBe('gain');
|
||||
expect(gainOf(chain.stages[0])).toBeCloseTo(0.501, 3);
|
||||
expect(chain.stages.slice(1)).toEqual([
|
||||
{ kind: 'filter', filter: 'lowshelf', frequency: 80, gain: 6, q: 1 },
|
||||
{ kind: 'filter', filter: 'highshelf', frequency: 12000, gain: -3, q: 1 },
|
||||
const stages = chain.stages.filter((s) => s.kind !== 'tap');
|
||||
expect(stages).toHaveLength(3);
|
||||
expect(stages[0]!.kind).toBe('gain');
|
||||
expect(gainOf(stages[0])).toBeCloseTo(0.501, 3);
|
||||
expect(stages.slice(1)).toEqual([
|
||||
{ kind: 'pass', filter: 'lowshelf', stages: 1, cutoff: 80, q: 1, gain: 6 },
|
||||
{ kind: 'pass', filter: 'highshelf', stages: 1, cutoff: 12000, q: 1, gain: -3 },
|
||||
]);
|
||||
});
|
||||
|
||||
it('leaves out what does nothing and holds a value to its row', () => {
|
||||
const quiet = compileSoundFx(line([{ type: 'SoundFxGain' }, { type: 'SoundFxEq' }]).fx.graph);
|
||||
const quiet = compileSoundFx(line([{ type: 'SoundFxGain' }, { type: 'SoundFxPan' }]).fx.graph);
|
||||
expect(quiet.compiled).toEqual(EMPTY_PLAN);
|
||||
expect(quiet.effects).toBe(2);
|
||||
|
||||
@@ -232,21 +243,21 @@ describe('compiling a sound FX graph', () => {
|
||||
});
|
||||
|
||||
describe('a Mix in a sound FX graph (#316)', () => {
|
||||
/** Input → Pitch +12 → Mix a, Input → Gain -6 → Mix b, Mix → EQ → Output. */
|
||||
/** Input → Pitch +12 → Mix a, Input → Gain -6 → Mix b, Mix → Pan → Output. */
|
||||
function forked(mix: Record<string, number> = {}) {
|
||||
const f = makeFx();
|
||||
const input = f.edit.addNode('SoundFxInput');
|
||||
const pitch = f.edit.addNode('SoundFxPitch', { semitones: 12 });
|
||||
const gain = f.edit.addNode('SoundFxGain', { gain: -6 });
|
||||
const node = f.edit.addNode('SoundFxMix', mix);
|
||||
const eq = f.edit.addNode('SoundFxEq', { low: 6 });
|
||||
const pan = f.edit.addNode('SoundFxPan', { pan: 0.5 });
|
||||
const output = f.edit.addNode('SoundFxOutput');
|
||||
f.edit.connect(input, 'out', pitch, 'in');
|
||||
f.edit.connect(input, 'out', gain, 'in');
|
||||
f.edit.connect(pitch, 'out', node, 'a');
|
||||
f.edit.connect(gain, 'out', node, 'b');
|
||||
f.edit.connect(node, 'out', eq, 'in');
|
||||
f.edit.connect(eq, 'out', output, 'in');
|
||||
f.edit.connect(node, 'out', pan, 'in');
|
||||
f.edit.connect(pan, 'out', output, 'in');
|
||||
return { ...f, input, mix: node };
|
||||
}
|
||||
|
||||
@@ -258,17 +269,17 @@ describe('a Mix in a sound FX graph (#316)', () => {
|
||||
mix: 0.5,
|
||||
a: { pitch: 12, stages: [] },
|
||||
b: { pitch: 0, stages: [{ kind: 'gain' }] },
|
||||
stages: [{ kind: 'filter', filter: 'lowshelf', gain: 6 }],
|
||||
stages: [{ kind: 'pan', pan: 0.5 }],
|
||||
});
|
||||
});
|
||||
|
||||
it('is only the one sound at 0 or 1, with what follows the Mix', () => {
|
||||
expect(fxChainOf(compileSoundFx(forked({ mix: 0 }).fx.graph).compiled!)).toMatchObject({
|
||||
pitch: 12,
|
||||
stages: [{ kind: 'filter' }],
|
||||
stages: [{ kind: 'pan' }],
|
||||
});
|
||||
const b = fxChainOf(compileSoundFx(forked({ mix: 5 }).fx.graph).compiled!);
|
||||
expect(b).toMatchObject({ pitch: 0, stages: [{ kind: 'gain' }, { kind: 'filter' }] });
|
||||
expect(b).toMatchObject({ pitch: 0, stages: [{ kind: 'gain' }, { kind: 'pan' }] });
|
||||
});
|
||||
|
||||
it('says its curve only where it is "Both at center", and is still one sound at 0 or 1', () => {
|
||||
@@ -294,15 +305,15 @@ describe('a Mix in a sound FX graph (#316)', () => {
|
||||
it('hands a Pitch behind it to both of its sounds', () => {
|
||||
const f = forked();
|
||||
const output = f.fx.graph.nodes.find((n) => n.type === 'SoundFxOutput')!;
|
||||
const eq = f.fx.graph.nodes.find((n) => n.type === 'SoundFxEq')!;
|
||||
const pan = f.fx.graph.nodes.find((n) => n.type === 'SoundFxPan')!;
|
||||
f.fx.graph.edges = f.fx.graph.edges.filter((e) => e.to.node !== output.id);
|
||||
const after = f.edit.addNode('SoundFxPitch', { semitones: -5 });
|
||||
f.edit.connect(eq, 'out', after, 'in');
|
||||
f.edit.connect(pan, 'out', after, 'in');
|
||||
f.edit.connect(after, 'out', output, 'in');
|
||||
expect(fxChainOf(compileSoundFx(f.fx.graph).compiled!)).toMatchObject({
|
||||
a: { pitch: 7 },
|
||||
b: { pitch: -5 },
|
||||
stages: [{ kind: 'filter' }],
|
||||
stages: [{ kind: 'pan' }],
|
||||
});
|
||||
});
|
||||
|
||||
@@ -626,7 +637,6 @@ describe('a Reverb in a sound FX graph (#328)', () => {
|
||||
describe('Bypass and Wet (#354)', () => {
|
||||
/** Every effect, and how it says its wet: a row of its own, or the "mix" it had. */
|
||||
const EFFECTS: Record<string, 'wet' | 'mix'> = {
|
||||
SoundFxEq: 'wet',
|
||||
SoundFxBitcrusher: 'mix',
|
||||
SoundFxCompressor: 'wet',
|
||||
SoundFxDelay: 'mix',
|
||||
@@ -665,12 +675,12 @@ describe('Bypass and Wet (#354)', () => {
|
||||
def.type === 'SoundFxMix',
|
||||
]);
|
||||
}
|
||||
expect(Object.keys(EFFECTS)).toHaveLength(16);
|
||||
expect(Object.keys(EFFECTS)).toHaveLength(15);
|
||||
});
|
||||
|
||||
it('bypassed, an effect is left out as if it were not there, and its node is drawn faded', () => {
|
||||
for (const type of Object.keys(EFFECTS)) {
|
||||
const on = { gain: -6, semitones: 5, low: 3, pan: 1, depth: 50, bands: [{ gain: 6 }] };
|
||||
const on = { gain: -6, semitones: 5, pan: 1, depth: 50, bands: [{ gain: 6 }] };
|
||||
expect([type, stagesOf(type, on).length > 0 || type === 'SoundFxPitch']).toEqual([type, true]);
|
||||
// The taps a Parametric EQ keeps do nothing to the sound (#374).
|
||||
const bypassed = compileSoundFx(line([{ type, params: { ...on, bypass: true } }]).fx.graph).compiled!;
|
||||
@@ -691,7 +701,8 @@ describe('Bypass and Wet (#354)', () => {
|
||||
{ kind: 'wet', wet: { value: 0.25 }, stages: [{ kind: 'gain', db: { value: -6 } }] },
|
||||
]);
|
||||
// Every band of an EQ in one blend, not each band on its own.
|
||||
const eq = stagesOf('SoundFxEq', { low: 6, high: -3, wet: 0.5 });
|
||||
const bands = [shelf('lowshelf', 80, 6), shelf('highshelf', 12000, -3)];
|
||||
const eq = stagesOf('SoundFxParametricEq', { bands, wet: 0.5 }).filter((s) => s.kind !== 'tap');
|
||||
expect(eq).toMatchObject([{ kind: 'wet', stages: [{ filter: 'lowshelf' }, { filter: 'highshelf' }] }]);
|
||||
// What came before the node stays outside the blend.
|
||||
const f = line([
|
||||
@@ -953,10 +964,10 @@ describe('the parameters of a sound FX', () => {
|
||||
expect(fxChainOf(soundFxBuildFor(typed.fx).compiled!)).toMatchObject({ pitch: 0, shifted: false });
|
||||
});
|
||||
|
||||
it('feed an EQ band as well', () => {
|
||||
const f = withParam('SoundFxEq', 'mid', [{ key: 'body', kind: 'number', default: 3 }]);
|
||||
const chain = fxChainOf(soundFxBuildFor(f.fx).compiled!, { body: -4 });
|
||||
expect(chain.stages).toEqual([{ kind: 'filter', filter: 'peaking', frequency: 1000, gain: -4, q: 1 }]);
|
||||
it('feed the cutoff of a Filter as well', () => {
|
||||
const f = withParam('SoundFxFilter', 'cutoff', [{ key: 'bright', kind: 'number', default: 500 }]);
|
||||
const chain = fxChainOf(soundFxBuildFor(f.fx).compiled!, { bright: 2000 });
|
||||
expect(chain.stages.filter((s) => s.kind !== 'tap')).toMatchObject([{ kind: 'pass', cutoff: 2000 }]);
|
||||
});
|
||||
|
||||
it('are kept with their defaults through a save, a default only on a number', () => {
|
||||
|
||||
Reference in new issue
Block a user