Other
- union overrides apply to the automatic EXTENSIBLE promotion
- Samples-union member selection — wSamplesPerBlock for block-compressed subformats
- EXTENSIBLE black-box interop fixtures + doc surface refresh
- automatic WAVE_FORMAT_EXTENSIBLE promotion in the muxer
- KSDATAFORMAT subtype naming, IEC 61937 family, resolver tag routing
- surface dwChannelMask through core ChannelLayout + mismatch flag
- EXTENSIBLE routing by container size + Samples-union validation
- describe Y4M little-endian + colorspace-token conventions on their own terms
- document WAV demuxer hostile-input robustness + bench
- add dependency-free read/write hot-path micro-benchmark
- per-chunk-id targeted fuzz reaching every parser directly
- cap sxml sub-chunk / alignment-point reservations to body size
- deterministic mutation-fuzz net over the demuxer parse path
- guard chunk-skip seeks against i64-overflow backward wrap
- cap ds64 table reservation to available records (OOM hardening)
- bound chunk-body allocation against size-lies (DoS hardening)
- add CI / crates.io / docs.rs / MIT-license badges
- smpl + inst chunks read+write symmetric — typed structs + writers
- document DISP / id3 / PAD chunks + LIST INFO write side
- typed WavDemuxer::disp() / info() accessors
- LIST INFO write side — InfoChunk/InfoEntry + with_info
- id3 chunk write side — WavMuxOptions::with_id3
- parse PAD (alignment-padding) chunk
- DISP (Display) chunk write side — typed DispChunk + with_disp
- parse DISP (Display) + id3 (embedded ID3v2 tag) chunks
- sxml (serialized-XML ADM carrier) read+write — README + CHANGELOG
- symmetric sxml (serialized-XML) muxer write support per BS.2088 §7
- read BW64/ADM sxml (serialized-XML) chunk per ITU-R BS.2088-2 §7
- end-to-end demux->re-emit->demux symmetry test for cue/plst/adtl
- cue/plst/adtl symmetric read+write + trailing-chunk scan (CHANGELOG + README)
- symmetric cue / plst / LIST adtl muxer write support
- typed cue / plst / LIST adtl read + post-data trailing-chunk scan
- test RF64 Reserve overflow→promotion via streamed >4 GiB sparse sink
- RF64/BW64 64-bit large-file write side (ds64 + JUNK on-the-fly conversion)
- integration round-trip test for combined acid + chna metadata
- classify chna audioID refs by ADM prefix + BS.2088 §3 definition scope
- BWF bext chunk read+write with typed BextChunk surface
- parse BW64/ADM bxml (compressed XML) chunk per ITU-R BS.2088-2 §6
- BW64/ADM chna (channel-allocation) chunk read+write
- recognise extended LIST INFO sub-IDs (ExifTool RIFF Info Tags)
Fixed
- WAV
WAVE_FORMAT_EXTENSIBLEcodec routing now dispatches on thewBitsPerSamplecontainer size, not the union'swValidBitsPerSample— perdocs/container/riff/waveformatextensible/ms-waveformatextensible.html§Samples.wValidBitsPerSample("wBitsPerSample is the container size") andms-extensible-wave-format.html("a 20-bit sample can be stored left-justified within a three-byte container"), thedatachunk carries container-sized samples and the union WORD only states how many bits carry signal. A 24-valid-bits-in-32-bit-container stream previously mis-resolved topcm_s24le(wrong interleave stride); it now resolves topcm_s32lewith the precision surfaced separately viawav:fmt.valid_bits_per_sample/WavDemuxer::valid_bits_per_sample
Other
-
WAV mux
Samples-union control decoupled from the explicit mask:WavMuxOptions::with_valid_bits_per_sample/with_subformatnow apply whenever the EXTENSIBLE form is written — the automatic promotion included — instead of silently no-oping withoutwith_extensible(a 20-in-24 stream needs no explicit mask any more). Mux/demux symmetry guard: a precision-familywValidBitsPerSampleoverride above the container failswrite_headerrather than producing a file the demuxer rejects -
black-box
WAVE_FORMAT_EXTENSIBLEinterop suite (tests/wav_extensible_interop.rs): tool-generated multi-channel (6ch s16), 24-bit and 8-channel float fixtures demux with correct GUID routing / typed layouts, and the muxer's automatically-promoted EXTENSIBLE output decodes byte-exactly through an external validator invoked as a black box. Skips silently when the tool is absent -
WAV muxer now follows the staged encoder guidance automatically (
docs/container/riff/waveformatextensible/README.md§"What's covered"): more than two channels or a container size above 16 bits promote thefmtchunk toWAVE_FORMAT_EXTENSIBLE(40-byte form), withdwChannelMaskderived from the stream's typedCodecParameters::channel_layoutwhen declared, else from the channel-count default (ChannelLayout::from_count; aDiscreteNlayout writes mask0, the legal "no assigned speaker positions" bitmap). Classic ≤ 2-channel / ≤ 16-bit output (incl. the 8-bit G.711 tags) stays byte-identical to the historical muxer.WavMuxOptions::with_extensiblestill forces the form with an explicit mask (and wins over everything); newWavMuxOptions::with_legacy_waveformatexsuppresses the automatic promotion for byte-conservative re-muxing of legacy-tagged input. Round-trip symmetric: the mask the muxer derives maps back to the same typed layout on demux -
WAV codec-tag resolution now routes unmapped
wFormatTagvalues through theCodecResolverthreaded intoopen()— the registry owns the RFC 2361 tag value space (docs/container/riff/rfc2361-wav.txt), so ADPCM-family / MP3 / any registry-claimed tag resolves to its codec crate's id instead of hard-erroring (legacy path) or falling to a synthesised GUID id (EXTENSIBLE template-GUID path, per the documented tag↔GUID equivalence). Probes see theCodecTag::WaveFormattag,bits/channels/sample_ratehints and the rawfmtchunk as the header blob. Native PCM/IEEE-float/A-law/μ-law mappings never consult the resolver. New publicwav::open_wav_demuxer_with(input, &dyn CodecResolver);open_wav_demuxerkeeps the resolver-free behaviour -
WAV EXTENSIBLE SubFormat GUIDs now surface their symbolic
KSDATAFORMAT_SUBTYPE_*name (wav:fmt.subformat_name) for every row of the staged consolidated catalog (docs/container/riff/waveformatextensible/ksdataformat-subtype-guids.md) — both theWAVEFORMATEX-derived family and the IEC 61937 compressed-pass-through family (Data2 == 0x0cea, incl. the Dolby MAT 2.0/2.1 revisions fromms-representing-formats-iec61937.md). IEC 61937 GUIDs additionally surfacewav:fmt.iec61937_stream_type(CEA-861 stream type,Data1low byte) and are guarded from the wave-format-tag route per the catalog's dispatch guidance — theirData1is a stream type, not a tag -
WAV
dwChannelMasknow surfaces through oxideav-core's typedChannelLayoutonCodecParameters::channel_layout— position-SET matching against the named core layouts via the stagedSPEAKER_*bit table (ms-waveformatextensible.html), with the two back-pair alias rows from the staged standard-layouts table (0x33→Quad,0x3F"5.1 (Microsoft)" →Surround51) and aDiscreteNfallback for assigned-but-unnamed position sets so a consumer never gets a named layout that contradicts the explicit mask. Mask0(no assigned positions) and a set-bit/nChannelsdisagreement leave the fieldNone; the disagreement additionally surfaces aswav:fmt.channel_mask_mismatch(the staged doc's "should be the same" is a flag, not a reject). New publicwav::channel_mask_for_layoutderives the mask for a core layout (muxer-side building block) -
WAV demuxer validates the EXTENSIBLE
Samplesunion per the staged struct doc:wValidBitsPerSample > wBitsPerSample(a precision claim exceeding the container) and a container size that is zero or not a multiple of 8 are both rejected as malformed; a zero union WORD (writer left it unset) stays tolerated with the container size as the effective precision -
WAV EXTENSIBLE
Samplesunion member selection: the union WORD now surfaces under the member the SubFormat gives it —wValidBitsPerSamplefor the precision-semantics family (PCM / IEEE-float / A-law / μ-law templates + the IEC 61937 family, whose staged worked examples all set 16-in-16) orwSamplesPerBlock(newwav:fmt.samples_per_blockkey +WavDemuxer::samples_per_blockaccessor,0= variable) for block-compressed subformats (other template tags + vendor GUIDs). The precision-family validation no longer fires on block-compressed streams, whose 4-bit nibble containers and large per-block sample counts are legal (ms-waveformatextensible.html§Samples);WavDemuxer::valid_bits_per_samplecorrespondingly returnsNonefor them — the union has exactly one active member -
smpl(Sampler) +inst(Instrument) chunks read+write symmetric (RIFF MCI §3) — promoted from read-only metadata-key surfaces to typedwav::SmplChunk/wav::SampleLoop/wav::InstChunkpublic types with byte-losslessparse/to_bytes(thesmplloop array + vendorsampler_datatail round-trip verbatim;num_sample_loopsre-derived from the actual loop count on write).WavMuxOptions::with_smpl/with_instwrite the chunks ahead ofdata(RIFF §2 word-aligned), and the typed views are reachable viaWavDemuxer::smpl()/inst(). The existingwav:smpl.*/wav:inst.*read keys are unchanged (the parsers now delegate to the typed structs) -
typed
WavDemuxer::disp()/info()accessors — theDISPandLIST INFOtyped views are now captured during the chunk walk and reachable on the concrete demuxer (matching the typed-view pattern of every other chunk),Nonewhen absent.info()carries every sub-ID verbatim (incl. unknown/vendor) for re-emission viawith_info -
LIST INFO(text metadata) chunk write side (WavMuxOptions::with_info) — gives read/write symmetry for WAV text tags (RIFF MCI §3 "INFO List Chunk"). New publicInfoChunk/InfoEntrytypes carry an ordered list of(sub-ID FOURCC, text)entries withparse/to_bytes(NUL-terminated, RIFF §2 word-aligned sub-chunks); the writer wraps them in aLIST(INFO) chunk ahead ofdata, and the demuxer reads recognised sub-IDs back through their snake_case metadata keys. Unknown / vendor sub-IDs round-trip throughInfoChunk. The existing read path now delegates toInfoChunk::parse -
DISP(Display) chunk read+write symmetric — clipboard-formattypeDWORD + payload; the WAV "SoundSchemeTitle" convention (ExifTool RIFF tag table). TypedDispChunkpublic type with byte-losslessparse/to_bytesplusDispChunk::text/titlefor the commonCF_TEXT(type==1) display-title form. The demuxer surfaceswav:disp.{body_len,type}always pluswav:disp.titlefor theCF_TEXTform (text trimmed at the first NUL); non-text clipboard formats surface header-only, truncated bodies are skipped-as-opaque.WavMuxOptions::with_dispwrites the chunk ahead ofdata(RIFF §2 word-aligned) -
parse
PAD(Pad / alignment-padding) chunk — the alignment sibling ofJUNKin the RIFF MCI §2 "skip/ignore" dispatch group. Semantic-free body, so only accounting surfaces under the parallelwav:pad.*key namespace (count/total_bytes/ per-chunk<n>.body_len), mirroring theJUNKcontract;JUNKandPADstay in separate namespaces. Absence is observable (nowav:pad.*keys emitted) -
id3/ID3(embedded ID3v2 tag) chunk read+write symmetric — the read side surfaces the 10-byte ID3v2 header fields for observability perdocs/container/id3/id3v2.3.0.html§3.1:wav:id3.{body_len,version,flags,tag_size}plus the three flag bits.unsynchronisation/.extended_header/.experimental, with the §3.1 synchsafe (28-bit) tag-size decode. The write side (WavMuxOptions::with_id3) carries a complete ID3v2 tag verbatim as anid3chunk ahead ofdata(RIFF §2 word-aligned). Frame encoding/decoding is left tooxideav-id3per the codec/container split; malformed/non-ID3-magic bodies surface onlybody_len -
BW64/ADM
<sxml>(serialized-XML) chunk read+write symmetric per ITU-R BS.2088-2 §7 — the third ADM XML carrier alongside<axml>(uncompressed) and<bxml>(compressed-whole). TypedSxmlChunk/SubXmlChunk/AlignmentPointpublic types with byte-losslessparse/to_bytes(the on-wiresubXMLCkTbSizeis preserved verbatim;computed_sub_table_byte_sizeyields the canonical value). Segments the (optionally gzip) XML into per-sample-run SubXMLChunk records (binding a run of audio samples to an XML fragment for time-variant / Serial ADM metadata) plus an optional AlignmentPoint table for timestamp-based access. Surfaceswav:sxml.{body_len,fmt_type,compression,sub_chunk_count,alignment_point_count,total_samples}+ per-record.<n>.{samples,xml_len,xml}(XML text only for the uncompressedfmtType==0x0000form; gzip/private codes surface header fields only, leaving RFC 1952 inflation to a higher-level ADM-aware consumer).WavMuxOptions::with_sxmlwrites the chunk ahead ofchna/data(§2.1 order) with RIFF §2 word-alignment;WavDemuxer::sxml()accessor. Ansxmlchunk (likechna) marks the file ADM-carrying, so a forced/promoted 64-bit file uses theBW64top-level magic. Malformed/truncated bodies are skipped-as-opaque -
WAV cue / playlist / associated-data symmetric read+write: typed
CuePoint/CueChunk,PlaylistSegment/PlaylistChunk, andAdtlEntry/AdtlChunk(labl/note/ltxt) public types with byte-exactparse/to_bytesper Microsoft RIFF MCI §3 (Cue-Points / Playlist / Associated Data Chunk).WavMuxOptions::with_cue/with_plst/with_adtlwriters emit the chunks in the trailer (afterdata, the conventional placement for sample-position-referencing chunks);WavDemuxer::cue()/plst()/adtl()accessors. Overclaimed counts are clamped;adtlsub-chunks get the RIFF word-alignment pad automatically -
WAV demuxer no longer treats
dataas the chunk-scan terminator — cue / plst /LIST adtlchunks written after the waveform (the common convention) are now read, by seeking over the word-aligneddatabody and walking remaining chunks to EOF -
WAV muxer RF64/BW64 64-bit large-file write side (read/write symmetry):
WavMuxOptions::with_rf64(Rf64Mode)—Forcealways emits ads64chunk +0xFFFFFFFFsentinels +RF64/BW64magic;Reservewrites ads64-sizedJUNKplaceholder and promotes it on-the-fly tods64only if the finished file overflows 32 bits (ITU-R BS.2088-2 §3.6/§4.2 + EBU Tech 3306 v2 §3);Never(default) is byte-identical to the historical 32-bit muxer and errors on overflow. BW64 magic selected automatically when an ADMchnachunk is present. New publicRf64Modeenum -
test:
Rf64Mode::Reserveoverflow→promotion path driven through a genuine >4 GiB streameddatapayload via a sparse seekable sink (no gigabyte allocation) — verifies the in-placeJUNK→ds64rewrite,RF64magic flip, and 64-bitriffSize/dataSize/sampleCountbody values -
integration test: full public mux → demux round-trip of a WAV carrying BOTH an Acidizer
acidloop chunk and a BW64/ADMchnachunk — PCM byte-for-byte, typed WavDemuxer::{acid,chna}() accessors, and the ADM kind/definition-scope metadata keys (tests/wav_adm_metadata_roundtrip.rs) -
BW64/ADM chna: classify each audioID reference (ATU_/AT_/AC_/AP_ → AdmRefKind) and resolve its definition scope per ITU-R BS.2088-2 §8.1/§3 (trailing four hex digits ≤ 0x0FFF = BS.2094 common, ≥ 0x1000 = file-local custom) — surfaced as wav:chna..{track_ref,pack_ref}_{kind,definition} keys, AdmRefKind/DefinitionScope public enums + AudioId accessors
-
BWF bext (Broadcast Audio Extension) chunk read+write with typed BextChunk surface (EBU Tech 3285 v2 §2.3) — WavMuxOptions::with_bext writer, WavDemuxer::bext() accessor, byte-exact parse/to_bytes round-trip
-
parse BW64/ADM bxml (compressed XML) chunk per ITU-R BS.2088-2 §6 — fmtType header + uncompressed-XML text surface
-
BW64/ADM chna (channel-allocation) chunk read+write with typed ChnaChunk/AudioId surface (ITU-R BS.2088-2 §8.1)
-
recognise extended LIST INFO sub-IDs (ExifTool RIFF Info Tags) — IAS1..IAS9 audio-language slots, Windows-Media "more info" set, production-credit tags