/*
Copyright (C) 2007, 2008, 2009, 2010, 2011 Her Majesty the Queen in
Right of Canada (Communications Research Center Canada)
Copyright (C) 2017
Matthias P. Braendli, matthias.braendli@mpb.li
Andreas Steger, andreas.steger@digris.ch
http://opendigitalradio.org
This block implements a memoryless polynom for digital predistortion.
For better performance, multiplying is done in another thread, leading
to a pipeline delay of two calls to MemlessPoly::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 .
*/
#pragma GCC optimize ("O3")
#include "MemlessPoly.h"
#include "PcDebug.h"
#include "Utils.h"
#include
#include
#include
#include
#include
#include
#include
#include
using namespace std;
#define NUM_COEFS_AM 5
#define NUM_COEFS_PM 5
MemlessPoly::MemlessPoly(const std::string& coefs_am_file, const std::string& coefs_pm_file, unsigned int num_threads) :
PipelinedModCodec(),
RemoteControllable("memlesspoly"),
m_num_threads(num_threads),
m_coefs_am(),
m_coefs_am_file(coefs_am_file),
m_coefs_am_mutex(),
m_coefs_pm(),
m_coefs_pm_file(coefs_pm_file),
m_coefs_pm_mutex()
{
PDEBUG("MemlessPoly::MemlessPoly(%s) @ %p\n",
coefs_am_file.c_str(), this);
PDEBUG("MemlessPoly::MemlessPoly(%s) @ %p\n",
coefs_pm_file.c_str(), this);
if (m_num_threads == 0) {
const unsigned int hw_concurrency = std::thread::hardware_concurrency();
etiLog.level(info) << "Polynomial Predistorter will use " <<
hw_concurrency << " threads (auto detected)";
}
else {
etiLog.level(info) << "Polynomial Predistorter will use " <<
m_num_threads << " threads (set in config file)";
}
RC_ADD_PARAMETER(ncoefs_am, "(Read-only) number of coefficients for amplitude.");
RC_ADD_PARAMETER(coeffile_am, "Filename containing coefficients for amplitude. When written to, the new file gets automatically loaded.");
RC_ADD_PARAMETER(ncoefs_pm, "(Read-only) number of coefficients for phase.");
RC_ADD_PARAMETER(coeffile_pm, "Filename containing coefficients for amplitude. When written to, the new file gets automatically loaded.");
load_coefficients_am(m_coefs_am_file);
load_coefficients_pm(m_coefs_pm_file);
start_pipeline_thread();
}
void MemlessPoly::load_coefficients_am(const std::string &coefFile_am)
{
std::vector coefs_am;
std::ifstream coef_fstream_am(coefFile_am.c_str());
if (!coef_fstream_am) {
throw std::runtime_error("MemlessPoly: Could not open file with coefs_am!");
}
int n_coefs_am;
coef_fstream_am >> n_coefs_am;
if (n_coefs_am <= 0) {
throw std::runtime_error("MemlessPoly: coefs_am file has invalid format.");
}
else if (n_coefs_am != NUM_COEFS_AM) {
throw std::runtime_error("MemlessPoly: invalid number of coefs_am: " +
std::to_string(n_coefs_am) + " expected " + std::to_string(NUM_COEFS_AM));
}
etiLog.log(debug, "MemlessPoly: Reading %d coefs_am...", n_coefs_am);
coefs_am.resize(n_coefs_am);
for (int n = 0; n < n_coefs_am; n++) {
float a;
coef_fstream_am >> a;
coefs_am[n] = a;
if (coef_fstream_am.eof()) {
etiLog.log(error, "MemlessPoly: file %s should contains %d coefs_am, "
"but EOF reached after %d coefs_am !",
coefFile_am.c_str(), n_coefs_am, n);
throw std::runtime_error("MemlessPoly: coefs_am file invalid !");
}
}
{
std::lock_guard lock(m_coefs_am_mutex);
m_coefs_am = coefs_am;
}
}
void MemlessPoly::load_coefficients_pm(const std::string &coefFile_pm)
{
std::vector coefs_pm;
std::ifstream coef_fstream_pm(coefFile_pm.c_str());
if (!coef_fstream_pm) {
throw std::runtime_error("MemlessPoly: Could not open file with coefs_pm!");
}
int n_coefs_pm;
coef_fstream_pm >> n_coefs_pm;
if (n_coefs_pm <= 0) {
throw std::runtime_error("MemlessPoly: coefs_pm file has invalid format.");
}
else if (n_coefs_pm != NUM_COEFS_PM) {
throw std::runtime_error("MemlessPoly: invalid number of coefs_pm: " +
std::to_string(n_coefs_pm) + " expected " + std::to_string(NUM_COEFS_PM));
}
etiLog.log(debug, "MemlessPoly: Reading %d coefs_pm...", n_coefs_pm);
coefs_pm.resize(n_coefs_pm);
for (int n = 0; n < n_coefs_pm; n++) {
float a;
coef_fstream_pm >> a;
coefs_pm[n] = a;
if (coef_fstream_pm.eof()) {
etiLog.log(error, "MemlessPoly: file %s should contains %d coefs_pm, "
"but EOF reached after %d coefs_pm !",
coefFile_pm.c_str(), n_coefs_pm, n);
throw std::runtime_error("MemlessPoly: coefs_pm file invalid !");
}
}
{
std::lock_guard lock(m_coefs_pm_mutex);
m_coefs_pm = coefs_pm;
}
}
/* The restrict keyword is C99, g++ and clang++ however support __restrict
* instead, and this allows the compiler to auto-vectorize the loop.
*/
static void apply_coeff(
const vector &coefs_am,
const vector &coefs_pm,
const complexf *__restrict in, size_t start, size_t stop,
complexf *__restrict out)
{
for (size_t i = start; i < stop; i+=1) {
float in_mag_sq = in[i].real() * in[i].real() + in[i].imag() * in[i].imag();
float amplitude_correction =
( coefs_am[0] + in_mag_sq *
( coefs_am[1] + in_mag_sq *
( coefs_am[2] + in_mag_sq *
( coefs_am[3] + in_mag_sq *
coefs_am[4]))));
float phase_correction = -1 *
( coefs_pm[0] + in_mag_sq *
( coefs_pm[1] + in_mag_sq *
( coefs_pm[2] + in_mag_sq *
( coefs_pm[3] + in_mag_sq *
coefs_pm[4]))));
float phase_correction_sq = phase_correction * phase_correction;
// Approximation for Cosinus 1 - 1/2 x^2 + 1/24 x^4 - 1/720 x^6
float re = (1.0f - phase_correction_sq *
( -0.5f + phase_correction_sq *
( 0.486666f + phase_correction_sq *
( -0.00138888f))));
// Approximation for Sinus x + 1/6 x^3 + 1/120 x^5
float im = phase_correction *
(1.0f + phase_correction_sq *
(0.166666f + phase_correction_sq *
(0.00833333f)));
out[i] = in[i] * amplitude_correction * complex(re, im);
}
}
int MemlessPoly::internal_process(Buffer* const dataIn, Buffer* dataOut)
{
dataOut->setLength(dataIn->getLength());
const complexf* in = reinterpret_cast(dataIn->getData());
complexf* out = reinterpret_cast(dataOut->getData());
size_t sizeOut = dataOut->getLength() / sizeof(complexf);
{
std::lock_guard lock_am(m_coefs_am_mutex);
std::lock_guard lock_pm(m_coefs_pm_mutex);
const unsigned int hw_concurrency = std::thread::hardware_concurrency();
const unsigned int num_threads =
(m_num_threads > 0) ? m_num_threads : hw_concurrency;
if (num_threads) {
const size_t step = sizeOut / num_threads;
vector > flags;
size_t start = 0;
for (size_t i = 0; i < num_threads - 1; i++) {
flags.push_back(async(launch::async, apply_coeff,
m_coefs_am, m_coefs_pm, in, start, start + step, out));
start += step;
}
// Do the last in this thread
apply_coeff(m_coefs_am, m_coefs_pm, in, start, sizeOut, out);
// Wait for completion of the tasks
for (auto& f : flags) {
f.get();
}
}
else {
apply_coeff(m_coefs_am, m_coefs_pm, in, 0, sizeOut, out);
}
}
return dataOut->getLength();
}
void MemlessPoly::set_parameter(const string& parameter, const string& value)
{
stringstream ss(value);
ss.exceptions ( stringstream::failbit | stringstream::badbit );
if (parameter == "ncoefs_am") {
throw ParameterError("Parameter 'ncoefs_am' is read-only");
}
else if (parameter == "ncoefs_pm") {
throw ParameterError("Parameter 'ncoefs_pm' is read-only");
}
else if (parameter == "coeffile_am") {
try {
load_coefficients_am(value);
m_coefs_am_file = value;
}
catch (std::runtime_error &e) {
throw ParameterError(e.what());
}
}
else if (parameter == "coeffile_pm") {
try {
load_coefficients_pm(value);
m_coefs_pm_file = value;
}
catch (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 MemlessPoly::get_parameter(const string& parameter) const
{
stringstream ss;
if (parameter == "ncoefs_am") {
ss << m_coefs_am.size();
}
else if (parameter == "ncoefs_pm") {
ss << m_coefs_pm.size();
}
else if (parameter == "coefFile_am") {
ss << m_coefs_am_file;
}
else if (parameter == "coefFile_pm") {
ss << m_coefs_pm_file;
}
else {
ss << "Parameter '" << parameter <<
"' is not exported by controllable " << get_rc_name();
throw ParameterError(ss.str());
}
return ss.str();
}