v0.0.9
Other
- resolve every DEFINE_WAVEFORMATEX_GUID SubFormat through the legacy wFormatTag path
- parse LIST('wavl') wave-list waveform container (RIFF MCI §3)
- decode WAVEFORMATEXTENSIBLE dwChannelMask into SPEAKER_* layout
- Acidizer acid chunk read+write with typed AcidChunk surface
- complete LIST adtl coverage — file sub-chunk + ltxt locale WORDs per RIFF MCI §3
- parse slnt (Silence) chunk per Microsoft RIFF MCI §3
- parse _PMX (Adobe XMP packet) chunk for AVI/WAV files
- parse chunk per EBU Tech 3285 Supplement 5
- drop release-plz.toml — use release-plz defaults across the workspace
- parse JUNK (Filler) chunk per Microsoft RIFF MCI §2
- typed scalar Reinhard 2002 simple global tone-mapping operator
- RF64 / BW64 64-bit-extended form + ds64 chunk (EBU Tech 3306)
- full RIFF MCI §3 INFO List Chunk baseline (12 added sub-IDs)
- parse CSET (Character Set) chunk per Microsoft RIFF MCI §3
- parse iXML chunk — third-party production-recorder metadata
- parse fact chunk (RIFF MCI §3) — authoritative per-channel sample count
Added
-
WAV
WAVE_FORMAT_EXTENSIBLESubFormat-GUID resolution now implements
theKSMedia.hDEFINE_WAVEFORMATEX_GUID(x)/
IS_VALID_WAVEFORMATEX_GUID/EXTRACT_WAVEFORMATEX_IDmacros (per
docs/container/riff/waveformatextensible/ms-converting-format-tags-and-subformat-guids.md):
any SubFormat GUID of the form{0000xxxx-0000-0010-8000-00AA00389B71}
is recognised as equivalent to the legacywFormatTagxand
dispatches through the samecodec_for_tagroute theWAVEFORMATEX
path uses. Generalises the four previously hand-listed GUID constants
(PCM/IEEE_FLOAT/ALAW/MULAW) to every tag-derived GUID and surfaces the
embedded legacy tag aswav:fmt.subformat_tag(e.g.0x0055for an
EXTENSIBLE MP3-tagged file). Non-template GUIDs and template GUIDs
whose embedded tag isn't a directly-mapped format still synthesise the
wav:guid_<canonical-text>id. -
WAV
LIST('wavl')wave-list waveform container — the segmented
waveform form per Microsoft RIFF MCI §3 "Storage of WAVE Data"
(<wave-data> -> { <data-ck> | <data-list> },
<wave-list> -> LIST('wavl' { <data-ck> | <silence-ck> }... )).
Previously awavl-form WAV had no decodable audio (the LIST type
fell through and nodataanchor was set). The demuxer now resolves
the first embeddeddatasub-chunk as the decode anchor and walks
every segment. New keys:wav:wavl.segment_count,
wav:wavl.data_count,wav:wavl.data_bytes, and per-segment
wav:wavl.<n>.kind(data/slnt) /.length. Embeddedslnt
silence segments feed the sharedwav:slnt.*accounting so the
silent-sample totals match a top-level-slntfile (no zero/baseline
samples are synthesised — §3 is explicit thatslntis a count of
silent samples). A silence-onlywavl(nodatasegment) is
rejected as having no waveform; odd-lengthdatasegments respect
RIFF word-alignment. -
WAV
WAVEFORMATEXTENSIBLE.dwChannelMaskdecoding — the channel
bitmap is now expanded into a human-readableSPEAKER_*layout
string (FRONT_LEFT+FRONT_RIGHT+...), joined least-significant-bit
first per the 18 documented flag bits (SPEAKER_FRONT_LEFT 0x1..
SPEAKER_TOP_BACK_RIGHT 0x20000) in
docs/container/riff/waveformatextensible/ms-waveformatextensible.html.
Surfaced through the newwav:fmt.channel_layoutmetadata key and a
typedWavDemuxer::channel_layout()accessor. A0mask (no
assigned positions) yieldsNone; bits above the highest defined
flag are preserved verbatim asUNKNOWN(0x...)so no round-trip
information is dropped. -
WAV Acidizer
acidchunk — read AND write sides with a typed
accessor surface, layout per
docs/container/riff/metadata/exiftool-riff-tags.html§ "RIFF
Acidizer Tags" (24-byte little-endian body: flags bit-field at
offset 0, root note at 4, six reserved bytes carried verbatim at
6..12, beats at 12, meter at 16, tempo at 20). New public
wav::AcidChunktype withparse/to_bytesand per-bit helpers
(one_shot/root_note_set/stretch/disk_based/
high_octave) plus the 48..=71 root-note name table
(root_note_name). Demuxer surfaceswav:acid.flags/
.one_shot/.root_note_set/.stretch/.disk_based/
.high_octave/.root_note/.root_note_name/.num_beats/
.meter/.tempo(+.reservedhex when nonzero and.body_len
when extension bytes ride past the fixed struct); truncated bodies
are skipped-as-opaque. Muxer gains
WavMuxOptions::with_acid(AcidChunk)emitting the chunk ahead of
data. Six new lib tests: byte-layout golden pin, full metadata
surface, out-of-table root note + reserved/body_len observability,
truncated-skip, mux→demux round-trip (serialized chunk pinned
verbatim in the output bytes), and typed-accessor equality. -
New public
wav::open_wav_demuxerreturning the concrete
WavDemuxerso the typed accessor surface (format_tag,
channel_mask,subformat,acid, …) is reachable without
downcasting; the registry path now wraps it. -
WAV demuxer completes
LIST adtl(Associated Data List) coverage per
docs/container/riff/metadata/microsoft-riffmci.pdf§3 "Associated
Data Chunk" — all four spec-defined sub-chunks are now parsed. The
filesub-chunk (§3 "Embedded File Information",
file( <dwName:DWORD> <dwMedType:DWORD> <fileData:BYTE>... )) is no
longer skipped:wav:adtl.file.<dwName>.med_typerenders the media
type as FOURCC text when printable, the spec-allowed zero value as
plain0("This field can contain a zero value"), and hex otherwise;
wav:adtl.file.<dwName>.body_lencarries the embeddedfileData
payload length (the payload bytes themselves are not exposed through
the string-typed metadata API —body_lenkeeps the attachment
observable without pretending the parser interprets the inner
format). Theltxtsub-chunk's four previously-undecoded locale
WORDs now surface:wav:adtl.ltxt.<dwName>.country/.language/
.dialect/.code_page(raw decimals, always emitted), with
.country_name/.language_nameresolved through the same §3
Chapter-2 "Country Codes" / "Language and Dialect Codes" tables the
CSETparser uses (emitted only when the code is in the spec's
enumerated set; zero resolves to the tables' explicitNonerows).
Sub-chunks shorter than their fixed headers (8 bytes forfile,
20 forltxt) remain skipped-as-opaque. Three new lib tests cover
thefileFOURCC + zero-med_type + payload-length path, the
truncated-fileopaque path, and the zero-localeltxtpath; the
existingltxttest now drives non-zero locale fields
(UK / UK English / code page 1252) end-to-end. -
WAV demuxer recognises the
slnt(Silence) chunk per
docs/container/riff/metadata/microsoft-riffmci.pdf§3 "Wave Data"
(<silence-ck> ➝ slnt( <dwSamples:DWORD> )— "Count of silent
samples"). Per the §3 note the chunk records a count of silent
samples rather than carrying any PCM payload, and the correct
playback fill value is context-dependent ("not necessarily a repeated
zero volume or baseline sample"), so the parser surfaces accounting
metadata without synthesising real zero/baseline samples into the
decoded stream. Each encounter emitswav:slnt.<n>.samples
(zero-based per-chunkdwSamplescount) and updates the rolling
aggregateswav:slnt.count(totalslntchunks seen) and
wav:slnt.total_samples(cumulative silent-sample count). The §3
grammar<wave-data> ➝ { <data-ck> | <data-list> }lets the chunk
appear at the top level as a sibling ofdata(as well as inside a
wavlLIST); the demuxer accounts for every top-level occurrence. A
body shorter than the 4-bytedwSamplesfield is counted but treated
as opaque — it contributes0to the running total and omits its
per-chunksampleskey, mirroring the other fixed-struct parsers;
a body longer than 4 bytes decodes the leading DWORD and tolerates
trailing forward-extension bytes. An odd-length body forces the
standard RIFF §2 word-align pad and thedatachunk that follows is
located correctly. Files with noslntchunk emit nowav:slnt.*
keys at all — absence is observable. Seven new lib tests cover the
single-chunk path, the multi-chunk accumulation path, the
zero-samples in-range path, the no-slnt absence guard, the
under-length opaque-body path, the over-length leading-DWORD path,
and the coexistence path withJUNK+LIST INFOinterleaved. -
WAV demuxer parses the
_PMXchunk (Adobe XMP packet, the WAV/AVI
carrier for an XMP serialised packet, catalogued in
docs/container/riff/metadata/exiftool-riff-tags.html§ "RIFF Main
tags" — entry'_PMX', familyXMP, scope "AVI and WAV files").
The FOURCC is little-endian "XMP_" reversed, the convention RIFF
uses for chunks whose payload originates in a little-endian
DWORD-aligned authoring tool; the payload is the XMP packet text
exactly as it would appear in an XMP sidecar (x:xmpmetawrapped
in<?xpacket begin=...?>/<?xpacket end=...?>processing
instructions). Surface shape mirrors the sibling third-party XML
parsers (iXML/<axml>) so the consumer keeps a single mental
model: the UTF-8 XMP text surfaces verbatim underwav:xmp
(trimmed at the first NUL and surrounding whitespace so writers
that NUL-pad a fixed-size XMP region for in-place editing do not
leak padding into the text key), and the raw on-wire chunk-body
length always surfaces underwav:xmp.body_lenwhen the chunk is
present — even for empty / NUL-only / whitespace-only bodies — so
downstream tooling can distinguish "no_PMXchunk" from "an
_PMXchunk reserved for later XMP authoring". An odd-length body
forces the standard RIFF 1-byte pad and thedatachunk that
follows is located correctly. The XMP schema (RDF, namespace
prefixes, xpacket processing instructions) is not interpreted at
this layer; a higher-level XMP-aware crate can apply
schema-specific decoding without re-walking the RIFF tree. Tests
cover a canonical Adobe-style packet, NUL-padded reservation
bodies, an empty body (body_len = 0, no text key),
whitespace-only bodies, odd-length pad rounding, and the absence
guard (a file without_PMXemits nowav:xmp.*keys). -
WAV demuxer parses the
<axml>chunk per
docs/container/riff/metadata/ebu-tech3285s5-ADM.pdf§3 ("AXML
chunk definition"). The chunk carries a UTF-8 XML document
(typically an EBUCore wrapper around an<audioFormatExtended>
ADM document — see §4.2 — or an ISRC identifier declaration —
§4.1) and is supplement-5's vehicle for transporting ADM
metadata inside BWF / RF64 / BW64 files. The parser surfaces
the textual payload verbatim underwav:axml(trimmed at the
first NUL and surrounding whitespace, so writers that NUL-pad
to reserve room for in-place editing of the ADM document do
not leak the padding into the text key) and always emits the
raw on-wire chunk-body length underwav:axml.body_len
whenever the chunk is present — even for empty / NUL-only /
whitespace-only bodies — so downstream tooling can distinguish
"no<axml>chunk" from "an<axml>chunk reserved for later
ADM authoring". An odd-length body forces the standard RIFF
1-byte pad and thedatachunk that follows is located
correctly. The XML schema is not interpreted at this layer —
the parser is schema-agnostic so a higher-level ADM-aware
crate (or a downstream tool walkingwav:axml) can apply the
EBUCore / BS.2076 decoding without re-walking the RIFF tree.
Six new lib tests cover the canonical EBUCore-wrapped ADM
document path, the ISRC identifier example from §4.1, the
NUL-padded reservation path, the empty-body absence guard, the
whitespace-only placeholder path, and the odd-length-body
padding regression guard. -
WAV demuxer recognises the
JUNK(Filler) chunk per
docs/container/riff/metadata/microsoft-riffmci.pdf§2 "JUNK
(Filler) Chunk" ("A JUNK chunk represents padding, filler or
outdated information. It contains no relevant data; it is a space
filler of arbitrary size."). The chunk body is deliberately not
surfaced — its bytes are spec-defined as having no semantic content
— but the demuxer accounts for everyJUNKchunk seen so a
downstream tool can observe how much filler the producer reserved
(commonly for in-place editing) without re-walking the file. Each
encounter emitswav:junk.<n>.body_len(zero-based, per-chunk
payload size) and updates the rolling aggregateswav:junk.count
(totalJUNKchunks seen) andwav:junk.total_bytes(cumulative
payload size; excludes the 8-byte chunk header and the implicit
word-align pad byte). MultipleJUNKchunks are allowed; empty
bodies (size = 0) still increment the count. Files with noJUNK
chunk emit nowav:junk.*keys at all — absence is observable.
Odd-length bodies forward the chunk-walk past the implicit RIFF §2
word-align pad byte correctly. Six new lib tests cover the single-
chunk path, the multi-chunk accumulation path, the zero-length
in-range path, the no-JUNK absence guard, the odd-length-body
padding regression guard, and the coexistence path withLIST INFOCSETinterleaved around the JUNK chunks.
-
New
filtermodule exposing a typed scalar form of the Reinhard 2002
simple global tone-mapping operator (Ld = L / (1 + L)) per
docs/image/filter/tone-mapping-operators.md§2.2. The forward map is
surfaced asSceneLuminance::to_display; the closed-form inverse
L = Ld / (1 − Ld)is surfaced asDisplayLuminance::to_scene. The
two newtypes separate scene (pre-tone-map, non-negative finite) and
display (post-tone-map,[0, 1)) luminance domains so callers can't
swap them at a function boundary. Constructors reject invalid inputs
(negative scene values, NaN, non-finite,Ld ≥ 1.0). Round-trip is
bit-exact at the three §2.2 reference points (L = 0 → 0,
L = 1 → 0.5,L = 3 → 0.75) and stays within a relative error of
1e-9across an 11-point span from1e-9to1e6of scene
luminance. Six in-crate unit tests plus three integration tests on
the public surface cover constructor rejection, known curve values,
theLd < 1asymptote, and round-trip in both directions. -
WAV demuxer recognises the
RF64andBW64top-level form magics
(EBU Tech 3306 v1 §3 and ITU-R BS.2088 / EBU Tech 3306 v2) and
parses the mandatoryds64chunk that follows. The 28-byte fixed
prefix decodes the 64-bitriffSize,dataSizeandsampleCount
overrides plus atableLengthcount of(chunkId, chunkSize64)
records — the optional table of per-chunk-ID 64-bit size overrides
for any non-datachunk that exceeds 4 GiB. When a chunk's 32-bit
on-wire size carries the0xFFFFFFFFsentinel the demuxer promotes
it to 64-bit via the dedicatedds64.dataSize(fordata) or the
ds64.tablelookup (for any other FOURCC). The legacy
fact.dwFileSizeis likewise promoted tods64.sampleCountwhen
it carries the sentinel. Surfaced underwav:rf64.magic,
wav:rf64.riff_size,wav:rf64.data_size,wav:rf64.sample_count,
wav:rf64.table.count, per-entrywav:rf64.table.<i>.id/
.size, andwav:rf64.body_len. Five new tests cover the RF64
ds64-promoted PCM path, the BW64 magic accepted on the same path,
the non-datatable-lookup path through aLIST INFOwhose size
is the sentinel, a sentinel-without-ds64rejection, and a
short-bodyds64rejection. -
WAV demuxer now resolves the complete Microsoft RIFF MCI §3 "INFO
List Chunk" baseline — all 23 sub-IDs registered by the 1991 spec
perdocs/container/riff/metadata/microsoft-riffmci.pdfpp. 2-14
to 2-16. The eleven previously-handled sub-IDs (INAM,IART,
IPRD,ICMT,ICRD,IGNR,ICOP,IENG,ITCH,ISFT,
ISBJ) keep their conventional short-form key names; the twelve
additions surface under spec-derived snake_case names:IARL→
archival_location,ICMS→commissioned,ICRP→cropped,
IDIM→dimensions,IDPI→dpi,IKEY→keywords,
ILGT→lightness,IMED→medium,IPLT→
palette_setting,ISHP→sharpness,ISRC→source,
ISRF→source_form. The non-baselineITRK→track
extension is retained for compatibility with the tag-writer
ecosystem. Unknown sub-IDs are skipped silently rather than
synthesised into ad-hoc keys, matching the §3 forward-compatibility
rule that "new chunks may be defined". -
WAV demuxer parses the
CSET(Character Set) chunk per
docs/container/riff/metadata/microsoft-riffmci.pdf§3
"CSET (Character Set) Chunk". The canonical 8-byte body carries four
16-bit little-endian fields —wCodePage,wCountryCode,
wLanguageCode,wDialect— declaring the code page, country,
language and dialect that file elements (notably theLIST INFO
ZSTR sub-chunks) are interpreted under. The parser surfaces raw
values underwav:cset.code_page/.country/.language/
.dialect, resolves the §3 "Country Codes" and "Language and
Dialect Codes" enumerations to human-readable
wav:cset.country_name/wav:cset.language_namekeys, and always
emitswav:cset.body_lenso writers that extend the chunk past the
canonical 8 bytes are still observable to downstream tooling.
All-zero fields are honoured as the spec's "use defaults" form (ISO
8859/1 / USA / US English). Bodies shorter than 8 bytes are treated
as opaque (onlybody_lenis emitted); bodies longer than 8 bytes
tolerate the trailing region for forward compatibility. CSET coexists
withLIST INFO: a single file may carry both, and the INFO sub-IDs
still resolve through the existing standard-tag mapping. -
WAV demuxer parses the
iXMLchunk (the third-party
production-recorder metadata block catalogued in
docs/container/riff/metadata/exiftool-riff-tags.html§iXML
and discussed in
docs/container/riff/metadata/README.md§ "iXML"). The chunk
carries a UTF-8 XML document —IXML_VERSION,PROJECT,
SCENE,TAKE,TAPE,NOTE,UBITS,FILE_UID, aBWF
sub-group mirroring thebextfields and aTRACK_LISTof
per-trackNAME/FUNCTION/CHANNEL_INDEXmappings. The
parser surfaces the text payload verbatim underwav:ixml
(trimmed at the first NUL and surrounding whitespace so writers
that NUL-pad the body to a fixed size for in-place editing do
not surface spurious trailing bytes) and always emits the raw
on-wire chunk-body length underwav:ixml.body_lenwhenever the
chunk is present — even for empty / NUL-only / whitespace-only
bodies — so downstream tooling can distinguish "noiXML
chunk" from "aniXMLchunk reserved for later population". An
odd-length body forces the standard RIFF 1-byte pad and the
datachunk that follows is located correctly.