Pulp 1.88.0
Cross-platform audio plugin framework
Loading...
Searching...
No Matches
sample_converter.hpp
Go to the documentation of this file.
1#pragma once
2
24
25#include <algorithm>
26#include <bit>
27#include <cmath>
28#include <cstddef>
29#include <cstdint>
30#include <cstring>
31#include <limits>
32
33namespace pulp::audio {
34
44
48enum class Endianness : uint8_t {
52};
53
55enum class Layout : uint8_t {
58};
59
61constexpr std::size_t bytes_per_sample(SampleFormat format) noexcept {
62 switch (format) {
63 case SampleFormat::Int8: return 1;
64 case SampleFormat::Int16: return 2;
65 case SampleFormat::Int24: return 3;
66 case SampleFormat::Int32: return 4;
67 case SampleFormat::Float32: return 4;
68 case SampleFormat::Float64: return 8;
69 }
70 return 0;
71}
72
74constexpr bool is_float(SampleFormat format) noexcept {
76}
77
78namespace detail {
79
80constexpr bool host_is_little_endian() noexcept {
81 return std::endian::native == std::endian::little;
82}
83
88
89template <typename T>
90inline T byteswap_int(T value) noexcept {
91 static_assert(std::is_integral_v<T>);
92 T out;
93 auto* in_bytes = reinterpret_cast<const uint8_t*>(&value);
94 auto* out_bytes = reinterpret_cast<uint8_t*>(&out);
95 for (std::size_t i = 0; i < sizeof(T); ++i) {
96 out_bytes[i] = in_bytes[sizeof(T) - 1 - i];
97 }
98 return out;
99}
100
101template <typename T>
102inline T load_int(const uint8_t* src, Endianness src_endian) noexcept {
103 T value;
104 std::memcpy(&value, src, sizeof(T));
105 const auto resolved = resolve_endianness(src_endian);
107 value = byteswap_int(value);
108 }
109 return value;
110}
111
112template <typename T>
113inline void store_int(uint8_t* dst, T value, Endianness dst_endian) noexcept {
114 const auto resolved = resolve_endianness(dst_endian);
116 value = byteswap_int(value);
117 }
118 std::memcpy(dst, &value, sizeof(T));
119}
120
121inline int32_t load_int24(const uint8_t* src, Endianness src_endian) noexcept {
122 const auto resolved = resolve_endianness(src_endian);
123 int32_t val;
124 if (resolved == Endianness::Little) {
125 val = static_cast<int32_t>(src[0])
126 | (static_cast<int32_t>(src[1]) << 8)
127 | (static_cast<int32_t>(src[2]) << 16);
128 } else {
129 val = static_cast<int32_t>(src[2])
130 | (static_cast<int32_t>(src[1]) << 8)
131 | (static_cast<int32_t>(src[0]) << 16);
132 }
133 // Sign-extend from 24-bit to 32-bit.
134 if (val & 0x800000) val |= static_cast<int32_t>(0xFF000000u);
135 return val;
136}
137
138inline void store_int24(uint8_t* dst, int32_t value, Endianness dst_endian) noexcept {
139 // Clamp to 24-bit signed range first so out-of-band int values
140 // don't corrupt adjacent bytes.
141 constexpr int32_t kMax = (1 << 23) - 1;
142 constexpr int32_t kMin = -(1 << 23);
143 if (value > kMax) value = kMax;
144 else if (value < kMin) value = kMin;
145 const auto resolved = resolve_endianness(dst_endian);
146 if (resolved == Endianness::Little) {
147 dst[0] = static_cast<uint8_t>(value & 0xFF);
148 dst[1] = static_cast<uint8_t>((value >> 8) & 0xFF);
149 dst[2] = static_cast<uint8_t>((value >> 16) & 0xFF);
150 } else {
151 dst[0] = static_cast<uint8_t>((value >> 16) & 0xFF);
152 dst[1] = static_cast<uint8_t>((value >> 8) & 0xFF);
153 dst[2] = static_cast<uint8_t>(value & 0xFF);
154 }
155}
156
157inline float finite_or_zero(float v) noexcept {
158 return std::isfinite(v) ? v : 0.0f;
159}
160
161// Scale factor for int → float normalization. We use 2^(N-1) (not
162// 2^(N-1) - 1) for the divide so the center maps cleanly to 0 and
163// the full negative range produces -1.0 exactly. The maximum
164// positive int produces (2^(N-1) - 1) / 2^(N-1) ≈ 0.99999 (within
165// 1 LSB of +1), matching the asymmetric int range.
166constexpr float kInt8Scale = 1.0f / 128.0f;
167constexpr float kInt16Scale = 1.0f / 32768.0f;
168constexpr float kInt24Scale = 1.0f / 8388608.0f;
169constexpr double kInt32Scale = 1.0 / 2147483648.0;
170
171} // namespace detail
172
176public:
181
182 SampleFormat format() const noexcept { return format_; }
183 Endianness endian() const noexcept { return endian_; }
184 Layout layout() const noexcept { return layout_; }
185
187 std::size_t bytes_per_sample() const noexcept {
188 return ::pulp::audio::bytes_per_sample(format_);
189 }
190
193 std::size_t bytes_for(std::size_t frames, std::size_t channels) const noexcept {
194 return frames * channels * bytes_per_sample();
195 }
196
202 void to_float(const void* src,
203 float* const* dst_channels,
204 std::size_t frames,
205 std::size_t channels) const noexcept {
206 to_float_scaled(src, dst_channels, frames, channels, 1.0f);
207 }
208
211 void to_float_scaled(const void* src,
212 float* const* dst_channels,
213 std::size_t frames,
214 std::size_t channels,
215 float gain) const noexcept {
216 if (!src || !dst_channels || frames == 0 || channels == 0) return;
217 const auto* bytes = static_cast<const uint8_t*>(src);
218 const std::size_t bps = bytes_per_sample();
219 for (std::size_t f = 0; f < frames; ++f) {
220 for (std::size_t c = 0; c < channels; ++c) {
221 const std::size_t byte_index = byte_offset(f, c, frames, channels, bps);
222 dst_channels[c][f] = decode_sample(bytes + byte_index) * gain;
223 }
224 }
225 }
226
230 void from_float(const float* const* src_channels,
231 void* dst,
232 std::size_t frames,
233 std::size_t channels) const noexcept {
234 if (!src_channels || !dst || frames == 0 || channels == 0) return;
235 auto* bytes = static_cast<uint8_t*>(dst);
236 const std::size_t bps = bytes_per_sample();
237 for (std::size_t f = 0; f < frames; ++f) {
238 for (std::size_t c = 0; c < channels; ++c) {
239 const std::size_t byte_index = byte_offset(f, c, frames, channels, bps);
240 encode_sample(bytes + byte_index, src_channels[c][f]);
241 }
242 }
243 }
244
245private:
246 std::size_t byte_offset(std::size_t frame,
247 std::size_t channel,
248 std::size_t frames,
249 std::size_t channels,
250 std::size_t bps) const noexcept {
251 if (layout_ == Layout::Interleaved) {
252 return (frame * channels + channel) * bps;
253 }
254 // Planar: channel `c` occupies bytes [c*frames*bps, (c+1)*frames*bps).
255 return (channel * frames + frame) * bps;
256 }
257
258 float decode_sample(const uint8_t* src) const noexcept {
259 switch (format_) {
260 case SampleFormat::Int8: {
261 const int8_t v = static_cast<int8_t>(*src);
262 return static_cast<float>(v) * detail::kInt8Scale;
263 }
264 case SampleFormat::Int16: {
265 const int16_t v = detail::load_int<int16_t>(src, endian_);
266 return static_cast<float>(v) * detail::kInt16Scale;
267 }
268 case SampleFormat::Int24: {
269 const int32_t v = detail::load_int24(src, endian_);
270 return static_cast<float>(v) * detail::kInt24Scale;
271 }
272 case SampleFormat::Int32: {
273 const int32_t v = detail::load_int<int32_t>(src, endian_);
274 return static_cast<float>(
275 static_cast<double>(v) * detail::kInt32Scale);
276 }
278 int32_t bits = detail::load_int<int32_t>(src, endian_);
279 float f;
280 std::memcpy(&f, &bits, sizeof(float));
281 return f;
282 }
284 int64_t bits = detail::load_int<int64_t>(src, endian_);
285 double d;
286 std::memcpy(&d, &bits, sizeof(double));
287 return static_cast<float>(d);
288 }
289 }
290 return 0.0f;
291 }
292
293 void encode_sample(uint8_t* dst, float value) const noexcept {
295 switch (format_) {
296 case SampleFormat::Int8: {
297 value = std::clamp(value, -1.0f, 1.0f);
298 const int8_t v = static_cast<int8_t>(
299 std::lrintf(value * 127.0f));
300 std::memcpy(dst, &v, 1);
301 return;
302 }
303 case SampleFormat::Int16: {
304 value = std::clamp(value, -1.0f, 1.0f);
305 const int16_t v = static_cast<int16_t>(
306 std::lrintf(value * 32767.0f));
307 detail::store_int<int16_t>(dst, v, endian_);
308 return;
309 }
310 case SampleFormat::Int24: {
311 value = std::clamp(value, -1.0f, 1.0f);
312 const int32_t v = static_cast<int32_t>(
313 std::lrintf(value * 8388607.0f));
314 detail::store_int24(dst, v, endian_);
315 return;
316 }
317 case SampleFormat::Int32: {
318 value = std::clamp(value, -1.0f, 1.0f);
319 int32_t v;
320 if (value >= 1.0f) {
321 v = std::numeric_limits<int32_t>::max();
322 } else if (value <= -1.0f) {
323 v = std::numeric_limits<int32_t>::min();
324 } else {
325 v = static_cast<int32_t>(
326 std::lrint(static_cast<double>(value) * 2147483647.0));
327 }
328 detail::store_int<int32_t>(dst, v, endian_);
329 return;
330 }
332 int32_t bits;
333 std::memcpy(&bits, &value, sizeof(int32_t));
334 detail::store_int<int32_t>(dst, bits, endian_);
335 return;
336 }
338 const double d = static_cast<double>(value);
339 int64_t bits;
340 std::memcpy(&bits, &d, sizeof(int64_t));
341 detail::store_int<int64_t>(dst, bits, endian_);
342 return;
343 }
344 }
345 }
346
347 SampleFormat format_;
348 Endianness endian_;
349 Layout layout_;
350};
351
352} // namespace pulp::audio
void from_float(const float *const *src_channels, void *dst, std::size_t frames, std::size_t channels) const noexcept
Convert planar float channels → PCM bytes.
Definition sample_converter.hpp:230
void to_float(const void *src, float *const *dst_channels, std::size_t frames, std::size_t channels) const noexcept
Convert PCM bytes → planar float channels.
Definition sample_converter.hpp:202
SampleConverter(SampleFormat format, Endianness endian=Endianness::Native, Layout layout=Layout::Interleaved) noexcept
Definition sample_converter.hpp:177
Layout layout() const noexcept
Definition sample_converter.hpp:184
std::size_t bytes_for(std::size_t frames, std::size_t channels) const noexcept
Total byte size for frames * channels PCM samples in the configured layout.
Definition sample_converter.hpp:193
SampleFormat format() const noexcept
Definition sample_converter.hpp:182
std::size_t bytes_per_sample() const noexcept
Bytes occupied per sample (independent of layout / channel count).
Definition sample_converter.hpp:187
Endianness endian() const noexcept
Definition sample_converter.hpp:183
void to_float_scaled(const void *src, float *const *dst_channels, std::size_t frames, std::size_t channels, float gain) const noexcept
Same as to_float with a per-sample gain multiplier applied during conversion (saves a second pass ove...
Definition sample_converter.hpp:211
float finite_or_zero(float v) noexcept
Definition sample_converter.hpp:157
constexpr float kInt24Scale
Definition sample_converter.hpp:168
void store_int24(uint8_t *dst, int32_t value, Endianness dst_endian) noexcept
Definition sample_converter.hpp:138
constexpr float kInt16Scale
Definition sample_converter.hpp:167
void store_int(uint8_t *dst, T value, Endianness dst_endian) noexcept
Definition sample_converter.hpp:113
constexpr bool host_is_little_endian() noexcept
Definition sample_converter.hpp:80
T byteswap_int(T value) noexcept
Definition sample_converter.hpp:90
constexpr float kInt8Scale
Definition sample_converter.hpp:166
int32_t load_int24(const uint8_t *src, Endianness src_endian) noexcept
Definition sample_converter.hpp:121
constexpr double kInt32Scale
Definition sample_converter.hpp:169
Endianness resolve_endianness(Endianness e) noexcept
Definition sample_converter.hpp:84
T load_int(const uint8_t *src, Endianness src_endian) noexcept
Definition sample_converter.hpp:102
Definition analyzer_provider.hpp:12
SampleFormat
Packed-PCM sample format the bytes are encoded in.
Definition sample_converter.hpp:36
@ Float32
IEEE-754 single.
Definition sample_converter.hpp:41
@ Int16
16-bit signed
Definition sample_converter.hpp:38
@ Int24
24-bit signed packed (3 bytes)
Definition sample_converter.hpp:39
@ Int8
8-bit signed (-128..127)
Definition sample_converter.hpp:37
@ Int32
32-bit signed
Definition sample_converter.hpp:40
@ Float64
IEEE-754 double.
Definition sample_converter.hpp:42
Endianness
Byte order.
Definition sample_converter.hpp:48
@ Little
Definition sample_converter.hpp:49
@ Native
Definition sample_converter.hpp:51
@ Big
Definition sample_converter.hpp:50
constexpr bool is_float(SampleFormat format) noexcept
True if format is a floating-point format.
Definition sample_converter.hpp:74
Layout
Memory layout of the byte stream relative to channels.
Definition sample_converter.hpp:55
@ Interleaved
[ch0_s0, ch1_s0, ch0_s1, ch1_s1, ...]
Definition sample_converter.hpp:56
@ Planar
[ch0_s0, ch0_s1, ..., ch1_s0, ch1_s1, ...]
Definition sample_converter.hpp:57
constexpr std::size_t bytes_per_sample(SampleFormat format) noexcept
Bytes per sample for format. Returns 0 for unknown values.
Definition sample_converter.hpp:61
@ Float32
Definition audio_file.hpp:51
@ Int16
Definition audio_file.hpp:51
@ Int24
Definition audio_file.hpp:51
What the product grants a device: the quota table indexed by capability tier and live thermal state.
Definition aax_editor.hpp:31
@ d
Definition pitch.hpp:22
@ f
Definition pitch.hpp:25
@ value
the widget's primary value (Knob, Fader, RangeSlider, Toggle, ProgressBar)
Definition binding_diagnostics.hpp:19
@ v
Definition input_events.hpp:41