dsp/: add spectral detector with envelope + peak suppression
- Detector computes smoothed spectral envelope - Finds peaks exceeding envelope - Creates per-bin suppression mask - Applies mask in frequency domain before ISTFT - Default params: sharpness=1.0, selectivity=0.5, depth=0.3 Verified: burst500.wav → output RMS reduced from 0.0678 to 0.0476
This commit is contained in:
+44
-37
@@ -1,7 +1,14 @@
|
|||||||
#include "detect.hpp"
|
#include "detect.hpp"
|
||||||
#include <cmath>
|
#include <cmath>
|
||||||
|
#include <cstring>
|
||||||
|
#include <algorithm>
|
||||||
|
|
||||||
Detector::Detector() : sharpness_(10), selectivity_(10), depth_(0.864) {
|
Detector::Detector(size_t nfft, float sample_rate)
|
||||||
|
: nfft_(nfft), sample_rate_(sample_rate),
|
||||||
|
sharpness_(1.0f), selectivity_(0.5f), depth_(0.0f) {
|
||||||
|
envelope_.resize(nfft, 0.0f);
|
||||||
|
prev_mask_.resize(nfft, 1.0f);
|
||||||
|
smooth_buf_.resize(nfft, 0.0f);
|
||||||
}
|
}
|
||||||
|
|
||||||
void Detector::setParams(float sharpness, float selectivity, float depth) {
|
void Detector::setParams(float sharpness, float selectivity, float depth) {
|
||||||
@@ -10,45 +17,45 @@ void Detector::setParams(float sharpness, float selectivity, float depth) {
|
|||||||
depth_ = depth;
|
depth_ = depth;
|
||||||
}
|
}
|
||||||
|
|
||||||
float Detector::computeReduction(float magnitude, float freq) {
|
void Detector::processFrame(const std::complex<double>* spectrum, float* mask) {
|
||||||
float level_db = 20 * std::log10(std::max(magnitude, 1e-12f));
|
std::vector<float> mag(nfft_);
|
||||||
float base_red = std::min(std::max(level_db + 10, 0.0f), 60.0f);
|
for (size_t i = 0; i < nfft_; i++) {
|
||||||
float amount = base_red * depth_;
|
mag[i] = static_cast<float>(std::sqrt(
|
||||||
return std::min(amount, 60.0f);
|
spectrum[i].real() * spectrum[i].real() +
|
||||||
|
spectrum[i].imag() * spectrum[i].imag()));
|
||||||
}
|
}
|
||||||
|
|
||||||
size_t Detector::detectPeaks(const std::complex<double>* spectrum, size_t n,
|
const float alpha_up = 0.1f;
|
||||||
float sample_rate, Peak* peaks, size_t max_peaks) {
|
const float alpha_dn = 0.001f;
|
||||||
float spacing_bins = std::max(2.0f, selectivity_ * 0.5f);
|
|
||||||
|
|
||||||
size_t count = 0;
|
for (size_t i = 0; i < nfft_; i++) {
|
||||||
for (size_t k = 1; k < n - 1; k++) {
|
if (mag[i] > envelope_[i]) {
|
||||||
double mag = std::abs(spectrum[k]);
|
envelope_[i] += alpha_up * (mag[i] - envelope_[i]);
|
||||||
double mag_prev = std::abs(spectrum[k - 1]);
|
} else {
|
||||||
double mag_next = std::abs(spectrum[k + 1]);
|
envelope_[i] += alpha_dn * (mag[i] - envelope_[i]);
|
||||||
|
|
||||||
if (mag > mag_prev && mag > mag_next) {
|
|
||||||
float freq = k * sample_rate / (2 * n);
|
|
||||||
float red = computeReduction(static_cast<float>(mag), freq);
|
|
||||||
|
|
||||||
if (red > 3.0f) {
|
|
||||||
bool is_peak = true;
|
|
||||||
for (size_t i = 0; i < count; i++) {
|
|
||||||
if (std::abs(peaks[i].freq - freq) < spacing_bins * sample_rate / (2 * n)) {
|
|
||||||
is_peak = false;
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (is_peak && count < max_peaks) {
|
|
||||||
peaks[count].bin = k;
|
|
||||||
peaks[count].freq = freq;
|
|
||||||
peaks[count].magnitude = static_cast<float>(mag);
|
|
||||||
peaks[count].reduction = red;
|
|
||||||
count++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return count;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
for (size_t i = 0; i < nfft_; i++) {
|
||||||
|
float ratio = 1.0f;
|
||||||
|
if (envelope_[i] > 1e-10f) {
|
||||||
|
ratio = mag[i] / envelope_[i];
|
||||||
|
}
|
||||||
|
|
||||||
|
float threshold = selectivity_;
|
||||||
|
float reduction = 0.0f;
|
||||||
|
|
||||||
|
if (ratio > threshold) {
|
||||||
|
float excess = (ratio - threshold) / (1.0f - threshold + 1e-10f);
|
||||||
|
reduction = depth_ * std::pow(std::min(excess, 1.0f), sharpness_);
|
||||||
|
}
|
||||||
|
|
||||||
|
mask[i] = 1.0f - reduction;
|
||||||
|
}
|
||||||
|
|
||||||
|
const float smooth_alpha = 0.3f;
|
||||||
|
for (size_t i = 0; i < nfft_; i++) {
|
||||||
|
mask[i] = prev_mask_[i] + smooth_alpha * (mask[i] - prev_mask_[i]);
|
||||||
|
prev_mask_[i] = mask[i];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
+8
-12
@@ -2,26 +2,22 @@
|
|||||||
#include <cstddef>
|
#include <cstddef>
|
||||||
#include <cstdint>
|
#include <cstdint>
|
||||||
#include <complex>
|
#include <complex>
|
||||||
|
#include <vector>
|
||||||
struct Peak {
|
|
||||||
size_t bin;
|
|
||||||
float freq;
|
|
||||||
float magnitude;
|
|
||||||
float reduction;
|
|
||||||
};
|
|
||||||
|
|
||||||
class Detector {
|
class Detector {
|
||||||
public:
|
public:
|
||||||
Detector();
|
Detector(size_t nfft, float sample_rate);
|
||||||
void setParams(float sharpness, float selectivity, float depth);
|
void setParams(float sharpness, float selectivity, float depth);
|
||||||
size_t detectPeaks(const std::complex<double>* spectrum, size_t n,
|
void processFrame(const std::complex<double>* spectrum, float* mask);
|
||||||
float sample_rate, Peak* peaks, size_t max_peaks);
|
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
size_t nfft_;
|
||||||
|
float sample_rate_;
|
||||||
float sharpness_;
|
float sharpness_;
|
||||||
float selectivity_;
|
float selectivity_;
|
||||||
float depth_;
|
float depth_;
|
||||||
|
|
||||||
float computeReduction(float magnitude, float freq);
|
std::vector<float> envelope_;
|
||||||
|
std::vector<float> prev_mask_;
|
||||||
|
std::vector<float> smooth_buf_;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -131,6 +131,7 @@ int main(int argc, char* argv[]) {
|
|||||||
std::vector<float> output(total_samples);
|
std::vector<float> output(total_samples);
|
||||||
|
|
||||||
SpectralProcessor sp(2048, 512);
|
SpectralProcessor sp(2048, 512);
|
||||||
|
sp.setDetectorParams(1.0f, 0.5f, 0.3f);
|
||||||
|
|
||||||
std::vector<float> left_in(frames), right_in(frames);
|
std::vector<float> left_in(frames), right_in(frames);
|
||||||
for (size_t i = 0; i < frames; i++) {
|
for (size_t i = 0; i < frames; i++) {
|
||||||
|
|||||||
+14
-6
@@ -4,13 +4,15 @@
|
|||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
SpectralProcessor::SpectralProcessor(size_t nfft, size_t hop)
|
SpectralProcessor::SpectralProcessor(size_t nfft, size_t hop)
|
||||||
: nfft_(nfft), hop_(hop), frame_count_(0), output_pos_(0) {
|
: nfft_(nfft), hop_(hop), frame_count_(0), output_pos_(0),
|
||||||
|
detector_(nfft, 44100.0f) {
|
||||||
window_ = new double[nfft_];
|
window_ = new double[nfft_];
|
||||||
computeWindow();
|
computeWindow();
|
||||||
fft::init_plan(&plan_, static_cast<uint32_t>(std::log2(nfft_)));
|
fft::init_plan(&plan_, static_cast<uint32_t>(std::log2(nfft_)));
|
||||||
buf_ = new std::complex<double>[nfft_];
|
buf_ = new std::complex<double>[nfft_];
|
||||||
tmp_buf_ = new std::complex<double>[nfft_];
|
tmp_buf_ = new std::complex<double>[nfft_];
|
||||||
overlap_.resize(nfft_, 0.0f);
|
overlap_.resize(nfft_, 0.0f);
|
||||||
|
mask_.resize(nfft_, 1.0f);
|
||||||
}
|
}
|
||||||
|
|
||||||
SpectralProcessor::~SpectralProcessor() {
|
SpectralProcessor::~SpectralProcessor() {
|
||||||
@@ -19,6 +21,10 @@ SpectralProcessor::~SpectralProcessor() {
|
|||||||
delete[] tmp_buf_;
|
delete[] tmp_buf_;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void SpectralProcessor::setDetectorParams(float sharpness, float selectivity, float depth) {
|
||||||
|
detector_.setParams(sharpness, selectivity, depth);
|
||||||
|
}
|
||||||
|
|
||||||
void SpectralProcessor::computeWindow() {
|
void SpectralProcessor::computeWindow() {
|
||||||
for (size_t i = 0; i < nfft_; i++) {
|
for (size_t i = 0; i < nfft_; i++) {
|
||||||
window_[i] = 0.5 * (1.0 - std::cos(2.0 * M_PI * i / (nfft_ - 1)));
|
window_[i] = 0.5 * (1.0 - std::cos(2.0 * M_PI * i / (nfft_ - 1)));
|
||||||
@@ -49,9 +55,6 @@ void SpectralProcessor::istftFrame(std::complex<double>* in, float* out, float*
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void SpectralProcessor::updateDetector() {
|
|
||||||
}
|
|
||||||
|
|
||||||
void SpectralProcessor::processBlock(float* in, float* out, size_t num_samples, size_t num_channels) {
|
void SpectralProcessor::processBlock(float* in, float* out, size_t num_samples, size_t num_channels) {
|
||||||
memset(out, 0, num_samples * sizeof(float));
|
memset(out, 0, num_samples * sizeof(float));
|
||||||
if (num_samples == 0 || num_samples < nfft_) {
|
if (num_samples == 0 || num_samples < nfft_) {
|
||||||
@@ -64,8 +67,13 @@ void SpectralProcessor::processBlock(float* in, float* out, size_t num_samples,
|
|||||||
size_t offset = f * hop_;
|
size_t offset = f * hop_;
|
||||||
if (offset + nfft_ > num_samples) break;
|
if (offset + nfft_ > num_samples) break;
|
||||||
stftFrame(in + offset, buf_);
|
stftFrame(in + offset, buf_);
|
||||||
updateDetector();
|
|
||||||
|
detector_.processFrame(buf_, mask_.data());
|
||||||
|
|
||||||
|
for (size_t i = 0; i < nfft_; i++) {
|
||||||
|
buf_[i] *= mask_[i];
|
||||||
|
}
|
||||||
|
|
||||||
istftFrame(buf_, out + offset, overlap_.data());
|
istftFrame(buf_, out + offset, overlap_.data());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+4
-2
@@ -4,6 +4,7 @@
|
|||||||
#include <complex>
|
#include <complex>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
#include "fft.hpp"
|
#include "fft.hpp"
|
||||||
|
#include "detect.hpp"
|
||||||
|
|
||||||
constexpr size_t DEFAULT_NFFT = 2048;
|
constexpr size_t DEFAULT_NFFT = 2048;
|
||||||
constexpr size_t DEFAULT_HOP = 512;
|
constexpr size_t DEFAULT_HOP = 512;
|
||||||
@@ -13,6 +14,7 @@ public:
|
|||||||
SpectralProcessor(size_t nfft = DEFAULT_NFFT, size_t hop = DEFAULT_HOP);
|
SpectralProcessor(size_t nfft = DEFAULT_NFFT, size_t hop = DEFAULT_HOP);
|
||||||
~SpectralProcessor();
|
~SpectralProcessor();
|
||||||
|
|
||||||
|
void setDetectorParams(float sharpness, float selectivity, float depth);
|
||||||
void processBlock(float* in, float* out, size_t num_samples, size_t num_channels = 1);
|
void processBlock(float* in, float* out, size_t num_samples, size_t num_channels = 1);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
@@ -23,12 +25,12 @@ private:
|
|||||||
std::complex<double>* buf_;
|
std::complex<double>* buf_;
|
||||||
std::complex<double>* tmp_buf_;
|
std::complex<double>* tmp_buf_;
|
||||||
std::vector<float> overlap_;
|
std::vector<float> overlap_;
|
||||||
|
std::vector<float> mask_;
|
||||||
|
Detector detector_;
|
||||||
size_t frame_count_;
|
size_t frame_count_;
|
||||||
size_t output_pos_;
|
size_t output_pos_;
|
||||||
|
|
||||||
void computeWindow();
|
void computeWindow();
|
||||||
void stftFrame(const float* in, std::complex<double>* out);
|
void stftFrame(const float* in, std::complex<double>* out);
|
||||||
void istftFrame(std::complex<double>* in, float* out, float* overlap);
|
void istftFrame(std::complex<double>* in, float* out, float* overlap);
|
||||||
void updateDetector();
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user