Skip to content

v0.0.9

Choose a tag to compare

@MagicalTux MagicalTux released this 15 Jun 05:12
· 41 commits to master since this release
f6269f1

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_EXTENSIBLE SubFormat-GUID resolution now implements
    the KSMedia.h DEFINE_WAVEFORMATEX_GUID(x) /
    IS_VALID_WAVEFORMATEX_GUID / EXTRACT_WAVEFORMATEX_ID macros (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 legacy wFormatTag x and
    dispatches through the same codec_for_tag route the WAVEFORMATEX
    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 as wav:fmt.subformat_tag (e.g. 0x0055 for 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 a wavl-form WAV had no decodable audio (the LIST type
    fell through and no data anchor was set). The demuxer now resolves
    the first embedded data sub-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. Embedded slnt
    silence segments feed the shared wav:slnt.* accounting so the
    silent-sample totals match a top-level-slnt file (no zero/baseline
    samples are synthesised — §3 is explicit that slnt is a count of
    silent samples). A silence-only wavl (no data segment) is
    rejected as having no waveform; odd-length data segments respect
    RIFF word-alignment.

  • WAV WAVEFORMATEXTENSIBLE.dwChannelMask decoding — the channel
    bitmap is now expanded into a human-readable SPEAKER_* 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 new wav:fmt.channel_layout metadata key and a
    typed WavDemuxer::channel_layout() accessor. A 0 mask (no
    assigned positions) yields None; bits above the highest defined
    flag are preserved verbatim as UNKNOWN(0x...) so no round-trip
    information is dropped.

  • WAV Acidizer acid chunk — 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::AcidChunk type with parse / to_bytes and 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 surfaces wav:acid.flags /
    .one_shot / .root_note_set / .stretch / .disk_based /
    .high_octave / .root_note / .root_note_name / .num_beats /
    .meter / .tempo (+ .reserved hex 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_demuxer returning the concrete
    WavDemuxer so 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
    file sub-chunk (§3 "Embedded File Information",
    file( <dwName:DWORD> <dwMedType:DWORD> <fileData:BYTE>... )) is no
    longer skipped: wav:adtl.file.<dwName>.med_type renders the media
    type as FOURCC text when printable, the spec-allowed zero value as
    plain 0 ("This field can contain a zero value"), and hex otherwise;
    wav:adtl.file.<dwName>.body_len carries the embedded fileData
    payload length (the payload bytes themselves are not exposed through
    the string-typed metadata API — body_len keeps the attachment
    observable without pretending the parser interprets the inner
    format). The ltxt sub-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_name resolved through the same §3
    Chapter-2 "Country Codes" / "Language and Dialect Codes" tables the
    CSET parser uses (emitted only when the code is in the spec's
    enumerated set; zero resolves to the tables' explicit None rows).
    Sub-chunks shorter than their fixed headers (8 bytes for file,
    20 for ltxt) remain skipped-as-opaque. Three new lib tests cover
    the file FOURCC + zero-med_type + payload-length path, the
    truncated-file opaque path, and the zero-locale ltxt path; the
    existing ltxt test 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 emits wav:slnt.<n>.samples
    (zero-based per-chunk dwSamples count) and updates the rolling
    aggregates wav:slnt.count (total slnt chunks 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 of data (as well as inside a
    wavl LIST); the demuxer accounts for every top-level occurrence. A
    body shorter than the 4-byte dwSamples field is counted but treated
    as opaque — it contributes 0 to the running total and omits its
    per-chunk samples key, 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 the data chunk that follows is
    located correctly. Files with no slnt chunk emit no wav: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 with JUNK + LIST INFO interleaved.

  • WAV demuxer parses the _PMX chunk (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', family XMP, 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:xmpmeta wrapped
    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 under wav: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 under wav:xmp.body_len when the chunk is
    present — even for empty / NUL-only / whitespace-only bodies — so
    downstream tooling can distinguish "no _PMX chunk" from "an
    _PMX chunk reserved for later XMP authoring". An odd-length body
    forces the standard RIFF 1-byte pad and the data chunk 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 _PMX emits no wav: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 under wav: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 under wav: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 the data chunk 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 walking wav: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 every JUNK chunk 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 emits wav:junk.<n>.body_len (zero-based, per-chunk
    payload size) and updates the rolling aggregates wav:junk.count
    (total JUNK chunks seen) and wav:junk.total_bytes (cumulative
    payload size; excludes the 8-byte chunk header and the implicit
    word-align pad byte). Multiple JUNK chunks are allowed; empty
    bodies (size = 0) still increment the count. Files with no JUNK
    chunk emit no wav: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 with LIST INFO

    • CSET interleaved around the JUNK chunks.
  • New filter module 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 as SceneLuminance::to_display; the closed-form inverse
    L = Ld / (1 − Ld) is surfaced as DisplayLuminance::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-9 across an 11-point span from 1e-9 to 1e6 of scene
    luminance. Six in-crate unit tests plus three integration tests on
    the public surface cover constructor rejection, known curve values,
    the Ld < 1 asymptote, and round-trip in both directions.

  • WAV demuxer recognises the RF64 and BW64 top-level form magics
    (EBU Tech 3306 v1 §3 and ITU-R BS.2088 / EBU Tech 3306 v2) and
    parses the mandatory ds64 chunk that follows. The 28-byte fixed
    prefix decodes the 64-bit riffSize, dataSize and sampleCount
    overrides plus a tableLength count of (chunkId, chunkSize64)
    records — the optional table of per-chunk-ID 64-bit size overrides
    for any non-data chunk that exceeds 4 GiB. When a chunk's 32-bit
    on-wire size carries the 0xFFFFFFFF sentinel the demuxer promotes
    it to 64-bit via the dedicated ds64.dataSize (for data) or the
    ds64.table lookup (for any other FOURCC). The legacy
    fact.dwFileSize is likewise promoted to ds64.sampleCount when
    it carries the sentinel. Surfaced under wav:rf64.magic,
    wav:rf64.riff_size, wav:rf64.data_size, wav:rf64.sample_count,
    wav:rf64.table.count, per-entry wav:rf64.table.<i>.id /
    .size, and wav:rf64.body_len. Five new tests cover the RF64
    ds64-promoted PCM path, the BW64 magic accepted on the same path,
    the non-data table-lookup path through a LIST INFO whose size
    is the sentinel, a sentinel-without-ds64 rejection, and a
    short-body ds64 rejection.

  • WAV demuxer now resolves the complete Microsoft RIFF MCI §3 "INFO
    List Chunk" baseline — all 23 sub-IDs registered by the 1991 spec
    per docs/container/riff/metadata/microsoft-riffmci.pdf pp. 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, ICMScommissioned, ICRPcropped,
    IDIMdimensions, IDPIdpi, IKEYkeywords,
    ILGTlightness, IMEDmedium, IPLT
    palette_setting, ISHPsharpness, ISRCsource,
    ISRFsource_form. The non-baseline ITRKtrack
    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 the LIST INFO
    ZSTR sub-chunks) are interpreted under. The parser surfaces raw
    values under wav: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_name keys, and always
    emits wav:cset.body_len so 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 (only body_len is emitted); bodies longer than 8 bytes
    tolerate the trailing region for forward compatibility. CSET coexists
    with LIST INFO: a single file may carry both, and the INFO sub-IDs
    still resolve through the existing standard-tag mapping.

  • WAV demuxer parses the iXML chunk (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, a BWF
    sub-group mirroring the bext fields and a TRACK_LIST of
    per-track NAME / FUNCTION / CHANNEL_INDEX mappings. The
    parser surfaces the text payload verbatim under wav: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 under wav:ixml.body_len whenever the
    chunk is present — even for empty / NUL-only / whitespace-only
    bodies — so downstream tooling can distinguish "no iXML
    chunk" from "an iXML chunk reserved for later population". An
    odd-length body forces the standard RIFF 1-byte pad and the
    data chunk that follows is located correctly.