Pulp 1.88.0
Cross-platform audio plugin framework
Loading...
Searching...
No Matches
spectral_frame_codec.hpp
Go to the documentation of this file.
1#pragma once
2
3#include <bit>
4#include <cstddef>
5#include <cstdint>
6#include <limits>
7#include <memory>
8#include <new>
9#include <optional>
10#include <span>
11
12namespace pulp::signal {
13
18 std::uint32_t fft_size = 256;
19 std::uint32_t hop = 64;
20 std::uint16_t channels = 1;
21 double sample_rate = 48000;
22 std::uint32_t profile = 1;
23 std::uint16_t layout = 1;
24};
25
27 std::uint64_t sequence = 0;
28 std::int64_t origin = 0;
29};
30
35 std::uint64_t frame_count = 0;
36 std::size_t component_count = 0;
37 std::unique_ptr<SpectralFrameStamp[]> frames;
38 std::unique_ptr<float[]> components;
39};
40
42 std::size_t size = 0;
43 std::unique_ptr<std::uint8_t[]> bytes;
44 [[nodiscard]] std::span<const std::uint8_t> view() const noexcept {
45 return {bytes.get(), size};
46 }
47};
48
60
63 std::uint64_t retained_bytes = 0;
65 std::uint64_t input_bytes = 0;
67 std::uint64_t temporary_bytes = 4096;
68};
69
70template <class T> struct SpectralCodecResult {
73 std::optional<T> value;
74 explicit operator bool() const noexcept {
75 return value.has_value();
76 }
77};
78
79inline constexpr std::uint64_t spectral_codec_memory_budget = 128ULL * 1024 * 1024;
80
82static_assert(sizeof(float) == 4 && std::numeric_limits<float>::is_iec559);
83static_assert(sizeof(double) == 8 && std::numeric_limits<double>::is_iec559);
84
85struct Shape {
86 std::uint64_t components = 0, record_bytes = 0, payload = 0;
87 std::int64_t first_origin = 0;
88};
89
90inline SpectralCodecError shape(const SpectralStreamGeometry& g, std::uint64_t count,
91 Shape& s) noexcept {
92 if (g.profile != 1 || g.layout != 1)
94 const auto rate = std::bit_cast<std::uint64_t>(g.sample_rate);
95 if (g.fft_size < 256 || g.fft_size > 16384 || (g.fft_size & (g.fft_size - 1)) || !g.hop ||
96 g.hop > g.fft_size / 2 || !g.channels || g.channels > 16 || (rate >> 63) ||
97 !(rate & 0x7fffffffffffffffULL) || (rate & 0x7ff0000000000000ULL) == 0x7ff0000000000000ULL)
99 if (!count || count > 1048576)
101 // All operands are capped before multiplication: no untrusted product can wrap.
102 s.components = 2ULL * g.channels * (g.fft_size / 2 + 1);
103 s.record_bytes = 16 + 4 * s.components;
104 s.payload = count * s.record_bytes;
105 if (s.payload > 67108864)
107 s.first_origin = -static_cast<std::int64_t>((g.fft_size - 1) / g.hop * g.hop);
109}
110
111inline std::uint64_t read(std::span<const std::uint8_t> b, std::size_t p, unsigned n) noexcept {
112 std::uint64_t v = 0;
113 for (unsigned i = 0; i < n; ++i)
114 v |= std::uint64_t(b[p + i]) << (8 * i);
115 return v;
116}
117inline void write(std::uint8_t* b, std::size_t p, std::uint64_t v, unsigned n) noexcept {
118 for (unsigned i = 0; i < n; ++i)
119 b[p + i] = static_cast<std::uint8_t>(v >> (8 * i));
120}
121inline SpectralCodecError component(std::uint32_t bits, std::uint64_t index, std::uint32_t fft,
122 bool strict) noexcept {
123 if ((bits & 0x7f800000U) == 0x7f800000U)
125 if (strict && bits == 0x80000000U)
127 const auto local = index % (2ULL * (fft / 2 + 1));
128 if ((local == 1 || local == fft + 1) && (bits & 0x7fffffffU))
131}
132} // namespace spectral_codec_detail
133
136inline SpectralCodecResult<OwnedSpectralStream>
138 std::uint64_t budget = spectral_codec_memory_budget) noexcept {
141 r.error = spectral_codec_detail::shape(g, count, s);
142 if (r.error != SpectralCodecError::None)
143 return r;
144 r.memory.retained_bytes =
145 sizeof(r) + count * sizeof(SpectralFrameStamp) + count * s.components * sizeof(float);
146 if (r.memory.retained_bytes > budget ||
147 r.memory.temporary_bytes > budget - r.memory.retained_bytes) {
149 return r;
150 }
152 out.geometry = g;
153 out.frame_count = count;
154 out.component_count = static_cast<std::size_t>(count * s.components);
155 out.frames.reset(new (std::nothrow) SpectralFrameStamp[static_cast<std::size_t>(count)]);
156 out.components.reset(new (std::nothrow) float[out.component_count]{});
157 if (!out.frames || !out.components) {
159 return r;
160 }
161 for (std::uint64_t i = 0; i < count; ++i)
162 out.frames[i] = {i, s.first_origin + static_cast<std::int64_t>(i * g.hop)};
163 r.value.emplace(std::move(out));
164 return r;
165}
166
169inline SpectralCodecResult<OwnedSpectralStream>
170decode_spectral_stream(std::span<const std::uint8_t> bytes,
171 std::uint64_t budget = spectral_codec_memory_budget) noexcept {
172 using namespace spectral_codec_detail;
174 r.memory.input_bytes = bytes.size();
175 auto fail = [&](SpectralCodecError e) {
176 r.error = e;
177 return std::move(r);
178 };
179 if (bytes.size() < 64)
181 constexpr char magic[] = "PULPSFRM";
182 for (unsigned i = 0; i < 8; ++i)
183 if (bytes[i] != magic[i])
185 if (read(bytes, 8, 2) != 1 || read(bytes, 10, 2) != 64 || read(bytes, 28, 4) ||
186 read(bytes, 56, 8))
189 g.profile = static_cast<std::uint32_t>(read(bytes, 12, 4));
190 g.fft_size = static_cast<std::uint32_t>(read(bytes, 16, 4));
191 g.hop = static_cast<std::uint32_t>(read(bytes, 20, 4));
192 g.channels = static_cast<std::uint16_t>(read(bytes, 24, 2));
193 g.layout = static_cast<std::uint16_t>(read(bytes, 26, 2));
194 g.sample_rate = std::bit_cast<double>(read(bytes, 32, 8));
195 const auto count = read(bytes, 40, 8);
196 Shape s;
197 const auto error = shape(g, count, s);
198 if (error != SpectralCodecError::None)
199 return fail(error);
200 if (read(bytes, 48, 8) != s.payload || bytes.size() != 64 + s.payload)
202 r.memory.retained_bytes =
203 sizeof(r) + count * sizeof(SpectralFrameStamp) + count * s.components * sizeof(float);
204 if (r.memory.retained_bytes > budget ||
205 r.memory.temporary_bytes > budget - r.memory.retained_bytes)
207 for (std::uint64_t i = 0; i < count; ++i) {
208 const auto p = static_cast<std::size_t>(64 + i * s.record_bytes);
209 // The capped count/hop keep origin and completion far inside signed i64.
210 const auto origin = s.first_origin + static_cast<std::int64_t>(i * g.hop);
211 if (read(bytes, p, 8) != i || read(bytes, p + 8, 8) != std::bit_cast<std::uint64_t>(origin))
213 for (std::uint64_t j = 0; j < s.components; ++j) {
214 const auto e = component(static_cast<std::uint32_t>(read(bytes, p + 16 + 4 * j, 4)), j,
215 g.fft_size, true);
217 return fail(e);
218 }
219 }
220 r = make_spectral_stream(g, count, budget);
221 r.memory.input_bytes = bytes.size();
222 if (!r)
223 return r;
224 for (std::uint64_t i = 0; i < count; ++i)
225 for (std::uint64_t j = 0; j < s.components; ++j)
226 r.value->components[i * s.components + j] = std::bit_cast<float>(
227 static_cast<std::uint32_t>(read(bytes, 80 + i * s.record_bytes + 4 * j, 4)));
228 return r;
229}
230
232inline SpectralCodecResult<EncodedSpectralStream>
234 std::uint64_t budget = spectral_codec_memory_budget) noexcept {
235 using namespace spectral_codec_detail;
237 auto fail = [&](SpectralCodecError e) {
238 r.error = e;
239 return std::move(r);
240 };
241 Shape s;
242 const auto error = shape(in.geometry, in.frame_count, s);
243 if (error != SpectralCodecError::None)
244 return fail(error);
245 if (in.component_count != in.frame_count * s.components || !in.frames || !in.components)
247 r.memory.input_bytes = sizeof(in) + in.frame_count * sizeof(SpectralFrameStamp) +
248 in.component_count * sizeof(float);
249 r.memory.retained_bytes = sizeof(r) + 64 + s.payload;
250 if (r.memory.retained_bytes > budget ||
251 r.memory.temporary_bytes > budget - r.memory.retained_bytes)
253 for (std::uint64_t i = 0; i < in.frame_count; ++i) {
254 if (in.frames[i].sequence != i ||
255 in.frames[i].origin != s.first_origin + static_cast<std::int64_t>(i * in.geometry.hop))
257 }
258 for (std::size_t j = 0; j < in.component_count; ++j) {
259 const auto e = component(std::bit_cast<std::uint32_t>(in.components[j]), j,
260 in.geometry.fft_size, false);
262 return fail(e);
263 }
265 out.size = static_cast<std::size_t>(64 + s.payload);
266 out.bytes.reset(new (std::nothrow) std::uint8_t[out.size]{});
267 if (!out.bytes)
269 auto* b = out.bytes.get();
270 constexpr char magic[] = "PULPSFRM";
271 for (unsigned i = 0; i < 8; ++i)
272 b[i] = magic[i];
273 write(b, 8, 1, 2);
274 write(b, 10, 64, 2);
275 write(b, 12, 1, 4);
276 write(b, 16, in.geometry.fft_size, 4);
277 write(b, 20, in.geometry.hop, 4);
278 write(b, 24, in.geometry.channels, 2);
279 write(b, 26, 1, 2);
280 write(b, 32, std::bit_cast<std::uint64_t>(in.geometry.sample_rate), 8);
281 write(b, 40, in.frame_count, 8);
282 write(b, 48, s.payload, 8);
283 for (std::uint64_t i = 0; i < in.frame_count; ++i) {
284 const auto p = static_cast<std::size_t>(64 + i * s.record_bytes);
285 write(b, p, in.frames[i].sequence, 8);
286 write(b, p + 8, std::bit_cast<std::uint64_t>(in.frames[i].origin), 8);
287 for (std::uint64_t j = 0; j < s.components; ++j) {
288 auto bits = std::bit_cast<std::uint32_t>(in.components[i * s.components + j]);
289 if ((bits & 0x7fffffffU) == 0)
290 bits = 0;
291 write(b, p + 16 + 4 * j, bits, 4);
292 }
293 }
294 r.value.emplace(std::move(out));
295 return r;
296}
297
298} // namespace pulp::signal
Definition spectral_frame_codec.hpp:81
SpectralCodecError shape(const SpectralStreamGeometry &g, std::uint64_t count, Shape &s) noexcept
Definition spectral_frame_codec.hpp:90
std::uint64_t read(std::span< const std::uint8_t > b, std::size_t p, unsigned n) noexcept
Definition spectral_frame_codec.hpp:111
SpectralCodecError component(std::uint32_t bits, std::uint64_t index, std::uint32_t fft, bool strict) noexcept
Definition spectral_frame_codec.hpp:121
void write(std::uint8_t *b, std::size_t p, std::uint64_t v, unsigned n) noexcept
Definition spectral_frame_codec.hpp:117
Definition additive_bank.hpp:218
@ AllocationFailure
Definition source_filter_analysis.hpp:38
SpectralCodecError
Definition spectral_frame_codec.hpp:49
@ ResourceLimit
Definition spectral_frame_codec.hpp:53
@ InvalidGeometry
Definition spectral_frame_codec.hpp:52
@ InvalidComponent
Definition spectral_frame_codec.hpp:56
@ None
Definition spectral_frame_codec.hpp:50
@ InvalidLength
Definition spectral_frame_codec.hpp:54
@ NoncanonicalZero
Definition spectral_frame_codec.hpp:57
@ UnsupportedFormat
Definition spectral_frame_codec.hpp:51
@ InvalidChronology
Definition spectral_frame_codec.hpp:55
@ AllocationFailure
Definition spectral_frame_codec.hpp:58
SpectralCodecResult< OwnedSpectralStream > make_spectral_stream(SpectralStreamGeometry g, std::uint64_t count, std::uint64_t budget=spectral_codec_memory_budget) noexcept
Allocate an editable owned stream with canonical chronology and zero bins.
Definition spectral_frame_codec.hpp:137
@ None
Definition beat_repeat_kernel.hpp:36
constexpr std::uint64_t spectral_codec_memory_budget
Definition spectral_frame_codec.hpp:79
SpectralCodecResult< EncodedSpectralStream > encode_spectral_stream(const OwnedSpectralStream &in, std::uint64_t budget=spectral_codec_memory_budget) noexcept
Canonicalize signed bin zeros without mutating the caller. Offline only.
Definition spectral_frame_codec.hpp:233
SpectralCodecResult< OwnedSpectralStream > decode_spectral_stream(std::span< const std::uint8_t > bytes, std::uint64_t budget=spectral_codec_memory_budget) noexcept
Validate the whole input before allocating; no partial stream is returned.
Definition spectral_frame_codec.hpp:170
@ i
Definition formant_filter_bank.hpp:20
Definition spectral_frame_codec.hpp:41
std::unique_ptr< std::uint8_t[]> bytes
Definition spectral_frame_codec.hpp:43
std::size_t size
Definition spectral_frame_codec.hpp:42
std::span< const std::uint8_t > view() const noexcept
Definition spectral_frame_codec.hpp:44
Arrays have exact element counts, with no hidden vector capacity.
Definition spectral_frame_codec.hpp:33
std::unique_ptr< float[]> components
Definition spectral_frame_codec.hpp:38
SpectralStreamGeometry geometry
Definition spectral_frame_codec.hpp:34
std::size_t component_count
Definition spectral_frame_codec.hpp:36
std::unique_ptr< SpectralFrameStamp[]> frames
Definition spectral_frame_codec.hpp:37
std::uint64_t frame_count
Definition spectral_frame_codec.hpp:35
Definition spectral_frame_codec.hpp:61
std::uint64_t retained_bytes
Destination arrays plus result object; allocator bookkeeping is excluded.
Definition spectral_frame_codec.hpp:63
std::uint64_t input_bytes
Caller-owned input is reported separately and never copied for validation.
Definition spectral_frame_codec.hpp:65
std::uint64_t temporary_bytes
Conservative allowance for fixed local validation/result objects; no heap scratch.
Definition spectral_frame_codec.hpp:67
Definition spectral_frame_codec.hpp:70
SpectralCodecError error
Definition spectral_frame_codec.hpp:71
SpectralCodecMemory memory
Definition spectral_frame_codec.hpp:72
std::optional< T > value
Definition spectral_frame_codec.hpp:73
Definition spectral_frame_codec.hpp:26
std::int64_t origin
Definition spectral_frame_codec.hpp:28
std::uint64_t sequence
Definition spectral_frame_codec.hpp:27
Offline profile 1: symmetric float Hann, unscaled forward FFT, 1/N inverse, local-origin phase,...
Definition spectral_frame_codec.hpp:17
std::uint32_t hop
Definition spectral_frame_codec.hpp:19
std::uint16_t layout
Definition spectral_frame_codec.hpp:23
std::uint16_t channels
Definition spectral_frame_codec.hpp:20
std::uint32_t profile
Definition spectral_frame_codec.hpp:22
double sample_rate
Definition spectral_frame_codec.hpp:21
std::uint32_t fft_size
Definition spectral_frame_codec.hpp:18
Definition spectral_frame_codec.hpp:85
std::uint64_t record_bytes
Definition spectral_frame_codec.hpp:86
std::uint64_t payload
Definition spectral_frame_codec.hpp:86
std::uint64_t components
Definition spectral_frame_codec.hpp:86
std::int64_t first_origin
Definition spectral_frame_codec.hpp:87