mirror of
https://github.com/AlexandreRouma/SDRPlusPlus.git
synced 2025-07-09 10:35:21 +02:00
even more stuff
This commit is contained in:
303
core/src/signal_path/iq_frontend.cpp
Normal file
303
core/src/signal_path/iq_frontend.cpp
Normal file
@ -0,0 +1,303 @@
|
||||
#include "iq_frontend.h"
|
||||
#include "../dsp/window/blackman.h"
|
||||
#include "../dsp/window/nuttall.h"
|
||||
#include <spdlog/spdlog.h>
|
||||
#include <gui/gui.h>
|
||||
#include <core.h>
|
||||
|
||||
IQFrontEnd::~IQFrontEnd() {
|
||||
if (!_init) { return; }
|
||||
stop();
|
||||
dsp::buffer::free(fftWindowBuf);
|
||||
fftwf_destroy_plan(fftwPlan);
|
||||
fftwf_free(fftInBuf);
|
||||
fftwf_free(fftOutBuf);
|
||||
}
|
||||
|
||||
void IQFrontEnd::init(dsp::stream<dsp::complex_t>* in, double sampleRate, bool buffering, int decimRatio, bool dcBlocking, int fftSize, double fftRate, FFTWindow fftWindow, float* (*acquireFFTBuffer)(void* ctx), void (*releaseFFTBuffer)(void* ctx), void* fftCtx) {
|
||||
_sampleRate = sampleRate;
|
||||
_decimRatio = decimRatio;
|
||||
_fftSize = fftSize;
|
||||
_fftRate = fftRate;
|
||||
_fftWindow = fftWindow;
|
||||
_acquireFFTBuffer = acquireFFTBuffer;
|
||||
_releaseFFTBuffer = releaseFFTBuffer;
|
||||
_fftCtx = fftCtx;
|
||||
|
||||
effectiveSr = _sampleRate / _decimRatio;
|
||||
|
||||
inBuf.init(in);
|
||||
inBuf.bypass = !buffering;
|
||||
|
||||
decim.init(NULL, _decimRatio);
|
||||
dcBlock.init(NULL, genDCBlockRate(effectiveSr));
|
||||
|
||||
preproc.init(&inBuf.out);
|
||||
preproc.addBlock(&decim, _decimRatio > 1);
|
||||
preproc.addBlock(&dcBlock, dcBlocking);
|
||||
|
||||
split.init(preproc.out);
|
||||
|
||||
// TODO: Do something to avoid basically repeating this code twice
|
||||
int skip;
|
||||
genReshapeParams(effectiveSr, _fftSize, _fftRate, skip, _nzFFTSize);
|
||||
reshape.init(&fftIn, fftSize, skip);
|
||||
fftSink.init(&reshape.out, handler, this);
|
||||
|
||||
fftWindowBuf = dsp::buffer::alloc<float>(_nzFFTSize);
|
||||
if (_fftWindow == FFTWindow::RECTANGULAR) {
|
||||
for (int i = 0; i < _nzFFTSize; i++) { fftWindowBuf[i] = 0; }
|
||||
}
|
||||
else if (_fftWindow == FFTWindow::BLACKMAN) {
|
||||
for (int i = 0; i < _nzFFTSize; i++) { fftWindowBuf[i] = dsp::window::blackman(i, _nzFFTSize); }
|
||||
}
|
||||
else if (_fftWindow == FFTWindow::NUTTALL) {
|
||||
for (int i = 0; i < _nzFFTSize; i++) { fftWindowBuf[i] = dsp::window::nuttall(i, _nzFFTSize); }
|
||||
}
|
||||
|
||||
fftInBuf = (fftwf_complex*)fftwf_malloc(_fftSize * sizeof(fftwf_complex));
|
||||
fftOutBuf = (fftwf_complex*)fftwf_malloc(_fftSize * sizeof(fftwf_complex));
|
||||
fftwPlan = fftwf_plan_dft_1d(_fftSize, fftInBuf, fftOutBuf, FFTW_FORWARD, FFTW_ESTIMATE);
|
||||
|
||||
// Clear the rest of the FFT input buffer
|
||||
dsp::buffer::clear(fftInBuf, _fftSize - _nzFFTSize, _nzFFTSize);
|
||||
|
||||
split.bindStream(&fftIn);
|
||||
|
||||
_init = true;
|
||||
}
|
||||
|
||||
void IQFrontEnd::setInput(dsp::stream<dsp::complex_t>* in) {
|
||||
inBuf.setInput(in);
|
||||
}
|
||||
|
||||
void IQFrontEnd::setSampleRate(double sampleRate) {
|
||||
// Temp stop the necessary blocks
|
||||
dcBlock.tempStop();
|
||||
for (auto& [name, vfo] : vfos) {
|
||||
vfo->tempStop();
|
||||
}
|
||||
|
||||
// Update the samplerate
|
||||
_sampleRate = sampleRate;
|
||||
effectiveSr = _sampleRate / _decimRatio;
|
||||
dcBlock.setRate(genDCBlockRate(effectiveSr));
|
||||
for (auto& [name, vfo] : vfos) {
|
||||
vfo->setInSamplerate(effectiveSr);
|
||||
}
|
||||
|
||||
// Reconfigure the FFT
|
||||
updateFFTPath();
|
||||
|
||||
// Restart blocks
|
||||
dcBlock.tempStart();
|
||||
for (auto& [name, vfo] : vfos) {
|
||||
vfo->tempStart();
|
||||
}
|
||||
}
|
||||
|
||||
void IQFrontEnd::setBuffering(bool enabled) {
|
||||
inBuf.bypass = !enabled;
|
||||
}
|
||||
|
||||
void IQFrontEnd::setDecimation(int ratio) {
|
||||
// Temp stop the decimator
|
||||
decim.tempStop();
|
||||
|
||||
// Update the decimation ratio
|
||||
_decimRatio = ratio;
|
||||
if (_decimRatio > 1) { decim.setRatio(_decimRatio); }
|
||||
setSampleRate(_sampleRate);
|
||||
|
||||
// Restart the decimator if it was running
|
||||
decim.tempStart();
|
||||
|
||||
// Enable or disable in the chain
|
||||
preproc.setBlockEnabled(&decim, _decimRatio > 1, [=](dsp::stream<dsp::complex_t>* out){ split.setInput(out); });
|
||||
|
||||
// Update the DSP sample rate (TODO: Find a way to get rid of this)
|
||||
core::setInputSampleRate(_sampleRate);
|
||||
}
|
||||
|
||||
void IQFrontEnd::setDCBlocking(bool enabled) {
|
||||
preproc.setBlockEnabled(&dcBlock, enabled, [=](dsp::stream<dsp::complex_t>* out){ split.setInput(out); });
|
||||
}
|
||||
|
||||
void IQFrontEnd::bindIQStream(dsp::stream<dsp::complex_t>* stream) {
|
||||
split.bindStream(stream);
|
||||
}
|
||||
|
||||
void IQFrontEnd::unbindIQStream(dsp::stream<dsp::complex_t>* stream) {
|
||||
split.unbindStream(stream);
|
||||
}
|
||||
|
||||
dsp::channel::RxVFO* IQFrontEnd::addVFO(std::string name, double sampleRate, double bandwidth, double offset) {
|
||||
// Make sure no other VFO with that name already exists
|
||||
if (vfos.find(name) != vfos.end()) {
|
||||
spdlog::error("[IQFrontEnd] Tried to add VFO with existing name.");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
// Create VFO and its input stream
|
||||
dsp::stream<dsp::complex_t>* vfoIn = new dsp::stream<dsp::complex_t>;
|
||||
dsp::channel::RxVFO* vfo = new dsp::channel::RxVFO(vfoIn, effectiveSr, sampleRate, bandwidth, offset);
|
||||
|
||||
// Register them
|
||||
vfoStreams[name] = vfoIn;
|
||||
vfos[name] = vfo;
|
||||
bindIQStream(vfoIn);
|
||||
|
||||
// Start VFO
|
||||
vfo->start();
|
||||
|
||||
return vfo;
|
||||
}
|
||||
|
||||
void IQFrontEnd::removeVFO(std::string name) {
|
||||
// Make sure that a VFO with that name exists
|
||||
if (vfos.find(name) == vfos.end()) {
|
||||
spdlog::error("[IQFrontEnd] Tried to remove a VFO that doesn't exist.");
|
||||
return;
|
||||
}
|
||||
|
||||
// Remove the VFO and stream from registry
|
||||
dsp::stream<dsp::complex_t>* vfoIn = vfoStreams[name];
|
||||
dsp::channel::RxVFO* vfo = vfos[name];
|
||||
|
||||
// Stop the VFO
|
||||
vfo->stop();
|
||||
|
||||
unbindIQStream(vfoIn);
|
||||
vfoStreams.erase(name);
|
||||
vfos.erase(name);
|
||||
|
||||
// Delete the VFO and its input stream
|
||||
delete vfo;
|
||||
delete vfoIn;
|
||||
}
|
||||
|
||||
void IQFrontEnd::setFFTSize(int size) {
|
||||
_fftSize = size;
|
||||
updateFFTPath();
|
||||
}
|
||||
|
||||
void IQFrontEnd::setFFTRate(double rate) {
|
||||
_fftRate = rate;
|
||||
updateFFTPath();
|
||||
}
|
||||
|
||||
void IQFrontEnd::setFFTWindow(FFTWindow fftWindow) {
|
||||
_fftWindow = fftWindow;
|
||||
updateFFTPath();
|
||||
}
|
||||
|
||||
void IQFrontEnd::flushInputBuffer() {
|
||||
inBuf.flush();
|
||||
}
|
||||
|
||||
void IQFrontEnd::start() {
|
||||
// Start input buffer
|
||||
inBuf.start();
|
||||
|
||||
// Start pre-proc chain (automatically start all bound blocks)
|
||||
preproc.start();
|
||||
|
||||
// Start IQ splitter
|
||||
split.start();
|
||||
|
||||
// Start all VFOs
|
||||
for (auto& [name, vfo] : vfos) {
|
||||
vfo->start();
|
||||
}
|
||||
|
||||
// Start FFT chain
|
||||
reshape.start();
|
||||
fftSink.start();
|
||||
}
|
||||
|
||||
void IQFrontEnd::stop() {
|
||||
// Stop input buffer
|
||||
inBuf.stop();
|
||||
|
||||
// Stop pre-proc chain (automatically start all bound blocks)
|
||||
preproc.stop();
|
||||
|
||||
// Stop IQ splitter
|
||||
split.stop();
|
||||
|
||||
// Stop all VFOs
|
||||
for (auto& [name, vfo] : vfos) {
|
||||
vfo->stop();
|
||||
}
|
||||
|
||||
// Stop FFT chain
|
||||
reshape.stop();
|
||||
fftSink.stop();
|
||||
}
|
||||
|
||||
double IQFrontEnd::getEffectiveSamplerate() {
|
||||
return effectiveSr;
|
||||
}
|
||||
|
||||
void IQFrontEnd::handler(dsp::complex_t* data, int count, void* ctx) {
|
||||
IQFrontEnd* _this = (IQFrontEnd*)ctx;
|
||||
|
||||
// Apply window
|
||||
volk_32fc_32f_multiply_32fc((lv_32fc_t*)_this->fftInBuf, (lv_32fc_t*)data, _this->fftWindowBuf, _this->_nzFFTSize);
|
||||
|
||||
// Execute FFT
|
||||
fftwf_execute(_this->fftwPlan);
|
||||
|
||||
// Aquire buffer
|
||||
float* fftBuf = _this->_acquireFFTBuffer(_this->_fftCtx);
|
||||
|
||||
// Convert the complex output of the FFT to dB amplitude
|
||||
if (fftBuf) {
|
||||
volk_32fc_s32f_power_spectrum_32f(fftBuf, (lv_32fc_t*)_this->fftOutBuf, _this->_fftSize, _this->_fftSize);
|
||||
}
|
||||
|
||||
// Release buffer
|
||||
_this->_releaseFFTBuffer(_this->_fftCtx);
|
||||
}
|
||||
|
||||
void IQFrontEnd::updateFFTPath() {
|
||||
// Temp stop branch
|
||||
reshape.tempStop();
|
||||
fftSink.tempStop();
|
||||
|
||||
// Update reshaper settings
|
||||
int skip;
|
||||
genReshapeParams(effectiveSr, _fftSize, _fftRate, skip, _nzFFTSize);
|
||||
reshape.setKeep(_nzFFTSize);
|
||||
reshape.setSkip(skip);
|
||||
|
||||
// Update window
|
||||
dsp::buffer::free(fftWindowBuf);
|
||||
fftWindowBuf = dsp::buffer::alloc<float>(_nzFFTSize);
|
||||
if (_fftWindow == FFTWindow::RECTANGULAR) {
|
||||
for (int i = 0; i < _nzFFTSize; i++) { fftWindowBuf[i] = 1.0f * ((i % 2) ? -1.0f : 1.0f); }
|
||||
}
|
||||
else if (_fftWindow == FFTWindow::BLACKMAN) {
|
||||
for (int i = 0; i < _nzFFTSize; i++) { fftWindowBuf[i] = dsp::window::blackman(i, _nzFFTSize) * ((i % 2) ? -1.0f : 1.0f); }
|
||||
}
|
||||
else if (_fftWindow == FFTWindow::NUTTALL) {
|
||||
for (int i = 0; i < _nzFFTSize; i++) { fftWindowBuf[i] = dsp::window::nuttall(i, _nzFFTSize) * ((i % 2) ? -1.0f : 1.0f); }
|
||||
}
|
||||
|
||||
// Update FFT plan
|
||||
fftwf_free(fftInBuf);
|
||||
fftwf_free(fftOutBuf);
|
||||
fftInBuf = (fftwf_complex*)fftwf_malloc(_fftSize * sizeof(fftwf_complex));
|
||||
fftOutBuf = (fftwf_complex*)fftwf_malloc(_fftSize * sizeof(fftwf_complex));
|
||||
fftwPlan = fftwf_plan_dft_1d(_fftSize, fftInBuf, fftOutBuf, FFTW_FORWARD, FFTW_ESTIMATE);
|
||||
|
||||
// Clear the rest of the FFT input buffer
|
||||
dsp::buffer::clear(fftInBuf, _fftSize - _nzFFTSize, _nzFFTSize);
|
||||
|
||||
// Update waterfall (TODO: This is annoying, it makes this module non testable and will constantly clear the waterfall for any reason)
|
||||
gui::waterfall.setRawFFTSize(_fftSize);
|
||||
|
||||
// Restart branch
|
||||
reshape.tempStart();
|
||||
fftSink.tempStart();
|
||||
}
|
104
core/src/signal_path/iq_frontend.h
Normal file
104
core/src/signal_path/iq_frontend.h
Normal file
@ -0,0 +1,104 @@
|
||||
#pragma once
|
||||
#include "../dsp/buffer/frame_buffer.h"
|
||||
#include "../dsp/buffer/reshaper.h"
|
||||
#include "../dsp/multirate/power_decimator.h"
|
||||
#include "../dsp/correction/dc_blocker.h"
|
||||
#include "../dsp/chain.h"
|
||||
#include "../dsp/routing/splitter.h"
|
||||
#include "../dsp/channel/rx_vfo.h"
|
||||
#include "../dsp/sink/handler_sink.h"
|
||||
#include <fftw3.h>
|
||||
|
||||
class IQFrontEnd {
|
||||
public:
|
||||
~IQFrontEnd();
|
||||
|
||||
enum FFTWindow {
|
||||
RECTANGULAR,
|
||||
BLACKMAN,
|
||||
NUTTALL
|
||||
};
|
||||
|
||||
void init(dsp::stream<dsp::complex_t>* in, double sampleRate, bool buffering, int decimRatio, bool dcBlocking, int fftSize, double fftRate, FFTWindow fftWindow, float* (*acquireFFTBuffer)(void* ctx), void (*releaseFFTBuffer)(void* ctx), void* fftCtx);
|
||||
|
||||
void setInput(dsp::stream<dsp::complex_t>* in);
|
||||
void setSampleRate(double sampleRate);
|
||||
inline double getSampleRate() { return _sampleRate; }
|
||||
|
||||
void setBuffering(bool enabled);
|
||||
void setDecimation(int ratio);
|
||||
void setDCBlocking(bool enabled);
|
||||
|
||||
void bindIQStream(dsp::stream<dsp::complex_t>* stream);
|
||||
void unbindIQStream(dsp::stream<dsp::complex_t>* stream);
|
||||
|
||||
dsp::channel::RxVFO* addVFO(std::string name, double sampleRate, double bandwidth, double offset);
|
||||
void removeVFO(std::string name);
|
||||
|
||||
void setFFTSize(int size);
|
||||
void setFFTRate(double rate);
|
||||
void setFFTWindow(FFTWindow fftWindow);
|
||||
|
||||
void flushInputBuffer();
|
||||
|
||||
void start();
|
||||
void stop();
|
||||
|
||||
double getEffectiveSamplerate();
|
||||
|
||||
protected:
|
||||
static void handler(dsp::complex_t* data, int count, void* ctx);
|
||||
void updateFFTPath();
|
||||
|
||||
static inline double genDCBlockRate(double sampleRate) {
|
||||
return 50.0 / sampleRate;
|
||||
}
|
||||
|
||||
static inline void genReshapeParams(double sampleRate, int size, double rate, int& skip, int& nzSampCount) {
|
||||
int fftInterval = round(sampleRate / rate);
|
||||
nzSampCount = std::min<int>(fftInterval, size);
|
||||
skip = fftInterval - nzSampCount;
|
||||
}
|
||||
|
||||
// Input buffer
|
||||
dsp::buffer::SampleFrameBuffer<dsp::complex_t> inBuf;
|
||||
|
||||
// Pre-processing chain
|
||||
dsp::multirate::PowerDecimator<dsp::complex_t> decim;
|
||||
dsp::correction::DCBlocker<dsp::complex_t> dcBlock;
|
||||
dsp::chain<dsp::complex_t> preproc;
|
||||
|
||||
// Splitting
|
||||
dsp::routing::Splitter<dsp::complex_t> split;
|
||||
|
||||
// FFT
|
||||
dsp::stream<dsp::complex_t> fftIn;
|
||||
dsp::buffer::Reshaper<dsp::complex_t> reshape;
|
||||
dsp::sink::Handler<dsp::complex_t> fftSink;
|
||||
|
||||
// VFOs
|
||||
std::map<std::string, dsp::stream<dsp::complex_t>*> vfoStreams;
|
||||
std::map<std::string, dsp::channel::RxVFO*> vfos;
|
||||
|
||||
// Parameters
|
||||
double _sampleRate;
|
||||
double _decimRatio;
|
||||
int _fftSize;
|
||||
double _fftRate;
|
||||
FFTWindow _fftWindow;
|
||||
float* (*_acquireFFTBuffer)(void* ctx);
|
||||
void (*_releaseFFTBuffer)(void* ctx);
|
||||
void* _fftCtx;
|
||||
|
||||
// Processing data
|
||||
int _nzFFTSize;
|
||||
float* fftWindowBuf;
|
||||
fftwf_complex *fftInBuf, *fftOutBuf;
|
||||
fftwf_plan fftwPlan;
|
||||
float* fftDbOut;
|
||||
|
||||
double effectiveSr;
|
||||
|
||||
bool _init = false;
|
||||
|
||||
};
|
Reference in New Issue
Block a user