Skip to content
⌂ Home

EQ Guide

EQ is the equalizer: make bass, vocals, or treble sound better. You can turn it off at any time. The first rule is not “look professional”. It is to show whether the sound is being changed, whether it might clip, and whether turning it off really returns to the original tone.

If you are not looking for implementation details, start here:

  • DSP Beginner Guide: DSP, bit-perfect, Headroom, EQ, correction, and output safety in plain language.
  • DSP Simple Guide: Bass, Vocal, Air, Warm, Flat, and Simple / Pro choice for lightweight tuning.

EQ genuinely changes the sound. It is not a decorative curve on a page.

It should:

  • React audibly as soon as you drag a band.
  • Never destabilize playback.
  • Make it clear that with EQ on, output is no longer the untouched file.
  • Let you save, import, and roll back presets.
  • Offer a simple mode for beginners and pro controls when needed.

It should not:

  • Pretend to be a generic “sound quality enhancer”.
  • Be enabled by default and silently change the user’s sound.
  • Treat the Flat preset as “EQ off”.
  • Slow down playback for the sake of curve animations.
  • Mix VST, convolution, room correction, or an online preset marketplace into the first stage.

Current EQ core scope:

  • 10-band graphic / parametric hybrid EQ.
  • Band gain: -12 dB to +12 dB.
  • Preamp: -12 dB to +6 dB.
  • Band center frequency: 20 Hz to 20 kHz.
  • Fixed Q, currently defaulting to 1.0.
  • Enable / bypass.
  • Built-in presets.
  • User presets.
  • Curve visualization.
  • Clipping / headroom warning.
  • Native realtime DSP hook.

Default band frequencies:

31 Hz, 62 Hz, 125 Hz, 250 Hz, 500 Hz, 1 kHz, 2 kHz, 4 kHz, 8 kHz, 16 kHz

Future capabilities may be added, but must never squeeze into the audio hot path:

  • Full parametric bands.
  • Realtime analyzer.
  • Dynamic EQ.
  • Auto gain.
  • A/B compare persistence.
  • Per-output profile.
  • Per-headphone profile.

Explicitly out of current scope:

  • VST host.
  • Convolution / room correction.
  • AutoEQ database.
  • Network preset marketplace.
  • Tight coupling with lyrics, themes, or remote libraries.

Whenever EQ is enabled, Audio Status must express:

  • eqEnabled = true
  • dspActive = true
  • bitPerfectCandidate = false
  • bitPerfectDisabledReason = eq_enabled
  • The UI shows that the current output is not bit-perfect.

After EQ is disabled or bypass completes:

  • The native processor crossfades back to the dry signal.
  • Once bypass smoothing reaches zero, samples are no longer modified.
  • bitPerfectCandidate may recover only if no other DSP, resampling, ReplayGain, channel balance, or output mismatch remains.

The Flat preset is not "disabled". Flat only means every band is at 0 dB and preamp is 0 dB. If EQ is still enabled, the signal still passes through the DSP chain, and the UI must never label Flat as bit-perfect.

Decoded PCM
-> optional ReplayGain / gain stage
-> EQ Processor
preamp
band filters
smoothing
bypass crossfade
clipping risk detection
-> output bridge

Principles:

  • DSP state must feed into the audio status.
  • UI control changes go through the control path, never into PCM stdin.
  • The audio callback only reads realtime-safe parameters.
  • Preset file IO never enters the audio callback.

Related native files:

  • native/audio-engine/EqTypes.h
  • native/audio-engine/EqBand.h
  • native/audio-engine/EqProcessor.h
  • native/audio-engine/EqProcessor.cpp
  • native/audio-engine/EqPresetStore.h
  • native/audio-engine/EqPresetStore.cpp
  • native/audio-engine/EqMessageProtocol.h
  • native/audio-engine/EqMessageProtocol.cpp

EqProcessor is responsible for:

  • Per-channel biquad state.
  • Atomic target parameters.
  • Preamp smoothing.
  • Band gain smoothing.
  • Frequency smoothing.
  • Bypass crossfade.
  • Clipping risk detection.
  • NaN / Inf protection.

EqMessageProtocol is responsible for:

  • Parsing JSON-line messages on the control thread.
  • Validating parameters.
  • Updating atomic targets.
  • Never parsing JSON inside the audio callback.

The JUCE/native audio callback must never:

  • Allocate large objects.
  • Read or write JSON.
  • Read or write preset files.
  • Access Electron / React / IPC.
  • Wait on a mutex.
  • Make network requests.
  • Log to slow IO.
  • Rebuild all filter coefficients on every sample.

Parameter updates must:

  • Clamp invalid values.
  • Use atomic targets.
  • Smooth gain / preamp over roughly 25 ms.
  • Crossfade bypass over roughly 15 ms.
  • Never output NaN / Inf during fast dragging.
  • Recompute coefficients only after frequency drag smoothing.

