/*
Copyright (C) 2007, 2008, 2009, 2010, 2011 Her Majesty the Queen in
Right of Canada (Communications Research Center Canada)
Copyright (C) 2023
Matthias P. Braendli, matthias.braendli@mpb.li
http://opendigitalradio.org
This block implements a FIR filter. The real filter taps are given
as floats, and the block can take advantage of SSE.
For better performance, filtering is done in another thread, leading
to a pipeline delay of two calls to FIRFilter::process
*/
/*
This file is part of ODR-DabMod.
ODR-DabMod is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as
published by the Free Software Foundation, either version 3 of the
License, or (at your option) any later version.
ODR-DabMod is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with ODR-DabMod. If not, see .
*/
#include "FIRFilter.h"
#include "PcDebug.h"
#include "Utils.h"
#include
#include
#include
#include
#include
#include
#ifdef __SSE__
# include
#endif
using namespace std;
/* This is the FIR Filter calculated with the doc/fir-filter/generate-filter.py script
* with settings
* gain = 1
* sampling_freq = 2.048e6
* cutoff = 810e3
* transition_width = 250e3
*
* It is a good default filter for the common scenarios.
*/
static const std::array default_filter_taps({
-0.00110450468492, 0.00120703084394, -0.000840645749122, -0.000187368263141,
0.00184351124335, -0.00355578539893, 0.00419321097434, -0.00254214904271,
-0.00183473504148, 0.00781436730176, -0.0125957569107, 0.0126200336963,
-0.00537294941023, -0.00866683479398, 0.0249746385962, -0.0356550291181,
0.0319730602205, -0.00795613788068, -0.0363943465054, 0.0938014090061,
-0.151176810265, 0.193567320704, 0.791776955128, 0.193567320704,
-0.151176810265, 0.0938014090061, -0.0363943465054, -0.00795613788068,
0.0319730602205, -0.0356550291181, 0.0249746385962, -0.00866683479398,
-0.00537294941023, 0.0126200336963, -0.0125957569107, 0.00781436730176,
-0.00183473504148, -0.00254214904271, 0.00419321097434, -0.00355578539893,
0.00184351124335, -0.000187368263141, -0.000840645749122, 0.00120703084394,
-0.00110450468492});
FIRFilter::FIRFilter(std::string& taps_file) :
PipelinedModCodec(),
RemoteControllable("firfilter"),
m_taps_file(taps_file)
{
PDEBUG("FIRFilter::FIRFilter(%s) @ %p\n",
taps_file.c_str(), this);
RC_ADD_PARAMETER(ntaps, "(Read-only) number of filter taps.");
RC_ADD_PARAMETER(tapsfile, "Filename containing filter taps. When written to, the new file gets automatically loaded.");
load_filter_taps(m_taps_file);
start_pipeline_thread();
}
FIRFilter::~FIRFilter()
{
stop_pipeline_thread();
}
void FIRFilter::load_filter_taps(const std::string &tapsFile)
{
std::vector filter_taps;
if (tapsFile == "default") {
std::copy(default_filter_taps.begin(), default_filter_taps.end(),
std::back_inserter(filter_taps));
}
else {
std::ifstream taps_fstream(tapsFile.c_str());
if (!taps_fstream) {
throw std::runtime_error("FIRFilter: Could not open taps file " + tapsFile);
}
int n_taps;
taps_fstream >> n_taps;
if (n_taps <= 0) {
throw std::runtime_error("FIRFilter: taps file has invalid format.");
}
if (n_taps > 100) {
etiLog.level(warn) << "FIRFilter: warning: taps file has more than 100 taps";
}
etiLog.level(debug) << "FIRFilter: Reading " << n_taps << " taps...";
filter_taps.resize(n_taps);
int n;
for (n = 0; n < n_taps; n++) {
taps_fstream >> filter_taps[n];
PDEBUG("FIRFilter: tap: %f\n", (double)filter_taps[n] );
if (taps_fstream.eof()) {
throw std::runtime_error(
"FIRFilter: file " + tapsFile +
" should contain " + to_string(n_taps) +
" taps, but EOF reached after " + to_string(n) +
" taps!");
}
}
}
{
std::lock_guard lock(m_taps_mutex);
m_taps = filter_taps;
}
}
int FIRFilter::internal_process(Buffer* const dataIn, Buffer* dataOut)
{
size_t i;
#if __SSE__
// The SSE accelerated version cannot work on the complex values,
// it is necessary to do the convolution on the real and imaginary
// parts separately. Thankfully, the taps are real, simplifying the
// procedure.
const float* in = reinterpret_cast(dataIn->getData());
float* out = reinterpret_cast(dataOut->getData());
size_t sizeIn = dataIn->getLength() / sizeof(float);
if ((uintptr_t)(&out[0]) % 16 != 0) {
throw std::runtime_error("FIRFilterWorker: out not aligned");
}
__m128 SSEout;
__m128 SSEtaps;
__m128 SSEin;
{
std::lock_guard lock(m_taps_mutex);
for (i = 0; i < sizeIn - 2*m_taps.size(); i += 4) {
SSEout = _mm_setr_ps(0,0,0,0);
for (size_t j = 0; j < m_taps.size(); j++) {
if ((uintptr_t)(&in[i+2*j]) % 16 == 0) {
SSEin = _mm_load_ps(&in[i+2*j]); //faster when aligned
}
else {
SSEin = _mm_loadu_ps(&in[i+2*j]);
}
SSEtaps = _mm_load1_ps(&m_taps[j]);
SSEout = _mm_add_ps(SSEout, _mm_mul_ps(SSEin, SSEtaps));
}
_mm_store_ps(&out[i], SSEout);
}
for (; i < sizeIn; i++) {
out[i] = 0.0;
for (int j = 0; i+2*j < sizeIn; j++) {
out[i] += in[i+2*j] * m_taps[j];
}
}
}
#else
// No SSE ? Loop unrolling should make this faster. As for the SSE,
// the real and imaginary parts are calculated separately.
const float* in = reinterpret_cast(dataIn->getData());
float* out = reinterpret_cast(dataOut->getData());
size_t sizeIn = dataIn->getLength() / sizeof(float);
{
std::lock_guard lock(m_taps_mutex);
// Convolve by aligning both frame and taps at zero.
for (i = 0; i < sizeIn - 2*m_taps.size(); i += 4) {
out[i] = 0.0;
out[i+1] = 0.0;
out[i+2] = 0.0;
out[i+3] = 0.0;
for (size_t j = 0; j < m_taps.size(); j++) {
out[i] += in[i + 2*j] * m_taps[j];
out[i+1] += in[i+1 + 2*j] * m_taps[j];
out[i+2] += in[i+2 + 2*j] * m_taps[j];
out[i+3] += in[i+3 + 2*j] * m_taps[j];
}
}
// At the end of the frame, we cut the convolution off.
// The beginning of the next frame starts with a NULL symbol
// anyway.
for (; i < sizeIn; i++) {
out[i] = 0.0;
for (int j = 0; i+2*j < sizeIn; j++) {
out[i] += in[i+2*j] * m_taps[j];
}
}
}
#endif
// The following implementations are for debugging only.
#if 0
// Same thing as above, without loop unrolling. For debugging.
const float* in = reinterpret_cast(dataIn->getData());
float* out = reinterpret_cast(dataOut->getData());
size_t sizeIn = dataIn->getLength() / sizeof(float);
std::lock_guard lock(m_taps_mutex);
for (i = 0; i < sizeIn - 2*m_taps.size(); i += 1) {
out[i] = 0.0;
for (size_t j = 0; j < m_taps.size(); j++) {
out[i] += in[i+2*j] * m_taps[j];
}
}
for (; i < sizeIn; i++) {
out[i] = 0.0;
for (int j = 0; i+2*j < sizeIn; j++) {
out[i] += in[i+2*j] * m_taps[j];
}
}
#elif 0
// An unrolled loop, but this time, the input data is cast to complex float.
// Makes indices more natural. For debugging.
const complexf* in = reinterpret_cast(dataIn->getData());
complexf* out = reinterpret_cast(dataOut->getData());
size_t sizeIn = dataIn->getLength() / sizeof(complexf);
std::lock_guard lock(m_taps_mutex);
for (i = 0; i < sizeIn - m_taps.size(); i += 4) {
out[i] = 0.0;
out[i+1] = 0.0;
out[i+2] = 0.0;
out[i+3] = 0.0;
for (size_t j = 0; j < m_taps.size(); j++) {
out[i] += in[i+j ] * m_taps[j];
out[i+1] += in[i+1+j] * m_taps[j];
out[i+2] += in[i+2+j] * m_taps[j];
out[i+3] += in[i+3+j] * m_taps[j];
}
}
for (; i < sizeIn; i++) {
out[i] = 0.0;
for (int j = 0; j+i < sizeIn; j++) {
out[i] += in[i+j] * m_taps[j];
}
}
#elif 0
// Simple implementation. Slow. For debugging.
const complexf* in = reinterpret_cast(dataIn->getData());
complexf* out = reinterpret_cast(dataOut->getData());
size_t sizeIn = dataIn->getLength() / sizeof(complexf);
std::lock_guard lock(m_taps_mutex);
for (i = 0; i < sizeIn - m_taps.size(); i += 1) {
out[i] = 0.0;
for (size_t j = 0; j < m_taps.size(); j++) {
out[i] += in[i+j ] * m_taps[j];
}
}
for (; i < sizeIn; i++) {
out[i] = 0.0;
for (int j = 0; j+i < sizeIn; j++) {
out[i] += in[i+j] * m_taps[j];
}
}
#endif
return dataOut->getLength();
}
void FIRFilter::set_parameter(const string& parameter, const string& value)
{
if (parameter == "ntaps") {
throw ParameterError("Parameter 'ntaps' is read-only");
}
else if (parameter == "tapsfile") {
try {
load_filter_taps(value);
m_taps_file = value;
}
catch (const std::runtime_error &e) {
throw ParameterError(e.what());
}
}
else {
stringstream ss;
ss << "Parameter '" << parameter <<
"' is not exported by controllable " << get_rc_name();
throw ParameterError(ss.str());
}
}
const string FIRFilter::get_parameter(const string& parameter) const
{
stringstream ss;
if (parameter == "ntaps") {
ss << m_taps.size();
}
else if (parameter == "tapsfile") {
ss << m_taps_file;
}
else {
ss << "Parameter '" << parameter <<
"' is not exported by controllable " << get_rc_name();
throw ParameterError(ss.str());
}
return ss.str();
}
const RemoteControllable::map_t FIRFilter::get_all_values() const
{
map_t map;
map["ntaps"] = m_taps.size();
map["tapsfile"] = m_taps_file;
return map;
}