Skip to content

Latest commit

 

History

History
178 lines (151 loc) · 10.7 KB

File metadata and controls

178 lines (151 loc) · 10.7 KB

Equaliser Roadmap

A sequential plan so we can ship the menu-bar equalizer step by step.

1. Bootstrap the Project (Completed)

  • Create a new SwiftUI macOS app targeting macOS 15+ on Apple Silicon.
  • Configure signing, hardened runtime, and microphone/audio entitlements.
  • Add basic README notes on installing BlackHole 2ch for loopback use.

2. Core Application Shell (Completed)

  • Implement the menu-bar status item with a SwiftUI popover host view.
  • Set up a shared EqualiserStore (ObservableObject) for global state.
  • Persist minimal preferences (selected devices, bypass state) via UserDefaults.

3. Audio Engine Foundation (Completed)

  • Build AudioEngineManager around AVAudioEngine with input/output nodes.
  • Insert two AUNBandEQ units (bands 1–16 and 17–32) plus optional limiter node.
  • Add smooth parameter ramping utilities to avoid zipper noise.

4. Device Selection Flow (Completed)

  • Implement DeviceManager to list Core Audio input/output devices (including BlackHole).
  • Allow users to pick input/output from the menu UI and reconfigure the engine safely.
  • Remember the last-used devices and auto-reconnect on launch.

5. HAL-Based Routing (Completed)

HALIOManager foundation

  • Create HALIOManager owning a kAudioUnitSubType_HALOutput Audio Unit.
  • Enable input/output scopes and expose setInputDevice(id:) / setOutputDevice(id:) helpers.
  • Read device stream formats (ASBD) and apply them to the HAL unit for each scope.
  • Add lifecycle controls (initialize, start, stop, uninitialize) with structured logging + error propagation.

Dual HAL Architecture

  • Refactor to use two separate HAL units (one input-only, one output-only) since a single HAL unit can only connect to one physical device.
  • Add HALIOMode enum (.inputOnly, .outputOnly) to configure each unit appropriately.
  • Implement ring buffer (AudioRingBuffer.swift) for lock-free audio transfer between input and output callbacks.
  • Register input callback on input HAL unit to capture audio and write to ring buffer.
  • Register output callback on output HAL unit to read from ring buffer and process through EQ.

Manual render pipeline

  • Register HAL input/output callbacks that pass audio buffers to/from the EQ pipeline.
  • Run the dual AUNBandEQ chain via AVAudioEngine manual rendering and handle buffer/latency alignment.
  • Guard against rate mismatches (resample or reject) and zero-fill if the EQ render returns insufficient data.

Store & UI integration

  • Update EqualiserStore to own RenderPipeline, persist selected device UIDs, and trigger rebuilds on change.
  • Surface routing status/errors to the menu UI (e.g., "BlackHole 2ch → Built-in Output" or warning on failure).
  • Add Start/Stop routing buttons with proper state management.
  • Add optional level meter or debug log toggle so we can verify signal presence without leaving the app.
  • Add device hot-swap handling (listener for device changes).

Testing & validation

  • Scenario: macOS output → BlackHole, app input=BlackHole, output=Built-in Output; verified audio through speakers.
  • Scenario: hot-swap output (e.g., to headphones) mid-stream and confirm seamless switch.
  • Scenario: device removed or mic permission denied; ensure graceful fallback messaging.

6. Window Architecture (Completed)

  • Refactor app to use MenuBarExtra + Window instead of WindowGroup + AppDelegate popover.
  • Hide dock icon permanently (NSApp.setActivationPolicy(.accessory)).
  • Add "Open EQ Settings" button in menu bar popover to show main window.
  • Create placeholder EQWindowView for the main EQ window.
  • Move mic permission request from AppDelegate to app initialization.
  • Remove AppDelegate (no longer needed with MenuBarExtra).
  • Main window should hide (not close) when user clicks close button.

Window Roles

Window Purpose
Menu Bar Popover Quick access: device selection, routing, bypass, preset picker, open EQ settings
Main EQ Window Detailed 32-band EQ controls, preset management, advanced settings

7. Equaliser Controls UI (Completed)

  • Design compact 32-band controls in the main EQ window (horizontal scrolling sliders).
  • Add gain/frequency readouts for each band.
  • Add "Flatten" button to reset all bands to 0 dB.
  • Double-tap on any band slider to reset it to 0 dB.
  • Display real-time level meters or band activity indicators (optional stretch goal).

8. Presets & Profiles (Completed)

  • Create preset model (name + band settings + metadata) in PresetModel.swift.
  • Add preset dropdown/list to menu bar popover for quick switching (CompactPresetPicker).
  • Support save, rename, delete presets in main EQ window (PresetToolbar, SavePresetSheet).
  • Presets stored in .eqpreset JSON files at ~/Library/Application Support/Equaliser/Presets/.
  • Add EasyEffects import/export support with Q-to-bandwidth conversion.
  • Add user preference to display bandwidth as octaves or Q factor.
  • Include factory presets: Flat, Bass Boost, Treble Boost, Vocal Presence, Loudness, Acoustic.
  • Show "modified" indicator when current settings differ from loaded preset.

