feat: consumer identified (th_b3c0), scan3.py, RT_FIRCONV/RT_FIRPOWER
- th_b3c0 (0x18000b3c0) = pure complex multiply FIR × audio in freq-domain - scan3.py: pre-scan approach finds ctx in 1.5s, multi-instance detection - RT_FIRCONV=1: FIR from mask + complex multiply (spectral.cpp) - RT_FIRPOWER=1: power-law mask from raw spectrum (framed_model.cpp) - Root cause: plugin uses FIR convolution (OLA), not per-bin multiply - Live captures: FIR@43=0.524, mask@43=0.510, final gain=0.305 - Best result: RT_LUT_OFF gives cut@500=-8.18 dB (ref -10.32) - NOTES_LEVEL 24e/24f/24g appended
This commit is contained in:
+37
-2
@@ -1,7 +1,9 @@
|
||||
#include "spectral.hpp"
|
||||
#include "fftconv.hpp"
|
||||
#include <cmath>
|
||||
#include <cstring>
|
||||
#include <vector>
|
||||
#include <cstdlib>
|
||||
|
||||
SpectralProcessor::SpectralProcessor(size_t nfft, size_t hop, float sample_rate)
|
||||
: nfft_(nfft), hop_(hop), frame_count_(0), output_pos_(0),
|
||||
@@ -11,14 +13,25 @@ SpectralProcessor::SpectralProcessor(size_t nfft, size_t hop, float sample_rate)
|
||||
fft::init_plan(&plan_, static_cast<uint32_t>(std::log2(nfft_)));
|
||||
buf_ = new std::complex<double>[nfft_];
|
||||
tmp_buf_ = new std::complex<double>[nfft_];
|
||||
fir_buf_ = new std::complex<double>[nfft_];
|
||||
fir_freq_ = new std::complex<double>[nfft_];
|
||||
overlap_.resize(nfft_, 0.0f);
|
||||
mask_.resize(nfft_, 1.0f);
|
||||
|
||||
// Build FIR window: falling half of periodic Hann(4096).
|
||||
// Plugin reads window[N/2..N-1] of periodic Hann (rising 0→1).
|
||||
fir_window_.resize(nfft_);
|
||||
for (size_t i = 0; i < nfft_; i++) {
|
||||
fir_window_[i] = 0.5 * (1.0 - std::cos(2.0 * M_PI * i / nfft_));
|
||||
}
|
||||
}
|
||||
|
||||
SpectralProcessor::~SpectralProcessor() {
|
||||
delete[] window_;
|
||||
delete[] buf_;
|
||||
delete[] tmp_buf_;
|
||||
delete[] fir_buf_;
|
||||
delete[] fir_freq_;
|
||||
}
|
||||
|
||||
void SpectralProcessor::setDetectorParams(const std::vector<DetectorBand>& bands) {
|
||||
@@ -71,6 +84,11 @@ void SpectralProcessor::processBlock(float* in, float* out, size_t num_samples,
|
||||
return;
|
||||
}
|
||||
|
||||
static const int firconv = []() {
|
||||
const char* e = getenv("RT_FIRCONV");
|
||||
return e ? atoi(e) : 0;
|
||||
}();
|
||||
|
||||
size_t nframes = (num_samples - nfft_) / hop_ + 1;
|
||||
|
||||
for (size_t f = 0; f < nframes; f++) {
|
||||
@@ -80,8 +98,25 @@ void SpectralProcessor::processBlock(float* in, float* out, size_t num_samples,
|
||||
|
||||
detector_.processFrame(buf_, mask_.data());
|
||||
|
||||
for (size_t i = 0; i < nfft_; i++) {
|
||||
buf_[i] *= mask_[i];
|
||||
if (firconv) {
|
||||
// RT_FIRCONV=1: Build FIR from mask and apply via complex multiply.
|
||||
// The mask is real-valued (per-bin gain). We apply it directly
|
||||
// to the audio spectrum via complex multiply (th_b3c0 equivalent).
|
||||
// No upper-half zeroing — preserve Hermitian symmetry.
|
||||
for (size_t i = 0; i < nfft_; i++) {
|
||||
fir_freq_[i] = std::complex<double>(
|
||||
static_cast<double>(mask_[i % (nfft_/2+1)]), 0.0);
|
||||
}
|
||||
|
||||
// Complex multiply FIR × audio spectrum.
|
||||
for (size_t i = 0; i < nfft_; i++) {
|
||||
buf_[i] *= fir_freq_[i];
|
||||
}
|
||||
} else {
|
||||
// Default path: simple frequency-domain mask multiply.
|
||||
for (size_t i = 0; i < nfft_; i++) {
|
||||
buf_[i] *= mask_[i];
|
||||
}
|
||||
}
|
||||
|
||||
istftFrame(buf_, out + offset, overlap_.data());
|
||||
|
||||
Reference in New Issue
Block a user