Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions tests/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,7 @@ tap_dsp_add_gtest_executable(tap_dsp_tests
test_fft.cpp
test_fft_arith.cpp
test_fft_backend.cpp
test_fft_fixed.cpp
test_fir_kernels.cpp
test_kaiser.cpp
test_log_mel.cpp
Expand Down
57 changes: 54 additions & 3 deletions tests/support/signals.h
Original file line number Diff line number Diff line change
Expand Up @@ -9,17 +9,67 @@
// helper Part 9 lists once a caller exists, is the Stage 6 hygiene item in
// docs/audit-fft-and-code-smells.md, not this file's job). Everything here is deterministic — fixed seeds, no wall
// clock, no filesystem — so it can run unchanged on the bare-metal QEMU legs.
//
// sample_scale<Sample> is how a generator lands in a profile's sample type:
// the identity cast for float and double, and round-to-nearest with saturation
// into Q0.15 / Q0.31 for the fixed-point profiles (Stage 3b), so the same
// seed produces the same signal in every profile up to the profile's own
// quantisation. Four files share it (test_fft.cpp, test_fft_fixed.cpp,
// test_fft_oracle.cpp, test_fft_rt.cpp), which is the bar for a helper here.

#pragma once

#include <cmath>
#include <concepts>
#include <cstddef>
#include <cstdint>
#include <limits>
#include <numbers>
#include <type_traits>
#include <vector>

#include "tap/dsp/sample_traits.h"

namespace tap::dsp::test {

/// How a value in the double domain (fractions of full scale, 1.0 = full
/// scale) crosses into and out of a profile's sample type.
/// - float, double : the plain cast; k_lsb is 0 and k_full_scale is 1.
/// - int16_t (Q0.15), int32_t (Q0.31): from_double rounds half away
/// from zero and saturates (the substrate's round_sat, the same
/// rounding the coefficient generators use); k_lsb is 2^-15 / 2^-31
/// and k_full_scale the largest representable positive value,
/// 1 - k_lsb. Full-scale negative (-1.0) is INT_MIN exactly.
template <typename Sample>
struct sample_scale;

template <std::floating_point F>
struct sample_scale<F> {
static constexpr int k_frac_bits = 0;
static constexpr double k_lsb = 0.0;
static constexpr double k_full_scale = 1.0;
static constexpr double to_double(F v) noexcept { return static_cast<double>(v); }
static constexpr F from_double(double v) noexcept { return static_cast<F>(v); }
};

template <std::integral I>
struct sample_scale<I> {
static_assert(std::is_same_v<I, std::int16_t> || std::is_same_v<I, std::int32_t>,
"the fixed-point profiles are Q0.15 (int16_t) and Q0.31 (int32_t)");
static constexpr int k_frac_bits = std::numeric_limits<I>::digits; // 15 or 31
static constexpr double k_scale = static_cast<double>(std::int64_t{1} << k_frac_bits);
static constexpr double k_lsb = 1.0 / k_scale;
static constexpr double k_full_scale = 1.0 - k_lsb;
static constexpr double to_double(I v) noexcept { return static_cast<double>(v) / k_scale; }
static constexpr I from_double(double v) noexcept { return tap::dsp::detail::round_sat<I>(v * k_scale); }
};

static_assert(sample_scale<std::int16_t>::from_double(1.0) == std::numeric_limits<std::int16_t>::max());
static_assert(sample_scale<std::int16_t>::from_double(-1.0) == std::numeric_limits<std::int16_t>::min());
static_assert(sample_scale<std::int32_t>::from_double(0.5) == std::int32_t{1} << 30);
static_assert(sample_scale<std::int32_t>::to_double(std::int32_t{1} << 30) == 0.5);
static_assert(sample_scale<float>::from_double(0.25) == 0.25f);

/// Marsaglia's xorshift32 (Journal of Statistical Software 8(14), 2003),
/// the generator every existing copy in this repo already uses. The state
/// must be non-zero; a zero seed is replaced by a fixed non-zero constant.
Expand All @@ -44,13 +94,14 @@ namespace tap::dsp::test {
std::uint32_t m_s;
};

/// n samples uniform in [-amplitude, amplitude), from a fixed seed.
/// n samples uniform in [-amplitude, amplitude), from a fixed seed, landed
/// in the profile through sample_scale (a plain cast for float/double).
template <typename Sample>
std::vector<Sample> random_signal(std::size_t n, std::uint32_t seed, double amplitude = 1.0) {
xorshift32 rng(seed);
std::vector<Sample> x(n);
for (auto& v : x) {
v = static_cast<Sample>(amplitude * rng.next_unit());
v = sample_scale<Sample>::from_double(amplitude * rng.next_unit());
}
return x;
}
Expand All @@ -70,7 +121,7 @@ namespace tap::dsp::test {
const double period = static_cast<double>(n);
for (std::size_t j = 0; j < n; ++j) {
const double turns = std::fmod(bin * static_cast<double>(j), period) / period;
x[j] = static_cast<Sample>(amplitude * std::cos(2.0 * std::numbers::pi * turns + phase));
x[j] = sample_scale<Sample>::from_double(amplitude * std::cos(2.0 * std::numbers::pi * turns + phase));
}
return x;
}
Expand Down
Loading
Loading