Repository navigation
Releases
v0.6.0
0.6.0 (2026-10-30)
0.6.0 makes four more calls leave zeros or silence behind when they fail, never data from an earlier call, as the Maul family's rule for outputs asks. A call refused as busy because the object's own callback made it is the exception: it writes none of its outputs, since the call it interrupted may still be filling them. No backend changes what it does, so the device checklist on real hardware, which runs before a release that changes a backend, is not run for it. The golden renders and the office bake are unchanged.
Changed
- Breaking:
maudStream_Renderwrites silence overframeCountframes of the stream's channels on any failure but a call back in, where it used to leave the frames as they were. A program that missed one status, on a stopped stream or a call of the wrong kind, played the previous block again; it now hears silence. A call back in is amaud_errorBusyrefusal of a render made from the stream's own callback or from the context's allocator. A render refused because another thread holds the stream is silenced, even when it comes from inside another stream's callback. A NULL context or a stale stream leaves the frames as they were, since no stream gives their size.maudStream_RenderObjectssilences the bed the same way. On any failure but a call back in, NULL and stale included, it clears each object record and the frames itssamplespoints to, keeping the pointer (#216). - Breaking:
maudContext_NextNotificationleaves its record as it was when it is refused as busy from one of the context's own callbacks (a stream's callback or the allocator), where it used to zero it. The interrupted code may still be reading that record. Every other failure still zeroes it (#215). - Breaking:
maudContext_GetDeviceswrites the null id over all ofidsOut's capacity before it answers, so the ids past the devices listed, and every id after a failure, are null instead of an earlier call's.maudDevice_GetNameandmaudDevice_GetKeywrite zeros over all ofbytesOut's capacity first. A stale device leaves zeros instead of an earlier device's name or key, and a text longer than the room (maud_errorCapacity) leaves zeros, with the length it needs inlengthOut(#207). - Breaking:
maudProbeSet_GetInfowritescountzero points overpointsOuton any failure, where it used to leave them as they were. A caller that reads a set's points into a buffer it reuses, and misses one refused call, no longer takes the previous read's points for this set's (#204).
v0.5.0
0.5.0 (2026-10-30)
0.5.0 ends the review of the API before 1.0.0 (#150): the last calls and defs that did not follow the Maul family's rules are renamed or corrected. No backend changes what it does (the offline backend's setters are only renamed), so the device checklist on real hardware, which runs before a release that changes a backend, is not run for it. Successful calls, the golden renders and the office bake are unchanged.
Changed
- Breaking: the two calls that choose the spatializer's probe sets are named after the spatializer:
maudProbeSet_UseForPathingismaudSpatializer_SetPathingProbeSetandmaudProbeSet_UseBakedReverbismaudSpatializer_SetReverbProbeSet. Their parameters, results and errors are the same, and the null id still takes a set out of use (#183). - Breaking: the four calls that script an offline device are named after it:
maudDevice_SetOfflineDefault,maudDevice_SetOfflineForm,maudDevice_SetOfflineFormatandmaudDevice_SetOfflineSpatializeraremaudOfflineDevice_SetDefault,maudOfflineDevice_SetForm,maudOfflineDevice_SetFormatandmaudOfflineDevice_SetSpatializer, with the same parameters and results (#191). - Breaking:
maudBinauralDefandmaudBinauralDecoderDefhave noversionfield, and their creations no longer returnmaud_errorVersion. Both take an HRTF set, andmaudLoadHrtfalready refuses headers of another release. A program that builds them withmaudDefaultBinauralDefandmaudDefaultBinauralDecoderDefchanges nothing; one that setversionitself removes that line. The two defaults are library functions now, andMAUD_BINAURAL_DEF_COOKIEandMAUD_BINAURAL_DECODER_DEF_COOKIEare gone (#190).
Fixed
- Breaking:
maudSource_GetDirectResultandmaudSpatializer_GetReverbResultreturnmaud_errorStateinstead ofmaud_errorInvalidbefore the spatializer has latched its first step. An audio thread that starts before the first step was told on its first blocks that it had passed a bad argument; it now reads that the results are not there yet. The results still read zero. A host that recognized those early blocks bymaud_errorInvalidchecks formaud_errorState(#189).
v0.4.0
0.4.0 (2026-10-29)
0.4.0 makes a refused call leave silence or zeros behind, never data from an earlier call. It changes no backend. Successful calls, the golden renders and the office bake are unchanged. The device checklist on real hardware is waived by the owner for releases through 2026-10-29, so 0.4.0 ships on its other gates, and the checklist runs in #161.
Changed
- Breaking: the binaural effect, the direct effect and the binaural and speaker decoders write silence over their output on any failure, where they used to leave it as it was (
maudBinaural_Process,maudDirectEffect_Process,maudBinauralDecoder_Decode,maudSpeakerDecoder_Decode). A host that played the buffer after an error it did not check heard the previous block again; it now hears silence. A refused call writes no more than a successful one could: at most the def'smaxFramesper ear for the binaural effect and decoder (MAUD_MAX_BINAURAL_FRAMESwhen the effect or decoder is NULL), nothing through a NULL buffer or channel pointer, and nothing from a NULL speaker decoder, which gives no channel count. A direct effect run in place silences the block it was given. A buffer a call adds into (the reverb's, encoder's and reflections' bed, a panner's channels) keeps what it held. A host that checks every status hears no difference (#170). - Breaking:
maudProbeSet_Savewritescapacityzero bytes over the caller's buffer on any failure, where it used to leave the buffer as it was. A buffer that held an earlier save kept it after a refusal, so a host that stored the buffer without checking the status wrote a stale bake that loads without complaint. When the capacity is short,sizeOutstill gives the size the file needs (#174).
v0.3.0
0.3.0 (2026-10-29)
0.3.0 settles the names of the API before 1.0.0. It changes how web programs link (#163), and the Core Audio and WASAPI object renderers read the new position type (#152); the other backends change only by name. The device checklist on real hardware is waived by the owner for releases through 2026-10-29, so 0.3.0 ships on its other gates, and the checklist runs against it in #161. The golden renders and the office bake are unchanged.
Changed
- Breaking: the API follows the Maul family's naming rule. A call on one object is named
maudObject_Verb, after the kind of its first id or the root it takes (maudStream_Start,maudContext_GetDevices,maudDevice_GetInfo). Creation, destruction, defaults and calls on no object keepmaudVerbNoun(maudCreateStream,maudDefaultStreamDef). Callback and hook types end inFunction, the acoustic scene ismaudScenethroughout, a field that counts things ends inCount, times are in seconds with no unit in the name, and a def field that switches a part on isenableThing(#165). A program renames each call, type and field by the tables below, and also:- moves the arguments of the three calls marked reordered.
maudProbeSet_GetInfoandmaudRotateAmbisoniceach move twouint32_targuments, so a call left in the old order still compiles: check each one; - declares a variable or field that holds a stream callback as
maudStreamFunction*.maudStreamCallbackwas a pointer type;maudStreamFunctionis a function type; - gives
maudVoiceDetectorDef.hangoverin seconds as afloat(0.2 for the old 200 milliseconds), andmaudEchoCancellerDef.tailTimein seconds as before.
- moves the arguments of the three calls marked reordered.
- Breaking:
maudStreamObject.positionis amaudVector3instead offloat[3], like every other position in the API. Writeposition.x,.yand.zinstead ofposition[0],[1]and[2]. Its frame, its default and its layout are unchanged (#152). - Breaking:
maudDirectEffectDef.sampleRate,maudReverbDef.sampleRateandmaudSpatializerDef.reflectionRateareuint32_tinstead offloat, like every other sample rate, defaulting to 48000. Their ranges are unchanged (#151). - Breaking:
MAUD_DIRECT_BANDSisMAUD_BANDS. It sizes every per-band array of the Spatial part, not only the direct path's. The count stays 3 (#154). - Breaking:
maudLimits.streams,devices,notificationsanddeviceTextBytesareuint32_tinstead ofuint16_t. The new constantsMAUD_MAX_STREAMS,MAUD_MAX_DEVICES,MAUD_MAX_NOTIFICATIONSandMAUD_MAX_DEVICE_TEXT_BYTEScap them at 65,535, the most the old fields held, andmaudCreateContextrefuses a limit past its constant withmaud_errorLimit. Every value accepted before is accepted as before (#153). - The voice detector still counts its hangover in 10 ms steps, and a hangover in the upper half of a step now rounds up to the next one instead of down: 207 ms used to give 200 ms and now gives 210 ms. The default, 0.2 seconds, is unchanged. A voice application that set its own hangover may hear speech held one step longer (#165).
Renamed functions
| Old | New | |
|---|---|---|
maudResumeContext |
maudContext_Unlock |
|
maudSetContextSuspended |
maudContext_SetSuspended |
|
maudGetContextBackend |
maudContext_GetBackend |
|
maudGetContextMisuse |
maudContext_GetMisuse |
|
maudGetDevices |
maudContext_GetDevices |
|
maudGetDefaultDevice |
maudContext_GetDefaultDevice |
|
maudGetDeviceInfo |
maudDevice_GetInfo |
|
maudGetDeviceName |
maudDevice_GetName |
|
maudGetDeviceKey |
maudDevice_GetKey |
|
maudRequestFocus |
maudContext_RequestFocus |
|
maudGetContextFocus |
maudContext_GetFocus |
|
maudNextNotification |
maudContext_NextNotification |
|
maudRenderObjects |
maudStream_RenderObjects |
|
maudSetOfflineDefaultDevice |
maudDevice_SetOfflineDefault |
reordered: (context, role, device) to (context, device, role) |
maudSetOfflineDeviceForm |
maudDevice_SetOfflineForm |
|
maudSetOfflineDeviceFormat |
maudDevice_SetOfflineFormat |
|
maudSetOfflineDeviceSpatializer |
maudDevice_SetOfflineSpatializer |
|
maudStartStream |
maudStream_Start |
|
maudStopStream |
maudStream_Stop |
|
maudGetStreamFormat |
maudStream_GetFormat |
|
maudGetStreamStatus |
maudStream_GetStatus |
|
maudGetStreamPosition |
maudStream_GetPosition |
|
maudGetStreamClock |
maudStream_GetClock |
|
maudRenderStream |
maudStream_Render |
|
maudFeedStream |
maudStream_Feed |
|
maudDecodeBinaural |
maudBinauralDecoder_Decode |
|
maudResetBinauralDecoder |
maudBinauralDecoder_Reset |
|
maudGetSpeakerDecoderMatrix |
maudSpeakerDecoder_GetMatrix |
|
maudDecodeToSpeakers |
maudSpeakerDecoder_Decode |
|
maudProcessBinaural |
maudBinaural_Process |
|
maudResetBinaural |
maudBinaural_Reset |
|
maudProcessDirect |
maudDirectEffect_Process |
|
maudResetDirectEffect |
maudDirectEffect_Reset |
|
maudGetHrtfInfo |
maudHrtf_GetInfo |
|
maudProcessReverb |
maudReverb_Process |
|
maudResetReverb |
maudReverb_Reset |
|
maudGetAcousticSceneMisuse |
maudAcousticScene_GetMisuse |
|
maudCommitAcousticScene |
maudAcousticScene_Commit |
|
maudMoveSceneInstance |
maudSceneInstance_Move |
|
maudGetSpeakerGains |
maudSpeakerPanner_GetGains |
|
maudPanToSpeakers |
maudSpeakerPanner_Pan |
|
maudGetSpatializerMisuse |
maudSpatializer_GetMisuse |
|
maudSetSourcePose |
maudSource_SetPose |
|
maudGetDirectResult |
maudSource_GetDirectResult |
|
maudSimulateDirect |
maudSpatializer_SimulateDirect |
|
maudSimulateReverb |
maudSpatializer_SimulateReverb |
|
maudSetMaterials |
maudSpatializer_SetMaterials |
|
maudLatchResults |
maudSpatializer_LatchResults |
|
maudRenderReflections |
maudSpatializer_RenderReflections |
|
maudGetReverbResult |
maudSpatializer_GetReverbResult |
|
maudGetProbeSet |
maudProbeSet_GetInfo |
reordered: (spatializer, set, infoOut, first, count, points) to (spatializer, set, first, infoOut, pointsOut, count) |
maudSetPathing |
maudProbeSet_UseForPathing |
|
maudUseBakedReverb |
maudProbeSet_UseBakedReverb |
|
maudBakeProbeSet |
maudProbeSet_Bake |
|
maudSaveProbeSet |
maudProbeSet_Save |
|
maudDetectVoice |
maudVoiceDetector_Detect |
|
maudApplyGainControl |
maudGainControl_Apply |
|
maudSuppressNoise |
maudNoiseSuppressor_Suppress |
|
maudCancelEcho |
maudEchoCanceller_Cancel |
|
maudAddOfflineDevice |
maudCreateOfflineDevice |
|
maudRemoveOfflineDevice |
maudDestroyOfflineDevice |
|
maudSceneAnyHit |
maudFindAnyHit |
|
maudSceneClosestHit |
maudFindClosestHit |
|
maudResultName |
maudGetResultName |
|
maudRotateAmbisonic |
maudRotateAmbisonic |
reordered: (order, from, to, bed, frames) to (bed, frames, order, from, to) |
Renamed types
| Old | New |
|---|---|
maudStreamCallback |
maudStreamFunction |
maudAnyHitFn |
maudAnyHitFunction |
maudClosestHitFn |
maudClosestHitFunction |
maudTaskFn |
maudTaskFunction |
maudEnqueueTaskFn |
maudEnqueueTaskFunction |
maudFinishTaskFn |
maudFinishTaskFunction |
Renamed scene names
| Old | New |
|---|---|
maudAcousticScene |
maudScene |
maudAcousticSceneDef |
maudSceneDef |
MAUD_ACOUSTIC_SCENE_DEF_COOKIE |
MAUD_SCENE_DEF_COOKIE |
maudDefaultAcousticSceneDef |
maudDefaultSceneDef |
maudCreateAcousticScene |
maudCreateScene |
maudDestroyAcousticScene |
maudDestroyScene |
maudAcousticScene_Commit |
maudScene_Commit |
maudAcousticScene_GetMisuse |
maudScene_GetMisuse |
maudInstanceTransform |
maudSceneInstanceTransform |
Renamed fields
| Old | New |
|---|---|
maudVoiceDetectorDef.hangoverMilliseconds (uint32_t, milliseconds) |
hangover (float, seconds) |
maudEchoCancellerDef.tailSeconds |
tailTime |
maudHrtfInfo.taps |
tapCount |
maudProbeSetInfo.probes |
probeCount |
maudProbeSetInfo.links |
linkCount |
maudReverbResult.estimates |
estimateCount |
maudDirectResult.surfaces |
surfaceCount |
maudDeviceInfo.spatialObjects |
spatialObjectCount |
maudStreamBlock.objectsAvailable |
availableObjectCount |
maudBinauralDef.nearField |
enableNearField |
maudReverbDef.tail |
enableTail |
maudSourceDef.transmission |
enableTransmission |
maudSourceDef.pathing |
enablePathing |
Fixed
- Web: a context or effect too large for the page's memory returns
maud_errorCapacityinstead of aborting the whole module withAborted(OOM). A program that links the library through CMake, from the build tree or the installed package, now links with-sABORTING_MALLOC=0; any other build links with it or with-sALLOW_MEMORY_GROWTH=1, as the web section of the platforms guide says. A program that relied on the abort now gets NULL from its own failed allocations (#163).
v0.2.0
0.2.0 (2026-10-28)
Changed
- Breaking: malformed
.maudhrtfand.maudbakebytes are refused with the newmaud_errorFormat(-8) instead ofmaud_errorInvalid, bymaudLoadHrtfandmaudLoadProbeSet. A count past a publishedMAUD_MAX_*constant, or below aMAUD_MIN_*one, is refused with the newmaud_errorLimit(-9) instead ofmaud_errorInvalid. This covers the context'slimits.periodFramesandlimits.notifications, a stream'sobjectCount, an ambisonicorder, the spatializer's, acoustic scene's and binaural renderer's capacities, and a probe box's columns, which wasmaud_errorCapacity. Most of these bounds were private and are now public constants beside their API, such asMAUD_MAX_SOURCES,MAUD_MAX_TRIANGLESandMAUD_MAX_PROBE_COLUMNS(16,777,216 columns; the documentation used to say 4,194,304).maudProcessBinauralandmaudDecodeBinauralrefuse more frames than the def'smaxFrameswithmaud_errorCapacityinstead ofmaud_errorInvalid. A program that tests formaud_errorInvalidafter these calls tests for the new results (#121). - Breaking: a call refused because the object is running host code (a stream's callback, an allocator, a spatializer's ray or task hook), or because another thread is rendering the stream, returns the new
maud_errorBusy(-10) instead ofmaud_errorState. The same call succeeds once that work ends.maud_errorStatenow means only a state another call changes, such as a stopped stream (#124). - Breaking:
maudSimulateReverb,maudRenderReflections,maudCreateProbeSet(generating probes) andmaudBakeProbeSetreturnmaud_errorUnsupported(-4) instead ofmaud_errorStatewhen the spatializer was created without the estimates, reflections, closest-hit query or rays the call needs. No later call can add those, so retrying is pointless (#126). - Breaking:
maudCreateContextrefuses alimits.periodFramespastMAUD_MAX_PERIOD_FRAMES(1,048,576 frames, over 20 seconds at 48 kHz) withmaud_errorLimit. A duplex stream created under a larger limit used to hang (#112).
Fixed
maudCreateContextrefuses, withmaud_errorCapacity, limits whose memory block the address space cannot hold. Wheresize_thas 32 bits, as on the web, the largest limits used to create a context over too small a block, and destroying it then crashed (#108).- A device rate the platform reports outside 8,000 to 384,000 frames per second, or not a number, reads as unknown (0) instead of reaching the device list. A native stream on such a device keeps its own rate. This covers WASAPI's device format, Core Audio's nominal rate and rate ranges, and PipeWire's
clock.rateandclock.force-rate. Core Audio's rate used to be converted without a range check (#120). - Core Audio: a device's channel count reads only within the configuration the system returned, and counts none for a list claiming more buffers than its bytes hold (#114).
- Web: a capture stream's block times no longer read early. In Chrome the output latency the capture clock subtracts jitters by about 2 ms, and the clock kept every low of that jitter, so captured frames were placed up to a few milliseconds early, more the longer the stream ran. A capture's frame times now follow the median of its last five deliveries, and a lasting change reaches them by the third. This changes the times of web capture blocks: a program that lines up captured sound with what it plays, such as an echo canceller or a latency measure, sees them a few milliseconds later and steadier. Output streams are unchanged (#134).
v0.1.0
0.1.0 (2026-10-27)
Changed
- Breaking: HRTF sets (
.maudhrtf) are version 3, made of tagged fields that a later version can extend.maudLoadHrtfreads only version 3 and refuses an earlier file, a later version or an unknown field withmaud_errorVersion(it wasmaud_errorUnsupported). Convert a SOFA source again withtools/sofa_to_maudhrtf.py. The shipped SADIE II set holds the same values, so every render is unchanged (#87). - Breaking: bakes (
.maudbake) are version 3 tagged fields too.maudLoadProbeSetreads only version 3 and refuses an earlier file, a later version or an unknown field withmaud_errorVersion(it wasmaud_errorUnsupported); bake the scene again. A bake's layers now follow from the fields it carries, andmaudSaveProbeSetanswersmaud_errorCapacityfor a set too large for the format rather than write a file no reader takes. Baking's results are unchanged (#88).
Fixed
- After an input or loopback stream moves or reconnects, its first block on the new device no longer starts with up to one period of the old device's sound, on every backend that reconnects (PipeWire, PulseAudio, Core Audio, WASAPI, AAudio, iOS and the web). That block's
positionskips the frames dropped (#75, #92). - PipeWire: when the session manager restarts and replaces its default metadata, the context binds the new one, so streams on the default keep following it and
maudGetDefaultDevicestays current (#83). - PipeWire: an input or loopback stream reads each captured buffer's offset and size within the buffer, as PipeWire's own clients do (#97).
- HRTF sets lay out their resampled responses through the checked layout, like every other part (#99).
v0.0.4
0.0.4 (2026-10-25)
Changed
- On 32-bit x86 the library is built with SSE2 math
(-msse2 -mfpmath=sse), so it needs a processor with SSE2, and every
build now leaves trapping math at the compiler's default, from the
family's compiler flags. On 64-bit builds the bake still matches byte
for byte and every test passes unchanged
(#57).
Fixed
- An ALSA output through ALSA's PulseAudio plugin, the default PCM
wherever ALSA programs are routed to a PulseAudio server (a PipeWire
system withoutpipewire-alsaincluded), counts the gaps it plays.
The plugin plays silence for a starved stream without-EPIPE, so
such gaps used to leaveunderrunsunchanged. A running output now
counts one underrun when it finds its PCM's queue empty before a
write, once the PCM has had the stream's frames, and when a write
comes more than a period after every frame written so far could have
played out, once the write before it found frames queued; the second
catches a stall thatpipewire-pulsereports with frames still
queued. Each gap counts once. The first fill after a start or a
recovery, silence written while the stream does not run, and an empty
queue reported before the frames could have played count nothing, and
neither does a sink that starts late. A sink that stops taking frames
mid-stream and resumes later also counts an underrun, since the server
reports it as it reports a starved stream. Hardware PCMs are
unchanged: their gaps still come as-EPIPE
(#35,
#62). - On 32-bit x86 the library's floating-point setting also holds the x87
control word, so a host that leaves x87 at single or double precision
no longer changes what the library computes there, andsqrtcompiles
to one rounded instruction rather than a C library call that may set
errno. The bake and the golden renders are unchanged
(#23).
Release gate: the nightly extended run on main. The owner waived device-checklist.md step 7 for this release on 2026-10-25.
v0.0.3
0.0.3 (2026-10-24)
Changed
- The host's allocator and its task hooks' enqueue and finish
functions now run in the host's own floating-point setting, as the
rest of the family does; before, they ran in the library's. Bakes,
paths and estimates are unchanged, and the ray queries still compute
in the library's setting, whether a task body or the calling thread
runs them.
(#17)
Fixed
- A web stream on a busy page no longer reports overruns or underruns
that did not happen, and its clock no longer resets for them. A
delivery placed late once is not a loss; a real loss is counted once,
up to five seconds later, and until then the stream's clock runs
behind by the lost frames.
(#18)
v0.0.2
0.0.2 (2026-10-24)
Fixed
- Bakes, probe sets, paths, reverberation estimates and scene ray
queries now give the same bits whatever floating-point mode the
host's threads run in. The simulation calls and the task bodies they
hand to the host switch to round to nearest with subnormals kept and
every exception masked, and restore the thread's mode on return.
Before, a host running flush-to-zero, denormals-are-zero or another
rounding mode got different bakes, and threads in different modes
gave different bits for different task splits. The rendering side
never touches the mode. Results in the default mode do not change.
(#14)
v0.0.1
0.0.1 (2026-10-24)
The first release on the organization's standard. Version numbers
restarted at 0.0.1, and the earlier tags and releases were removed.
Added
- Playback, capture, duplex and loopback streams of 32-bit float
frames, in fixed periods, on PipeWire, PulseAudio and ALSA (each
opened at run time), WASAPI, Core Audio on macOS, iOS, AAudio on
Android 11 and later, and the web through an AudioWorklet, with a
private backend slot for platforms whose APIs cannot be public. - Device lists with stable keys, hotplug, the default device per role,
and streams that follow the default or stay on a named device; every
change, move and suspension reported as a typed notification, and
streams kept across a restart of the sound server. - Rates that never change silently: the device's own, one the device
must run at, or one the platform converts. Exclusive streams where
the platform has them, object streams for Windows' and Apple's
spatial renderers, and a stream clock that maps frames to the host
clock with the latency, for synchronization. - The platform's voice processing on capture streams, reported as the
platform applies it, and the library's own voice activity detector,
automatic gain control, noise suppressor and echo canceller, which
run on any frames without a device. - An offline backend that renders and captures at the caller's clock
and scripts device changes, for tests and rendering to memory. - Binaural rendering through HRTF sets loaded from bytes, with the
head's near field, and a default set made from the SADIE II KU100
measurements. - A first- to third-order ambisonic bed with encoding, rotation and
binaural and speaker decoders, and vector base amplitude panning to
speaker layouts. - The direct effect (air absorption, directivity, occlusion and
transmission in three bands) and a parametric reverb with a second
slope for coupled spaces. - The spatializer: occlusion and transmission traced through the
host's geometry or the library's own scenes, reverberation estimates,
geometric reflections, paths around corners over probe sets, and
probe sets baked to the same bytes on every platform and saved to
files. - Build options to leave out either part, a shared library option, a
CMake package and a pkg-config module.
Removed
- Reading version-1
.maudbakefiles, made before reverb tails:
maudLoadProbeSetreturnsmaud_errorUnsupported. Bake the scene
again with this release and save it.