All notable changes to this project are recorded here, grouped as Added, Changed, Fixed, Removed and Security. The project uses Semantic Versioning. Before 1.0.0, any release may change the API, the ABI and every file format.
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.
maudStream_Render writes silence over frameCount frames 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 a maud_errorBusy refusal 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_RenderObjects silences 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 its samples points to, keeping the pointer (#216).maudContext_NextNotification leaves 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).maudContext_GetDevices writes the null id over all of idsOut'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_GetName and maudDevice_GetKey write zeros over all of bytesOut'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 in lengthOut (#207).maudProbeSet_GetInfo writes count zero points over pointsOut on 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).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.
maudProbeSet_UseForPathing is maudSpatializer_SetPathingProbeSet and maudProbeSet_UseBakedReverb is maudSpatializer_SetReverbProbeSet. Their parameters, results and errors are the same, and the null id still takes a set out of use (#183).maudDevice_SetOfflineDefault, maudDevice_SetOfflineForm, maudDevice_SetOfflineFormat and maudDevice_SetOfflineSpatializer are maudOfflineDevice_SetDefault, maudOfflineDevice_SetForm, maudOfflineDevice_SetFormat and maudOfflineDevice_SetSpatializer, with the same parameters and results (#191).maudBinauralDef and maudBinauralDecoderDef have no version field, and their creations no longer return maud_errorVersion. Both take an HRTF set, and maudLoadHrtf already refuses headers of another release. A program that builds them with maudDefaultBinauralDef and maudDefaultBinauralDecoderDef changes nothing; one that set version itself removes that line. The two defaults are library functions now, and MAUD_BINAURAL_DEF_COOKIE and MAUD_BINAURAL_DECODER_DEF_COOKIE are gone (#190).maudSource_GetDirectResult and maudSpatializer_GetReverbResult return maud_errorState instead of maud_errorInvalid before 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 by maud_errorInvalid checks for maud_errorState (#189).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.
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's maxFrames per ear for the binaural effect and decoder (MAUD_MAX_BINAURAL_FRAMES when 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).maudProbeSet_Save writes capacity zero 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, sizeOut still gives the size the file needs (#174).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.
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 keep maudVerbNoun (maudCreateStream, maudDefaultStreamDef). Callback and hook types end in Function, the acoustic scene is maudScene throughout, a field that counts things ends in Count, times are in seconds with no unit in the name, and a def field that switches a part on is enableThing (#165). A program renames each call, type and field by the tables below, and also:maudProbeSet_GetInfo and maudRotateAmbisonic each move two uint32_t arguments, so a call left in the old order still compiles: check each one;maudStreamFunction*. maudStreamCallback was a pointer type; maudStreamFunction is a function type;maudVoiceDetectorDef.hangover in seconds as a float (0.2 for the old 200 milliseconds), and maudEchoCancellerDef.tailTime in seconds as before.maudStreamObject.position is a maudVector3 instead of float[3], like every other position in the API. Write position.x, .y and .z instead of position[0], [1] and [2]. Its frame, its default and its layout are unchanged (#152).maudDirectEffectDef.sampleRate, maudReverbDef.sampleRate and maudSpatializerDef.reflectionRate are uint32_t instead of float, like every other sample rate, defaulting to 48000. Their ranges are unchanged (#151).MAUD_DIRECT_BANDS is MAUD_BANDS. It sizes every per-band array of the Spatial part, not only the direct path's. The count stays 3 (#154).maudLimits.streams, devices, notifications and deviceTextBytes are uint32_t instead of uint16_t. The new constants MAUD_MAX_STREAMS, MAUD_MAX_DEVICES, MAUD_MAX_NOTIFICATIONS and MAUD_MAX_DEVICE_TEXT_BYTES cap them at 65,535, the most the old fields held, and maudCreateContext refuses a limit past its constant with maud_errorLimit. Every value accepted before is accepted as before (#153).| 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) |
| Old | New |
|---|---|
maudStreamCallback |
maudStreamFunction |
maudAnyHitFn |
maudAnyHitFunction |
maudClosestHitFn |
maudClosestHitFunction |
maudTaskFn |
maudTaskFunction |
maudEnqueueTaskFn |
maudEnqueueTaskFunction |
maudFinishTaskFn |
maudFinishTaskFunction |
| 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 |
| 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 |
maud_errorCapacity instead of aborting the whole module with Aborted(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)..maudhrtf and .maudbake bytes are refused with the new maud_errorFormat (-8) instead of maud_errorInvalid, by maudLoadHrtf and maudLoadProbeSet. A count past a published MAUD_MAX_* constant, or below a MAUD_MIN_* one, is refused with the new maud_errorLimit (-9) instead of maud_errorInvalid. This covers the context's limits.periodFrames and limits.notifications, a stream's objectCount, an ambisonic order, the spatializer's, acoustic scene's and binaural renderer's capacities, and a probe box's columns, which was maud_errorCapacity. Most of these bounds were private and are now public constants beside their API, such as MAUD_MAX_SOURCES, MAUD_MAX_TRIANGLES and MAUD_MAX_PROBE_COLUMNS (16,777,216 columns; the documentation used to say 4,194,304). maudProcessBinaural and maudDecodeBinaural refuse more frames than the def's maxFrames with maud_errorCapacity instead of maud_errorInvalid. A program that tests for maud_errorInvalid after these calls tests for the new results (#121).maud_errorBusy (-10) instead of maud_errorState. The same call succeeds once that work ends. maud_errorState now means only a state another call changes, such as a stopped stream (#124).maudSimulateReverb, maudRenderReflections, maudCreateProbeSet (generating probes) and maudBakeProbeSet return maud_errorUnsupported (-4) instead of maud_errorState when 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).maudCreateContext refuses a limits.periodFrames past MAUD_MAX_PERIOD_FRAMES (1,048,576 frames, over 20 seconds at 48 kHz) with maud_errorLimit. A duplex stream created under a larger limit used to hang (#112).maudCreateContext refuses, with maud_errorCapacity, limits whose memory block the address space cannot hold. Where size_t has 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).clock.rate and clock.force-rate. Core Audio's rate used to be converted without a range check (#120)..maudhrtf) are version 3, made of tagged fields that a later version can extend. maudLoadHrtf reads only version 3 and refuses an earlier file, a later version or an unknown field with maud_errorVersion (it was maud_errorUnsupported). Convert a SOFA source again with tools/sofa_to_maudhrtf.py. The shipped SADIE II set holds the same values, so every render is unchanged (#87)..maudbake) are version 3 tagged fields too. maudLoadProbeSet reads only version 3 and refuses an earlier file, a later version or an unknown field with maud_errorVersion (it was maud_errorUnsupported); bake the scene again. A bake's layers now follow from the fields it carries, and maudSaveProbeSet answers maud_errorCapacity for a set too large for the format rather than write a file no reader takes. Baking's results are unchanged (#88).position skips the frames dropped (#75, #92).maudGetDefaultDevice stays current (#83).-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).pipewire-alsa included), counts the gaps it plays.
The plugin plays silence for a starved stream without -EPIPE, so
such gaps used to leave underruns unchanged. 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 that pipewire-pulse reports 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).sqrt compiles
to one rounded instruction rather than a C library call that may set
errno. The bake and the golden renders are unchanged
(#23).The first release on the organization's standard. Version numbers restarted at 0.0.1, and the earlier tags and releases were removed.
.maudbake files, made before reverb tails:
maudLoadProbeSet returns maud_errorUnsupported. Bake the scene
again with this release and save it.