Pulp 1.88.0
Cross-platform audio plugin framework
Loading...
Searching...
No Matches
transient_phase_policy.hpp
Go to the documentation of this file.
1#pragma once
2
23
25
26#include <algorithm>
27#include <cassert>
28#include <cmath>
29#include <complex>
30#include <cstring>
31#include <vector>
32
33namespace pulp::signal {
34
35template <typename SampleType = float>
37public:
38 struct Config {
39 int fft_size = 2048;
41 float sensitivity = 1.0f;
43 float strength = 1.0f;
54 };
55
56 static bool checked_retained_bytes(int fft_size, std::uint64_t target_max_bytes,
57 std::uint64_t& bytes) noexcept {
58 CheckedRetainedByteCharge charge(target_max_bytes);
59 if (!charge.add<SampleType>(static_cast<std::uint64_t>(fft_size / 2 + 1)))
60 return false;
61 bytes = charge.total();
62 return true;
63 }
64
68 void prepare(const Config& config) {
69 assert(config.fft_size >= 256 && (config.fft_size & (config.fft_size - 1)) == 0);
70 config_ = config;
71 num_bins_ = config.fft_size / 2 + 1;
72 prev_mag_.assign(static_cast<size_t>(num_bins_), SampleType{0});
73 reset();
74 }
75
76 void reset() {
77 std::fill(prev_mag_.begin(), prev_mag_.end(), SampleType{0});
78 std::memset(flux_history_, 0, sizeof(flux_history_));
79 history_count_ = 0;
80 history_pos_ = 0;
81 refractory_remaining_ = 0;
82 }
83
87 SampleType analyze(const std::complex<SampleType>* const* frames, int channels, int num_bins) {
88 assert(num_bins == num_bins_);
89 double flux = 0.0;
90 double total = 0.0;
91 for (int k = 0; k < num_bins_; ++k) {
92 std::complex<SampleType> sum(SampleType{0}, SampleType{0});
93 for (int ch = 0; ch < channels; ++ch) sum += frames[ch][k];
94 const SampleType mag = std::abs(sum);
95 const SampleType d = mag - prev_mag_[static_cast<size_t>(k)];
96 if (d > SampleType{0}) flux += static_cast<double>(d);
97 total += static_cast<double>(mag);
98 prev_mag_[static_cast<size_t>(k)] = mag;
99 }
100
101 flux_history_[history_pos_] = static_cast<SampleType>(flux);
102 history_pos_ = (history_pos_ + 1) % kHistory;
103 if (history_count_ < kHistory) ++history_count_;
104
105 if (history_count_ < 9 || total < 1e-9)
106 return SampleType{0};
107
108 SampleType sorted[kHistory];
109 std::copy(flux_history_, flux_history_ + history_count_, sorted);
110 std::nth_element(sorted, sorted + history_count_ / 2, sorted + history_count_);
111 const double median = static_cast<double>(sorted[history_count_ / 2]);
112
113 // Median-relative AND a significant fraction of frame energy.
114 const bool transient =
115 flux * static_cast<double>(config_.sensitivity) > 3.0 * median
116 && flux > 0.05 * total;
117
118 // Refractory gate: count every analyzed frame down, and only let a fresh
119 // transient fire once the guard has elapsed. This collapses a hit's
120 // multi-frame decay into a SINGLE reset at its onset instead of one reset
121 // per ringing frame — the over-firing that bled the synthesis-phase lead.
122 if (refractory_remaining_ > 0) --refractory_remaining_;
123 if (transient && refractory_remaining_ == 0) {
124 refractory_remaining_ = config_.refractory_frames;
125 return static_cast<SampleType>(config_.strength);
126 }
127 return SampleType{0};
128 }
129
130private:
131 static constexpr int kHistory = 32;
132
133 Config config_;
134 int num_bins_ = 0;
135 std::vector<SampleType> prev_mag_;
136 SampleType flux_history_[kHistory] = {};
137 int history_count_ = 0;
138 int history_pos_ = 0;
139 int refractory_remaining_ = 0; // frames left in the post-reset suppression window
140};
141
144
145} // namespace pulp::signal
Definition checked_allocation.hpp:57
bool add(std::uint64_t elements) noexcept
Definition checked_allocation.hpp:63
std::uint64_t total() const noexcept
Definition checked_allocation.hpp:83
Definition transient_phase_policy.hpp:36
void reset()
Definition transient_phase_policy.hpp:76
SampleType analyze(const std::complex< SampleType > *const *frames, int channels, int num_bins)
Analyze one frame group (all channels of the same time index) and return the phase-reset confidence: ...
Definition transient_phase_policy.hpp:87
void prepare(const Config &config)
RT contract: prepare() allocates the previous-magnitude buffer and is not audio-thread safe.
Definition transient_phase_policy.hpp:68
static bool checked_retained_bytes(int fft_size, std::uint64_t target_max_bytes, std::uint64_t &bytes) noexcept
Definition transient_phase_policy.hpp:56
Definition additive_bank.hpp:218
TransientPhasePolicyT< double > TransientPhasePolicy64
Definition transient_phase_policy.hpp:143
@ e
Definition formant_filter_bank.hpp:20
TransientPhasePolicyT< float > TransientPhasePolicy
Definition transient_phase_policy.hpp:142
Definition transient_phase_policy.hpp:38
int fft_size
Definition transient_phase_policy.hpp:39
int refractory_frames
Refractory period: after a reset fires, suppress further resets for this many analysis frames.
Definition transient_phase_policy.hpp:53
float sensitivity
Definition transient_phase_policy.hpp:41
float strength
Phase-reset amount handed back on detection (0..1).
Definition transient_phase_policy.hpp:43