The Device part runs on Linux, Windows, macOS, iOS, Android and the web; the Spatial part runs anywhere a C compiler does. This page lists what a program must do on each platform, and what to look at when there is no sound.
| Platform | Backend | Loopback | Exclusive | Objects | Audio focus |
|---|---|---|---|---|---|
| Linux | PipeWire, then PulseAudio, then ALSA | PipeWire, PulseAudio | ALSA hardware devices | no | no |
| Windows 10 and later | WASAPI | yes | yes | spatial sound objects | no |
| macOS | Core Audio | no | hog mode | Apple's spatial mixer | no |
| iOS | RemoteIO with an audio session | no | no | Apple's spatial mixer | yes |
| Android 11 (API 30) and later | AAudio | no | AAudio's exclusive mode | no | with the Java half |
| Browsers | Web Audio with an AudioWorklet | the user's pick | no | no | no |
The library opens libpipewire, libpulse and libasound at run time, so a program built with all three runs on a machine that has any of them, and creation fails with maud_errorUnsupported only where none answers. A native context on PipeWire waits up to two seconds on the calling thread for the device list when it is made.
ALSA lists the cards' hardware endpoints without opening them, so their rates read 0 until a stream opens one. A hardware endpoint opened even in shared mode keeps other programs off the device; the status's exclusive says so.
The library needs nothing from the program's threads: every WASAPI call runs on a thread of the context's own in COM's multithreaded apartment, so the program's main thread may be in a single-threaded apartment, as one with drag and drop is. Streams on a device that plays nothing get no loopback blocks while it is silent. Build with clang-cl; MSVC's cl compiles the headers but not the library.
On iOS the context manages the audio session: the category follows the streams there are (playback, recording or both, the speaker the default output with Bluetooth allowed), and other applications' audio mixes with the program's until it asks for focus. iosSilencedBySwitch in the context def lets the Ring/Silent switch silence output that is not recording, as a game's sound effects should be. An interruption (a call, an alarm) suspends streams with maud_suspendPolicy; one that ends without the hint to resume waits for maudContext_Unlock or a focus request.
An iOS application that captures needs NSMicrophoneUsageDescription in its Info.plist, or the system ends it at the first capture.
AAudio alone plays the default output and captures the default input. With the Java half, the context lists every device, follows their changes, asks for the microphone, and takes audio focus:
java/maul/audio with the application's own Java sources.JavaVM* and an android.content.Context in the context def's androidJavaVm and androidContext. When the Context is an Activity, the context asks for the RECORD_AUDIO permission once.java/proguard-rules.pro to its rules. The native library finds the classes by name, which a shrinker cannot see; without the rules, creating a context with those handles fails with maud_errorPlatform.An application targeting Android 15 may ask for audio focus only while it is the top application or runs a foreground service.
Build with Emscripten. Each stream plays through an AudioWorklet; the library renders ahead on the page's main thread, from the browser's event loop. Four things are the page's to do:
maudContext_Unlock from a click's or a key's handler; until then streams are suspended with maud_suspendPolicy. A loopback stream is made in such a handler too, since it opens the browser's picker of what to share.Cross-Origin-Opener-Policy: same-origin and Cross-Origin-Embedder-Policy: require-corp is cross-origin isolated, and the library then hands frames to the worklet through shared memory. Elsewhere it posts them, which on a loaded machine needs more latency to avoid dropouts. maudStream_GetClock reports the latency a stream actually has.maud_errorCapacity never reaches the program: with Emscripten's defaults the module aborts before the library sees it. A program that links maul-audio through CMake gets -sABORTING_MALLOC=0 from the library's target. Any other build links with it, or with -sALLOW_MEMORY_GROWTH=1.maudLoadHrtf.The microphone needs the page served from https or localhost. Until the user allows it, capture streams wait with maud_suspendPermission.
maud_errorUnsupported. No backend answered: on Linux no sound server is running and libasound is missing, or the build left the platform's backend out.maudStream_GetStatus: its suspension says why (see Devices and streams). On the web the usual reason is a missing gesture.maud_errorBusy. The call came from the stream's callback, or from the allocator or a hook the object was running; maudContext_GetMisuse and maudSpatializer_GetMisuse count such calls.maud_errorVersion. The program was built against headers of another version than the library it runs with. Rebuild it against the library's headers.maud_errorFormat from maudLoadHrtf or maudLoadProbeSet. The bytes are not a well-formed file of that kind: damaged, cut short, or another file. A file of another format version gives maud_errorVersion instead.