Repository navigation
Audio Engine
Relevant source files
The following files were used as context for generating this wiki page:
- scripts/README_ASIO.txt
- scripts/disable_asio.bat
- scripts/enable_asio.bat
- src/core/audio_engine.py
- src/core/calibration.py
- src/core/config_manager.py
- src/gui/widgets/boxcar_averager.py
- src/gui/widgets/settings.py
- tests/logic_verification/core/test_audio_engine.py
- tests/logic_verification/core/test_config_manager.py
- tests/logic_verification/core/test_config_manager_logic.py
- tests/logic_verification/gui/test_recorder_player_race.py
- tests/logic_verification/gui/widgets/test_distortion_analyzer_widget.py
The AudioEngine is the central hub for all audio I/O operations in MeasureLab. It manages the lifecycle of hardware streams via sounddevice, implements a multi-client mixer for simultaneous measurement modules, and provides high-precision signal paths with support for 64-bit processing and TPDF dithering.
The AudioEngine operates as a singleton-like service that abstracts the complexities of different host APIs (ASIO, CoreAudio, ALSA) and provides a unified interface for measurement modules.
The engine uses a single master callback (_master_callback) to drive all active measurement modules. This ensures perfect synchronization between multiple analysis tools (e.g., running an Oscilloscope and Spectrum Analyzer simultaneously).
- Hardware Input: Receives raw buffers from the sound card.
-
Channel Mapping: Applies input channel modes (Stereo, Left-only, Right-only)
src/core/audio_engine.py:151-155. -
Client Distribution: Iterates through registered client callbacks, providing them with the processed input data
src/core/audio_engine.py:157-160. -
Mixing: Collects output from clients into a mix buffer
src/core/audio_engine.py:173-175. -
Post-Processing: Applies master volume, software loopback, and dithering
src/core/audio_engine.py:165-185.
The following diagram illustrates how the AudioEngine bridges hardware I/O with measurement modules.
Audio Engine Data Flow
graph TD
subgraph Hardware_Space ["Hardware / OS Layer"]
SD_Stream["sounddevice.Stream"]
PortAudio["PortAudio (ASIO/CoreAudio/ALSA)"]
end
subgraph AudioEngine_Core ["AudioEngine Class"]
MasterCB["_master_callback"]
MixBuffer["_mix_buffer"]
LogicalIn["_logical_in_buffer"]
DitherScratch["_dither_scratch_buffer"]
Status["accumulated_status"]
end
subgraph Client_Space ["Measurement Modules"]
SpecAn["SpectrumAnalyzer"]
DistAn["DistortionAnalyzer"]
Boxcar["BoxcarAverager"]
end
SD_Stream -->|indata| MasterCB
MasterCB -->|Process Mapping| LogicalIn
LogicalIn -->|Client Callback| SpecAn
LogicalIn -->|Client Callback| DistAn
LogicalIn -->|Client Callback| Boxcar
SpecAn -->|outdata| MixBuffer
DistAn -->|outdata| MixBuffer
MixBuffer -->|Dithering/Mute| MasterCB
MasterCB -->|outdata| SD_Stream
SD_Stream <--> PortAudio
Sources: src/core/audio_engine.py:117-185, src/gui/widgets/boxcar_averager.py:122-127
The AudioEngine supports multiple simultaneous clients through a registration system. Each client registers a callback that receives input data and provides output data.
-
Registration: Clients use
register_callback(callback)which returns a unique IDsrc/core/audio_engine.py:157-159. -
Thread Safety: A
threading.Lockprotects the callback registry during stream restarts or UI interactionssrc/core/audio_engine.py:160. -
Buffer Management: To minimize GC pressure and latency, the engine uses pre-allocated buffers (
_mix_buffer,_client_buffer)src/core/audio_engine.py:172-176.
When outputting to hardware, MeasureLab can apply Triangular Probability Density Function (TPDF) dithering. This prevents quantization distortion when converting the internal floating-point representation to fixed-point bit depths (16 or 24-bit).
-
Implementation: Uses a NumPy random generator (
np.random.default_rng) to generate the dither signalsrc/core/audio_engine.py:185-186. -
Configuration: Users can toggle dithering and select target bit depth in the settings
src/core/config_manager.py:44-45.
For extreme precision requirements, the engine can be switched to float64 mode.
-
Logic: The
_get_dtype()method returns eitherfloat32orfloat64based on theaudio_engine_64bitflagsrc/core/audio_engine.py:197-199. -
Impact: This affects all internal buffer allocations and signal processing within the master callback
src/core/audio_engine.py:143-145.
For testing and demonstration without hardware, MeasureLab includes a VirtualStream class.
| Feature | Implementation |
|---|---|
| Timing | Driven by a high-priority thread with drift correction src/core/audio_engine.py:62-81. |
| Mocking | Uses _DummyTime to simulate sounddevice time structures src/core/audio_engine.py:13-21. |
| Loopback | Automatically routes output back to input for self-measurement src/core/audio_engine.py:102-109. |
Sources: src/core/audio_engine.py:22-116, tests/logic_verification/core/test_audio_engine.py:25-89
ASIO is considered experimental and is disabled by default for stability. It requires swapping PortAudio DLLs via specialized scripts scripts/README_ASIO.txt:1-12.
-
Enablement:
enable_asio.batreplaces the standardlibportaudio64bit.dllwith an ASIO-enabled versionscripts/enable_asio.bat:18-34.
Specific settings for macOS ensure high-fidelity capture:
-
coreaudio_fail_if_conversion_required: Prevents the OS from silently resampling audiosrc/core/audio_engine.py:193. -
coreaudio_conversion_quality: Set tominby default to reduce latencysrc/core/audio_engine.py:195.
On Linux, the engine supports a "resident" mode where the PortAudio stream remains open for the entire application lifetime, preventing port flickering in the system patchbay src/core/audio_engine.py:135-137.
The AudioEngine works closely with the CalibrationManager to ensure measurement accuracy.
Calibration Mapping Logic
graph LR
subgraph AudioEngine ["AudioEngine"]
RawIn["Raw Input (FS)"]
RawOut["Raw Output (FS)"]
end
subgraph CalibrationManager ["CalibrationManager"]
Sens["Input Sensitivity (V/FS)"]
Gain["Output Gain (V/FS)"]
SPL["SPL Offset (dB)"]
end
RawIn -->|Multiply| Sens
Sens -->|Result| VoltsIn["Input (Volts)"]
VoltsIn -->|Add Offset| SPL
SPL -->|Result| SPLVal["SPL (dB)"]
TargetVolts["Target (Volts)"] -->|Divide| Gain
Gain -->|Result| RawOut
Sources: src/core/calibration.py:33-44, src/gui/widgets/settings.py:173-183
-
Sensitivity: Defined in Volts per Full Scale (V/FS)
src/core/calibration.py:33. -
SPL Calibration: Maps C-weighted dBFS to absolute SPL values using a stored offset
src/core/calibration.py:42-44. -
1PPS Sync: Can use an external 1PPS signal to calibrate the sample clock frequency
src/core/calibration.py:39-40.
Sources: src/core/audio_engine.py:147, src/core/calibration.py:10-53