Pulp 1.88.0
Cross-platform audio plugin framework
Loading...
Searching...
No Matches
wav_metadata.hpp
Go to the documentation of this file.
1// wav_metadata.hpp — read + write WAV ancillary metadata chunks.
2//
3// Handles BWAV / iXML / ASWG / ACID metadata in RIFF-WAVE and RF64-
4// extended WAV files. Metadata can be mutated in-memory and written back
5// without rewriting the audio payload; the original `fmt ` and `data`
6// chunks are preserved verbatim.
7//
8// Why a hand-rolled chunk codec on top of CHOC: CHOC's WAV reader
9// gives us PCM samples but only surfaces a small subset of metadata
10// (bext fields as a string map, no iXML/axml/acid). We need exact,
11// reversible byte-level access to the named chunks so production-audio
12// metadata round-trips losslessly through Pulp tooling.
13//
14// Chunks supported (canonical four-CCs in parens):
15// • bext — EBU R 128 / EBU Tech 3285 "Broadcast Wave Format" extension.
16// • iXML — Gallery iXML production-metadata XML chunk.
17// • axml — ASWG / EBU 3285s5 generic XML metadata chunk (often
18// called "ASWG" by field-recorder makers).
19// • acid — Sony ACIDized-loop info chunk (tempo / root note / loop
20// markers, used by ACID, Ableton Live, FL Studio, etc.).
21//
22// Unknown chunks present in the source file are preserved on write so
23// `replace_metadata_in_file()` only touches the four supported chunk
24// kinds.
25
26#pragma once
27
28#include <array>
29#include <cstdint>
30#include <optional>
31#include <string>
32#include <vector>
33
34namespace pulp::audio {
35
36// ── Broadcast Wave Format (bext) ────────────────────────────────────────────
37//
38// EBU Tech 3285 v2 layout. Field sizes are fixed by the spec; we model
39// the strings as `std::string` for ergonomic round-trip but they are
40// padded / truncated to the exact byte counts during encoding.
42 std::string description; // 256 bytes
43 std::string originator; // 32 bytes
44 std::string originator_reference; // 32 bytes
45 std::string origination_date; // 10 bytes "YYYY-MM-DD"
46 std::string origination_time; // 8 bytes "HH:MM:SS"
47 uint64_t time_reference = 0; // samples since midnight (low+high u32)
48 uint16_t version = 1; // bext version (1 or 2)
49 std::array<uint8_t, 64> umid{}; // SMPTE 330M UMID (zero by default)
50 int16_t loudness_value = 0; // 1/100 LUFS (bext v2 only)
51 int16_t loudness_range = 0; // 1/100 LU
52 int16_t max_true_peak_level = 0; // 1/100 dBTP
55 std::string coding_history; // free-form ASCII (variable length)
56};
57
58// ── ACID loop info (acid) ───────────────────────────────────────────────────
59//
60// Sony ACIDized-loop chunk, 24 bytes total. `flags` bit semantics:
61// 0x01 one-shot (no loop)
62// 0x02 root note set
63// 0x04 stretch
64// 0x08 disk-based
65// 0x10 ACIDizer (the file claims its metadata is authoritative)
67 uint32_t flags = 0;
68 uint16_t root_note = 60; // MIDI note number; only valid when flags & 0x02
69 uint16_t reserved1 = 0;
70 float reserved2 = 0.0f;
71 uint32_t num_beats = 0;
72 uint16_t meter_denominator = 4; // bottom of time signature (2,4,8,16)
73 uint16_t meter_numerator = 4; // top of time signature
74 float tempo_bpm = 0.0f;
75};
76
77// ── Aggregate ───────────────────────────────────────────────────────────────
78//
79// Each metadata kind is optional. Unset = chunk not present (read) or
80// will not be written (write). `unknown_chunks` lets `replace_metadata_in_file`
81// preserve every other RIFF chunk verbatim.
83 std::optional<BwavMetadata> bwav;
84 std::optional<AcidMetadata> acid;
85 std::optional<std::string> ixml; // raw XML payload (UTF-8)
86 std::optional<std::string> axml; // raw XML payload (ASWG)
87
88 struct UnknownChunk {
89 std::array<char, 4> id{};
90 std::vector<uint8_t> data;
91 };
92 std::vector<UnknownChunk> unknown_chunks; // preserved as-is on write
93};
94
95// ── Public API ──────────────────────────────────────────────────────────────
96//
97// `read_wav_metadata` returns nullopt only on I/O error or a malformed
98// RIFF header. A successful read with zero recognized chunks returns an
99// empty WavMetadata struct, not nullopt.
100std::optional<WavMetadata> read_wav_metadata(const std::string& path);
101
102// `write_wav_metadata` writes a brand-new WAV with the given metadata
103// chunks plus a minimal silent `fmt ` + `data`. Useful for fixtures
104// and round-trip tests; prefer `replace_metadata_in_file` for editing
105// existing files in place.
106bool write_wav_metadata(const std::string& path,
107 const WavMetadata& metadata,
108 uint32_t sample_rate = 48000,
109 uint16_t num_channels = 1,
110 uint16_t bits_per_sample = 16);
111
112// `replace_metadata_in_file` rewrites the metadata chunks of an
113// existing WAV without touching its `fmt ` or `data` payload. Returns
114// false on I/O error or if the source is not a RIFF-WAVE file. Any
115// supported chunk that is absent from `metadata` is removed; unknown
116// chunks present in `metadata.unknown_chunks` are written back as-is.
117bool replace_metadata_in_file(const std::string& path, const WavMetadata& metadata);
118
119// ── Codec primitives (exposed for tests / advanced callers) ─────────────────
120std::vector<uint8_t> encode_bext_chunk(const BwavMetadata& bwav);
121std::vector<uint8_t> encode_acid_chunk(const AcidMetadata& acid);
122
123std::optional<BwavMetadata> decode_bext_chunk(const uint8_t* data, size_t size);
124std::optional<AcidMetadata> decode_acid_chunk(const uint8_t* data, size_t size);
125
126} // namespace pulp::audio
Definition analyzer_provider.hpp:12
bool replace_metadata_in_file(const std::string &path, const WavMetadata &metadata)
bool write_wav_metadata(const std::string &path, const WavMetadata &metadata, uint32_t sample_rate=48000, uint16_t num_channels=1, uint16_t bits_per_sample=16)
std::vector< uint8_t > encode_bext_chunk(const BwavMetadata &bwav)
std::optional< AcidMetadata > decode_acid_chunk(const uint8_t *data, size_t size)
std::optional< WavMetadata > read_wav_metadata(const std::string &path)
std::vector< uint8_t > encode_acid_chunk(const AcidMetadata &acid)
std::optional< BwavMetadata > decode_bext_chunk(const uint8_t *data, size_t size)
Definition wav_metadata.hpp:66
uint32_t num_beats
Definition wav_metadata.hpp:71
float tempo_bpm
Definition wav_metadata.hpp:74
uint16_t reserved1
Definition wav_metadata.hpp:69
uint32_t flags
Definition wav_metadata.hpp:67
uint16_t meter_denominator
Definition wav_metadata.hpp:72
uint16_t meter_numerator
Definition wav_metadata.hpp:73
float reserved2
Definition wav_metadata.hpp:70
uint16_t root_note
Definition wav_metadata.hpp:68
Definition wav_metadata.hpp:41
uint64_t time_reference
Definition wav_metadata.hpp:47
int16_t max_momentary_loudness
Definition wav_metadata.hpp:53
std::string description
Definition wav_metadata.hpp:42
std::string origination_time
Definition wav_metadata.hpp:46
int16_t loudness_range
Definition wav_metadata.hpp:51
std::string origination_date
Definition wav_metadata.hpp:45
int16_t max_short_term_loudness
Definition wav_metadata.hpp:54
std::string originator_reference
Definition wav_metadata.hpp:44
int16_t loudness_value
Definition wav_metadata.hpp:50
std::array< uint8_t, 64 > umid
Definition wav_metadata.hpp:49
int16_t max_true_peak_level
Definition wav_metadata.hpp:52
uint16_t version
Definition wav_metadata.hpp:48
std::string coding_history
Definition wav_metadata.hpp:55
std::string originator
Definition wav_metadata.hpp:43
Definition wav_metadata.hpp:88
std::vector< uint8_t > data
Definition wav_metadata.hpp:90
Definition wav_metadata.hpp:82
std::vector< UnknownChunk > unknown_chunks
Definition wav_metadata.hpp:92
std::optional< AcidMetadata > acid
Definition wav_metadata.hpp:84
std::optional< BwavMetadata > bwav
Definition wav_metadata.hpp:83
std::optional< std::string > ixml
Definition wav_metadata.hpp:85
std::optional< std::string > axml
Definition wav_metadata.hpp:86