The renderer controls EQ only through window.echo.eq.

Commands:

  • eq:get-state
  • eq:set-enabled
  • eq:set-band-gain
  • eq:set-band-frequency
  • eq:set-preamp
  • eq:set-preset
  • eq:reset
  • eq:list-presets
  • eq:save-preset
  • eq:import-preset
  • eq:export-preset
  • eq:delete-preset

The renderer must not:

  • Access audio buffers directly.
  • Control the native socket directly.
  • Write preset files directly.
  • Decide bit-perfect state on its own.

Control message examples:

{ "type": "eq:set-band-gain", "band": 3, "gainDb": 2.5 }
{ "type": "eq:set-band-frequency", "band": 3, "frequencyHz": 360 }

State example:

{
"type": "eq:state",
"enabled": true,
"preampDb": -3,
"bands": [
{ "frequencyHz": 31, "gainDb": 0, "q": 1 }
],
"dspActive": true,
"bitPerfectCandidate": false,
"bitPerfectDisabledReason": "eq_enabled"
}
{
"id": "bass-boost",
"name": "Bass Boost",
"preampDb": -2,
"bands": [
{ "frequencyHz": 31, "gainDb": 4, "q": 1 }
],
"createdAt": "built-in",
"updatedAt": "built-in",
"readonly": true
}

Suggested built-in presets:

  • Flat
  • Bass Boost
  • Vocal Clear
  • Treble Sparkle
  • Loudness
  • Night
  • Headphone Warm
  • Anime / J-Pop
  • Rock
  • Classical

Rules:

  • Built-in presets are read-only.
  • User presets live in Electron userData.
  • Validate fields, ranges, and band counts when loading.
  • A malformed preset must never blank out the settings page.
  • Importing a preset with an existing id generates a new id instead of silently overwriting local tuning.
  • After deleting a user preset, fall back to a safe state.

The EQ UI should be layered:

For everyday users:

  • Master toggle.
  • Preset selector.
  • Preamp.
  • Headroom / clipping warning.
  • Reset.
  • Bit-perfect impact notice.

For advanced users:

  • Curve view.
  • Draggable band nodes.
  • Precise frequency / gain input.
  • Selected-band controls.
  • A/B compare.
  • Undo / redo.
  • Preset save / import / export / delete.

Always visible:

  • Whether EQ is enabled.
  • Whether it is currently bypassed.
  • Whether it currently affects bit-perfect.
  • Whether there is clipping risk.
  • Whether the current preset has unsaved modifications.

Do not fill the page with complex explanations. Everyday users only need to know: is the sound being changed right now, what is the risk, and how do I turn it off.

Curve interaction must stay stable:

  • Throttle messages while dragging.
  • Send the exact final value on release.
  • Keep band node sizes stable.
  • Show frequency and gain in tooltips.
  • Fast dragging must never cause UI stutter or a flood of native parameter updates.
  • Keyboard and input fields also allow precise adjustment.

The curve is only a control view, not the source of truth. The source of truth is the EQ state.

High-gain EQ can cause clipping.

The UI should:

  • Suggest lowering the preamp when risk appears.
  • Never silently modify a user preset unless auto gain is explicitly enabled.
  • Distinguish “may clip” from “clipping risk detected”.
  • Ship night, bass boost, and similar presets with sensible preamp defaults.

Native DSP tests should cover:

  • Disabled EQ returns the dry input exactly.
  • An enabled Flat preset is numerically transparent, but status still reports DSP active.
  • After high gain, a completed bypass crossfade returns to dry output.
  • Fast gain / frequency / preamp changes never output NaN / Inf.
  • Frequency clamping stays stable at the 20 Hz and 20 kHz boundaries.
  • Steady state does not recompute all biquads on every sample.

TypeScript / renderer tests should cover:

  • EqBridge input validation.
  • Preset persistence.
  • Malformed preset fallback.
  • UI toggles and preset operations.
  • Curve editing, undo/redo, and A/B.
  • Bit-perfect status disabled when EQ or channel balance is on.
  • Headroom / clipping-risk telemetry.

Available entry point:

npm run test:audio-engine

Documentation-only changes do not require these tests; run the relevant narrow tests only when changing native DSP or the bridge.

EQ may affect bit-perfect together with:

  • ReplayGain.
  • Preamp.
  • Volume.
  • Channel balance.
  • Resampling.
  • Speed / pitch.
  • Crossfade / automix.

Audio Status must merge reasons instead of showing only the last one. The UI may simplify the display, but diagnostics must expose the full reason list.

ECHO Next’s EQ should make sound adjustment more controllable, not make the audio chain more mysterious. Whenever EQ is on, the user should clearly know it changes the signal; whenever EQ is off, the system should truly return to a path that leaves samples untouched.