9. Testing & Release Prep

  • Add unit tests for band-mapping logic and preset serialization.
  • Add release script to bundle and package application as a .dmg.
  • Prepare signed/notarized builds and optionally integrate Sparkle or TestFlight for updates.

10. Bypass & Compare Mode (Completed)

  • System EQ toggle (master bypass) — complete bypass of EQ and gains when OFF
  • Compare mode segmented control ([EQ|Flat]) — A/B comparison with gains still applied in Flat mode
  • Auto-revert timer (5 minutes) to switch back to EQ from Flat
  • Thread-safe bypass flag access using atomic operations
  • Help button (?) with popover explaining Compare Mode

11. GPU-Rendered Meters (Completed)

  • Replace SwiftUI shape-based meters with NSView + Core Animation (CALayer/CAGradientLayer)
  • Leverage GPU-accelerated rendering without Metal complexity
  • Target 30 FPS smooth animations with minimal CPU overhead
  • Use observer pattern for direct meter updates bypassing SwiftUI re-rendering

12. Built-in Virtual Audio Device (Completed)

  • Bundle custom virtual audio driver with the app (no external dependency like BlackHole)
  • In-app driver installation with authentication prompt and status feedback
  • Automatic driver selection on startup with transparent device naming
  • Volume sync: system volume slider controls both driver and output device
  • Bluetooth volume support with VirtualMasterVolume fallback
  • Sample rate sync: driver matches output device sample rate
  • Driver uninstall option in settings
  • Driver visibility: only appears when Equaliser is running

13. Application-Specific Routing

  • Allow users to select which applications route through the EQ (e.g., Spotify, Safari)
  • Build app picker UI showing all audio-producing processes with icons
  • Handle apps that launch/quit dynamically (add/remove from routing list)
  • Support excluding specific apps from processing while others pass through

14. Multiple EQ Chains

  • Support parallel or serial EQ configurations (e.g., one for music, one for voice)
  • Allow chaining multiple EQ presets with different band counts
  • Design UI for managing EQ chain slots (add/remove/reorder)
  • Consider latency implications of serial processing

15. Remove Microphone Indicator (Completed)

  • Eliminate the orange microphone indicator and microphone permission requirement.
  • No orange menu bar dot appears while audio routing is active

16. AirPlay Support

  • Investigate AirPlay SDK integration using AVRouteDetector and AVOutputContext APIs
  • Add AirPlay device discovery separate from CoreAudio HAL enumeration
  • Implement AirPlay routing alongside standard output device selection
  • Handle AirPlay-specific latency (2-5 second buffer) with appropriate UI warnings
  • Test with various AirPlay receivers (Apple TV, HomePod, AirPlay speakers)
  • Document AirPlay limitations and latency considerations for users

17. Custom DSP Implementation (Completed)

  • Create FilterType enum with 7 industry-standard filter types (parametric, low/high pass, low/high shelf, band pass, notch)
  • Implement BiquadMath with RBJ Cookbook coefficient calculation (pure functions)
  • Create BiquadCoefficients value type (Equatable, Sendable)
  • Implement BiquadFilter using vDSP biquad with pre-allocated delay elements
  • Implement EQChain with lock-free coefficient updates via ManagedAtomic
  • Add dirty-tracking to only rebuild changed filters in applyPendingUpdates()
  • Add resetState parameter to preserve filter memory during slider drags
  • Remove AVAudioEngine and AVAudioUnitEQ dependencies
  • Delete ManualRenderingEngine.swift and AudioRenderContext.swift
  • Integrate custom DSP into RenderCallbackContext with per-channel chains
  • Migrate EQBandConfiguration.filterType from AVAudioUnitEQFilterType to FilterType
  • Add backward-compatible preset decoding for legacy presets
  • Add unit tests for BiquadMath, BiquadFilter, EQChain, and FilterType
  • Add preset migration tests for legacy format compatibility

18. L/R Channel EQ (Completed)

  • Per-channel EQ state in EQConfiguration (leftState, rightState, channelMode)
  • Channel selection UI (Linked/Stereo modes with L/R focus toggle)
  • EQChain instantiated per-channel in RenderCallbackContext
  • EQChannelTarget routes coefficient updates to correct chain(s)
  • Per-channel band storage in preset model (rightBands optional field)
  • Backward-compatible preset decoding for legacy presets without channel mode

19. REW Filter Import (Completed)

  • Create REWImporter enum-based parser for Room EQ Wizard filter files
  • Support all REW filter type codes (PK, LS, HS, LP, HP, BP, NOTCH variants)
  • Handle Q and BW/60 bandwidth formats with proper conversion
  • Parse ON/OFF filter states (OFF maps to bypassed bands)
  • Integrate with Presets menu (Import REW Preset...)
  • Apply imported bands to current channel focus in stereo mode
  • Add user documentation (docs/user/REW-Import.md)

20. Keyboard Navigation (Completed)

  • Global shortcuts: Cmd+B (toggle bypass), Cmd+S (save preset)
  • Band-to-band navigation: Tab/Shift+Tab moves between adjacent bands
  • Value adjustment: Arrow keys increment/decrement focused value
  • Popover field navigation: Enter moves through gain → frequency → bandwidth