miniaudioengine is a cross-platform, lightweight, audio processing C++ SDK. The intention is for audio applications written using this SDK to run on platforms as minimal as a Raspberry Pi.
- Introduction
- Contents
- Requirements
- Background
- High-Level Design
- Software Implementation
- Testing
- Conclusion
- Monitor audio device inputs.
- Read WAV audio files.
- Monitor MIDI device inputs and process MIDI messages.
- Read MIDI files and process MIDI messages.
- Playback to audio output device.
- Write to WAV audio file.
- Create processing components in between I/O.
- Real-time processing.
- Efficient resource management.
- Cross-platform on x86 and ARM64 architectures.
- Modern, C++ code following best practices.
@startuml
skinparam componentStyle rectangle
skinparam shadowing false
skinparam packageStyle rectangle
package "Layer 4 - application" #lightgray {
[application] <<application>>
}
package "Layer 3 - control (sync)" #lightsalmon {
[control] <<control>>
}
package "Layer 2 - processing" #lightyellow {
[processing] <<processing>>
}
package "Layer 1 - data (real-time)" #lightgreen {
[data] <<data>>
}
package "Layer 0 - framework" #lightblue {
[framework] <<framework>>
}
package "External" {
[RtAudio] <<external>>
[RtMidi] <<external>>
[libsndfile] <<external>>
}
note right of [application]
Apps, tools, or CLI programs.
Accesses the audio engine via the control layer.
end note
' Layer dependencies
[application] --> [control]
[control] --> [processing]
[control] --> [data]
[processing] --> [framework]
[data] --> [framework]
[control] --> [framework]
' External dependencies
[control] --> [RtAudio]
[data] --> [RtAudio]
[control] --> [RtMidi]
[data] --> [RtMidi]
[framework] --> [libsndfile]
@endumlI/O
- Device Manager
- File Manager
- Audio Device
- MIDI Device
- WAV File
- MIDI File
Tracks
- Track Manager
- Track
cmake/
docker/
examples/
samples/
include/
src/
public/
framework/
control/
data/
processing/
tests/
mocks/
unit/
CMakeLists.txt
CMakePresets.json
Dockerfile
vcpkg.json
Requirements:
- Visual Studio 2022 or later (with C++20 support)
- CMake 3.25+
- vcpkg
Build steps:
# Configure with vcpkg integration
cmake -S . -B build -DCMAKE_EXPORT_COMPILE_COMMANDS=ON
# Build
cmake --build buildRequirements:
- GCC 11+ or Clang 14+ (with C++20 support)
- CMake 3.25+
- vcpkg or system packages (RtAudio, RtMidi, libsndfile)
Build steps:
# Configure
cmake -S . -B build -DCMAKE_EXPORT_COMPILE_COMMANDS=ON
# Build
cmake --build buildFor reproducible Linux builds across x86_64 and ARM64:
# Build multi-arch Docker image
cd docker
./docker-build.sh
# Run interactive container
./docker-run.sh
# Execute build inside container
cmake -S . -B build
cmake --build buildTasks
Instructions
Prompts
@startuml
top to bottom direction
skinparam componentStyle rectangle
skinparam packageStyle rectangle
skinparam shadowing false
skinparam classAttributeIconSize 0
package "Framework (Layer 0)" #lightblue {
interface "IController" <<framework>>
interface "IProcessor" <<framework>>
interface "IDataPlane" <<framework>>
interface "IDevice" <<framework>>
interface "IAudioDevice" <<framework>>
}
package "Control Plane (Layer 3)" #lightsalmon {
[AudioController] <<control>>
[MidiController] <<control>>
}
package "Data Plane (Layer 1)" #lightgreen {
[AudioCallbackHandler] <<data>>
[AudioDataPlane] <<data>>
[MidiCallbackHandler] <<data>>
[MidiDataPlane] <<data>>
}
package "Processing Plane (Layer 2)" #lightyellow {
interface "IAudioProcessor" <<processing>>
[SamplePlayer] <<processing>>
[Sample] <<processing>>
}
package "Public / CLI / Examples (Layer 4)" #lightgray {
[Application] <<public>>
[TrackManager] <<public>>
[MainTrack] <<public>>
[Track] <<public>>
[DeviceManager] <<public>>
[FileManager] <<public>>
[DeviceHandle] <<public>>
[FileHandle] <<public>>
[CLI] <<public>>
}
' Ordering hints for readability
"IProcessor" -[hidden]-> "IAudioProcessor"
"IAudioDevice" -[hidden]-> [AudioController]
' Public layer wiring
[Application] --> [TrackManager]
[Application] --> [DeviceManager]
[Application] --> [FileManager]
[Application] --> [CLI]
[TrackManager] --> [MainTrack]
[MainTrack] -|> [Track]
[Track] --> [AudioDataPlane]
[Track] --> [MidiDataPlane]
[Track] --> [DeviceHandle] : audio/midi input
[Track] --> [FileHandle] : audio/midi input
[Track] --> "IAudioProcessor" : add_audio_processor()
[MainTrack] --> [AudioController]
[MainTrack] --> [MidiController]
[DeviceManager] --> [AudioController]
[DeviceManager] --> [MidiController]
[DeviceManager] --> [DeviceHandle]
[FileManager] --> [FileHandle]
' Processing relationships
[SamplePlayer] ..|> "IAudioProcessor"
[SamplePlayer] o-- [Sample]
' Inheritance (framework)
[AudioController] ..|> "IController"
[MidiController] ..|> "IController"
"IAudioProcessor" ..|> "IProcessor"
[AudioDataPlane] ..|> "IDataPlane"
[MidiDataPlane] ..|> "IDataPlane"
"IAudioDevice" ..|> "IDevice"
' Control plane -> data plane
[AudioController] --> [AudioCallbackHandler]
[AudioCallbackHandler] --> [AudioDataPlane] : process_audio()
[MidiController] --> [MidiCallbackHandler]
[MidiCallbackHandler] --> [MidiDataPlane] : process_midi_message()
@endumlDeviceManager device_manager = DeviceManager::instance();
TrackManager track_manager = TrackManager::instance();
// Get default audio input device
DeviceHandlePtr input_device = device_manager.get_default_audio_input_device();
DeviceHandlePtr output_device = nullptr;
// Get all audio devices and search for output
std::vector<DeviceHandlePtr> devices = device_manager.get_audio_devices();
for (DeviceHandlePtr device : devices)
{
if (device->is_output())
{
output_device = device;
}
}
// Audio output is global. Set it using the TrackManager
// (TODO - Move this to another component)
if (output_device)
{
track_manager.set_audio_output_device(output_device);
}DeviceManager device_manager = DeviceManager::instance();
TrackManager track_manager = TrackManager::instance();
DeviceHandlePtr output_device = device_manager.get_default_audio_output_device();
DeviceHandlePtr input_device = device_manager.get_default_audio_input_device();
// Set audio output device
track_manager.set_audio_output_device(output_device);
// Create miniaudioengine Track
TrackPtr track = track_manager.create_child_track();
// Set audio device input to track
track->add_audio_input(input_device);
// Play track. Monitors audio input and routes it to audio output
track->play();
// ...
track->stop();| Element | Convention | Example |
|---|---|---|
| Classes | PascalCase | AudioDataPlane, FileManager |
| Interfaces | I prefix + PascalCase |
IController, IDataPlane |
| Methods | snake_case | get_output_device(), is_running() |
| Data members | m_ prefix |
m_running, m_stream_state |
| Pointer members | p_ prefix |
p_device, p_statistics |
| Enums | e prefix + PascalCase type, PascalCase values |
eStreamState::Playing |
shared_ptr aliases |
Ptr suffix |
IDataPlanePtr, AudioDataPlanePtr |
overrideused consistently on all derived virtual methodsnoexcepton real-time audio callbacks and time-critical methodsexpliciton single-parameter constructorsconstextensively applied to getters and parametersconstexprfor compile-time constants/mappings
std::shared_ptris the primary ownership mechanismstd::weak_ptrfor parent references (avoiding cycles)- Raw pointers only for non-owning references (e.g., RtAudio callback buffers)
std::make_sharedused for allocation
- Thread-safe Meyers singleton via
static T& instance()with private constructor and deleted copy
- Doxygen format (
@brief,@param,@return,@throws,@deprecated) on all public APIs - Brief inline comments for non-obvious logic only