Build eqMac
KINDAreplaces $3/mosaves $36/yrback to the verdict
A system-wide audio processor whose difficulty is almost entirely in the platform layer: a virtual output device that survives device changes and sleep, a processing chain that must never add audible latency or artefacts, and a failure mode where a bug means the user's machine has no sound at all.
Before step 1
Everything below is assumed from the first step. Tick each one when you actually have it, not when you plan to.
- have readyfree if you own one
Why Core Audio taps require 14.2 and the audio server plugin interfaces have changed over recent versions. Older systems need a different approach entirely.
Get it Check the version before starting and decide the minimum you will support.
Verify
The system version is 14.2 or higher and the version is written down - installfree
Why An audio server plugin is a native bundle and cannot be built without them.
Get it Install from the App Store, then the command line tools.
Verify
A sample audio server plugin builds and loads - accountan annual fee
Why An unsigned driver does not load on a current system, and the symptom is the device simply never appearing.
Get it Enrol, create the certificates, and notarise a trivial build before writing the real driver. open ↗
Verify
A trivial signed and notarised bundle loads on a second machine - have readyfree if you have one
Why A broken audio driver can leave a machine silent, and debugging that on the machine you are working on is miserable.
Get it A second Mac, a virtual machine, or at minimum a separate user account and a known-good recovery path.
Verify
You can install, break and recover audio without losing your working environment - installfree
Why Every claim in this build, from bit-identical passthrough to the absence of clicks, is verified by recording rather than by listening.
Get it Any audio editor that shows a waveform and a spectrum, plus a loopback path to record your own output.
Verify
You can record output and inspect the waveform at a transition
Data model
Create these before the first phase that stores anything. Changing a table later is the expensive kind of change.
- presets(id, name, bands_json, preamp_db, created_at) · a preset is a filter chain - bands(preset_id, ordinal, kind, frequency_hz, gain_db, q) · kind is peaking, shelf or pass - devices(uid, name, is_default, sample_rate, channels, last_seen_at) - routes(id, source_device_uid, target_device_uid, active) · what goes where - state(key, value) · current preset, bypass, volume. Read by the driver, written by the interface - The driver reads coefficients, never the model. Filter design happens in the interface and only coefficients cross into the audio thread.
Environment variables
These go in a .env file the app reads at startup. The pack's .env.example is this table as a file · copy it, never commit the filled-in version.
| Variable | Needed | Example | Where the value comes from |
|---|---|---|---|
STATE_PATH | required | ~/Library/Application Support/eqmac/state.json | Presets and routing, read by the interface. The driver reads only coefficients. |
DEFAULT_PRESET | required | flat | Applied at startup. Flat is the safe default, since a boosted preset at login is unpleasant. |
RAMP_MS | required | 20 | How long a gain change takes. Too short clicks, too long feels unresponsive. |
FOLLOW_SYSTEM_DEFAULT | required | true | Whether to re-target when the system default changes, which is what unplugging headphones does. |
LOG_PATH | required | ~/Library/Logs/eqmac.log | Written by the interface only. The audio callback never logs. |
The build, in order
A virtual device the system will actually use
Your device appears in sound settings and audio passes through it unchanged.
User space, not a kernel extension. Kernel extensions are deprecated.
Files
driver/Device.cppProve the path before processing anything. Record both and compare.
Files
tests/passthrough.shBefore the device is ever default. A broken audio device is hard to undo by hand.
Files
scripts/uninstall.sh
done when · tick each as it passeswatch out- A virtual output device that fails to forward audio leaves the machine with no sound at all, and the user cannot easily work out why or undo it, especially since your interface may be the thing that is broken. Build and test the uninstaller in this phase, before the device is ever the default, and make the failure path fall back to the real device rather than to silence.
Filters that do not click
Equalisation applies with no audible artefact and no added latency.
The callback consumes coefficients. It never designs filters.
Files
src/biquad.tsdriver/Filter.cppAtomic swap of a coefficient block. No locks on the audio thread, ever.
Files
driver/State.cppAn instant gain change is a discontinuity and a discontinuity is a click.
Files
driver/Filter.cpp
done when · tick each as it passeswatch out- Anything in the audio callback that allocates, takes a lock or writes a log will eventually miss its deadline and produce a click, and it will do so under load rather than during testing. The callback touches pre-allocated memory and lock-free state only, and coefficient updates are published atomically from outside it.
Devices appearing and disappearing
Unplugging headphones does not break audio or lose your settings.
Arrival, departure and default changes. All three are routine.
Files
driver/Devices.cppLosing the target device should cost a moment of audio, not the session.
Files
driver/Route.cppTest it repeatedly. It is where audio software reliably breaks.
Files
tests/sleep-wake.md
done when · tick each as it passeswatch out- Holding a reference to an output device that is then unplugged gives a driver writing into a device that no longer exists, which on a good day is silence and on a bad day is a kernel-level audio stall requiring a restart. Watch for device changes and re-target, treating disappearance as routine rather than exceptional.
Presets worth switching between
Saved chains you can move between instantly, including a true bypass.
Crossfade or ramp. A gap between presets is as bad as a click.
Files
driver/Preset.cppCompute maximum chain gain. Digital clipping is worse than the problem.
Files
src/headroom.tssrc/headroom.test.tsBit-identical to no processing, not a flat curve. Verify by recording.
Files
driver/Bypass.cpp
done when · tick each as it passeswatch out- Boosting several bands without reducing the preamp clips the signal, and digital clipping sounds far worse than the problem the boost was meant to solve. Compute the chain's maximum gain and either apply automatic preamp reduction or refuse the setting, rather than letting the user build a preset that distorts.
Routing, and shipping something installableproduct builder
Audio goes where you want it, and another person can install this.
An unsigned driver silently does not appear. Do this early, not at release.
Files
scripts/sign.shThere is no public API to request audio capture permission. The user needs telling.
Files
README.mdOn a machine that has never had it. Then remove it and confirm nothing remains.
Files
tests/install.md
done when · tick each as it passeswatch out- An unsigned or un-notarised audio driver will not load on a current system, and the failure surfaces as the device simply not appearing, with no useful message anywhere. Sort out signing and notarisation early rather than at release, because it also affects how the driver is installed and updated.
That is the whole plan for eqMac. What it deliberately does not cover is below · check the gaps before you call it a replacement.
- Cross-platform support.
- Convolution, dynamics and room correction.
- Per-application volume for arbitrary processes.
- Automatic updates for a system driver.
- A first-party, notarized audio driver: you are borrowing someone else's virtual device instead
- Automatic output-device following, so plugging in headphones means switching things by hand
- Very low latency and rock-solid handling of sample rate mismatches and hotplug events
- Extras like per-device presets, balance control, and a curated preset library
- Updates, crash fixes, and someone else's problem when a macOS point release breaks audio
- A spectrum analyser fed from the driver, once the audio thread is provably safe
- Per-device presets that switch automatically with the output
- Loudness matching between presets, which makes comparing them honest
Need the files? The project pack on the verdict page hands your agent the whole brief · more music & audio.