A binaural spatializer audio plugin built with C++ and the JUCE framework. ELEV8 places any mono audio source in three-dimensional space around the listener by convolving the input signal with Head-Related Transfer Function (HRTF) impulse responses.
Binaural audio simulates how humans perceive the direction and elevation of sound sources using the shape of the outer ear, head, and torso. HRTFs encode these acoustic filtering properties for a dense grid of spatial positions. ELEV8 loads the appropriate impulse response for a given azimuth and elevation, then applies it to the input in real time using frequency-domain convolution. This is the same core technique behind Apple Spatial Audio, Meta's VR audio pipeline, and binaural streaming formats — implemented here from first principles as a native audio plugin.
Formats: VST3, AU, Standalone Channel layout: Mono in → Stereo out Platform: macOS (Xcode build via JUCE Projucer)
- Real-time binaural spatialization using measured HRTFs
- Independent azimuth (−180° to +180°) and elevation (−40° to +90°) controls
- Over 1,800 embedded HRTF impulse responses covering 14 elevation planes
- Nearest-neighbor HRTF selection with per-elevation azimuth resolution tables
- Frequency-domain convolution via
juce::dsp::Convolution
The impulse responses are sourced from the MIT Media Lab KEMAR HRTF dataset (Gardner & Martin, 1994) — one of the most widely used public-domain HRTF databases in spatial audio research.
Measurements are taken at:
| Elevation | Azimuth samples | Resolution |
|---|---|---|
| −20° to +20° | 74 | 5° |
| ±30° | 62 | 6° |
| ±40° | 58 | ~6.2° |
| 50° | 48 | ~7.5° |
| 60° | 40 | 9° |
| 70° | 28 | ~13° |
| 80° | 14 | ~26° |
| 90° | 2 | — |
All impulse responses are embedded as binary resources at compile time, so the plugin has no runtime file dependencies.
When a slider value changes, updateParameters():
- Finds the closest valid elevation in the 14-entry lookup table
- Selects the corresponding azimuth table for that elevation
- Finds the closest valid azimuth in that table
- Constructs the HRTF filename:
H{elevation}e{azimuth}a.wav - Locates the matching entry in
BinaryDataand flags the convolution object for reload
On each processBlock call:
- If a new HRTF was selected, it is loaded into
juce::dsp::Convolution - A gain factor is applied to the mono input buffer
- The mono signal is copied to both output channels
- Stereo convolution is performed against the selected HRTF, producing the binaural output
Mono input
│
├─ applyGain()
│
├─ copyFrom(ch 1 ← ch 0)
│
└─ dsp::Convolution::process() ← HRTF impulse response
│
Stereo binaural output
- macOS with Xcode installed
- JUCE framework (version used during development: JUCE 6)
- Projucer to regenerate the Xcode project if needed
-
Clone the repository:
git clone https://github.com/gregormcw/elev8.git cd elev8 -
Open
elev8.jucerin Projucer and click Save and Open in IDE, or open the existing Xcode project directly:open Builds/MacOSX/ELEV8.xcodeproj
-
Select the desired build target (VST3, AU, or Standalone) and build with
⌘B. -
The compiled plugin will be placed in the standard macOS plugin directories:
- AU:
~/Library/Audio/Plug-Ins/Components/ - VST3:
~/Library/Audio/Plug-Ins/VST3/
- AU:
Note:
BinaryData.cppandBinaryData.hare generated by Projucer from the HRTF WAV files inSource/compact/. If regenerating, ensure the compact directory is included as a resource in the Projucer project.
elev8/
├── elev8.jucer # JUCE Projucer project file
├── Source/
│ ├── PluginProcessor.h/cpp # DSP logic, HRTF selection, convolution
│ ├── PluginEditor.h/cpp # Plugin UI (sliders, layout, painting)
│ ├── BinaryData.h/cpp # Auto-generated embedded HRTF resources
│ └── compact/ # Source HRTF WAV files (MIT KEMAR dataset)
│ ├── elev-40/ # 58 impulse responses at −40° elevation
│ ├── elev0/ # 74 impulse responses at 0° elevation
│ └── ... # One directory per elevation plane
└── Builds/
└── MacOSX/
└── ELEV8.xcodeproj # Xcode project (generated by Projucer)
- No HRTF interpolation — parameters snap to the nearest measured position rather than blending between adjacent HRTFs, which can produce audible artifacts when sweeping angles quickly. Planned fix: linear interpolation between the four nearest HRTF measurements.
- Hardcoded gain — output gain is fixed at 10× and cannot be adjusted from the UI. Planned fix: expose a gain parameter via
AudioProcessorValueTreeState. - No preset saving —
getStateInformation/setStateInformationare not yet implemented, so DAW automation and preset recall are unsupported. Planned fix: serialize parameters using JUCE'sValueTree. - macOS only — no Windows or Linux builds are currently provided. Planned fix: CMake-based cross-platform build replacing the Projucer/Xcode workflow.
- Gardner, W. G., & Martin, K. D. (1994). HRTF Measurements of a KEMAR Dummy-Head Microphone. MIT Media Lab Perceptual Computing Technical Report #280.
- JUCE Framework — https://juce.com
- Smith, J. O. (2010). Physical Audio Signal Processing. W3K Publishing. https://ccrma.stanford.edu/~jos/pasp/
Gregor McWilliam — gregormcw.com · g@gregormcw.com

