/*
Copyright (C) 2005, 2006, 2007, 2008, 2009, 2010, 2011 Her Majesty
the Queen in Right of Canada (Communications Research Center Canada)
Copyright (C) 2024
Matthias P. Braendli, matthias.braendli@mpb.li
http://opendigitalradio.org
*/
/*
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 "TII.h"
#include "PcDebug.h"
#include
#include
/* TII pattern for TM I, II, IV */
const int pattern_tm1_2_4[][8] = { // {{{
{0,0,0,0,1,1,1,1},
{0,0,0,1,0,1,1,1},
{0,0,0,1,1,0,1,1},
{0,0,0,1,1,1,0,1},
{0,0,0,1,1,1,1,0},
{0,0,1,0,0,1,1,1},
{0,0,1,0,1,0,1,1},
{0,0,1,0,1,1,0,1},
{0,0,1,0,1,1,1,0},
{0,0,1,1,0,0,1,1},
{0,0,1,1,0,1,0,1},
{0,0,1,1,0,1,1,0},
{0,0,1,1,1,0,0,1},
{0,0,1,1,1,0,1,0},
{0,0,1,1,1,1,0,0},
{0,1,0,0,0,1,1,1},
{0,1,0,0,1,0,1,1},
{0,1,0,0,1,1,0,1},
{0,1,0,0,1,1,1,0},
{0,1,0,1,0,0,1,1},
{0,1,0,1,0,1,0,1},
{0,1,0,1,0,1,1,0},
{0,1,0,1,1,0,0,1},
{0,1,0,1,1,0,1,0},
{0,1,0,1,1,1,0,0},
{0,1,1,0,0,0,1,1},
{0,1,1,0,0,1,0,1},
{0,1,1,0,0,1,1,0},
{0,1,1,0,1,0,0,1},
{0,1,1,0,1,0,1,0},
{0,1,1,0,1,1,0,0},
{0,1,1,1,0,0,0,1},
{0,1,1,1,0,0,1,0},
{0,1,1,1,0,1,0,0},
{0,1,1,1,1,0,0,0},
{1,0,0,0,0,1,1,1},
{1,0,0,0,1,0,1,1},
{1,0,0,0,1,1,0,1},
{1,0,0,0,1,1,1,0},
{1,0,0,1,0,0,1,1},
{1,0,0,1,0,1,0,1},
{1,0,0,1,0,1,1,0},
{1,0,0,1,1,0,0,1},
{1,0,0,1,1,0,1,0},
{1,0,0,1,1,1,0,0},
{1,0,1,0,0,0,1,1},
{1,0,1,0,0,1,0,1},
{1,0,1,0,0,1,1,0},
{1,0,1,0,1,0,0,1},
{1,0,1,0,1,0,1,0},
{1,0,1,0,1,1,0,0},
{1,0,1,1,0,0,0,1},
{1,0,1,1,0,0,1,0},
{1,0,1,1,0,1,0,0},
{1,0,1,1,1,0,0,0},
{1,1,0,0,0,0,1,1},
{1,1,0,0,0,1,0,1},
{1,1,0,0,0,1,1,0},
{1,1,0,0,1,0,0,1},
{1,1,0,0,1,0,1,0},
{1,1,0,0,1,1,0,0},
{1,1,0,1,0,0,0,1},
{1,1,0,1,0,0,1,0},
{1,1,0,1,0,1,0,0},
{1,1,0,1,1,0,0,0},
{1,1,1,0,0,0,0,1},
{1,1,1,0,0,0,1,0},
{1,1,1,0,0,1,0,0},
{1,1,1,0,1,0,0,0},
{1,1,1,1,0,0,0,0} }; // }}}
TII::TII(unsigned int dabmode, tii_config_t& tii_config, bool fixedPoint) :
ModCodec(),
RemoteControllable("tii"),
m_dabmode(dabmode),
m_conf(tii_config),
m_fixedPoint(fixedPoint)
{
PDEBUG("TII::TII(%u) @ %p\n", dabmode, this);
RC_ADD_PARAMETER(enable, "enable TII [0-1]");
RC_ADD_PARAMETER(comb, "TII comb number [0-23]");
RC_ADD_PARAMETER(pattern, "TII pattern number [0-69]");
RC_ADD_PARAMETER(old_variant, "select old TII variant for old (buggy) receivers [0-1]");
switch (m_dabmode) {
case 1:
m_carriers = 1536;
if (not(0 <= m_conf.pattern and m_conf.pattern <= 69) ) {
throw TIIError("TII::TII pattern not valid!");
}
break;
case 2:
m_carriers = 384;
if (not(0 <= m_conf.pattern and m_conf.pattern <= 69) ) {
throw TIIError("TII::TII pattern not valid!");
}
break;
/* unsupported
case 3:
m_carriers = 192;
break;
case 4:
d_dabmode = 0;
case 0:
*/
default:
std::stringstream ss_exception;
ss_exception <<
"TII::TII DAB mode " << m_dabmode << " not valid!";
throw TIIError(ss_exception.str());
}
if (not(0 <= m_conf.comb and m_conf.comb <= 23) ) {
throw TIIError("TII::TII comb not valid!");
}
m_Acp.clear();
m_Acp.resize(m_carriers);
prepare_pattern();
}
const char* TII::name()
{
// Calculate name on demand because comb and pattern are
// modifiable through RC
std::stringstream ss;
ss << "TII(c:" << m_conf.comb <<
" p:" << m_conf.pattern <<
" vrnt:" << (m_conf.old_variant ? "old" : "new") << ")";
m_name = ss.str();
return m_name.c_str();
}
template
void do_process(size_t carriers, bool old_variant, const std::vector& Acp, Buffer* dataIn, Buffer* dataOut)
{
const T* in = reinterpret_cast(dataIn->getData());
T* out = reinterpret_cast(dataOut->getData());
/* Normalise the TII carrier power according to ETSI TR 101 496-3
* Clause 5.4.2.2 Paragraph 7:
*
* > The ratio of carriers in a TII symbol to a normal DAB symbol
* > is 1:48 for all Modes, so that the signal power in a TII symbol is
* > 16 dB below the signal power of the other symbols.
*
* This is because we only enable 32 out of 1536 carriers, not because
* every carrier is lower power.
*/
for (size_t i = 0; i < Acp.size(); i++) {
/* See header file for an explanation of the old variant.
*
* A_{c,p}(k) and A_{c,p}(k-1) are never both simultaneously true,
* so instead of doing the sum inside z_{m,0,k}, we could do
*
* if (m_Acp[i]) out[i] = in[i];
* if (m_Acp[i-1]) out[i] = in[i-1]
*
* (Considering only the new variant)
*
* To avoid messing with indices, we substitute j = i-1
*
* if (m_Acp[i]) out[i] = in[i];
* if (m_Acp[j]) out[j+1] = in[j]
*
* and fuse the two conditionals together:
*/
if (Acp[i]) {
out[i] = in[i];
out[i+1] = (old_variant ? in[i+1] : in[i]);
}
}
}
int TII::process(Buffer* dataIn, Buffer* dataOut)
{
const size_t sizeof_samples = m_fixedPoint ? sizeof(complexfix) : sizeof(complexf);
PDEBUG("TII::process(dataOut: %p)\n",
dataOut);
if ( (dataIn == NULL) or
(dataIn->getLength() != m_carriers * sizeof_samples)) {
throw TIIError("TII::process input size not valid!");
}
dataOut->setLength(m_carriers * sizeof_samples);
memset(dataOut->getData(), 0, dataOut->getLength());
if (m_conf.enable and m_insert) {
std::lock_guard lock(m_enabled_carriers_mutex);
if (m_fixedPoint) {
do_process(
m_carriers, m_conf.old_variant, m_Acp,
dataIn, dataOut);
}
else {
do_process(
m_carriers, m_conf.old_variant, m_Acp,
dataIn, dataOut);
}
}
// Align with frames containing the right data (when FC.fp is first quarter)
m_insert = not m_insert;
return 1;
}
void TII::enable_carrier(int k)
{
/* The OFDMGenerator shifts all positive frequencies by one,
* i.e. index 0 is not the DC component, it's the first positive
* frequency. Because this is different from the definition of k
* from the spec, we need to compensate this here.
*
* Positive frequencies are k > 0
*/
int ix = m_carriers/2 + k + (k>=0 ? -1 : 0);
if (ix < 0 or ix+1 >= (ssize_t)m_Acp.size()) {
throw TIIError("TII::enable_carrier invalid k!");
}
m_Acp[ix] = true;
}
void TII::prepare_pattern()
{
int comb = m_conf.comb; // Convert from unsigned to signed
std::lock_guard lock(m_enabled_carriers_mutex);
// Clear previous pattern
for (size_t i = 0; i < m_Acp.size(); i++) {
m_Acp[i] = false;
}
// This could be written more efficiently, but since it is
// not performance-critial, it makes sense to write it
// in the same way as the specification in
// ETSI EN 300 401 Clause 14.8
if (m_dabmode == 1) {
for (int k = -768; k < -384; k++) {
for (int b = 0; b < 8; b++) {
if ( k == -768 + 2 * comb + 48 * b and
pattern_tm1_2_4[m_conf.pattern][b]) {
enable_carrier(k);
}
}
}
for (int k = -384; k < -0; k++) {
for (int b = 0; b < 8; b++) {
if ( k == -384 + 2 * comb + 48 * b and
pattern_tm1_2_4[m_conf.pattern][b]) {
enable_carrier(k);
}
}
}
for (int k = 1; k <= 384; k++) {
for (int b = 0; b < 8; b++) {
if ( k == 1 + 2 * comb + 48 * b and
pattern_tm1_2_4[m_conf.pattern][b]) {
enable_carrier(k);
}
}
}
for (int k = 385; k <= 768; k++) {
for (int b = 0; b < 8; b++) {
if ( k == 385 + 2 * comb + 48 * b and
pattern_tm1_2_4[m_conf.pattern][b]) {
enable_carrier(k);
}
}
}
}
else if (m_dabmode == 2) {
for (int k = -192; k <= 192; k++) {
for (int b = 0; b < 4; b++) {
if ( k == -192 + 2 * comb + 48 * b and
pattern_tm1_2_4[m_conf.pattern][b]) {
enable_carrier(k);
}
}
for (int b = 4; b < 8; b++) {
if ( k == -191 + 2 * comb + 48 * b and
pattern_tm1_2_4[m_conf.pattern][b]) {
enable_carrier(k);
}
}
}
}
else {
throw TIIError("TII::TII DAB mode not valid!");
}
}
void TII::set_parameter(const std::string& parameter, const std::string& value)
{
using namespace std;
stringstream ss(value);
ss.exceptions ( stringstream::failbit | stringstream::badbit );
if (parameter == "enable") {
ss >> m_conf.enable;
}
else if (parameter == "pattern") {
int new_pattern;
ss >> new_pattern;
if ( (m_dabmode == 1 or m_dabmode == 2) and
not(0 <= new_pattern and new_pattern <= 69) ) {
throw TIIError("TII pattern not valid!");
}
m_conf.pattern = new_pattern;
prepare_pattern();
}
else if (parameter == "comb") {
int new_comb;
ss >> new_comb;
if (not(0 <= new_comb and new_comb <= 23) ) {
throw TIIError("TII comb not valid!");
}
m_conf.comb = new_comb;
prepare_pattern();
}
else if (parameter == "old_variant") {
ss >> m_conf.old_variant;
}
else {
stringstream ss_err;
ss_err << "Parameter '" << parameter <<
"' is not exported by controllable " << get_rc_name();
throw ParameterError(ss_err.str());
}
}
const std::string TII::get_parameter(const std::string& parameter) const
{
using namespace std;
stringstream ss;
if (parameter == "enable") {
ss << (m_conf.enable ? 1 : 0);
}
else if (parameter == "pattern") {
ss << m_conf.pattern;
}
else if (parameter == "comb") {
ss << m_conf.comb;
}
else if (parameter == "old_variant") {
ss << (m_conf.old_variant ? 1 : 0);
}
else {
ss << "Parameter '" << parameter <<
"' is not exported by controllable " << get_rc_name();
throw ParameterError(ss.str());
}
return ss.str();
}
const json::map_t TII::get_all_values() const
{
json::map_t map;
map["enable"].v = m_conf.enable;
map["pattern"].v = m_conf.pattern;
map["comb"].v = m_conf.comb;
map["old_variant"].v = m_conf.old_variant;
return map;
}