feat: integrate RTL-TCP server support

This commit is contained in:
Nedifinita
2025-11-01 22:39:52 +08:00
parent 356738ac10
commit 5aa19ada14
379 changed files with 109413 additions and 47 deletions

View File

@@ -72,6 +72,12 @@ android {
universalApk true
}
}
externalNativeBuild {
cmake {
path "src/main/cpp/CMakeLists.txt"
version "3.22.1"
}
}
}
dependencies {

View File

@@ -0,0 +1,22 @@
# android/app/src/main/cpp/CMakeLists.txt
cmake_minimum_required(VERSION 3.22.1)
project("railwaypagerdemod")
add_library(${CMAKE_PROJECT_NAME} SHARED
demod.cpp
demod.h
native-lib.cpp
${CMAKE_CURRENT_SOURCE_DIR}/dsp/firfilter.cpp
)
target_link_libraries(${CMAKE_PROJECT_NAME}
android
log
m)
add_definitions(-DSDRBASE_API=)
include_directories(
${CMAKE_CURRENT_SOURCE_DIR}
${CMAKE_CURRENT_SOURCE_DIR}/fcdlib
)

View File

@@ -0,0 +1,410 @@
#include "demod.h"
bool is_message_ready = false;
PhaseDiscriminators phaseDiscri;
Lowpass<double> lowpassBaud;
MovingAverageUtil<double, double, 2048> preambleMovingAverage;
bool got_SC = false;
double dc_offset = 0.0;
bool prev_data, bit_inverted, data_bit;
int sync_cnt, bit_cnt = 0, word_cnt = 0;
uint32_t bits;
uint32_t code_words[PAGERDEMOD_BATCH_WORDS];
bool code_words_bch_error[PAGERDEMOD_BATCH_WORDS];
std::string numeric_msg, alpha_msg;
int function_bits;
uint32_t address;
uint32_t alpha_bit_buffer; // Bit buffer to 7-bit chars spread across codewords
int alpha_bit_buffer_bits; // Count of bits in alpha_bit_buffer
int parity_errors; // Count of parity errors in current message
int bch_errors; // Count of BCH errors in current message
int batch_num; // Count of batches in current transmission
double magsqRaw;
int pop_cnt(uint32_t cw)
{
int cnt = 0;
for (int i = 0; i < 32; i++)
{
cnt += cw & 1;
cw = cw >> 1;
}
return cnt;
}
uint32_t bchEncode(const uint32_t cw)
{
uint32_t bit = 0;
uint32_t localCW = cw & 0xFFFFF800; // Mask off BCH parity and even parity bits
uint32_t cwE = localCW;
// Calculate BCH bits
for (bit = 1; bit <= 21; bit++)
{
if (cwE & 0x80000000)
{
cwE ^= 0xED200000;
}
cwE <<= 1;
}
localCW |= (cwE >> 21);
return localCW;
}
// Use BCH decoding to try to fix any bit errors
// Returns true if able to be decode/repair successful
// See: https://www.eevblog.com/forum/microcontrollers/practical-guides-to-bch-fec/
bool bchDecode(const uint32_t cw, uint32_t &correctedCW)
{
// Calculate syndrome
// We do this by recalculating the BCH parity bits and XORing them against the received ones
uint32_t syndrome = ((bchEncode(cw) ^ cw) >> 1) & 0x3FF;
if (syndrome == 0)
{
// Syndrome of zero indicates no repair required
correctedCW = cw;
return true;
}
// Meggitt decoder
uint32_t result = 0;
uint32_t damagedCW = cw;
// Calculate BCH bits
for (uint32_t xbit = 0; xbit < 31; xbit++)
{
// Produce the next corrected bit in the high bit of the result
result <<= 1;
if ((syndrome == 0x3B4) || // 0x3B4: Syndrome when a single error is detected in the MSB
(syndrome == 0x26E) || // 0x26E: Two adjacent errors
(syndrome == 0x359) || // 0x359: Two errors, one OK bit between
(syndrome == 0x076) || // 0x076: Two errors, two OK bits between
(syndrome == 0x255) || // 0x255: Two errors, three OK bits between
(syndrome == 0x0F0) || // 0x0F0: Two errors, four OK bits between
(syndrome == 0x216) ||
(syndrome == 0x365) ||
(syndrome == 0x068) ||
(syndrome == 0x25A) ||
(syndrome == 0x343) ||
(syndrome == 0x07B) ||
(syndrome == 0x1E7) ||
(syndrome == 0x129) ||
(syndrome == 0x14E) ||
(syndrome == 0x2C9) ||
(syndrome == 0x0BE) ||
(syndrome == 0x231) ||
(syndrome == 0x0C2) ||
(syndrome == 0x20F) ||
(syndrome == 0x0DD) ||
(syndrome == 0x1B4) ||
(syndrome == 0x2B4) ||
(syndrome == 0x334) ||
(syndrome == 0x3F4) ||
(syndrome == 0x394) ||
(syndrome == 0x3A4) ||
(syndrome == 0x3BC) ||
(syndrome == 0x3B0) ||
(syndrome == 0x3B6) ||
(syndrome == 0x3B5))
{
// Syndrome matches an error in the MSB
// Correct that error and adjust the syndrome to account for it
syndrome ^= 0x3B4;
result |= (~damagedCW & 0x80000000) >> 30;
}
else
{
// No error
result |= (damagedCW & 0x80000000) >> 30;
}
damagedCW <<= 1;
// Handle syndrome shift register feedback
if (syndrome & 0x200)
{
syndrome <<= 1;
syndrome ^= 0x769; // 0x769 = POCSAG generator polynomial -- x^10 + x^9 + x^8 + x^6 + x^5 + x^3 + 1
}
else
{
syndrome <<= 1;
}
// Mask off bits which fall off the end of the syndrome shift register
syndrome &= 0x3FF;
}
// Check if error correction was successful
if (syndrome != 0)
{
// Syndrome nonzero at end indicates uncorrectable errors
correctedCW = cw;
return false;
}
correctedCW = result;
return true;
}
int xorBits(uint32_t word, int firstBit, int lastBit)
{
int x = 0;
for (int i = firstBit; i <= lastBit; i++)
{
x ^= (word >> i) & 1;
}
return x;
}
// Check for even parity
bool evenParity(uint32_t word, int firstBit, int lastBit, int parityBit)
{
return xorBits(word, firstBit, lastBit) == parityBit;
}
// Reverse order of bits
uint32_t reverse(uint32_t x)
{
x = (((x & 0xaaaaaaaa) >> 1) | ((x & 0x55555555) << 1));
x = (((x & 0xcccccccc) >> 2) | ((x & 0x33333333) << 2));
x = (((x & 0xf0f0f0f0) >> 4) | ((x & 0x0f0f0f0f) << 4));
x = (((x & 0xff00ff00) >> 8) | ((x & 0x00ff00ff) << 8));
return ((x >> 16) | (x << 16));
}
// Decode a batch of codewords to addresses and messages
// Messages may be spreadout over multiple batches
// https://www.itu.int/dms_pubrec/itu-r/rec/m/R-REC-M.584-1-198607-S!!PDF-E.pdf
// https://www.itu.int/dms_pubrec/itu-r/rec/m/R-REC-M.584-2-199711-I!!PDF-E.pdf
void decodeBatch()
{
int i = 1;
for (int frame = 0; frame < PAGERDEMOD_FRAMES_PER_BATCH; frame++)
{
for (int word = 0; word < PAGERDEMOD_CODEWORDS_PER_FRAME; word++)
{
bool addressCodeWord = ((code_words[i] >> 31) & 1) == 0;
// Check parity bit
bool parityError = !evenParity(code_words[i], 1, 31, code_words[i] & 0x1);
if (code_words[i] == PAGERDEMOD_POCSAG_IDLECODE)
{
// Idle
}
else if (addressCodeWord)
{
// Address
function_bits = (code_words[i] >> 11) & 0x3;
int addressBits = (code_words[i] >> 13) & 0x3ffff;
address = (addressBits << 3) | frame;
numeric_msg = "";
alpha_msg = "";
alpha_bit_buffer_bits = 0;
alpha_bit_buffer = 0;
parity_errors = parityError ? 1 : 0;
bch_errors = code_words_bch_error[i] ? 1 : 0;
}
else
{
// Message - decode as both numeric and ASCII - not all operators use functionBits to indidcate encoding
int messageBits = (code_words[i] >> 11) & 0xfffff;
if (parityError)
{
parity_errors++;
}
if (code_words_bch_error[i])
{
bch_errors++;
}
// Numeric format
for (int j = 16; j >= 0; j -= 4)
{
uint32_t numericBits = (messageBits >> j) & 0xf;
numericBits = reverse(numericBits) >> (32 - 4);
// Spec has 0xa as 'spare', but other decoders treat is as .
const char numericChars[] = {
'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '.', 'U', ' ', '-', ')', '('};
char numericChar = numericChars[numericBits];
numeric_msg.push_back(numericChar);
}
// 7-bit ASCII alpnanumeric format
alpha_bit_buffer = (alpha_bit_buffer << 20) | messageBits;
alpha_bit_buffer_bits += 20;
while (alpha_bit_buffer_bits >= 7)
{
// Extract next 7-bit character from bit buffer
char c = (alpha_bit_buffer >> (alpha_bit_buffer_bits - 7)) & 0x7f;
// Reverse bit ordering
c = reverse(c) >> (32 - 7);
// Add to received message string (excluding, null, end of text, end ot transmission)
if (c != 0 && c != 0x3 && c != 0x4)
{
alpha_msg.push_back(c);
}
// Remove from bit buffer
alpha_bit_buffer_bits -= 7;
if (alpha_bit_buffer_bits == 0)
{
alpha_bit_buffer = 0;
}
else
{
alpha_bit_buffer &= (1 << alpha_bit_buffer_bits) - 1;
}
}
}
// Move to next codeword
i++;
}
}
}
void processOneSample(int8_t i, int8_t q)
{
float fi = ((float)i) / 128.0f;
float fq = ((float)q) / 128.0f;
std::complex<float> iq(fi, fq);
float deviation;
double fmDemod = phaseDiscri.phaseDiscriminatorDelta(iq, magsqRaw, deviation);
// printf("fmDemod: %.3f\n", fmDemod);
double filt = lowpassBaud.filter(fmDemod);
if (!got_SC)
{
preambleMovingAverage(filt);
dc_offset = preambleMovingAverage.asDouble();
}
bool data = (filt - dc_offset) >= 0.0;
// printf("filt - dc: %.3f\n", filt - dc_offset);
if (data != prev_data)
{
sync_cnt = SAMPLES_PER_SYMBOL / 2; // reset
}
else
{
sync_cnt--; // wait until next bit's midpoint
if (sync_cnt <= 0)
{
if (bit_inverted)
{
data_bit = data;
}
else
{
data_bit = !data;
}
// printf("%d", data_bit);
bits = (bits << 1) | data_bit;
bit_cnt++;
if (bit_cnt > 32)
{
bit_cnt = 32;
}
if (bit_cnt == 32 && !got_SC)
{
// printf("pop count: %d\n", pop_cnt(bits ^ POCSAG_SYNCCODE));
// printf("pop count inv: %d\n", pop_cnt(bits ^ POCSAG_SYNCCODE_INV));
if (bits == POCSAG_SYNCCODE)
{
got_SC = true;
bit_inverted = false;
printf("\nSync code found\n");
}
else if (bits == POCSAG_SYNCCODE_INV)
{
got_SC = true;
bit_inverted = true;
printf("\nSync code found\n");
}
else if (pop_cnt(bits ^ POCSAG_SYNCCODE) <= 3)
{
uint32_t corrected_cw;
if (bchDecode(bits, corrected_cw) && corrected_cw == POCSAG_SYNCCODE)
{
got_SC = true;
bit_inverted = false;
printf("\nSync code found\n");
}
// else printf("\nSync code not found\n");
}
else if (pop_cnt(bits ^ POCSAG_SYNCCODE_INV) <= 3)
{
uint32_t corrected_cw;
if (bchDecode(~bits, corrected_cw) && corrected_cw == POCSAG_SYNCCODE)
{
got_SC = true;
bit_inverted = true;
printf("\nSync code found\n");
}
// else printf("\nSync code not found\n");
}
if (got_SC)
{
bits = 0;
bit_cnt = 0;
code_words[0] = POCSAG_SYNCCODE;
word_cnt = 1;
}
}
else if (bit_cnt == 32 && got_SC)
{
uint32_t corrected_cw;
code_words_bch_error[word_cnt] = !bchDecode(bits, corrected_cw);
code_words[word_cnt] = corrected_cw;
word_cnt++;
if (word_cnt == 1 && corrected_cw != POCSAG_SYNCCODE)
{
got_SC = false;
bit_inverted = false;
}
if (word_cnt == PAGERDEMOD_BATCH_WORDS)
{
decodeBatch();
batch_num++;
word_cnt = 0;
}
bits = 0;
bit_cnt = 0;
if (address > 0 && !numeric_msg.empty())
{
is_message_ready = true;
printf("Addr: %d | Numeric: %s | Alpha: %s\n", address, numeric_msg.c_str(), alpha_msg.c_str());
}
else
{
is_message_ready = false;
}
}
sync_cnt = SAMPLES_PER_SYMBOL;
}
}
prev_data = data;
}

View File

@@ -0,0 +1,39 @@
#ifndef DEMOD_H
#define DEMOD_H
#include <complex>
#include <stdbool.h>
#include <stdint.h>
#include <string>
#include <iostream>
#include "dsp/phasediscri.h"
#include "dsp/firfilter.h"
#include "util/movingaverage.h"
#define SDR_RX_SCALED 32768.0
#define SAMPLE_RATE 48000.0
#define BAUD_RATE 1200.0
#define DEVIATION 4500.0
#define SAMPLES_PER_SYMBOL (SAMPLE_RATE / BAUD_RATE)
#define POCSAG_SYNCCODE 0x7CD215D8
#define POCSAG_SYNCCODE_INV ~0x7CD215D8
#define PAGERDEMOD_POCSAG_IDLECODE 0x7A89C197
#define PAGERDEMOD_BATCH_WORDS 17
#define PAGERDEMOD_FRAMES_PER_BATCH 8
#define PAGERDEMOD_CODEWORDS_PER_FRAME 2
extern bool is_message_ready;
extern std::string numeric_msg;
extern std::string alpha_msg;
extern uint32_t address;
extern int function_bits;
extern PhaseDiscriminators phaseDiscri;
extern Lowpass<double> lowpassBaud;
extern MovingAverageUtil<double, double, 2048> preambleMovingAverage;
extern double magsqRaw;
void processOneSample(int8_t i, int8_t q);
#endif

View File

@@ -0,0 +1,278 @@
///////////////////////////////////////////////////////////////////////////////////
// Copyright (C) 2015-2019 Edouard Griffiths, F4EXB <f4exb06@gmail.com> //
// Copyright (C) 2020 Kacper Michajłow <kasper93@gmail.com> //
// //
// This program 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 as version 3 of the License, or //
// (at your option) any later version. //
// //
// This program 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 V3 for more details. //
// //
// You should have received a copy of the GNU General Public License //
// along with this program. If not, see <http://www.gnu.org/licenses/>. //
///////////////////////////////////////////////////////////////////////////////////
#include <cmath>
#include <QDebug>
#include "dsp/afsquelch.h"
AFSquelch::AFSquelch() :
m_nbAvg(128),
m_N(24),
m_sampleRate(48000),
m_samplesProcessed(0),
m_samplesAvgProcessed(0),
m_maxPowerIndex(0),
m_nTones(2),
m_samplesAttack(0),
m_attackCount(0),
m_samplesDecay(0),
m_decayCount(0),
m_squelchCount(0),
m_isOpen(false),
m_threshold(0.0)
{
m_k = new double[m_nTones];
m_coef = new double[m_nTones];
m_toneSet = new double[m_nTones];
m_u0 = new double[m_nTones];
m_u1 = new double[m_nTones];
m_power = new double[m_nTones];
m_movingAverages.resize(m_nTones, MovingAverage<double>(m_nbAvg, 0.0f));
for (unsigned int j = 0; j < m_nTones; ++j)
{
m_toneSet[j] = j == 0 ? 1000.0 : 6000.0;
m_k[j] = ((double)m_N * m_toneSet[j]) / (double) m_sampleRate;
m_coef[j] = 2.0 * cos((2.0 * M_PI * m_toneSet[j])/(double) m_sampleRate);
m_u0[j] = 0.0;
m_u1[j] = 0.0;
m_power[j] = 0.0;
m_movingAverages[j].fill(0.0);
}
}
AFSquelch::~AFSquelch()
{
delete[] m_k;
delete[] m_coef;
delete[] m_toneSet;
delete[] m_u0;
delete[] m_u1;
delete[] m_power;
}
void AFSquelch::setCoefficients(
unsigned int N,
unsigned int nbAvg,
unsigned int sampleRate,
unsigned int samplesAttack,
unsigned int samplesDecay,
const double *tones)
{
m_N = N; // save the basic parameters for use during analysis
m_nbAvg = nbAvg;
m_sampleRate = sampleRate;
m_samplesAttack = samplesAttack;
m_samplesDecay = samplesDecay;
m_movingAverages.resize(m_nTones, MovingAverage<double>(m_nbAvg, 0.0));
m_samplesProcessed = 0;
m_samplesAvgProcessed = 0;
m_maxPowerIndex = 0;
m_attackCount = 0;
m_decayCount = 0;
m_squelchCount = 0;
m_isOpen = false;
m_threshold = 0.0;
// for each of the frequencies (tones) of interest calculate
// k and the associated filter coefficient as per the Goertzel
// algorithm. Note: we are using a real value (as opposed to
// an integer as described in some references. k is retained
// for later display. The tone set is specified in the
// constructor. Notice that the resulting coefficients are
// independent of N.
for (unsigned int j = 0; j < m_nTones; ++j)
{
m_toneSet[j] = tones[j] < ((double) m_sampleRate) * 0.4 ? tones[j] : ((double) m_sampleRate) * 0.4; // guarantee 80% Nyquist rate
m_k[j] = ((double)m_N * m_toneSet[j]) / (double)m_sampleRate;
m_coef[j] = 2.0 * cos((2.0 * M_PI * m_toneSet[j])/(double)m_sampleRate);
m_u0[j] = 0.0;
m_u1[j] = 0.0;
m_power[j] = 0.0;
m_movingAverages[j].fill(0.0);
}
}
// Analyze an input signal
bool AFSquelch::analyze(double sample)
{
feedback(sample); // Goertzel feedback
if (m_samplesProcessed < m_N) // completed a block of N
{
m_samplesProcessed++;
return false;
}
else
{
feedForward(); // calculate the power at each tone
m_samplesProcessed = 0;
if (m_samplesAvgProcessed < m_nbAvg)
{
m_samplesAvgProcessed++;
return false;
}
else
{
return true; // have a result
}
}
}
void AFSquelch::feedback(double in)
{
double t;
// feedback for each tone
for (unsigned int j = 0; j < m_nTones; ++j)
{
t = m_u0[j];
m_u0[j] = in + (m_coef[j] * m_u0[j]) - m_u1[j];
m_u1[j] = t;
}
}
void AFSquelch::feedForward()
{
for (unsigned int j = 0; j < m_nTones; ++j)
{
m_power[j] = (m_u0[j] * m_u0[j]) + (m_u1[j] * m_u1[j]) - (m_coef[j] * m_u0[j] * m_u1[j]);
m_movingAverages[j].feed(m_power[j]);
m_u0[j] = 0.0;
m_u1[j] = 0.0; // reset for next block.
}
evaluate();
}
void AFSquelch::reset()
{
for (unsigned int j = 0; j < m_nTones; ++j)
{
m_u0[j] = 0.0;
m_u1[j] = 0.0;
m_power[j] = 0.0;
m_movingAverages[j].fill(0.0);
}
m_samplesProcessed = 0;
m_maxPowerIndex = 0;
m_isOpen = false;
}
bool AFSquelch::evaluate()
{
double maxPower = 0.0;
double minPower;
int minIndex = 0, maxIndex = 0;
for (unsigned int j = 0; j < m_nTones; ++j)
{
if (m_movingAverages[j].sum() > maxPower)
{
maxPower = m_movingAverages[j].sum();
maxIndex = j;
}
}
if (maxPower == 0.0)
{
return m_isOpen;
}
minPower = maxPower;
for (unsigned int j = 0; j < m_nTones; ++j)
{
if (m_movingAverages[j].sum() < minPower) {
minPower = m_movingAverages[j].sum();
minIndex = j;
}
}
// m_isOpen = ((minPower/maxPower < m_threshold) && (minIndex > maxIndex));
if ((minPower/maxPower < m_threshold) && (minIndex > maxIndex)) // open condition
{
if (m_squelchCount < m_samplesAttack + m_samplesDecay)
{
m_squelchCount++;
}
}
else
{
if (m_squelchCount > m_samplesAttack)
{
m_squelchCount--;
}
else
{
m_squelchCount = 0;
}
}
m_isOpen = (m_squelchCount >= m_samplesAttack) ;
// if ((minPower/maxPower < m_threshold) && (minIndex > maxIndex)) // open condition
// {
// if ((m_samplesAttack > 0) && (m_attackCount < m_samplesAttack))
// {
// m_isOpen = false;
// m_attackCount++;
// }
// else
// {
// m_isOpen = true;
// m_decayCount = 0;
// }
// }
// else
// {
// if ((m_samplesDecay > 0) && (m_decayCount < m_samplesDecay))
// {
// m_isOpen = true;
// m_decayCount++;
// }
// else
// {
// m_isOpen = false;
// m_attackCount = 0;
// }
// }
return m_isOpen;
}
void AFSquelch::setThreshold(double threshold)
{
qDebug("AFSquelch::setThreshold: threshold: %f", threshold);
m_threshold = threshold;
reset();
}

View File

@@ -0,0 +1,91 @@
///////////////////////////////////////////////////////////////////////////////////
// Copyright (C) 2015-2019 Edouard Griffiths, F4EXB <f4exb06@gmail.com> //
// //
// This program 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 as version 3 of the License, or //
// (at your option) any later version. //
// //
// This program 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 V3 for more details. //
// //
// You should have received a copy of the GNU General Public License //
// along with this program. If not, see <http://www.gnu.org/licenses/>. //
///////////////////////////////////////////////////////////////////////////////////
#ifndef INCLUDE_GPL_DSP_AFSQUELCH_H_
#define INCLUDE_GPL_DSP_AFSQUELCH_H_
#include "dsp/movingaverage.h"
#include "export.h"
/** AFSquelch: AF squelch class based on the Modified Goertzel
* algorithm.
*/
class SDRBASE_API AFSquelch {
public:
// constructor with default values
AFSquelch();
virtual ~AFSquelch();
// setup the basic parameters and coefficients
void setCoefficients(
unsigned int N, //!< the algorithm "block" size
unsigned int nbAvg, //!< averaging size
unsigned int sampleRate, //!< input signal sample rate
unsigned int samplesAttack, //!< number of results before squelch opens
unsigned int samplesDecay, //!< number of results keeping squelch open
const double *tones); //!< center frequency of tones tested
// set the detection threshold
void setThreshold(double _threshold);
// analyze a sample set and optionally filter
// the tone frequencies.
bool analyze(double sample); // input signal sample
bool evaluate(); // evaluate result
// get the tone set
const double *getToneSet() const
{
return m_toneSet;
}
bool open() const {
return m_isOpen;
}
void reset(); // reset the analysis algorithm
protected:
void feedback(double sample);
void feedForward();
private:
unsigned int m_nbAvg; //!< number of power samples taken for moving average
unsigned int m_N;
unsigned int m_sampleRate;
unsigned int m_samplesProcessed;
unsigned int m_samplesAvgProcessed;
unsigned int m_maxPowerIndex;
unsigned int m_nTones;
unsigned int m_samplesAttack;
unsigned int m_attackCount;
unsigned int m_samplesDecay;
unsigned int m_decayCount;
unsigned int m_squelchCount;
bool m_isOpen;
double m_threshold;
double *m_k;
double *m_coef;
double *m_toneSet;
double *m_u0;
double *m_u1;
double *m_power;
std::vector<MovingAverage<double> > m_movingAverages;
};
#endif /* INCLUDE_GPL_DSP_CTCSSDETECTOR_H_ */

View File

@@ -0,0 +1,191 @@
///////////////////////////////////////////////////////////////////////////////////////
// Copyright (C) 2015-2019 Edouard Griffiths, F4EXB <f4exb06@gmail.com> //
// //
// This program 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 as version 3 of the License, or //
// (at your option) any later version. //
// //
// This program 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 V3 for more details. //
// //
// You should have received a copy of the GNU General Public License //
// along with this program. If not, see <http://www.gnu.org/licenses/>. //
///////////////////////////////////////////////////////////////////////////////////////
#include <algorithm>
#include "dsp/agc.h"
#include "util/stepfunctions.h"
AGC::AGC(int historySize, double R) :
m_u0(1.0),
m_R(R),
m_moving_average(historySize, m_R),
m_historySize(historySize),
m_count(0)
{}
AGC::~AGC()
{}
void AGC::resize(int historySize, double R)
{
m_R = R;
m_moving_average.resize(historySize, R);
m_historySize = historySize;
m_count = 0;
}
Real AGC::getValue()
{
return m_u0;
}
Real AGC::getAverage()
{
return m_moving_average.average();
}
MagAGC::MagAGC(int historySize, double R, double threshold) :
AGC(historySize, R),
m_squared(false),
m_magsq(0.0),
m_threshold(threshold),
m_thresholdEnable(true),
m_gate(0),
m_stepLength(std::min(2400, historySize/2)), // max 50 ms (at 48 kHz)
m_stepDelta(1.0/m_stepLength),
m_stepUpCounter(0),
m_stepDownCounter(0),
m_gateCounter(0),
m_stepDownDelay(historySize),
m_hardLimiting(false)
{}
MagAGC::~MagAGC()
{}
void MagAGC::resize(int historySize, int stepLength, Real R)
{
m_stepLength = stepLength;
m_stepDelta = 1.0 / m_stepLength;
m_stepUpCounter = 0;
m_stepDownCounter = 0;
AGC::resize(historySize, R);
m_moving_average.fill(m_squared ? R : R*R);
}
void MagAGC::setOrder(double R)
{
AGC::setOrder(R);
m_moving_average.fill(m_squared ? R : R*R);
}
void MagAGC::setThresholdEnable(bool enable)
{
if (m_thresholdEnable != enable)
{
m_stepUpCounter = 0;
m_stepDownCounter = 0;
}
m_thresholdEnable = enable;
}
void MagAGC::feed(Complex& ci)
{
ci *= feedAndGetValue(ci);
}
double MagAGC::hardLimiter(double multiplier, double magsq)
{
if ((m_hardLimiting) && (multiplier*multiplier*magsq > 1.0)) {
return 1.0 / (multiplier*sqrt(magsq));
} else {
return multiplier;
}
}
double MagAGC::feedAndGetValue(const Complex& ci)
{
m_magsq = ci.real()*ci.real() + ci.imag()*ci.imag();
m_moving_average.feed(m_magsq);
m_u0 = m_R / (m_squared ? m_moving_average.average() : sqrt(m_moving_average.average()));
if (m_thresholdEnable)
{
bool open = false;
if (m_magsq > m_threshold)
{
if (m_gateCounter < m_gate) {
m_gateCounter++;
} else {
open = true;
}
}
else
{
m_gateCounter = 0;
}
if (open)
{
m_count = m_stepDownDelay; // delay before step down (grace delay)
}
else
{
m_count--;
m_gateCounter = m_gate; // keep gate open during grace
}
if (m_count > 0) // up phase
{
m_stepDownCounter = m_stepUpCounter; // prepare for step down
if (m_stepUpCounter < m_stepLength) // step up
{
m_stepUpCounter++;
return hardLimiter(m_u0 * StepFunctions::smootherstep(m_stepUpCounter * m_stepDelta), m_magsq);
}
else // steady open
{
return hardLimiter(m_u0, m_magsq);
}
}
else // down phase
{
m_stepUpCounter = m_stepDownCounter; // prepare for step up
if (m_stepDownCounter > 0) // step down
{
m_stepDownCounter--;
return hardLimiter(m_u0 * StepFunctions::smootherstep(m_stepDownCounter * m_stepDelta), m_magsq);
}
else // steady closed
{
return 0.0;
}
}
}
else
{
return hardLimiter(m_u0, m_magsq);
}
}
float MagAGC::getStepValue() const
{
if (m_count > 0) // up phase
{
return StepFunctions::smootherstep(m_stepUpCounter * m_stepDelta); // step up
}
else // down phase
{
return StepFunctions::smootherstep(m_stepDownCounter * m_stepDelta); // step down
}
}

View File

@@ -0,0 +1,137 @@
///////////////////////////////////////////////////////////////////////////////////////
// Copyright (C) 2015-2019 Edouard Griffiths, F4EXB <f4exb06@gmail.com> //
// //
// This program 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 as version 3 of the License, or //
// (at your option) any later version. //
// //
// This program 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 V3 for more details. //
// //
// You should have received a copy of the GNU General Public License //
// along with this program. If not, see <http://www.gnu.org/licenses/>. //
///////////////////////////////////////////////////////////////////////////////////////
#ifndef INCLUDE_GPL_DSP_AGC_H_
#define INCLUDE_GPL_DSP_AGC_H_
#include "dsp/dsptypes.h"
#include "movingaverage.h"
#include "export.h"
class SDRBASE_API AGC
{
public:
AGC(int historySize, double R);
virtual ~AGC();
void resize(int historySize, double R);
void setOrder(double R) { m_R = R; }
Real getValue();
Real getAverage();
void reset(double R) { m_moving_average.fill(R); }
virtual void feed(Complex& ci) = 0;
protected:
double m_u0; //!< AGC factor
double m_R; //!< ordered magnitude
MovingAverage<double> m_moving_average; //!< Averaging engine. The stack length conditions the smoothness of AGC.
int m_historySize; //!< Averaging length (the longer the slower the AGC)
int m_count; //!< Samples counter
};
class SDRBASE_API MagAGC : public AGC
{
public:
MagAGC(int historySize, double R, double threshold);
virtual ~MagAGC();
void setSquared(bool squared) { m_squared = squared; }
void resize(int historySize, int stepLength, Real R);
void setOrder(double R);
virtual void feed(Complex& ci);
double feedAndGetValue(const Complex& ci);
double getMagSq() const { return m_magsq; }
void setThreshold(double threshold) { m_threshold = threshold; }
void setThresholdEnable(bool enable);
void setGate(int gate) { m_gate = gate; m_gateCounter = 0; m_count = 0; }
void setStepDownDelay(int stepDownDelay) { m_stepDownDelay = stepDownDelay; m_gateCounter = 0; m_count = 0; }
int getStepDownDelay() const { return m_stepDownDelay; }
float getStepValue() const;
void setHardLimiting(bool hardLimiting) { m_hardLimiting = hardLimiting; }
void resetStepCounters() { m_stepUpCounter = 0; m_stepDownCounter = 0; }
private:
bool m_squared; //!< use squared magnitude (power) to compute AGC value
double m_magsq; //!< current squared magnitude (power)
double m_threshold; //!< squelch on magsq average
bool m_thresholdEnable; //!< enable squelch on power threshold
int m_gate; //!< power threshold gate in number of samples
int m_stepLength; //!< transition step length in number of samples
double m_stepDelta; //!< transition step unit by sample
int m_stepUpCounter; //!< step up transition samples counter
int m_stepDownCounter; //!< step down transition samples counter
int m_gateCounter; //!< threshold gate samples counter
int m_stepDownDelay; //!< delay in samples before cutoff (release)
bool m_hardLimiting; //!< hard limit multiplier so that resulting sample magnitude does not exceed 1.0
double hardLimiter(double multiplier, double magsq);
};
template<uint32_t AvgSize>
class SimpleAGC
{
public:
SimpleAGC(Real initial, Real cutoff=0, Real clip=0) :
m_cutoff(cutoff),
m_clip(clip),
m_moving_average(AvgSize, initial)
{
}
void resize(Real initial, Real cutoff=0, Real clip=0)
{
m_cutoff = cutoff;
m_clip = clip;
m_moving_average.resize(AvgSize, initial);
}
void resizeNew(uint32_t newSize, Real initial, Real cutoff=0, Real clip=0)
{
m_cutoff = cutoff;
m_clip = clip;
m_moving_average.resize(newSize, initial);
}
void fill(double value)
{
m_moving_average.fill(value);
}
Real getValue()
{
if ((Real) m_moving_average.average() > m_clip) {
return (Real) m_moving_average.average();
} else {
return m_clip;
}
}
void feed(Real value)
{
if (value > m_cutoff) {
m_moving_average.feed(value);
}
}
private:
Real m_cutoff; // consider samples only above this level
Real m_clip; // never go below this level
MovingAverage<double> m_moving_average; // Averaging engine. The stack length conditions the smoothness of AGC.
//MovingAverageUtil<Real, double, AvgSize> m_moving_average;
};
#endif /* INCLUDE_GPL_DSP_AGC_H_ */

View File

@@ -0,0 +1,106 @@
///////////////////////////////////////////////////////////////////////////////////
// Copyright (C) 2018-2019 Edouard Griffiths, F4EXB <f4exb06@gmail.com> //
// //
// This program 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 as version 3 of the License, or //
// (at your option) any later version. //
// //
// This program 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 V3 for more details. //
// //
// You should have received a copy of the GNU General Public License //
// along with this program. If not, see <http://www.gnu.org/licenses/>. //
///////////////////////////////////////////////////////////////////////////////////
#ifndef SDRBASE_DSP_AUTOCORRECTOR_H_
#define SDRBASE_DSP_AUTOCORRECTOR_H_
#include "util/movingaverage.h"
template<typename T>
class AutoCorrector
{
public:
AutoCorrector(qint32 intBits);
void process(const T& inreal, const T& inimag, qint32& outreal, qint32& outimag);
void process(qint32& xreal, qint32& ximag);
void setIQCorrection(bool iqCorrection) { m_iqCorrection = iqCorrection; }
private:
bool m_iqCorrection;
float m_scalef;
MovingAverageUtil<int32_t, int64_t, 1024> m_iBeta;
MovingAverageUtil<int32_t, int64_t, 1024> m_qBeta;
MovingAverageUtil<float, double, 128> m_avgII;
MovingAverageUtil<float, double, 128> m_avgIQ;
MovingAverageUtil<float, double, 128> m_avgII2;
MovingAverageUtil<float, double, 128> m_avgQQ2;
MovingAverageUtil<double, double, 128> m_avgPhi;
MovingAverageUtil<double, double, 128> m_avgAmp;
};
template<typename T>
AutoCorrector<T>::AutoCorrector(qint32 intBits) :
m_iqCorrection(false),
m_scalef((float) (1<<(intBits-1)))
{
}
template<typename T>
void AutoCorrector<T>::process(const T& inreal, const T& inimag, qint32& outreal, qint32& outimag)
{
outreal = inreal;
outimag = inimag;
process(outreal, outimag);
}
template<typename T>
void AutoCorrector<T>::process(qint32& xreal, qint32& ximag)
{
m_iBeta(xreal);
m_qBeta(ximag);
if (m_iqCorrection)
{
// DC correction and conversion
float xi = (xreal - (int32_t) m_iBeta) / m_scalef;
float xq = (ximag - (int32_t) m_qBeta) / m_scalef;
// phase imbalance
m_avgII(xi*xi); // <I", I">
m_avgIQ(xi*xq); // <I", Q">
if (m_avgII.asDouble() != 0) {
m_avgPhi(m_avgIQ.asDouble()/m_avgII.asDouble());
}
float yi = xi - m_avgPhi.asDouble()*xq;
float yq = xq;
// amplitude I/Q imbalance
m_avgII2(yi*yi); // <I, I>
m_avgQQ2(yq*yq); // <Q, Q>
if (m_avgQQ2.asDouble() != 0) {
m_avgAmp(sqrt(m_avgII2.asDouble() / m_avgQQ2.asDouble()));
}
// final correction
float zi = yi;
float zq = m_avgAmp.asDouble() * yq;
// convert and store
xreal = zi * m_scalef;
ximag = zq * m_scalef;
}
else
{
xreal -= (int32_t) m_iBeta;
ximag -= (int32_t) m_qBeta;
}
}
#endif /* SDRBASE_DSP_AUTOCORRECTOR_H_ */

View File

@@ -0,0 +1,28 @@
///////////////////////////////////////////////////////////////////////////////////
// Copyright (C) 2012 maintech GmbH, Otto-Hahn-Str. 15, 97204 Hoechberg, Germany //
// written by Christian Daniel //
// Copyright (C) 2015-2018, 2020-2022 Edouard Griffiths, F4EXB <f4exb06@gmail.com> //
// //
// This program 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 as version 3 of the License, or //
// (at your option) any later version. //
// //
// This program 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 V3 for more details. //
// //
// You should have received a copy of the GNU General Public License //
// along with this program. If not, see <http://www.gnu.org/licenses/>. //
///////////////////////////////////////////////////////////////////////////////////
#include "basebandsamplesink.h"
BasebandSampleSink::BasebandSampleSink()
{
}
BasebandSampleSink::~BasebandSampleSink()
{
}

View File

@@ -0,0 +1,44 @@
///////////////////////////////////////////////////////////////////////////////////
// Copyright (C) 2012 maintech GmbH, Otto-Hahn-Str. 15, 97204 Hoechberg, Germany //
// written by Christian Daniel //
// Copyright (C) 2015-2022 Edouard Griffiths, F4EXB <f4exb06@gmail.com> //
// //
// This program 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 as version 3 of the License, or //
// (at your option) any later version. //
// //
// This program 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 V3 for more details. //
// //
// You should have received a copy of the GNU General Public License //
// along with this program. If not, see <http://www.gnu.org/licenses/>. //
///////////////////////////////////////////////////////////////////////////////////
#ifndef INCLUDE_SAMPLESINK_H
#define INCLUDE_SAMPLESINK_H
#include <string>
#include "dsp/dsptypes.h"
#include "export.h"
class Message;
class SDRBASE_API BasebandSampleSink {
public:
BasebandSampleSink();
virtual ~BasebandSampleSink();
virtual void start() = 0;
virtual void stop() = 0;
virtual void feed(const SampleVector::const_iterator& begin, const SampleVector::const_iterator& end, bool positiveOnly) = 0;
virtual void feed(const Complex*, unsigned int) { //!< Special feed directly with complex array
}
virtual void pushMessage(Message *msg) = 0;
virtual std::string getSinkName() = 0;
};
#endif // INCLUDE_SAMPLESINK_H

View File

@@ -0,0 +1,28 @@
///////////////////////////////////////////////////////////////////////////////////
// Copyright (C) 2012 maintech GmbH, Otto-Hahn-Str. 15, 97204 Hoechberg, Germany //
// written by Christian Daniel //
// Copyright (C) 2016-2019, 2021-2022 Edouard Griffiths, F4EXB <f4exb06@gmail.com> //
// //
// This program 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 as version 3 of the License, or //
// (at your option) any later version. //
// //
// This program 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 V3 for more details. //
// //
// You should have received a copy of the GNU General Public License //
// along with this program. If not, see <http://www.gnu.org/licenses/>. //
///////////////////////////////////////////////////////////////////////////////////
#include "dsp/basebandsamplesource.h"
BasebandSampleSource::BasebandSampleSource()
{
}
BasebandSampleSource::~BasebandSampleSource()
{
}

View File

@@ -0,0 +1,40 @@
///////////////////////////////////////////////////////////////////////////////////
// Copyright (C) 2012 maintech GmbH, Otto-Hahn-Str. 15, 97204 Hoechberg, Germany //
// written by Christian Daniel //
// Copyright (C) 2015-2019, 2021-2022 Edouard Griffiths, F4EXB <f4exb06@gmail.com> //
// //
// This program 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 as version 3 of the License, or //
// (at your option) any later version. //
// //
// This program 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 V3 for more details. //
// //
// You should have received a copy of the GNU General Public License //
// along with this program. If not, see <http://www.gnu.org/licenses/>. //
///////////////////////////////////////////////////////////////////////////////////
#ifndef SDRBASE_DSP_BASEBANDSAMPLESOURCE_H_
#define SDRBASE_DSP_BASEBANDSAMPLESOURCE_H_
#include "dsp/dsptypes.h"
#include "export.h"
class Message;
class SDRBASE_API BasebandSampleSource {
public:
BasebandSampleSource();
virtual ~BasebandSampleSource();
virtual void start() = 0;
virtual void stop() = 0;
virtual void pull(SampleVector::iterator& begin, unsigned int nbSamples) = 0;
virtual void pushMessage(Message *msg) = 0;
virtual QString getSourceName() = 0;
};
#endif /* SDRBASE_DSP_BASEBANDSAMPLESOURCE_H_ */

View File

@@ -0,0 +1,260 @@
///////////////////////////////////////////////////////////////////////////////////
// Copyright (C) 2012 maintech GmbH, Otto-Hahn-Str. 15, 97204 Hoechberg, Germany //
// written by Christian Daniel //
// Copyright (C) 2015, 2017-2019, 2021 Edouard Griffiths, F4EXB <f4exb06@gmail.com> //
// //
// This program 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 as version 3 of the License, or //
// (at your option) any later version. //
// //
// This program 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 V3 for more details. //
// //
// You should have received a copy of the GNU General Public License //
// along with this program. If not, see <http://www.gnu.org/licenses/>. //
///////////////////////////////////////////////////////////////////////////////////
#include "SWGChannelMarker.h"
#include "dsp/channelmarker.h"
#include "util/simpleserializer.h"
QRgb ChannelMarker::m_colorTable[] = {
qRgb(0xc0, 0x00, 0x00),
qRgb(0x00, 0xc0, 0x00),
qRgb(0x00, 0x00, 0xc0),
qRgb(0xc0, 0xc0, 0x00),
qRgb(0xc0, 0x00, 0xc0),
qRgb(0x00, 0xc0, 0xc0),
qRgb(0xc0, 0x60, 0x00),
qRgb(0xc0, 0x00, 0x60),
qRgb(0x60, 0x00, 0xc0),
qRgb(0x60, 0x00, 0x00),
qRgb(0x00, 0x60, 0x00),
qRgb(0x00, 0x00, 0x60),
qRgb(0x60, 0x60, 0x00),
qRgb(0x60, 0x00, 0x60),
qRgb(0x00, 0x60, 0x60),
0
};
int ChannelMarker::m_nextColor = 0;
ChannelMarker::ChannelMarker(QObject* parent) :
QObject(parent),
m_centerFrequency(0),
m_bandwidth(0),
m_oppositeBandwidth(0),
m_lowCutoff(0),
m_shift(0),
m_sidebands(dsb),
m_visible(false),
m_highlighted(false),
m_color(m_colorTable[m_nextColor]),
m_movable(true),
m_fScaleDisplayType(FScaleDisplay_freq),
m_sourceOrSinkStream(true),
m_enabledStreamsBits(1)
{
++m_nextColor;
if(m_colorTable[m_nextColor] == 0)
m_nextColor = 0;
}
void ChannelMarker::emitChangedByAPI()
{
emit changedByAPI();
}
void ChannelMarker::setTitle(const QString& title)
{
m_title = title;
emit changedByAPI();
}
void ChannelMarker::setCenterFrequency(int centerFrequency)
{
m_centerFrequency = centerFrequency;
emit changedByAPI();
}
void ChannelMarker::setCenterFrequencyByCursor(int centerFrequency)
{
m_centerFrequency = centerFrequency;
emit changedByCursor();
}
void ChannelMarker::setBandwidth(int bandwidth)
{
m_bandwidth = bandwidth;
emit changedByAPI();
}
void ChannelMarker::setOppositeBandwidth(int bandwidth)
{
m_oppositeBandwidth = bandwidth;
emit changedByAPI();
}
void ChannelMarker::setLowCutoff(int lowCutoff)
{
m_lowCutoff = lowCutoff;
emit changedByAPI();
}
void ChannelMarker::setShift(int shift)
{
m_shift = shift;
emit changedByAPI();
}
void ChannelMarker::setSidebands(sidebands_t sidebands)
{
m_sidebands = sidebands;
emit changedByAPI();
}
void ChannelMarker::setVisible(bool visible)
{
m_visible = visible;
emit changedByAPI();
}
void ChannelMarker::setHighlighted(bool highlighted)
{
m_highlighted = highlighted;
emit changedByAPI();
}
void ChannelMarker::setHighlightedByCursor(bool highlighted)
{
m_highlighted = highlighted;
emit highlightedByCursor();
}
void ChannelMarker::setColor(const QColor& color)
{
m_color = color;
emit changedByAPI();
}
void ChannelMarker::resetToDefaults()
{
setTitle("Channel");
setCenterFrequency(0);
setColor(Qt::white);
setFrequencyScaleDisplayType(FScaleDisplay_freq);
}
QByteArray ChannelMarker::serialize() const
{
SimpleSerializer s(1);
s.writeS32(1, getCenterFrequency());
s.writeU32(2, getColor().rgb());
s.writeString(3, getTitle());
s.writeS32(7, (int) getFrequencyScaleDisplayType());
return s.final();
}
bool ChannelMarker::deserialize(const QByteArray& data)
{
SimpleDeserializer d(data);
if(!d.isValid())
{
resetToDefaults();
return false;
}
if(d.getVersion() == 1)
{
quint32 u32tmp;
qint32 tmp;
QString strtmp;
blockSignals(true);
d.readS32(1, &tmp, 0);
setCenterFrequency(tmp);
if(d.readU32(2, &u32tmp)) {
setColor(u32tmp);
}
d.readString(3, &strtmp, "Channel");
setTitle(strtmp);
d.readS32(7, &tmp, 0);
if ((tmp >= 0) && (tmp < FScaleDisplay_none)) {
setFrequencyScaleDisplayType((frequencyScaleDisplay_t) tmp);
} else {
setFrequencyScaleDisplayType(FScaleDisplay_freq);
}
blockSignals(false);
return true;
}
else
{
resetToDefaults();
return false;
}
}
void ChannelMarker::formatTo(SWGSDRangel::SWGObject *swgObject) const
{
SWGSDRangel::SWGChannelMarker *swgChannelMarker = static_cast<SWGSDRangel::SWGChannelMarker *>(swgObject);
swgChannelMarker->setCenterFrequency(getCenterFrequency());
swgChannelMarker->setColor(getColor().rgb());
swgChannelMarker->setFrequencyScaleDisplayType((int) getFrequencyScaleDisplayType());
if (swgChannelMarker->getTitle()) {
*swgChannelMarker->getTitle() = getTitle();
} else {
swgChannelMarker->setTitle(new QString(getTitle()));
}
}
void ChannelMarker::updateFrom(const QStringList& keys, const SWGSDRangel::SWGObject *swgObject)
{
SWGSDRangel::SWGChannelMarker *swgChannelMarker =
static_cast<SWGSDRangel::SWGChannelMarker *>(const_cast<SWGSDRangel::SWGObject *>(swgObject));
if (keys.contains("channelMarker.centerFrequency")) {
setCenterFrequency(swgChannelMarker->getCenterFrequency());
}
if (keys.contains("channelMarker.color")) {
setColor(swgChannelMarker->getColor());
}
if (keys.contains("channelMarker.frequencyScaleDisplayType")) {
setFrequencyScaleDisplayType((frequencyScaleDisplay_t) swgChannelMarker->getFrequencyScaleDisplayType());
}
if (keys.contains("channelMarker.title")) {
setTitle(*swgChannelMarker->getTitle());
}
}
void ChannelMarker::updateSettings(const ChannelMarker *channelMarker)
{
m_fScaleDisplayType = channelMarker->m_fScaleDisplayType;
}
void ChannelMarker::addStreamIndex(int streamIndex)
{
m_enabledStreamsBits |= (1<<streamIndex);
}
void ChannelMarker::removeStreamIndex(int streamIndex)
{
m_enabledStreamsBits &= ~(1<<streamIndex);
}
void ChannelMarker::clearStreamIndexes()
{
m_enabledStreamsBits = 0;
}

View File

@@ -0,0 +1,142 @@
///////////////////////////////////////////////////////////////////////////////////
// Copyright (C) 2012 maintech GmbH, Otto-Hahn-Str. 15, 97204 Hoechberg, Germany //
// written by Christian Daniel //
// Copyright (C) 2015-2019, 2021 Edouard Griffiths, F4EXB <f4exb06@gmail.com> //
// //
// This program 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 as version 3 of the License, or //
// (at your option) any later version. //
// //
// This program 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 V3 for more details. //
// //
// You should have received a copy of the GNU General Public License //
// along with this program. If not, see <http://www.gnu.org/licenses/>. //
///////////////////////////////////////////////////////////////////////////////////
#ifndef INCLUDE_CHANNELMARKER_H
#define INCLUDE_CHANNELMARKER_H
#include <QObject>
#include <QColor>
#include <QByteArray>
#include "settings/serializable.h"
#include "export.h"
class SDRBASE_API ChannelMarker : public QObject, public Serializable {
Q_OBJECT
public:
typedef enum sidebands_e
{
dsb,
lsb,
usb,
vusb, //!< USB with vestigial LSB
vlsb //!< LSB with vestigial USB
} sidebands_t;
typedef enum frequencyScaleDisplay_e
{
FScaleDisplay_freq,
FScaleDisplay_title,
FScaleDisplay_addressSend,
FScaleDisplay_addressReceive,
FScaleDisplay_none
} frequencyScaleDisplay_t;
ChannelMarker(QObject* parent = NULL);
void emitChangedByAPI();
void setTitle(const QString& title);
const QString& getTitle() const { return m_title; }
void setCenterFrequency(int centerFrequency);
void setCenterFrequencyByCursor(int centerFrequency);
int getCenterFrequency() const { return m_centerFrequency; }
void setBandwidth(int bandwidth);
int getBandwidth() const { return m_bandwidth; }
void setOppositeBandwidth(int bandwidth);
int getOppositeBandwidth() const { return m_oppositeBandwidth; }
void setLowCutoff(int lowCutoff);
int getLowCutoff() const { return m_lowCutoff; }
void setShift(int shift);
int getShift() const { return m_shift; }
void setSidebands(sidebands_t sidebands);
sidebands_t getSidebands() const { return m_sidebands; }
void setVisible(bool visible);
bool getVisible() const { return m_visible; }
void setHighlighted(bool highlighted);
void setHighlightedByCursor(bool highlighted);
bool getHighlighted() const { return m_highlighted; }
void setColor(const QColor& color);
const QColor& getColor() const { return m_color; }
void setMovable(bool movable) { m_movable = movable; }
bool getMovable() const { return m_movable; }
void setFrequencyScaleDisplayType(frequencyScaleDisplay_t type) { m_fScaleDisplayType = type; }
frequencyScaleDisplay_t getFrequencyScaleDisplayType() const { return m_fScaleDisplayType; }
const QString& getDisplayAddressSend() const { return m_displayAddressSend; }
const QString& getDisplayAddressReceive() const { return m_displayAddressReceive; }
void setSourceOrSinkStream(bool sourceOrSinkStream) { m_sourceOrSinkStream = sourceOrSinkStream; }
bool getSourceOrSinkStream() const { return m_sourceOrSinkStream; }
void addStreamIndex(int streamIndex);
void removeStreamIndex(int streamIndex);
void clearStreamIndexes();
bool streamIndexApplies(int streamIndex) const {
return m_enabledStreamsBits & (1<<streamIndex);
}
virtual QByteArray serialize() const;
virtual bool deserialize(const QByteArray& data);
virtual void formatTo(SWGSDRangel::SWGObject *swgObject) const;
virtual void updateFrom(const QStringList& keys, const SWGSDRangel::SWGObject *swgObject);
void updateSettings(const ChannelMarker *channelMarker);
protected:
static QRgb m_colorTable[];
static int m_nextColor;
QString m_title;
QString m_displayAddressSend;
QString m_displayAddressReceive;
int m_centerFrequency;
int m_bandwidth;
int m_oppositeBandwidth;
int m_lowCutoff;
int m_shift;
sidebands_t m_sidebands;
bool m_visible;
bool m_highlighted;
QColor m_color;
bool m_movable;
frequencyScaleDisplay_t m_fScaleDisplayType;
bool m_sourceOrSinkStream;
uint32_t m_enabledStreamsBits;
void resetToDefaults();
signals:
void changedByAPI();
void changedByCursor();
void highlightedByCursor();
};
#endif // INCLUDE_CHANNELMARKER_H

View File

@@ -0,0 +1,26 @@
///////////////////////////////////////////////////////////////////////////////////
// Copyright (C) 2012 maintech GmbH, Otto-Hahn-Str. 15, 97204 Hoechberg, Germany //
// written by Christian Daniel //
// Copyright (C) 2015-2019 Edouard Griffiths, F4EXB <f4exb06@gmail.com> //
// //
// This program 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 as version 3 of the License, or //
// (at your option) any later version. //
// //
// This program 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 V3 for more details. //
// //
// You should have received a copy of the GNU General Public License //
// along with this program. If not, see <http://www.gnu.org/licenses/>. //
///////////////////////////////////////////////////////////////////////////////////
#include "channelsamplesink.h"
ChannelSampleSink::ChannelSampleSink()
{}
ChannelSampleSink::~ChannelSampleSink()
{}

View File

@@ -0,0 +1,38 @@
///////////////////////////////////////////////////////////////////////////////////
// Copyright (C) 2012 maintech GmbH, Otto-Hahn-Str. 15, 97204 Hoechberg, Germany //
// written by Christian Daniel //
// Copyright (C) 2015-2019 Edouard Griffiths, F4EXB <f4exb06@gmail.com> //
// //
// This program 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 as version 3 of the License, or //
// (at your option) any later version. //
// //
// This program 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 V3 for more details. //
// //
// You should have received a copy of the GNU General Public License //
// along with this program. If not, see <http://www.gnu.org/licenses/>. //
///////////////////////////////////////////////////////////////////////////////////
// This is a lightweight channel sample source interface
#ifndef SDRBASE_DSP_CHANNELSAMPLESINK_H_
#define SDRBASE_DSP_CHANNELSAMPLESINK_H_
#include "export.h"
#include "dsp/dsptypes.h"
class Message;
class SDRBASE_API ChannelSampleSink {
public:
ChannelSampleSink();
virtual ~ChannelSampleSink();
virtual void feed(const SampleVector::const_iterator& begin, const SampleVector::const_iterator& end) = 0;
};
#endif // SDRBASE_DSP_CHANNELSAMPLESINK_H_

View File

@@ -0,0 +1,26 @@
///////////////////////////////////////////////////////////////////////////////////
// Copyright (C) 2012 maintech GmbH, Otto-Hahn-Str. 15, 97204 Hoechberg, Germany //
// written by Christian Daniel //
// Copyright (C) 2015-2019 Edouard Griffiths, F4EXB <f4exb06@gmail.com> //
// //
// This program 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 as version 3 of the License, or //
// (at your option) any later version. //
// //
// This program 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 V3 for more details. //
// //
// You should have received a copy of the GNU General Public License //
// along with this program. If not, see <http://www.gnu.org/licenses/>. //
///////////////////////////////////////////////////////////////////////////////////
#include "channelsamplesource.h"
ChannelSampleSource::ChannelSampleSource()
{}
ChannelSampleSource::~ChannelSampleSource()
{}

View File

@@ -0,0 +1,38 @@
///////////////////////////////////////////////////////////////////////////////////
// Copyright (C) 2012 maintech GmbH, Otto-Hahn-Str. 15, 97204 Hoechberg, Germany //
// written by Christian Daniel //
// Copyright (C) 2015-2019 Edouard Griffiths, F4EXB <f4exb06@gmail.com> //
// //
// This program 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 as version 3 of the License, or //
// (at your option) any later version. //
// //
// This program 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 V3 for more details. //
// //
// You should have received a copy of the GNU General Public License //
// along with this program. If not, see <http://www.gnu.org/licenses/>. //
///////////////////////////////////////////////////////////////////////////////////
// This is a lightweight channel sample source interface
#ifndef SDRBASE_DSP_CHANNELSAMPLESOURCE_H_
#define SDRBASE_DSP_CHANNELSAMPLESOURCE_H_
#include "export.h"
#include "dsp/dsptypes.h"
class SDRBASE_API ChannelSampleSource {
public:
ChannelSampleSource();
virtual ~ChannelSampleSource();
virtual void pull(SampleVector::iterator begin, unsigned int nbSamples) = 0; //!< pull nbSamples from the source and write them starting at begin
virtual void pullOne(Sample& sample) = 0; //!< pull a single sample from the source
virtual void prefetch(unsigned int nbSamples) = 0; //!< Do operation(s) before pulling nbSamples
};
#endif // SDRBASE_DSP_CHANNELSAMPLESOURCE_H_

View File

@@ -0,0 +1,61 @@
///////////////////////////////////////////////////////////////////////////////////////
// Copyright (C) 2012 maintech GmbH, Otto-Hahn-Str. 15, 97204 Hoechberg, Germany //
// written by Christian Daniel //
// Copyright (C) 2015-2016 Edouard Griffiths, F4EXB <f4exb06@gmail.com> //
// //
// This program 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 as version 3 of the License, or //
// (at your option) any later version. //
// //
// This program 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 V3 for more details. //
// //
// You should have received a copy of the GNU General Public License //
// along with this program. If not, see <http://www.gnu.org/licenses/>. //
///////////////////////////////////////////////////////////////////////////////////////
// ----------------------------------------------------------------------------
// complex.h -- Complex arithmetic
//
// Copyright (C) 2006-2008
// Dave Freese, W1HKJ
// Copyright (C) 2008
// Stelios Bounanos, M0GLD
//
// This file is part of fldigi.
//
// Fldigi 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.
//
// Fldigi 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 fldigi. If not, see <http://www.gnu.org/licenses/>.
// ----------------------------------------------------------------------------
#ifndef _COMPLEX_H
#define _COMPLEX_H
#include <cmath>
#include <complex>
typedef std::complex<float> cmplx;
inline cmplx cmac (const cmplx *a, const cmplx *b, int ptr, int len) {
cmplx z;
ptr %= len;
for (int i = 0; i < len; i++) {
z += a[i] * b[ptr];
ptr = (ptr + 1) % len;
}
return z;
}
#endif

View File

@@ -0,0 +1,112 @@
///////////////////////////////////////////////////////////////////////////////////
// Copyright (C) 2021 Jon Beniston, M7RCE <jon@beniston.com> //
// Copyright 2006-2021 Free Software Foundation, Inc. //
// Copyright (C) 2018 Edouard Griffiths, F4EXB //
// //
// Based on the Costas Loop from GNU Radio //
// //
// This program 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 as version 3 of the License, or //
// (at your option) any later version. //
// //
// This program 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 V3 for more details. //
// //
// You should have received a copy of the GNU General Public License //
// along with this program. If not, see <http://www.gnu.org/licenses/>. //
///////////////////////////////////////////////////////////////////////////////////
#include "costasloop.h"
#include <cmath>
// Loop bandwidth supposedly ~ 2pi/100 rads/sample
// pskOrder 2, 4 or 8
CostasLoop::CostasLoop(float loopBW, unsigned int pskOrder) :
m_maxFreq(1.0f),
m_minFreq(-1.0f),
m_pskOrder(pskOrder)
{
computeCoefficients(loopBW);
reset();
}
CostasLoop::~CostasLoop()
{
}
void CostasLoop::reset()
{
m_y.real(1.0f);
m_y.imag(0.0f);
m_freq = 0.0f;
m_phase = 0.0f;
m_freq = 0.0f;
m_error = 0.0f;
}
// 2nd order loop with critical damping
void CostasLoop::computeCoefficients(float loopBW)
{
float damping = sqrtf(2.0f) / 2.0f;
float denom = (1.0 + 2.0 * damping * loopBW + loopBW * loopBW);
m_alpha = (4 * damping * loopBW) / denom;
m_beta = (4 * loopBW * loopBW) / denom;
}
void CostasLoop::setSampleRate(unsigned int sampleRate)
{
(void) sampleRate;
reset();
}
static float branchlessClip(float x, float clip)
{
return 0.5f * (std::abs(x + clip) - std::abs(x - clip));
}
// Don't use built-in complex.h multiply to avoid NaN/INF checking
static void fastComplexMultiply(std::complex<float> &out, const std::complex<float> cc1, const std::complex<float> cc2)
{
float o_r, o_i;
o_r = (cc1.real() * cc2.real()) - (cc1.imag() * cc2.imag());
o_i = (cc1.real() * cc2.imag()) + (cc1.imag() * cc2.real());
out.real(o_r);
out.imag(o_i);
}
void CostasLoop::feed(float re, float im)
{
std::complex<float> nco(::cosf(-m_phase), ::sinf(-m_phase));
std::complex<float> in, out;
in.real(re);
in.imag(im);
fastComplexMultiply(out, in, nco);
switch (m_pskOrder)
{
case 2:
m_error = phaseDetector2(out);
break;
case 4:
m_error = phaseDetector4(out);
break;
case 8:
m_error = phaseDetector8(out);
break;
}
m_error = branchlessClip(m_error, 1.0f);
advanceLoop(m_error);
phaseWrap();
frequencyLimit();
m_y.real(-nco.real());
m_y.imag(nco.imag());
}

View File

@@ -0,0 +1,122 @@
///////////////////////////////////////////////////////////////////////////////////
// Copyright (C) 2021 Jon Beniston, M7RCE <jon@beniston.com> //
// Copyright 2006-2021 Free Software Foundation, Inc. //
// Copyright (C) 2018 Edouard Griffiths, F4EXB //
// //
// Based on the Costas Loop from GNU Radio //
// //
// This program 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 as version 3 of the License, or //
// (at your option) any later version. //
// //
// This program 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 V3 for more details. //
// //
// You should have received a copy of the GNU General Public License //
// along with this program. If not, see <http://www.gnu.org/licenses/>. //
///////////////////////////////////////////////////////////////////////////////////
#ifndef SDRBASE_DSP_COSTASLOOP_H_
#define SDRBASE_DSP_COSTASLOOP_H_
#include <QDebug>
#include "dsp/dsptypes.h"
#include "export.h"
/** Costas Loop for phase and frequency tracking. */
class SDRBASE_API CostasLoop
{
public:
CostasLoop(float loopBW, unsigned int pskOrder);
~CostasLoop();
void computeCoefficients(float loopBW);
void setPskOrder(unsigned int pskOrder) { m_pskOrder = pskOrder; }
void reset();
void setSampleRate(unsigned int sampleRate);
void feed(float re, float im);
const std::complex<float>& getComplex() const { return m_y; }
float getReal() const { return m_y.real(); }
float getImag() const { return m_y.imag(); }
float getFreq() const { return m_freq; }
float getPhiHat() const { return m_phase; }
private:
std::complex<float> m_y;
float m_phase;
float m_freq;
float m_error;
float m_maxFreq;
float m_minFreq;
float m_alpha;
float m_beta;
unsigned int m_pskOrder;
void advanceLoop(float error)
{
m_freq = m_freq + m_beta * error;
m_phase = m_phase + m_freq + m_alpha * error;
}
void phaseWrap()
{
const float two_pi = (float)(2.0 * M_PI);
while (m_phase > two_pi)
m_phase -= two_pi;
while (m_phase < -two_pi)
m_phase += two_pi;
}
void frequencyLimit()
{
if (m_freq > m_maxFreq)
m_freq = m_maxFreq;
else if (m_freq < m_minFreq)
m_freq = m_minFreq;
}
void setMaxFreq(float freq)
{
m_maxFreq = freq;
}
void setMinFreq(float freq)
{
m_minFreq = freq;
}
float phaseDetector2(std::complex<float> sample) const // for BPSK
{
return (sample.real() * sample.imag());
}
float phaseDetector4(std::complex<float> sample) const // for QPSK
{
return ((sample.real() > 0.0f ? 1.0f : -1.0f) * sample.imag() -
(sample.imag() > 0.0f ? 1.0f : -1.0f) * sample.real());
};
float phaseDetector8(std::complex<float> sample) const // for 8PSK
{
const float K = (sqrtf(2.0) - 1);
if (fabsf(sample.real()) >= fabsf(sample.imag()))
{
return ((sample.real() > 0.0f ? 1.0f : -1.0f) * sample.imag() -
(sample.imag() > 0.0f ? 1.0f : -1.0f) * sample.real() * K);
}
else
{
return ((sample.real() > 0.0f ? 1.0f : -1.0f) * sample.imag() * K -
(sample.imag() > 0.0f ? 1.0f : -1.0f) * sample.real());
}
};
};
#endif /* SDRBASE_DSP_COSTASLOOP_H_ */

View File

@@ -0,0 +1,159 @@
//////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Copyright (C) 2015-2017, 2020 Edouard Griffiths, F4EXB <f4exb06@gmail.com> //
// Copyright (C) 2020 Kacper Michajłow <kasper93@gmail.com> //
// //
// See: http://www.embedded.com/design/connectivity/4025660/Detecting-CTCSS-tones-with-Goertzel-s-algorithm //
// //
// This program 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 as version 3 of the License, or //
// (at your option) any later version. //
// //
// This program 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 V3 for more details. //
// //
// You should have received a copy of the GNU General Public License //
// along with this program. If not, see <http://www.gnu.org/licenses/>. //
//////////////////////////////////////////////////////////////////////////////////////////////////////////////
#include <cmath>
#include "dsp/ctcssdetector.h"
CTCSSDetector::CTCSSDetector() :
m_N(0),
m_sampleRate(0),
m_samplesProcessed(0),
m_maxPowerIndex(0),
m_toneDetected(false),
m_maxPower(0.0)
{
m_k = new Real[CTCSSFrequencies::m_nbFreqs];
m_coef = new Real[CTCSSFrequencies::m_nbFreqs];
m_u0 = new Real[CTCSSFrequencies::m_nbFreqs];
m_u1 = new Real[CTCSSFrequencies::m_nbFreqs];
m_power = new Real[CTCSSFrequencies::m_nbFreqs];
}
CTCSSDetector::~CTCSSDetector()
{
delete[] m_k;
delete[] m_coef;
delete[] m_u0;
delete[] m_u1;
delete[] m_power;
}
void CTCSSDetector::setCoefficients(int N, int sampleRate)
{
m_N = N; // save the basic parameters for use during analysis
m_sampleRate = sampleRate;
// for each of the frequencies (tones) of interest calculate
// k and the associated filter coefficient as per the Goertzel
// algorithm. Note: we are using a real value (as apposed to
// an integer as described in some references. k is retained
// for later display. The tone set is specified in the
// constructor. Notice that the resulting coefficients are
// independent of N.
for (int j = 0; j < CTCSSFrequencies::m_nbFreqs; ++j)
{
m_k[j] = ((double) m_N * CTCSSFrequencies::m_Freqs[j]) / (double)m_sampleRate;
m_coef[j] = 2.0 * cos((2.0 * M_PI * CTCSSFrequencies::m_Freqs[j])/(double)m_sampleRate);
}
}
// Analyze an input signal for the presence of CTCSS tones.
bool CTCSSDetector::analyze(Real *sample)
{
feedback(*sample); // Goertzel feedback
m_samplesProcessed += 1;
if (m_samplesProcessed == m_N) // completed a block of N
{
feedForward(); // calculate the m_power at each tone
m_samplesProcessed = 0;
return true; // have a result
}
else
{
return false;
}
}
void CTCSSDetector::feedback(Real in)
{
Real t;
// feedback for each tone
for (int j = 0; j < CTCSSFrequencies::m_nbFreqs; ++j)
{
t = m_u0[j];
m_u0[j] = in + (m_coef[j] * m_u0[j]) - m_u1[j];
m_u1[j] = t;
}
}
void CTCSSDetector::feedForward()
{
initializePower();
for (int j = 0; j < CTCSSFrequencies::m_nbFreqs; ++j)
{
m_power[j] = (m_u0[j] * m_u0[j]) + (m_u1[j] * m_u1[j]) - (m_coef[j] * m_u0[j] * m_u1[j]);
m_u0[j] = m_u1[j] = 0.0; // reset for next block.
}
evaluatePower();
}
void CTCSSDetector::reset()
{
for (int j = 0; j < CTCSSFrequencies::m_nbFreqs; ++j)
{
m_power[j] = m_u0[j] = m_u1[j] = 0.0; // reset
}
m_samplesProcessed = 0;
m_maxPower = 0.0;
m_maxPowerIndex = 0;
m_toneDetected = false;
}
void CTCSSDetector::initializePower()
{
for (int j = 0; j < CTCSSFrequencies::m_nbFreqs; ++j)
{
m_power[j] = 0.0; // reset
}
}
void CTCSSDetector::evaluatePower()
{
Real sumPower = 0.0;
Real aboveAvg = 2.0; // Arbitrary max m_power above average threshold
m_maxPower = 0.0;
for (int j = 0; j < CTCSSFrequencies::m_nbFreqs; ++j)
{
sumPower += m_power[j];
if (m_power[j] > m_maxPower)
{
m_maxPower = m_power[j];
m_maxPowerIndex = j;
}
}
m_toneDetected = (m_maxPower > (sumPower/CTCSSFrequencies::m_nbFreqs) + aboveAvg);
}

View File

@@ -0,0 +1,94 @@
//////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Copyright (C) 2015-2016, 2018, 2020 Edouard Griffiths, F4EXB <f4exb06@gmail.com> //
// //
// See: http://www.embedded.com/design/connectivity/4025660/Detecting-CTCSS-tones-with-Goertzel-s-algorithm //
// //
// This program 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 as version 3 of the License, or //
// (at your option) any later version. //
// //
// This program 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 V3 for more details. //
// //
// You should have received a copy of the GNU General Public License //
// along with this program. If not, see <http://www.gnu.org/licenses/>. //
//////////////////////////////////////////////////////////////////////////////////////////////////////////////
#ifndef INCLUDE_GPL_DSP_CTCSSDETECTOR_H_
#define INCLUDE_GPL_DSP_CTCSSDETECTOR_H_
#include "dsp/dsptypes.h"
#include "export.h"
#include "ctcssfrequencies.h"
/** CTCSSDetector: Continuous Tone Coded Squelch System
* tone detector class based on the Modified Goertzel
* algorithm.
*/
class SDRBASE_API CTCSSDetector {
public:
CTCSSDetector();
virtual ~CTCSSDetector();
// setup the basic parameters and coefficients
void setCoefficients(
int N, // the algorithm "block" size
int sampleRate // input signal sample rate
);
// set the detection threshold
void setThreshold(double thold);
// analyze a sample set and optionally filter the tone frequencies.
bool analyze(Real *sample); // input signal sample
// get the number of defined tones.
static int getNTones() {
return CTCSSFrequencies::m_nbFreqs;
}
// get the tone set
static const Real *getToneSet() {
return CTCSSFrequencies::m_Freqs;
}
// get the currently detected tone, if any
bool getDetectedTone(int &maxTone) const
{
maxTone = m_maxPowerIndex;
return m_toneDetected;
}
// Get the max m_power at the detected tone.
Real getMaxPower() const {
return m_maxPower;
}
void reset(); // reset the analysis algorithm
protected:
// Override these to change behavior of the detector
virtual void initializePower();
virtual void evaluatePower();
void feedback(Real sample);
void feedForward();
private:
int m_N;
int m_sampleRate;
int m_samplesProcessed;
int m_maxPowerIndex;
bool m_toneDetected;
Real m_maxPower;
Real *m_k;
Real *m_coef;
Real *m_u0;
Real *m_u1;
Real *m_power;
};
#endif /* INCLUDE_GPL_DSP_CTCSSDETECTOR_H_ */

View File

@@ -0,0 +1,77 @@
///////////////////////////////////////////////////////////////////////////////////
// Copyright (C) 2012 maintech GmbH, Otto-Hahn-Str. 15, 97204 Hoechberg, Germany //
// written by Christian Daniel //
// Copyright (C) 2015-2020 Edouard Griffiths, F4EXB <f4exb06@gmail.com> //
// //
// This program 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 as version 3 of the License, or //
// (at your option) any later version. //
// //
// This program 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 V3 for more details. //
// //
// You should have received a copy of the GNU General Public License //
// along with this program. If not, see <http://www.gnu.org/licenses/>. //
///////////////////////////////////////////////////////////////////////////////////
#include "ctcssfrequencies.h"
// The 51 tones from various standards (https://en.wikipedia.org/wiki/Continuous_Tone-Coded_Squelch_System)
const float CTCSSFrequencies::m_Freqs[] = {
67.0, // 0
69.3,
71.9,
74.4,
77.0,
79.7,
82.5,
85.4,
88.5,
91.5,
94.8, // 10
97.4,
100.0,
103.5,
107.2,
110.9,
114.8,
118.8,
123.0,
127.3,
131.8, // 20
136.5,
141.3,
146.2,
150.0,
151.4,
156.7,
159.8,
162.2,
165.5,
167.9, // 30
171.3,
173.8,
177.3,
179.9,
183.5,
186.2,
189.9,
192.8,
196.6,
199.5, // 40
203.5,
206.5,
210.7,
218.1,
225.7,
229.1,
233.6,
241.8,
250.3,
254.1, // 50
};
const int CTCSSFrequencies::m_nbFreqs = 51;

View File

@@ -0,0 +1,25 @@
///////////////////////////////////////////////////////////////////////////////////
// Copyright (C) 2012 maintech GmbH, Otto-Hahn-Str. 15, 97204 Hoechberg, Germany //
// written by Christian Daniel //
// Copyright (C) 2015-2020 Edouard Griffiths, F4EXB <f4exb06@gmail.com> //
// //
// This program 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 as version 3 of the License, or //
// (at your option) any later version. //
// //
// This program 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 V3 for more details. //
// //
// You should have received a copy of the GNU General Public License //
// along with this program. If not, see <http://www.gnu.org/licenses/>. //
///////////////////////////////////////////////////////////////////////////////////
#include "export.h"
struct SDRBASE_API CTCSSFrequencies {
static const int m_nbFreqs;
static const float m_Freqs[];
};

View File

@@ -0,0 +1,155 @@
///////////////////////////////////////////////////////////////////////////////////
// Copyright (C) 2023 Jon Beniston, M7RCE <jon@beniston.com> //
// //
// This program 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 as version 3 of the License, or //
// (at your option) any later version. //
// //
// This program 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 V3 for more details. //
// //
// You should have received a copy of the GNU General Public License //
// along with this program. If not, see <http://www.gnu.org/licenses/>. //
///////////////////////////////////////////////////////////////////////////////////
#include <QDebug>
#include "dsp/cudavkfftengine.h"
#include "util/profiler.h"
CUDAvkFFTEngine::CUDAvkFFTEngine()
{
VkFFTResult resFFT;
resFFT = gpuInit();
if (resFFT != VKFFT_SUCCESS)
{
qDebug() << "CUDAvkFFTEngine::CUDAvkFFTEngine: Failed to initialise GPU" << getVkFFTErrorString(resFFT);
delete vkGPU;
vkGPU = nullptr;
}
}
CUDAvkFFTEngine::~CUDAvkFFTEngine()
{
if (vkGPU)
{
freeAll();
cuCtxDestroy(vkGPU->context);
}
}
const QString CUDAvkFFTEngine::m_name = "vkFFT (CUDA)";
QString CUDAvkFFTEngine::getName() const
{
return m_name;
}
VkFFTResult CUDAvkFFTEngine::gpuInit()
{
CUresult res = CUDA_SUCCESS;
cudaError_t res2 = cudaSuccess;
res = cuInit(0);
if (res != CUDA_SUCCESS) {
return VKFFT_ERROR_FAILED_TO_INITIALIZE;
}
res2 = cudaSetDevice((int)vkGPU->device_id);
if (res2 != cudaSuccess) {
return VKFFT_ERROR_FAILED_TO_SET_DEVICE_ID;
}
res = cuDeviceGet(&vkGPU->device, (int)vkGPU->device_id);
if (res != CUDA_SUCCESS) {
return VKFFT_ERROR_FAILED_TO_GET_DEVICE;
}
res = cuDevicePrimaryCtxRetain(&vkGPU->context, (int)vkGPU->device);
if (res != CUDA_SUCCESS) {
return VKFFT_ERROR_FAILED_TO_CREATE_CONTEXT;
}
return VKFFT_SUCCESS;
}
VkFFTResult CUDAvkFFTEngine::gpuAllocateBuffers()
{
cudaError_t res;
CUDAPlan *plan = reinterpret_cast<CUDAPlan *>(m_currentPlan);
// Allocate DMA accessible pinned memory, which may be faster than malloc'ed memory
res = cudaHostAlloc(&plan->m_in, sizeof(Complex) * plan->n, cudaHostAllocMapped);
if (res != cudaSuccess) {
return VKFFT_ERROR_FAILED_TO_ALLOCATE;
}
res = cudaHostAlloc(&plan->m_out, sizeof(Complex) * plan->n, cudaHostAllocMapped);
if (res != cudaSuccess) {
return VKFFT_ERROR_FAILED_TO_ALLOCATE;
}
// Allocate GPU memory
res = cudaMalloc((void**)&plan->m_buffer, sizeof(cuFloatComplex) * plan->n * 2);
if (res != cudaSuccess) {
return VKFFT_ERROR_FAILED_TO_ALLOCATE;
}
plan->m_configuration->buffer = (void**)&plan->m_buffer;
return VKFFT_SUCCESS;
}
VkFFTResult CUDAvkFFTEngine::gpuConfigure()
{
return VKFFT_SUCCESS;
}
void CUDAvkFFTEngine::transform()
{
if (m_currentPlan)
{
CUDAPlan *plan = reinterpret_cast<CUDAPlan *>(m_currentPlan);
cudaError_t res = cudaSuccess;
void* buffer = ((void**)&plan->m_buffer)[0];
// Transfer input from CPU to GPU memory
PROFILER_START()
res = cudaMemcpy(buffer, plan->m_in, plan->m_bufferSize, cudaMemcpyHostToDevice);
PROFILER_STOP(QString("%1 TX %2").arg(getName()).arg(m_currentPlan->n))
if (res != cudaSuccess) {
qDebug() << "CUDAvkFFTEngine::transform: cudaMemcpy host to device failed";
}
// Perform FFT
PROFILER_RESTART()
VkFFTLaunchParams launchParams = {};
VkFFTResult resFFT = VkFFTAppend(plan->m_app, plan->m_inverse ? 1 : -1, &launchParams);
PROFILER_STOP(QString("%1 FFT %2").arg(getName()).arg(m_currentPlan->n))
if (resFFT != VKFFT_SUCCESS) {
qDebug() << "CUDAvkFFTEngine::transform: VkFFTAppend failed:" << getVkFFTErrorString(resFFT);
}
// Transfer result from GPU to CPU memory
PROFILER_RESTART()
res = cudaMemcpy(plan->m_out, buffer, plan->m_bufferSize, cudaMemcpyDeviceToHost);
PROFILER_STOP(QString("%1 RX %2").arg(getName()).arg(m_currentPlan->n))
if (res != cudaSuccess) {
qDebug() << "CUDAvkFFTEngine::transform: cudaMemcpy device to host failed";
}
}
}
vkFFTEngine::Plan *CUDAvkFFTEngine::gpuAllocatePlan()
{
return new CUDAPlan();
}
void CUDAvkFFTEngine::gpuDeallocatePlan(Plan *p)
{
CUDAPlan *plan = reinterpret_cast<CUDAPlan *>(p);
cudaFree(plan->m_in);
plan->m_in = nullptr;
cudaFree(plan->m_out);
plan->m_out = nullptr;
cudaFree(plan->m_buffer);
}

View File

@@ -0,0 +1,55 @@
///////////////////////////////////////////////////////////////////////////////////
// Copyright (C) 2012 maintech GmbH, Otto-Hahn-Str. 15, 97204 Hoechberg, Germany //
// written by Christian Daniel //
// Copyright (C) 2015-2019 Edouard Griffiths, F4EXB <f4exb06@gmail.com> //
// Copyright (C) 2021, 2023 Jon Beniston, M7RCE <jon@beniston.com> //
// //
// This program 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 as version 3 of the License, or //
// (at your option) any later version. //
// //
// This program 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 V3 for more details. //
// //
// You should have received a copy of the GNU General Public License //
// along with this program. If not, see <http://www.gnu.org/licenses/>. //
///////////////////////////////////////////////////////////////////////////////////
#ifndef INCLUDE_CUDAVKFFTENGINE_H
#define INCLUDE_CUDAVKFFTENGINE_H
#include "vkfftengine.h"
#include <cuda.h>
#include <cuda_runtime.h>
#include <nvrtc.h>
#include <cuda_runtime_api.h>
#include <cuComplex.h>
class SDRBASE_API CUDAvkFFTEngine : public vkFFTEngine {
public:
CUDAvkFFTEngine();
virtual ~CUDAvkFFTEngine();
void transform() override;
QString getName() const override;
static const QString m_name;
protected:
struct CUDAPlan : Plan {
cuFloatComplex* m_buffer; // GPU memory
};
VkFFTResult gpuInit() override;
VkFFTResult gpuAllocateBuffers() override;
VkFFTResult gpuConfigure() override;
Plan *gpuAllocatePlan() override;
void gpuDeallocatePlan(Plan *) override;
};
#endif // INCLUDE_CUDAVKFFTENGINE_H

View File

@@ -0,0 +1,625 @@
///////////////////////////////////////////////////////////////////////////////////
// Copyright (C) 2016-2019, 2021 Edouard Griffiths, F4EXB <f4exb06@gmail.com> //
// Copyright (C) 2022 Jiří Pinkava <jiri.pinkava@rossum.ai> //
// //
// This program 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 as version 3 of the License, or //
// (at your option) any later version. //
// //
// This program 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 V3 for more details. //
// //
// You should have received a copy of the GNU General Public License //
// along with this program. If not, see <http://www.gnu.org/licenses/>. //
///////////////////////////////////////////////////////////////////////////////////
#include <stdint.h>
#include <QChar>
#include <QDebug>
#include "cwkeyer.h"
#include "util/stepfunctions.h"
MESSAGE_CLASS_DEFINITION(CWKeyer::MsgConfigureCWKeyer, Message)
/**
* 0: dot
* 1: dash
* -1: end of sequence
*/
const signed char CWKeyer::m_asciiToMorse[128][7] = {
{-1,0,0,0,0,0,0}, // 0
{-1,0,0,0,0,0,0}, // 1
{-1,0,0,0,0,0,0}, // 2
{-1,0,0,0,0,0,0}, // 3
{-1,0,0,0,0,0,0}, // 4
{-1,0,0,0,0,0,0}, // 5
{-1,0,0,0,0,0,0}, // 6
{-1,0,0,0,0,0,0}, // 7
{-1,0,0,0,0,0,0}, // 8
{-1,0,0,0,0,0,0}, // 9
{-1,0,0,0,0,0,0}, // 10
{-1,0,0,0,0,0,0}, // 11
{-1,0,0,0,0,0,0}, // 12
{-1,0,0,0,0,0,0}, // 13
{-1,0,0,0,0,0,0}, // 14
{-1,0,0,0,0,0,0}, // 15
{-1,0,0,0,0,0,0}, // 16
{-1,0,0,0,0,0,0}, // 17
{-1,0,0,0,0,0,0}, // 18
{-1,0,0,0,0,0,0}, // 19
{-1,0,0,0,0,0,0}, // 20
{-1,0,0,0,0,0,0}, // 21
{-1,0,0,0,0,0,0}, // 22
{-1,0,0,0,0,0,0}, // 23
{-1,0,0,0,0,0,0}, // 24
{-1,0,0,0,0,0,0}, // 25
{-1,0,0,0,0,0,0}, // 26
{-1,0,0,0,0,0,0}, // 27
{-1,0,0,0,0,0,0}, // 28
{-1,0,0,0,0,0,0}, // 29
{-1,0,0,0,0,0,0}, // 30
{-1,0,0,0,0,0,0}, // 31
{-1,0,0,0,0,0,0}, // 32 space is treated as word separator
{1,0,1,0,1,1,-1}, // 33 !
{0,1,0,0,1,0,-1}, // 34 "
{-1,0,0,0,0,0,0}, // 35
{-1,0,0,0,0,0,0}, // 36
{-1,0,0,0,0,0,0}, // 37
{-1,0,0,0,0,0,0}, // 38
{0,1,1,1,1,0,-1}, // 39 '
{1,0,1,1,0,1,-1}, // 40 (
{1,0,1,1,0,1,-1}, // 41 )
{-1,0,0,0,0,0,0}, // 42
{0,1,0,1,0,-1,0}, // 43 +
{1,1,0,0,1,1,-1}, // 44 ,
{1,0,0,0,0,1,-1}, // 45 -
{0,1,0,1,0,1,-1}, // 46 .
{1,0,0,1,0,-1,0}, // 47 /
{1,1,1,1,1,-1,0}, // 48 0
{0,1,1,1,1,-1,0}, // 49 1
{0,0,1,1,1,-1,0}, // 50 2
{0,0,0,1,1,-1,0}, // 51 3
{0,0,0,0,1,-1,0}, // 52 4
{0,0,0,0,0,-1,0}, // 53 5
{1,0,0,0,0,-1,0}, // 54 6
{1,1,0,0,0,-1,0}, // 55 7
{1,1,1,0,0,-1,0}, // 56 8
{1,1,1,1,0,-1,0}, // 57 9
{1,1,1,0,0,0,-1}, // 58 :
{1,0,1,0,1,0,-1}, // 59 ;
{-1,0,0,0,0,0,0}, // 60 <
{1,0,0,0,1,-1,0}, // 61 =
{-1,0,0,0,0,0,0}, // 62 >
{0,0,1,1,0,0,-1}, // 63 ?
{0,1,1,0,1,0,-1}, // 64 @
{0,1,-1,0,0,0,0}, // 65 A
{1,0,0,0,-1,0,0}, // 66 B
{1,0,1,0,-1,0,0}, // 67 C
{1,0,0,-1,0,0,0}, // 68 D
{0,-1,0,0,0,0,0}, // 69 E
{0,0,1,0,-1,0,0}, // 70 F
{1,1,0,-1,0,0,0}, // 71 G
{0,0,0,0,-1,0,0}, // 72 H
{0,0,-1,0,0,0,0}, // 73 I
{0,1,1,1,-1,0,0}, // 74 J
{1,0,1,-1,0,0,0}, // 75 K
{0,1,0,0,-1,0,0}, // 76 L
{1,1,-1,0,0,0,0}, // 77 M
{1,0,-1,0,0,0,0}, // 78 N
{1,1,1,-1,0,0,0}, // 79 O
{0,1,1,0,-1,0,0}, // 80 P
{1,1,0,1,-1,0,0}, // 81 Q
{0,1,0,-1,0,0,0}, // 82 R
{0,0,0,-1,0,0,0}, // 83 S
{1,-1,0,0,0,0,0}, // 84 T
{0,0,1,-1,0,0,0}, // 85 U
{0,0,0,1,-1,0,0}, // 86 V
{0,1,1,-1,0,0,0}, // 87 W
{1,0,0,1,-1,0,0}, // 88 X
{1,0,1,1,-1,0,0}, // 89 Y
{1,1,0,0,-1,0,0}, // 90 Z
{-1,0,0,0,0,0,0}, // 91 [
{-1,0,0,0,0,0,0}, // 92 back /
{-1,0,0,0,0,0,0}, // 93 ]
{-1,0,0,0,0,0,0}, // 94 ^
{-1,0,0,0,0,0,0}, // 95 _
{-1,0,0,0,0,0,0}, // 96 `
{0,1,-1,0,0,0,0}, // 97 A lowercase same as uppercase
{1,0,0,0,-1,0,0}, // 98 B
{1,0,1,0,-1,0,0}, // 99 C
{1,0,0,-1,0,0,0}, // 100 D
{0,-1,0,0,0,0,0}, // 101 E
{0,0,1,0,-1,0,0}, // 102 F
{1,1,0,-1,0,0,0}, // 103 G
{0,0,0,0,-1,0,0}, // 104 H
{0,0,-1,0,0,0,0}, // 105 I
{0,1,1,1,-1,0,0}, // 106 J
{1,0,1,-1,0,0,0}, // 107 K
{0,1,0,0,-1,0,0}, // 108 L
{1,1,-1,0,0,0,0}, // 109 M
{1,0,-1,0,0,0,0}, // 110 N
{1,1,1,-1,0,0,0}, // 111 O
{0,1,1,0,-1,0,0}, // 112 P
{1,1,0,1,-1,0,0}, // 113 Q
{0,1,0,-1,0,0,0}, // 114 R
{0,0,0,-1,0,0,0}, // 115 S
{1,-1,0,0,0,0,0}, // 116 T
{0,0,1,-1,0,0,0}, // 117 U
{0,0,0,1,-1,0,0}, // 118 V
{0,1,1,-1,0,0,0}, // 119 W
{1,0,0,1,-1,0,0}, // 120 X
{1,0,1,1,-1,0,0}, // 121 Y
{1,1,0,0,-1,0,0}, // 122 Z
{-1,0,0,0,0,0,0}, // 123 {
{-1,0,0,0,0,0,0}, // 124 |
{-1,0,0,0,0,0,0}, // 125 }
{-1,0,0,0,0,0,0}, // 126 ~
{-1,0,0,0,0,0,0}, // 127 DEL
};
CWKeyer::CWKeyer() :
m_textPointer(0),
m_elementPointer(0),
m_samplePointer(0),
m_elementSpace(false),
m_characterSpace(false),
m_key(false),
m_dot(false),
m_dash(false),
m_elementOn(false),
m_asciiChar('\0'),
m_keyIambicState(KeySilent),
m_textState(TextStart)
{
connect(&m_inputMessageQueue, SIGNAL(messageEnqueued()), this, SLOT(handleInputMessages()));
applySettings(m_settings, true);
}
CWKeyer::~CWKeyer()
{
}
void CWKeyer::setSampleRate(int sampleRate)
{
CWKeyerSettings settings = m_settings;
settings.m_sampleRate = sampleRate;
MsgConfigureCWKeyer *msg = MsgConfigureCWKeyer::create(settings, false);
m_inputMessageQueue.push(msg);
}
int CWKeyer::getSample()
{
QMutexLocker mutexLocker(&m_mutex);
if (m_settings.m_mode == CWKeyerSettings::CWText)
{
nextStateText();
return m_key ? 1 : 0;
}
else if ((m_settings.m_mode == CWKeyerSettings::CWDots) || (m_settings.m_mode == CWKeyerSettings::CWDashes))
{
nextStateIambic();
return m_key ? 1 : 0;
}
else if (m_settings.m_mode == CWKeyerSettings::CWKeyboard)
{
if (m_settings.m_keyboardIambic)
{
nextStateIambic();
return m_key ? 1 : 0;
}
else
{
return (m_dot || m_dash) ? 1 : 0;
}
}
else
{
return 0;
}
}
void CWKeyer::nextStateIambic()
{
switch (m_keyIambicState)
{
case KeySilent:
if (m_dot)
{
m_keyIambicState = KeyDot;
m_samplePointer = 0;
}
else if (m_dash)
{
m_keyIambicState = KeyDash;
m_samplePointer = 0;
}
m_key = false;
break;
case KeyDot:
if (m_samplePointer < m_dotLength) // dot key
{
m_key = true;
m_samplePointer++;
}
else if (m_samplePointer < 2*m_dotLength) // dot silence (+1 dot length)
{
m_key = false;
m_samplePointer++;
}
else // end
{
if (m_dash)
{
m_keyIambicState = KeyDash;
}
else if (!m_dot)
{
m_keyIambicState = KeySilent;
}
m_samplePointer = 0;
m_key = false;
}
break;
case KeyDash:
if (m_samplePointer < 3*m_dotLength) // dash key
{
m_key = true;
m_samplePointer++;
}
else if (m_samplePointer < 4*m_dotLength) // dash silence (+1 dot length)
{
m_key = false;
m_samplePointer++;
}
else // end
{
if (m_dot)
{
m_keyIambicState = KeyDot;
}
else if (!m_dash)
{
m_keyIambicState = KeySilent;
}
m_samplePointer = 0;
m_key = false;
}
break;
default:
m_samplePointer = 0;
m_key = false;
break;
}
}
void CWKeyer::nextStateText()
{
switch (m_textState)
{
case TextStart:
m_samplePointer = 0;
m_elementPointer = 0;
m_textPointer = 0;
m_textState = TextStartChar;
m_key = false;
m_dot = false;
m_dash = false;
break;
case TextStartChar:
m_samplePointer = 0;
m_elementPointer = 0;
if (m_textPointer < m_settings.m_text.length())
{
m_asciiChar = (m_settings.m_text.at(m_textPointer)).toLatin1();
// qDebug() << "CWKeyer::nextStateText: TextStartChar: " << m_asciiChar;
if (m_asciiChar < 0) { // non ASCII
m_asciiChar = 0;
}
if (m_asciiChar == ' ')
{
m_textState = TextWordSpace;
}
else
{
m_textState = TextStartElement;
}
m_textPointer++;
}
else // end of text
{
m_textState = TextEnd;
}
break;
case TextStartElement:
m_samplePointer = 0;
// qDebug() << "CWKeyer::nextStateText: TextStartElement: " << (int) m_asciiToMorse[m_asciiChar][m_elementPointer];
if (m_asciiToMorse[(uint8_t)(m_asciiChar&0x7F)][m_elementPointer] == -1) // end of morse character
{
m_elementPointer = 0;
m_textState = TextCharSpace;
}
else
{
if (m_asciiToMorse[(uint8_t)(m_asciiChar&0x7F)][m_elementPointer] == 0) // dot
{
m_dot = true;
m_dash = false;
}
else // dash
{
m_dot = false;
m_dash = true;
}
m_keyIambicState = KeySilent; // reset iambic state
nextStateIambic(); // init dash or dot
m_dot = false; // release keys
m_dash = false;
m_textState = TextElement;
m_elementPointer++;
}
break;
case TextElement:
nextStateIambic(); // dash or dot
if (m_keyIambicState == KeySilent) // done
{
m_textState = TextStartElement; // next element
}
break;
case TextCharSpace:
if (m_samplePointer < 2*m_dotLength) // - 1 dot length space from element
{
m_samplePointer++;
m_key = false;
}
else
{
m_textState = TextStartChar;
}
break;
case TextWordSpace:
if (m_samplePointer < 4*m_dotLength) // - 3 dot length space from character
{
m_samplePointer++;
m_key = false;
}
else
{
m_textState = TextStartChar;
}
break;
case TextEnd:
if (m_settings.m_loop)
{
m_textState = TextStart;
}
m_key = false;
m_dot = false;
m_dash = false;
break;
case TextStop:
default:
m_key = false;
m_dot = false;
m_dash = false;
break;
}
}
bool CWKeyer::eom()
{
return !(m_textPointer < m_settings.m_text.length());
}
void CWKeyer::setKeyboardDots()
{
m_dot = true;
m_dash = false;
m_keyIambicState = KeySilent;
}
void CWKeyer::setKeyboardDashes()
{
m_dot = false;
m_dash = true;
m_keyIambicState = KeySilent;
}
void CWKeyer::setKeyboardSilence()
{
m_dot = false;
m_dash = false;
}
CWSmoother::CWSmoother() :
m_fadeInCounter(0),
m_fadeOutCounter(0),
m_nbFadeSamples(0),
m_fadeInSamples(0),
m_fadeOutSamples(0)
{
setNbFadeSamples(192); // default is 4 ms at 48 kHz sample rate
}
CWSmoother::~CWSmoother()
{
delete[] m_fadeInSamples;
delete[] m_fadeOutSamples;
}
void CWSmoother::setNbFadeSamples(unsigned int nbFadeSamples)
{
if (nbFadeSamples != m_nbFadeSamples)
{
QMutexLocker mutexLocker(&m_mutex);
m_nbFadeSamples = nbFadeSamples;
if (m_fadeInSamples) delete[] m_fadeInSamples;
if (m_fadeOutSamples) delete[] m_fadeOutSamples;
m_fadeInSamples = new float[m_nbFadeSamples];
m_fadeOutSamples = new float[m_nbFadeSamples];
for (unsigned int i = 0; i < m_nbFadeSamples; i++)
{
float x = i/ (float) m_nbFadeSamples;
float y = 1.0f -x;
m_fadeInSamples[i] = StepFunctions::smootherstep(x);
m_fadeOutSamples[i] = StepFunctions::smootherstep(y);
}
m_fadeInCounter = 0;
m_fadeOutCounter = 0;
}
}
bool CWSmoother::getFadeSample(bool on, float& sample)
{
QMutexLocker mutexLocker(&m_mutex);
if (on)
{
m_fadeOutCounter = 0;
if (m_fadeInCounter < m_nbFadeSamples)
{
sample = m_fadeInSamples[m_fadeInCounter];
m_fadeInCounter++;
return true;
}
else
{
sample = 1.0f;
return false;
}
}
else
{
m_fadeInCounter = 0;
if (m_fadeOutCounter < m_nbFadeSamples)
{
sample = m_fadeOutSamples[m_fadeOutCounter];
m_fadeOutCounter++;
return true;
}
else
{
sample = 0.0f;
return false;
}
}
}
bool CWKeyer::handleMessage(const Message& cmd)
{
if (MsgConfigureCWKeyer::match(cmd))
{
MsgConfigureCWKeyer& cfg = (MsgConfigureCWKeyer&) cmd;
qDebug() << "CWKeyer::handleMessage: MsgConfigureCWKeyer";
applySettings(cfg.getSettings(), cfg.getForce());
return true;
}
return true;
}
void CWKeyer::handleInputMessages()
{
Message* message;
while ((message = m_inputMessageQueue.pop()) != 0)
{
if (handleMessage(*message)) {
delete message;
}
}
}
void CWKeyer::applySettings(const CWKeyerSettings& settings, bool force)
{
qDebug() << "CWKeyer::applySettings: "
<< " m_dashKey: " << settings.m_dashKey
<< " m_dashKeyModifiers: " << settings.m_dashKeyModifiers
<< " m_dotKey: " << settings.m_dotKey
<< " m_dotKeyModifiers: " << settings.m_dotKeyModifiers
<< " m_keyboardIambic: " << settings.m_keyboardIambic
<< " m_loop: " << settings.m_loop
<< " m_mode: " << settings.m_mode
<< " m_sampleRate: " << settings.m_sampleRate
<< " m_text: " << settings.m_text
<< " m_wpm: " << settings.m_wpm;
if ((m_settings.m_wpm != settings.m_wpm)
|| (m_settings.m_sampleRate != settings.m_sampleRate) || force)
{
QMutexLocker mutexLocker(&m_mutex);
m_dotLength = (int) ((1.2f / settings.m_wpm) * settings.m_sampleRate);
m_cwSmoother.setNbFadeSamples(m_dotLength/10); // 10% the dot time
}
if ((m_settings.m_mode != settings.m_mode) || force)
{
QMutexLocker mutexLocker(&m_mutex);
if (settings.m_mode == CWKeyerSettings::CWText)
{
m_textState = TextStart;
}
else if (settings.m_mode == CWKeyerSettings::CWDots)
{
m_dot = true;
m_dash = false;
m_keyIambicState = KeySilent;
}
else if (settings.m_mode == CWKeyerSettings::CWDashes)
{
m_dot = false;
m_dash = true;
m_keyIambicState = KeySilent;
}
else if (settings.m_mode == CWKeyerSettings::CWKeyboard)
{
m_dot = false;
m_dash = false;
m_keyIambicState = KeySilent;
}
}
if ((m_settings.m_text != settings.m_text) || force)
{
QMutexLocker mutexLocker(&m_mutex);
m_textState = TextStart;
}
m_settings = settings;
}
void CWKeyer::webapiSettingsPutPatch(
const QStringList& channelSettingsKeys,
CWKeyerSettings& cwKeyerSettings,
SWGSDRangel::SWGCWKeyerSettings *apiCwKeyerSettings
)
{
cwKeyerSettings.updateFrom(channelSettingsKeys, apiCwKeyerSettings);
}
void CWKeyer::webapiFormatChannelSettings(
SWGSDRangel::SWGCWKeyerSettings *apiCwKeyerSettings,
const CWKeyerSettings& cwKeyerSettings
)
{
cwKeyerSettings.formatTo(apiCwKeyerSettings);
}

View File

@@ -0,0 +1,163 @@
///////////////////////////////////////////////////////////////////////////////////
// Copyright (C) 2012 maintech GmbH, Otto-Hahn-Str. 15, 97204 Hoechberg, Germany //
// written by Christian Daniel //
// Copyright (C) 2015-2019 Edouard Griffiths, F4EXB <f4exb06@gmail.com> //
// Copyright (C) 2022 Jiří Pinkava <jiri.pinkava@rossum.ai> //
// //
// This program 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 as version 3 of the License, or //
// (at your option) any later version. //
// //
// This program 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 V3 for more details. //
// //
// You should have received a copy of the GNU General Public License //
// along with this program. If not, see <http://www.gnu.org/licenses/>. //
///////////////////////////////////////////////////////////////////////////////////
#ifndef SDRBASE_DSP_CWKEYER_H_
#define SDRBASE_DSP_CWKEYER_H_
#include <QObject>
#include <QRecursiveMutex>
#include "export.h"
#include "util/message.h"
#include "util/messagequeue.h"
#include "cwkeyersettings.h"
#include "SWGChannelSettings.h"
/**
* Ancillary class to smooth out CW transitions with a sine shape
*/
class SDRBASE_API CWSmoother
{
public:
CWSmoother();
~CWSmoother();
void setNbFadeSamples(unsigned int nbFadeSamples);
bool getFadeSample(bool on, float& sample);
private:
QRecursiveMutex m_mutex;
unsigned int m_fadeInCounter;
unsigned int m_fadeOutCounter;
unsigned int m_nbFadeSamples;
float *m_fadeInSamples;
float *m_fadeOutSamples;
};
class SDRBASE_API CWKeyer : public QObject {
Q_OBJECT
public:
class SDRBASE_API MsgConfigureCWKeyer : public Message {
MESSAGE_CLASS_DECLARATION
public:
const CWKeyerSettings& getSettings() const { return m_settings; }
bool getForce() const { return m_force; }
static MsgConfigureCWKeyer* create(const CWKeyerSettings& settings, bool force)
{
return new MsgConfigureCWKeyer(settings, force);
}
private:
CWKeyerSettings m_settings;
bool m_force;
MsgConfigureCWKeyer(const CWKeyerSettings& settings, bool force) :
Message(),
m_settings(settings),
m_force(force)
{ }
};
enum CWKeyIambicState
{
KeySilent,
KeyDot,
KeyDash
};
enum CWTextState
{
TextStart,
TextStartChar,
TextStartElement,
TextElement,
TextCharSpace,
TextWordSpace,
TextEnd,
TextStop
};
CWKeyer();
~CWKeyer();
void resetToDefaults();
QByteArray serialize() const;
bool deserialize(const QByteArray& data);
MessageQueue *getInputMessageQueue() { return &m_inputMessageQueue; } //!< Get the queue for asynchronous inbound communication
void setSampleRate(int sampleRate);
const CWKeyerSettings& getSettings() const { return m_settings; }
void reset() { m_keyIambicState = KeySilent; }
CWSmoother& getCWSmoother() { return m_cwSmoother; }
int getSample();
bool eom();
void resetText() { m_textState = TextStart; }
void stopText() { m_textState = TextStop; }
void setKeyboardDots();
void setKeyboardDashes();
void setKeyboardSilence();
static void webapiSettingsPutPatch(
const QStringList& channelSettingsKeys,
CWKeyerSettings& cwKeyerSettings,
SWGSDRangel::SWGCWKeyerSettings *apiCwKeyerSettings
);
static void webapiFormatChannelSettings(
SWGSDRangel::SWGCWKeyerSettings *apiCwKeyerSettings,
const CWKeyerSettings& cwKeyerSettings
);
private:
QRecursiveMutex m_mutex;
CWKeyerSettings m_settings;
MessageQueue m_inputMessageQueue;
int m_dotLength; //!< dot length in samples
int m_textPointer;
int m_elementPointer;
int m_samplePointer;
bool m_elementSpace;
bool m_characterSpace;
bool m_key;
bool m_dot;
bool m_dash;
bool m_elementOn;
signed char m_asciiChar;
CWKeyIambicState m_keyIambicState;
CWTextState m_textState;
CWSmoother m_cwSmoother;
static const signed char m_asciiToMorse[128][7];
void applySettings(const CWKeyerSettings& settings, bool force = false);
bool handleMessage(const Message& cmd);
void nextStateIambic();
void nextStateText();
private slots:
void handleInputMessages();
};
#endif /* SDRBASE_DSP_CWKEYER_H_ */

View File

@@ -0,0 +1,158 @@
///////////////////////////////////////////////////////////////////////////////////
// Copyright (C) 2012 maintech GmbH, Otto-Hahn-Str. 15, 97204 Hoechberg, Germany //
// written by Christian Daniel //
// Copyright (C) 2015-2017, 2019, 2021 Edouard Griffiths, F4EXB <f4exb06@gmail.com> //
// //
// This program 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 as version 3 of the License, or //
// (at your option) any later version. //
// //
// This program 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 V3 for more details. //
// //
// You should have received a copy of the GNU General Public License //
// along with this program. If not, see <http://www.gnu.org/licenses/>. //
///////////////////////////////////////////////////////////////////////////////////
#include "SWGCWKeyerSettings.h"
#include "util/simpleserializer.h"
#include "cwkeyersettings.h"
CWKeyerSettings::CWKeyerSettings()
{
resetToDefaults();
}
void CWKeyerSettings::resetToDefaults()
{
m_loop = false;
m_mode = CWNone;
m_sampleRate = 48000;
m_text = "";
m_wpm = 13;
m_keyboardIambic = true;
m_dotKey = Qt::Key_Period;
m_dotKeyModifiers = Qt::NoModifier;
m_dashKey = Qt::Key_Minus;
m_dashKeyModifiers = Qt::NoModifier;
}
QByteArray CWKeyerSettings::serialize() const
{
SimpleSerializer s(1);
s.writeBool(2, m_loop);
s.writeS32(3, (int) m_mode);
s.writeS32(4, m_sampleRate);
s.writeString(5, m_text);
s.writeS32(6, m_wpm);
s.writeS32(7, (int) m_dotKey);
s.writeU32(8, (unsigned int) m_dotKeyModifiers);
s.writeS32(9, (int) m_dashKey);
s.writeU32(10, (unsigned int) m_dashKeyModifiers);
s.writeBool(11, m_keyboardIambic);
return s.final();
}
bool CWKeyerSettings::deserialize(const QByteArray& data)
{
SimpleDeserializer d(data);
if (!d.isValid())
{
resetToDefaults();
return false;
}
if (d.getVersion() == 1)
{
int intval;
unsigned int uintval;
d.readBool(2, &m_loop, false);
d.readS32(3, &intval, 0);
m_mode = (CWMode) intval;
d.readS32(4, &m_sampleRate, 48000);
d.readString(5, &m_text, "");
d.readS32(6, &m_wpm, 13);
d.readS32(7, &intval, (int) Qt::Key_Period);
m_dotKey = (Qt::Key) (intval < 0 ? 0 : intval);
d.readU32(8, &uintval, 0);
m_dotKeyModifiers = (Qt::KeyboardModifiers) uintval;
d.readS32(9, &intval, (int) Qt::Key_Minus);
m_dashKey = (Qt::Key) (intval < 0 ? 0 : intval);
d.readU32(10, &uintval, 0);
m_dashKeyModifiers = (Qt::KeyboardModifiers) uintval;
d.readBool(11, &m_keyboardIambic, true);
return true;
}
else
{
resetToDefaults();
return false;
}
}
void CWKeyerSettings::formatTo(SWGSDRangel::SWGObject *swgObject) const
{
SWGSDRangel::SWGCWKeyerSettings *apiCwKeyerSettings = static_cast<SWGSDRangel::SWGCWKeyerSettings *>(swgObject);
apiCwKeyerSettings->setLoop(m_loop ? 1 : 0);
apiCwKeyerSettings->setMode((int) m_mode);
apiCwKeyerSettings->setSampleRate(m_sampleRate);
if (apiCwKeyerSettings->getText()) {
*apiCwKeyerSettings->getText() = m_text;
} else {
apiCwKeyerSettings->setText(new QString(m_text));
}
apiCwKeyerSettings->setWpm(m_wpm);
apiCwKeyerSettings->setKeyboardIambic(m_keyboardIambic ? 1 : 0);
apiCwKeyerSettings->setDotKey((int) m_dotKey);
apiCwKeyerSettings->setDotKeyModifiers((unsigned int) m_dotKeyModifiers);
apiCwKeyerSettings->setDashKey((int) m_dashKey);
apiCwKeyerSettings->setDashKeyModifiers((unsigned int) m_dashKeyModifiers);
}
void CWKeyerSettings::updateFrom(const QStringList& keys, const SWGSDRangel::SWGObject *swgObject)
{
SWGSDRangel::SWGCWKeyerSettings *apiCwKeyerSettings =
static_cast<SWGSDRangel::SWGCWKeyerSettings *>(const_cast<SWGSDRangel::SWGObject *>(swgObject));
if (keys.contains("cwKeyer.loop")) {
m_loop = apiCwKeyerSettings->getLoop() != 0;
}
if (keys.contains("cwKeyer.mode")) {
m_mode = (CWKeyerSettings::CWMode) apiCwKeyerSettings->getMode();
}
if (keys.contains("cwKeyer.text")) {
m_text = *apiCwKeyerSettings->getText();
}
if (keys.contains("cwKeyer.sampleRate")) {
m_sampleRate = apiCwKeyerSettings->getSampleRate();
}
if (keys.contains("cwKeyer.wpm")) {
m_wpm = apiCwKeyerSettings->getWpm();
}
if (keys.contains("cwKeyer.keyboardIambic")) {
m_keyboardIambic = apiCwKeyerSettings->getKeyboardIambic() != 0;
}
if (keys.contains("cwKeyer.dotKey")) {
m_dotKey = (Qt::Key) apiCwKeyerSettings->getDotKey();
}
if (keys.contains("cwKeyer.dotKeyModifiers")) {
m_dotKeyModifiers = (Qt::KeyboardModifiers) apiCwKeyerSettings->getDotKeyModifiers();
}
if (keys.contains("cwKeyer.dashKey")) {
m_dashKey = (Qt::Key) apiCwKeyerSettings->getDashKey();
}
if (keys.contains("cwKeyer.dashKeyModifiers")) {
m_dashKeyModifiers = (Qt::KeyboardModifiers) apiCwKeyerSettings->getDashKeyModifiers();
}
}

View File

@@ -0,0 +1,63 @@
///////////////////////////////////////////////////////////////////////////////////
// Copyright (C) 2012 maintech GmbH, Otto-Hahn-Str. 15, 97204 Hoechberg, Germany //
// written by Christian Daniel //
// Copyright (C) 2016-2019, 2021 Edouard Griffiths, F4EXB <f4exb06@gmail.com> //
// //
// This program 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 as version 3 of the License, or //
// (at your option) any later version. //
// //
// This program 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 V3 for more details. //
// //
// You should have received a copy of the GNU General Public License //
// along with this program. If not, see <http://www.gnu.org/licenses/>. //
///////////////////////////////////////////////////////////////////////////////////
#ifndef SDRBASE_DSP_CWKEYERSETTINGS_H_
#define SDRBASE_DSP_CWKEYERSETTINGS_H_
#include <QString>
#include <QByteArray>
#include "export.h"
#include "settings/serializable.h"
class SDRBASE_API CWKeyerSettings: public Serializable
{
public:
typedef enum
{
CWNone,
CWText,
CWDots,
CWDashes,
CWKeyboard
} CWMode;
bool m_loop;
CWMode m_mode;
int m_sampleRate;
QString m_text;
int m_wpm;
bool m_keyboardIambic;
Qt::Key m_dotKey;
Qt::KeyboardModifiers m_dotKeyModifiers;
Qt::Key m_dashKey;
Qt::KeyboardModifiers m_dashKeyModifiers;
CWKeyerSettings();
void resetToDefaults();
QByteArray serialize() const;
bool deserialize(const QByteArray& data);
virtual void formatTo(SWGSDRangel::SWGObject *swgObject) const;
virtual void updateFrom(const QStringList& keys, const SWGSDRangel::SWGObject *swgObject);
};
#endif /* SDRBASE_DSP_CWKEYERSETTINGS_H_ */

View File

@@ -0,0 +1,314 @@
///////////////////////////////////////////////////////////////////////////////////
// Copyright (C) 2012 maintech GmbH, Otto-Hahn-Str. 15, 97204 Hoechberg, Germany //
// written by Christian Daniel //
// Copyright (C) 2016, 2018-2022 Edouard Griffiths, F4EXB <f4exb06@gmail.com> //
// Copyright (C) 2022 Jiří Pinkava <jiri.pinkava@rossum.ai> //
// //
// This program 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 as version 3 of the License, or //
// (at your option) any later version. //
// //
// This program 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 V3 for more details. //
// //
// You should have received a copy of the GNU General Public License //
// along with this program. If not, see <http://www.gnu.org/licenses/>. //
///////////////////////////////////////////////////////////////////////////////////
#include "datafifo.h"
void DataFifo::create(unsigned int s)
{
m_size = 0;
m_fill = 0;
m_head = 0;
m_tail = 0;
m_data.resize(s);
m_size = m_data.size();
}
void DataFifo::reset()
{
QMutexLocker mutexLocker(&m_mutex);
m_suppressed = -1;
m_fill = 0;
m_head = 0;
m_tail = 0;
}
DataFifo::DataFifo(QObject* parent) :
QObject(parent),
m_data(),
m_currentDataType(DataTypeI16)
{
setObjectName("DataFifo");
m_suppressed = -1;
m_size = 0;
m_fill = 0;
m_head = 0;
m_tail = 0;
}
DataFifo::DataFifo(int size, QObject* parent) :
QObject(parent),
m_data(),
m_currentDataType(DataTypeI16)
{
setObjectName("DataFifo");
m_suppressed = -1;
create(size);
}
DataFifo::DataFifo(const DataFifo& other) :
QObject(other.parent()),
m_data(other.m_data),
m_currentDataType(DataTypeI16)
{
setObjectName("DataFifo");
m_suppressed = -1;
m_size = m_data.size();
m_fill = 0;
m_head = 0;
m_tail = 0;
}
DataFifo::~DataFifo()
{
QMutexLocker mutexLocker(&m_mutex);
m_size = 0;
}
bool DataFifo::setSize(int size)
{
QMutexLocker mutexLocker(&m_mutex);
create(size);
return m_data.size() == size;
}
unsigned int DataFifo::write(const quint8* data, unsigned int count, DataType dataType)
{
QMutexLocker mutexLocker(&m_mutex);
if (dataType != m_currentDataType)
{
m_suppressed = -1;
m_fill = 0;
m_head = 0;
m_tail = 0;
m_currentDataType = dataType;
}
unsigned int total;
unsigned int remaining;
unsigned int len;
const quint8* begin = (const quint8*) data;
//count /= sizeof(Sample);
total = std::min(count, m_size - m_fill);
if (total < count)
{
if (m_suppressed < 0)
{
m_suppressed = 0;
m_msgRateTimer.start();
qCritical("DataFifo::write: overflow - dropping %u samples (size=%u)", count - total, m_size);
}
else
{
if (m_msgRateTimer.elapsed() > 2500)
{
qCritical("DataFifo::write: %u messages dropped", m_suppressed);
qCritical("DataFifo::write: overflow - dropping %u samples (size=%u)", count - total, m_size);
m_suppressed = -1;
}
else
{
m_suppressed++;
}
}
}
remaining = total;
while (remaining > 0)
{
len = std::min(remaining, m_size - m_tail);
std::copy(begin, begin + len, m_data.begin() + m_tail);
m_tail += len;
m_tail %= m_size;
m_fill += len;
begin += len;
remaining -= len;
}
if (m_fill > 0) {
emit dataReady();
}
return total;
}
unsigned int DataFifo::write(QByteArray::const_iterator begin, QByteArray::const_iterator end, DataType dataType)
{
QMutexLocker mutexLocker(&m_mutex);
if (dataType != m_currentDataType)
{
m_suppressed = -1;
m_fill = 0;
m_head = 0;
m_tail = 0;
m_currentDataType = dataType;
}
unsigned int count = end - begin;
unsigned int total;
unsigned int remaining;
unsigned int len;
total = std::min(count, m_size - m_fill);
if (total < count)
{
if (m_suppressed < 0)
{
m_suppressed = 0;
m_msgRateTimer.start();
qCritical("DataFifo::write: overflow - dropping %u samples", count - total);
}
else
{
if (m_msgRateTimer.elapsed() > 2500)
{
qCritical("DataFifo::write: %u messages dropped", m_suppressed);
qCritical("DataFifo::write: overflow - dropping %u samples", count - total);
m_suppressed = -1;
}
else
{
m_suppressed++;
}
}
}
remaining = total;
while (remaining > 0)
{
len = std::min(remaining, m_size - m_tail);
std::copy(begin, begin + len, m_data.begin() + m_tail);
m_tail += len;
m_tail %= m_size;
m_fill += len;
begin += len;
remaining -= len;
}
if (m_fill > 0) {
emit dataReady();
}
return total;
}
unsigned int DataFifo::read(QByteArray::iterator begin, QByteArray::iterator end, DataType& dataType)
{
QMutexLocker mutexLocker(&m_mutex);
dataType = m_currentDataType;
unsigned int count = end - begin;
unsigned int total;
unsigned int remaining;
unsigned int len;
total = std::min(count, m_fill);
if (total < count) {
qCritical("DataFifo::read: underflow - missing %u samples", count - total);
}
remaining = total;
while (remaining > 0)
{
len = std::min(remaining, m_size - m_head);
std::copy(m_data.begin() + m_head, m_data.begin() + m_head + len, begin);
m_head += len;
m_head %= m_size;
m_fill -= len;
begin += len;
remaining -= len;
}
return total;
}
unsigned int DataFifo::readBegin(unsigned int count,
QByteArray::iterator* part1Begin, QByteArray::iterator* part1End,
QByteArray::iterator* part2Begin, QByteArray::iterator* part2End,
DataType& dataType)
{
QMutexLocker mutexLocker(&m_mutex);
dataType = m_currentDataType;
unsigned int total;
unsigned int remaining;
unsigned int len;
unsigned int head = m_head;
total = std::min(count, m_fill);
if (total < count) {
qCritical("DataFifo::readBegin: underflow - missing %u samples", count - total);
}
remaining = total;
if (remaining > 0)
{
len = std::min(remaining, m_size - head);
*part1Begin = m_data.begin() + head;
*part1End = m_data.begin() + head + len;
head += len;
head %= m_size;
remaining -= len;
}
else
{
*part1Begin = m_data.end();
*part1End = m_data.end();
}
if (remaining > 0)
{
len = std::min(remaining, m_size - head);
*part2Begin = m_data.begin() + head;
*part2End = m_data.begin() + head + len;
}
else
{
*part2Begin = m_data.end();
*part2End = m_data.end();
}
return total;
}
unsigned int DataFifo::readCommit(unsigned int count)
{
QMutexLocker mutexLocker(&m_mutex);
if (count > m_fill)
{
qCritical("DataFifo::readCommit: cannot commit more than available samples");
count = m_fill;
}
m_head = (m_head + count) % m_size;
m_fill -= count;
return count;
}

View File

@@ -0,0 +1,79 @@
///////////////////////////////////////////////////////////////////////////////////
// Copyright (C) 2012 maintech GmbH, Otto-Hahn-Str. 15, 97204 Hoechberg, Germany //
// written by Christian Daniel //
// Copyright (C) 2015-2016, 2018-2021 Edouard Griffiths, F4EXB <f4exb06@gmail.com> //
// Copyright (C) 2022 Jiří Pinkava <jiri.pinkava@rossum.ai> //
// //
// This program 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 as version 3 of the License, or //
// (at your option) any later version. //
// //
// This program 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 V3 for more details. //
// //
// You should have received a copy of the GNU General Public License //
// along with this program. If not, see <http://www.gnu.org/licenses/>. //
///////////////////////////////////////////////////////////////////////////////////
#ifndef INCLUDE_DATAFIFO_H
#define INCLUDE_DATAFIFO_H
#include <QObject>
#include <QRecursiveMutex>
#include <QElapsedTimer>
#include <QByteArray>
#include "export.h"
class SDRBASE_API DataFifo : public QObject {
Q_OBJECT
public:
enum DataType
{
DataTypeI16, //!< 16 bit signed integer
DataTypeCI16 //!< Complex (i.e. Re, Im pair of) 16 bit signed integer
};
DataFifo(QObject* parent = nullptr);
DataFifo(int size, QObject* parent = nullptr);
DataFifo(const DataFifo& other);
~DataFifo();
bool setSize(int size);
void reset();
inline unsigned int size() const { return m_size; }
inline unsigned int fill() { QMutexLocker mutexLocker(&m_mutex); unsigned int fill = m_fill; return fill; }
unsigned int write(const quint8* data, unsigned int count, DataType dataType);
unsigned int write(QByteArray::const_iterator begin, QByteArray::const_iterator end, DataType dataType);
unsigned int read(QByteArray::iterator begin, QByteArray::iterator end, DataType& dataType);
unsigned int readBegin(unsigned int count,
QByteArray::iterator* part1Begin, QByteArray::iterator* part1End,
QByteArray::iterator* part2Begin, QByteArray::iterator* part2End,
DataType& daaType);
unsigned int readCommit(unsigned int count);
signals:
void dataReady();
private:
QElapsedTimer m_msgRateTimer;
int m_suppressed;
QByteArray m_data;
DataType m_currentDataType;
QRecursiveMutex m_mutex;
unsigned int m_size;
unsigned int m_fill;
unsigned int m_head;
unsigned int m_tail;
void create(unsigned int s);
};
#endif // INCLUDE_DATAFIFO_H

View File

@@ -0,0 +1,465 @@
//////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Copyright (C) 2021 Edouard Griffiths, F4EXB <f4exb06@gmail.com> //
// //
// This program 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 as version 3 of the License, or //
// (at your option) any later version. //
// //
// This program 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 V3 for more details. //
// //
// You should have received a copy of the GNU General Public License //
// along with this program. If not, see <http://www.gnu.org/licenses/>. //
// //
// Source: http://onfreq.com/syntorx/dcs.html //
// //
//////////////////////////////////////////////////////////////////////////////////////////////////////////////
#include "dcscodes.h"
const QMap<unsigned int, unsigned int> DCSCodes::m_toCanonicalCode {
{0023, 0023},
{0340, 0023},
{0766, 0023},
{0025, 0025},
{0026, 0026},
{0566, 0026},
{0031, 0031},
{0374, 0031},
{0643, 0031},
{0032, 0032},
{0036, 0036},
{0137, 0036},
{0043, 0043},
{0355, 0043},
{0047, 0047},
{0375, 0047},
{0707, 0047},
{0051, 0051},
{0771, 0051},
{0520, 0051},
{0053, 0053},
{0054, 0054},
{0405, 0054},
{0675, 0054},
{0065, 0065},
{0301, 0065},
{0071, 0071},
{0603, 0071},
{0717, 0071},
{0746, 0071},
{0072, 0072},
{0470, 0072},
{0701, 0072},
{0073, 0073},
{0640, 0073},
{0074, 0074},
{0360, 0074},
{0721, 0074},
{0112, 0112},
{0250, 0112},
{0505, 0112},
{0512, 0112},
{0114, 0114},
{0327, 0114},
{0615, 0114},
{0115, 0115},
{0534, 0115},
{0674, 0115},
{0116, 0116},
{0060, 0116},
{0737, 0116},
{0122, 0122},
{0535, 0125},
{0125, 0125},
{0173, 0125},
{0131, 0131},
{0572, 0131},
{0702, 0131},
{0132, 0132},
{0605, 0132},
{0634, 0132},
{0714, 0132},
{0134, 0134},
{0273, 0134},
{0143, 0143},
{0333, 0143},
{0145, 0145},
{0525, 0145},
{0152, 0152},
{0366, 0152},
{0415, 0152},
{0155, 0155},
{0233, 0155},
{0660, 0155},
{0156, 0156},
{0517, 0156},
{0741, 0156},
{0162, 0162},
{0416, 0162},
{0553, 0162},
{0165, 0165},
{0354, 0165},
{0172, 0172},
{0057, 0172},
{0174, 0174},
{0142, 0174},
{0270, 0174},
{0205, 0205},
{0135, 0205},
{0610, 0205},
{0212, 0212},
{0253, 0212},
{0223, 0223},
{0350, 0223},
{0475, 0223},
{0750, 0223},
{0225, 0225},
{0536, 0225},
{0226, 0226},
{0104, 0226},
{0557, 0226},
{0243, 0243},
{0267, 0243},
{0342, 0243},
{0244, 0244},
{0176, 0244},
{0417, 0244},
{0245, 0245},
{0370, 0245},
{0246, 0246},
{0542, 0246},
{0653, 0246},
{0554, 0245},
{0251, 0251},
{0236, 0251},
{0704, 0251},
{0742, 0251},
{0252, 0252},
{0661, 0252},
{0255, 0255},
{0425, 0255},
{0261, 0261},
{0227, 0261},
{0567, 0261},
{0263, 0263},
{0213, 0263},
{0736, 0263},
{0265, 0265},
{0171, 0265},
{0426, 0265},
{0266, 0266},
{0655, 0266},
{0271, 0271},
{0427, 0271},
{0510, 0271},
{0762, 0271},
{0274, 0274},
{0652, 0274},
{0306, 0306},
{0147, 0306},
{0303, 0306},
{0761, 0306},
{0311, 0311},
{0330, 0311},
{0456, 0311},
{0561, 0311},
{0315, 0315},
{0321, 0315},
{0673, 0315},
{0325, 0325},
{0550, 0325},
{0626, 0325},
{0331, 0331},
{0372, 0331},
{0507, 0331},
{0332, 0332},
{0433, 0332},
{0552, 0332},
{0343, 0343},
{0324, 0343},
{0570, 0343},
{0346, 0346},
{0616, 0346},
{0635, 0346},
{0724, 0346},
{0351, 0351},
{0353, 0351},
{0435, 0351},
{0356, 0356},
{0521, 0356},
{0364, 0364},
{0130, 0364},
{0641, 0364},
{0365, 0365},
{0107, 0365},
{0371, 0371},
{0217, 0371},
{0453, 0371},
{0530, 0371},
{0411, 0411},
{0117, 0411},
{0756, 0411},
{0412, 0412},
{0127, 0412},
{0441, 0412},
{0711, 0412},
{0413, 0413},
{0133, 0413},
{0620, 0413},
{0423, 0423},
{0234, 0423},
{0563, 0423},
{0621, 0423},
{0713, 0423},
{0431, 0431},
{0262, 0431},
{0316, 0431},
{0730, 0431},
{0432, 0432},
{0432, 0432},
{0276, 0432},
{0326, 0432},
{0445, 0445},
{0222, 0445},
{0457, 0445},
{0575, 0445},
{0446, 0446},
{0467, 0446},
{0511, 0446},
{0672, 0446},
{0452, 0452},
{0524, 0452},
{0765, 0452},
{0454, 0454},
{0545, 0454},
{0513, 0454},
{0564, 0454},
{0455, 0455},
{0533, 0455},
{0551, 0455},
{0462, 0462},
{0462, 0462},
{0472, 0462},
{0623, 0462},
{0725, 0462},
{0464, 0464},
{0237, 0464},
{0642, 0464},
{0772, 0464},
{0465, 0465},
{0056, 0465},
{0656, 0465},
{0466, 0466},
{0144, 0466},
{0666, 0466},
{0503, 0503},
{0157, 0503},
{0322, 0503},
{0506, 0506},
{0224, 0506},
{0313, 0506},
{0574, 0506},
{0516, 0516},
{0067, 0516},
{0720, 0516},
{0523, 0523},
{0647, 0523},
{0726, 0523},
{0526, 0526},
{0562, 0526},
{0645, 0526},
{0532, 0532},
{0161, 0532},
{0345, 0532},
{0546, 0546},
{0317, 0546},
{0614, 0546},
{0751, 0546},
{0565, 0565},
{0307, 0565},
{0362, 0565},
{0606, 0606},
{0153, 0606},
{0630, 0606},
{0612, 0612},
{0254, 0612},
{0314, 0612},
{0706, 0612},
{0624, 0624},
{0075, 0624},
{0501, 0624},
{0627, 0627},
{0037, 0627},
{0560, 0627},
{0631, 0631},
{0231, 0631},
{0504, 0631},
{0636, 0631},
{0745, 0631},
{0632, 0632},
{0123, 0632},
{0657, 0632},
{0654, 0654},
{0163, 0654},
{0460, 0654},
{0607, 0654},
{0662, 0662},
{0363, 0662},
{0436, 0662},
{0443, 0662},
{0444, 0662},
{0664, 0664},
{0344, 0664},
{0471, 0664},
{0715, 0664},
{0703, 0703},
{0150, 0703},
{0256, 0703},
{0712, 0712},
{0136, 0712},
{0502, 0712},
{0723, 0723},
{0235, 0723},
{0671, 0723},
{0611, 0723},
{0731, 0731},
{0447, 0731},
{0473, 0731},
{0474, 0731},
{0744, 0731},
{0732, 0732},
{0164, 0732},
{0207, 0732},
{0734, 0734},
{0066, 0734},
{0743, 0743},
{0312, 0743},
{0515, 0743},
{0663, 0743},
{0754, 0754},
{0076, 0754},
{0203, 0754},
};
const QMap<unsigned int, unsigned int> DCSCodes::m_signFlip = {
{0023, 0047},
{0025, 0244},
{0026, 0464},
{0031, 0627},
{0032, 0051},
{0043, 0445},
{0047, 0023},
{0051, 0032},
{0053, 0452},
{0054, 0413},
{0065, 0271},
{0071, 0306},
{0072, 0245},
{0073, 0506},
{0074, 0174},
{0114, 0712},
{0115, 0152},
{0116, 0754},
{0122, 0225},
{0125, 0365},
{0131, 0364},
{0132, 0546},
{0134, 0223},
{0143, 0412},
{0145, 0274},
{0152, 0115},
{0155, 0731},
{0156, 0265},
{0162, 0503},
{0165, 0251},
{0172, 0036},
{0174, 0074},
{0205, 0263},
{0212, 0356},
{0223, 0134},
{0225, 0122},
{0226, 0411},
{0243, 0351},
{0244, 0025},
{0245, 0072},
{0246, 0523},
{0251, 0165},
{0252, 0462},
{0255, 0511},
{0261, 0732},
{0263, 0205},
{0265, 0156},
{0266, 0454},
{0271, 0065},
{0274, 0145},
{0306, 0071},
{0311, 0664},
{0315, 0423},
{0325, 0526},
{0331, 0465},
{0332, 0455},
{0343, 0532},
{0346, 0612},
{0351, 0243},
{0356, 0212},
{0364, 0131},
{0365, 0125},
{0371, 0734},
{0411, 0226},
{0412, 0143},
{0413, 0054},
{0423, 0315},
{0431, 0723},
{0432, 0516},
{0445, 0043},
{0446, 0255},
{0452, 0053},
{0454, 0655},
{0455, 0332},
{0462, 0252},
{0464, 0026},
{0465, 0331},
{0466, 0662},
{0503, 0162},
{0506, 0073},
{0516, 0432},
{0523, 0246},
{0526, 0325},
{0532, 0343},
{0546, 0132},
{0565, 0703},
{0606, 0631},
{0612, 0346},
{0624, 0632},
{0627, 0031},
{0631, 0606},
{0632, 0624},
{0654, 0743},
{0662, 0466},
{0664, 0311},
{0703, 0565},
{0712, 0114},
{0723, 0431},
{0731, 0155},
{0732, 0261},
{0734, 0371},
{0743, 0654},
{0754, 0116},
};
void DCSCodes::getCanonicalCodes(QList<unsigned int>& codes)
{
codes.clear();
for (auto code : m_toCanonicalCode.keys())
{
if (code == m_toCanonicalCode.value(code)) {
codes.append(code);
}
}
}

View File

@@ -0,0 +1,35 @@
//////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Copyright (C) 2021 Edouard Griffiths, F4EXB <f4exb06@gmail.com> //
// //
// This program 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 as version 3 of the License, or //
// (at your option) any later version. //
// //
// This program 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 V3 for more details. //
// //
// You should have received a copy of the GNU General Public License //
// along with this program. If not, see <http://www.gnu.org/licenses/>. //
//////////////////////////////////////////////////////////////////////////////////////////////////////////////
#ifndef INCLUDE_DSP_DCSCODES_H_
#define INCLUDE_DSP_DCSCODES_H_
#include <QList>
#include <QMap>
#include "export.h"
class SDRBASE_API DCSCodes
{
public:
static void getCanonicalCodes(QList<unsigned int>& codes);
static const int m_nbCodes;
static const QMap<unsigned int, unsigned int> m_toCanonicalCode;
static const QMap<unsigned int, unsigned int> m_signFlip;
};
#endif // INCLUDE_DSP_DCSCODES_H_

View File

@@ -0,0 +1,223 @@
///////////////////////////////////////////////////////////////////////////////////
// Copyright (C) 2020 Edouard Griffiths, F4EXB <f4exb06@gmail.com> //
// //
// This program 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 as version 3 of the License, or //
// (at your option) any later version. //
// //
// This program 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 V3 for more details. //
// //
// You should have received a copy of the GNU General Public License //
// along with this program. If not, see <http://www.gnu.org/licenses/>. //
///////////////////////////////////////////////////////////////////////////////////
#include "decimatorc.h"
DecimatorC::DecimatorC() :
m_log2Decim(0),
m_decim(1)
{}
void DecimatorC::setLog2Decim(unsigned int log2Decim)
{
m_log2Decim = log2Decim;
m_decim = 1 << log2Decim;
}
bool DecimatorC::decimate(Complex c, Complex& cd)
{
if (m_log2Decim == 0) { // no decimation hence no translation possible
return true;
}
if (m_log2Decim == 1) {
return decimate2(c, cd);
} else if (m_log2Decim == 2) {
return decimate4(c, cd);
} else if (m_log2Decim == 3) {
return decimate8(c, cd);
} else if (m_log2Decim == 4) {
return decimate16(c, cd);
} else if (m_log2Decim == 5) {
return decimate32(c, cd);
} else if (m_log2Decim == 6) {
return decimate64(c, cd);
} else {
return true; // no decimation
}
}
bool DecimatorC::decimate2(Complex c, Complex& cd)
{
float x = c.real();
float y = c.imag();
bool done2 = m_decimator2.workDecimateCenter(&x, &y);
if (done2)
{
cd.real(x);
cd.imag(y);
}
return done2;
}
bool DecimatorC::decimate4(Complex c, Complex& cd)
{
float x = c.real();
float y = c.imag();
bool done2 = m_decimator2.workDecimateCenter(&x, &y);
if (done2)
{
bool done4 = m_decimator4.workDecimateCenter(&x, &y);
if (done4)
{
cd.real(x);
cd.imag(y);
return true;
}
}
return false;
}
bool DecimatorC::decimate8(Complex c, Complex& cd)
{
float x = c.real();
float y = c.imag();
bool done2 = m_decimator2.workDecimateCenter(&x, &y);
if (done2)
{
bool done4 = m_decimator4.workDecimateCenter(&x, &y);
if (done4)
{
bool done8 = m_decimator8.workDecimateCenter(&x, &y);
if (done8)
{
cd.real(x);
cd.imag(y);
return true;
}
}
}
return false;
}
bool DecimatorC::decimate16(Complex c, Complex& cd)
{
float x = c.real();
float y = c.imag();
bool done2 = m_decimator2.workDecimateCenter(&x, &y);
if (done2)
{
bool done4 = m_decimator4.workDecimateCenter(&x, &y);
if (done4)
{
bool done8 = m_decimator8.workDecimateCenter(&x, &y);
if (done8)
{
bool done16 = m_decimator16.workDecimateCenter(&x, &y);
if (done16)
{
cd.real(x);
cd.imag(y);
return true;
}
}
}
}
return false;
}
bool DecimatorC::decimate32(Complex c, Complex& cd)
{
float x = c.real();
float y = c.imag();
bool done2 = m_decimator2.workDecimateCenter(&x, &y);
if (done2)
{
bool done4 = m_decimator4.workDecimateCenter(&x, &y);
if (done4)
{
bool done8 = m_decimator8.workDecimateCenter(&x, &y);
if (done8)
{
bool done16 = m_decimator16.workDecimateCenter(&x, &y);
if (done16)
{
bool done32 = m_decimator32.workDecimateCenter(&x, &y);
if (done32)
{
cd.real(x);
cd.imag(y);
return true;
}
}
}
}
}
return false;
}
bool DecimatorC::decimate64(Complex c, Complex& cd)
{
float x = c.real();
float y = c.imag();
bool done2 = m_decimator2.workDecimateCenter(&x, &y);
if (done2)
{
bool done4 = m_decimator4.workDecimateCenter(&x, &y);
if (done4)
{
bool done8 = m_decimator8.workDecimateCenter(&x, &y);
if (done8)
{
bool done16 = m_decimator16.workDecimateCenter(&x, &y);
if (done16)
{
bool done32 = m_decimator32.workDecimateCenter(&x, &y);
if (done32)
{
bool done64 = m_decimator32.workDecimateCenter(&x, &y);
if (done64)
{
cd.real(x);
cd.imag(y);
return true;
}
}
}
}
}
}
return false;
}

View File

@@ -0,0 +1,58 @@
///////////////////////////////////////////////////////////////////////////////////
// Copyright (C) 2012 maintech GmbH, Otto-Hahn-Str. 15, 97204 Hoechberg, Germany //
// written by Christian Daniel //
// Copyright (C) 2016-2020 Edouard Griffiths, F4EXB <f4exb06@gmail.com> //
// //
// This program 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 as version 3 of the License, or //
// (at your option) any later version. //
// //
// This program 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 V3 for more details. //
// //
// You should have received a copy of the GNU General Public License //
// along with this program. If not, see <http://www.gnu.org/licenses/>. //
///////////////////////////////////////////////////////////////////////////////////
// Half-band centered decimators with Complex (i.e. omplex<float>) input/output
// Decimates by a power of two using centered half-band filters
#ifndef INCLUDE_DSP_DECIMATORC_H_
#define INCLUDE_DSP_DECIMATORC_H_
#include "dsp/dsptypes.h"
#include "dsp/inthalfbandfiltereof.h"
#include "export.h"
#define DECIMATORSXS_HB_FILTER_ORDER 64
class SDRBASE_API DecimatorC
{
public:
DecimatorC();
void setLog2Decim(unsigned int log2Decim);
bool decimate(Complex c, Complex& cd);
unsigned int getDecim() const { return m_decim; }
private:
IntHalfbandFilterEOF<DECIMATORSXS_HB_FILTER_ORDER, true> m_decimator2; // 1st stages
IntHalfbandFilterEOF<DECIMATORSXS_HB_FILTER_ORDER, true> m_decimator4; // 2nd stages
IntHalfbandFilterEOF<DECIMATORSXS_HB_FILTER_ORDER, true> m_decimator8; // 3rd stages
IntHalfbandFilterEOF<DECIMATORSXS_HB_FILTER_ORDER, true> m_decimator16; // 4th stages
IntHalfbandFilterEOF<DECIMATORSXS_HB_FILTER_ORDER, true> m_decimator32; // 5th stages
IntHalfbandFilterEOF<DECIMATORSXS_HB_FILTER_ORDER, true> m_decimator64; // 6th stages
unsigned int m_log2Decim;
unsigned int m_decim;
bool decimate2(Complex c, Complex& cd);
bool decimate4(Complex c, Complex& cd);
bool decimate8(Complex c, Complex& cd);
bool decimate16(Complex c, Complex& cd);
bool decimate32(Complex c, Complex& cd);
bool decimate64(Complex c, Complex& cd);
};
#endif // INCLUDE_DSP_DECIMATORC_H_

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,213 @@
///////////////////////////////////////////////////////////////////////////////////
// Copyright (C) 2018-2020 Edouard Griffiths, F4EXB <f4exb06@gmail.com> //
// Copyright (C) 2020 Kacper Michajłow <kasper93@gmail.com> //
// //
// This program 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 as version 3 of the License, or //
// (at your option) any later version. //
// //
// This program 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 V3 for more details. //
// //
// You should have received a copy of the GNU General Public License //
// along with this program. If not, see <http://www.gnu.org/licenses/>. //
///////////////////////////////////////////////////////////////////////////////////
#include "decimatorsff.h"
template<>
SDRBASE_API void DecimatorsFF<true>::decimate1(FSampleVector::iterator* it, const float* buf, qint32 nbIAndQ)
{
float xreal, yimag;
for (int pos = 0; pos < nbIAndQ - 1; pos += 2)
{
xreal = buf[pos+0];
yimag = buf[pos+1];
(**it).setReal(xreal);
(**it).setImag(yimag);
++(*it); // Valgrind optim (comment not repeated)
}
}
template<>
SDRBASE_API void DecimatorsFF<false>::decimate1(FSampleVector::iterator* it, const float* buf, qint32 nbIAndQ)
{
float xreal, yimag;
for (int pos = 0; pos < nbIAndQ - 1; pos += 2)
{
xreal = buf[pos+1];
yimag = buf[pos+0];
(**it).setReal(xreal);
(**it).setImag(yimag);
++(*it); // Valgrind optim (comment not repeated)
}
}
template<>
void DecimatorsFF<true>::decimate2_inf(FSampleVector::iterator* it, const float* buf, qint32 nbIAndQ)
{
float xreal, yimag;
for (int pos = 0; pos < nbIAndQ - 7; pos += 8)
{
xreal = (buf[pos+0] - buf[pos+3]);
yimag = (buf[pos+1] + buf[pos+2]);
(**it).setReal(xreal);
(**it).setImag(yimag);
++(*it);
xreal = (buf[pos+7] - buf[pos+4]);
yimag = (- buf[pos+5] - buf[pos+6]);
(**it).setReal(xreal);
(**it).setImag(yimag);
++(*it);
}
}
template<>
void DecimatorsFF<false>::decimate2_inf(FSampleVector::iterator* it, const float* buf, qint32 nbIAndQ)
{
float xreal, yimag;
for (int pos = 0; pos < nbIAndQ - 7; pos += 8)
{
xreal = (buf[pos+1] + buf[pos+2]);
yimag = (buf[pos+0] - buf[pos+3]);
(**it).setReal(xreal);
(**it).setImag(yimag);
++(*it);
xreal = (- buf[pos+5] - buf[pos+6]);
yimag = (buf[pos+7] - buf[pos+4]);
(**it).setReal(xreal);
(**it).setImag(yimag);
++(*it);
}
}
template<>
void DecimatorsFF<true>::decimate2_sup(FSampleVector::iterator* it, const float* buf, qint32 nbIAndQ)
{
float xreal, yimag;
for (int pos = 0; pos < nbIAndQ - 7; pos += 8)
{
xreal = (buf[pos+1] - buf[pos+2]);
yimag = (- buf[pos+0] - buf[pos+3]);
(**it).setReal(xreal);
(**it).setImag(yimag);
++(*it);
xreal = (buf[pos+6] - buf[pos+5]);
yimag = (buf[pos+4] + buf[pos+7]);
(**it).setReal(xreal);
(**it).setImag(yimag);
++(*it);
}
}
template<>
void DecimatorsFF<false>::decimate2_sup(FSampleVector::iterator* it, const float* buf, qint32 nbIAndQ)
{
float xreal, yimag;
for (int pos = 0; pos < nbIAndQ - 7; pos += 8)
{
xreal = (- buf[pos+0] - buf[pos+3]);
yimag = (buf[pos+1] - buf[pos+2]);
(**it).setReal(xreal);
(**it).setImag(yimag);
++(*it);
xreal = (buf[pos+4] + buf[pos+7]);
yimag = (buf[pos+6] - buf[pos+5]);
(**it).setReal(xreal);
(**it).setImag(yimag);
++(*it);
}
}
template<>
void DecimatorsFF<true>::decimate4_inf(FSampleVector::iterator* it, const float* buf, qint32 nbIAndQ)
{
float xreal, yimag;
for (int pos = 0; pos < nbIAndQ - 7; pos += 8)
{
xreal = (buf[pos+0] - buf[pos+3] + buf[pos+7] - buf[pos+4]);
yimag = (buf[pos+1] - buf[pos+5] + buf[pos+2] - buf[pos+6]);
(**it).setReal(xreal);
(**it).setImag(yimag);
++(*it);
}
}
template<>
void DecimatorsFF<false>::decimate4_inf(FSampleVector::iterator* it, const float* buf, qint32 nbIAndQ)
{
float xreal, yimag;
for (int pos = 0; pos < nbIAndQ - 7; pos += 8)
{
xreal = (buf[pos+1] - buf[pos+5] + buf[pos+2] - buf[pos+6]);
yimag = (buf[pos+0] - buf[pos+3] + buf[pos+7] - buf[pos+4]);
(**it).setReal(xreal);
(**it).setImag(yimag);
++(*it);
}
}
template<>
void DecimatorsFF<true>::decimate4_sup(FSampleVector::iterator* it, const float* buf, qint32 nbIAndQ)
{
// Sup (USB):
// x y x y x y x y / x -> 1,-2,-5,6 / y -> -0,-3,4,7
// [ rotate: 1, 0, -2, 3, -5, -4, 6, -7]
// Inf (LSB):
// x y x y x y x y / x -> 0,-3,-4,7 / y -> 1,2,-5,-6
// [ rotate: 0, 1, -3, 2, -4, -5, 7, -6]
float xreal, yimag;
for (int pos = 0; pos < nbIAndQ - 7; pos += 8)
{
xreal = (buf[pos+1] - buf[pos+2] - buf[pos+5] + buf[pos+6]);
yimag = (- buf[pos+0] - buf[pos+3] + buf[pos+4] + buf[pos+7]);
(**it).setReal(xreal);
(**it).setImag(yimag);
++(*it);
}
}
template<>
void DecimatorsFF<false>::decimate4_sup(FSampleVector::iterator* it, const float* buf, qint32 nbIAndQ)
{
// Sup (USB):
// x y x y x y x y / x -> 1,-2,-5,6 / y -> -0,-3,4,7
// [ rotate: 1, 0, -2, 3, -5, -4, 6, -7]
// Inf (LSB):
// x y x y x y x y / x -> 0,-3,-4,7 / y -> 1,2,-5,-6
// [ rotate: 0, 1, -3, 2, -4, -5, 7, -6]
float xreal, yimag;
for (int pos = 0; pos < nbIAndQ - 7; pos += 8)
{
xreal = (- buf[pos+0] - buf[pos+3] + buf[pos+4] + buf[pos+7]);
yimag = (buf[pos+1] - buf[pos+2] - buf[pos+5] + buf[pos+6]);
(**it).setReal(xreal);
(**it).setImag(yimag);
++(*it);
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,214 @@
///////////////////////////////////////////////////////////////////////////////////
// Copyright (C) 2018-2020 Edouard Griffiths, F4EXB <f4exb06@gmail.com> //
// Copyright (C) 2020 Kacper Michajłow <kasper93@gmail.com> //
// //
// This program 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 as version 3 of the License, or //
// (at your option) any later version. //
// //
// This program 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 V3 for more details. //
// //
// You should have received a copy of the GNU General Public License //
// along with this program. If not, see <http://www.gnu.org/licenses/>. //
///////////////////////////////////////////////////////////////////////////////////
#include "decimatorsfi.h"
template<>
SDRBASE_API void DecimatorsFI<true>::decimate1(SampleVector::iterator* it, const float* buf, qint32 nbIAndQ)
{
float xreal, yimag;
for (int pos = 0; pos < nbIAndQ - 1; pos += 2)
{
xreal = buf[pos+0];
yimag = buf[pos+1];
(**it).setReal(xreal * SDR_RX_SCALEF);
(**it).setImag(yimag * SDR_RX_SCALEF);
++(*it); // Valgrind optim (comment not repeated)
}
}
template<>
SDRBASE_API void DecimatorsFI<false>::decimate1(SampleVector::iterator* it, const float* buf, qint32 nbIAndQ)
{
float xreal, yimag;
for (int pos = 0; pos < nbIAndQ - 1; pos += 2)
{
xreal = buf[pos+1];
yimag = buf[pos+0];
(**it).setReal(xreal * SDR_RX_SCALEF);
(**it).setImag(yimag * SDR_RX_SCALEF);
++(*it); // Valgrind optim (comment not repeated)
}
}
template<>
SDRBASE_API void DecimatorsFI<true>::decimate2_inf(SampleVector::iterator* it, const float* buf, qint32 nbIAndQ)
{
float xreal, yimag;
for (int pos = 0; pos < nbIAndQ - 7; pos += 8)
{
xreal = (buf[pos+0] - buf[pos+3]);
yimag = (buf[pos+1] + buf[pos+2]);
(**it).setReal(xreal * SDR_RX_SCALED);
(**it).setImag(yimag * SDR_RX_SCALED);
++(*it);
xreal = (buf[pos+7] - buf[pos+4]);
yimag = (- buf[pos+5] - buf[pos+6]);
(**it).setReal(xreal * SDR_RX_SCALED);
(**it).setImag(yimag * SDR_RX_SCALED);
++(*it);
}
}
template<>
SDRBASE_API void DecimatorsFI<false>::decimate2_inf(SampleVector::iterator* it, const float* buf, qint32 nbIAndQ)
{
float xreal, yimag;
for (int pos = 0; pos < nbIAndQ - 7; pos += 8)
{
xreal = (buf[pos+1] + buf[pos+2]);
yimag = (buf[pos+0] - buf[pos+3]);
(**it).setReal(xreal * SDR_RX_SCALED);
(**it).setImag(yimag * SDR_RX_SCALED);
++(*it);
xreal = (- buf[pos+5] - buf[pos+6]);
yimag = (buf[pos+7] - buf[pos+4]);
(**it).setReal(xreal * SDR_RX_SCALED);
(**it).setImag(yimag * SDR_RX_SCALED);
++(*it);
}
}
template<>
SDRBASE_API void DecimatorsFI<true>::decimate2_sup(SampleVector::iterator* it, const float* buf, qint32 nbIAndQ)
{
float xreal, yimag;
for (int pos = 0; pos < nbIAndQ - 7; pos += 8)
{
xreal = (buf[pos+1] - buf[pos+2]);
yimag = (- buf[pos+0] - buf[pos+3]);
(**it).setReal(xreal * SDR_RX_SCALED);
(**it).setImag(yimag * SDR_RX_SCALED);
++(*it);
xreal = (buf[pos+6] - buf[pos+5]);
yimag = (buf[pos+4] + buf[pos+7]);
(**it).setReal(xreal * SDR_RX_SCALED);
(**it).setImag(yimag * SDR_RX_SCALED);
++(*it);
}
}
template<>
SDRBASE_API void DecimatorsFI<false>::decimate2_sup(SampleVector::iterator* it, const float* buf, qint32 nbIAndQ)
{
float xreal, yimag;
for (int pos = 0; pos < nbIAndQ - 7; pos += 8)
{
xreal = (- buf[pos+0] - buf[pos+3]);
yimag = (buf[pos+1] - buf[pos+2]);
(**it).setReal(xreal * SDR_RX_SCALED);
(**it).setImag(yimag * SDR_RX_SCALED);
++(*it);
xreal = (buf[pos+4] + buf[pos+7]);
yimag = (buf[pos+6] - buf[pos+5]);
(**it).setReal(xreal * SDR_RX_SCALED);
(**it).setImag(yimag * SDR_RX_SCALED);
++(*it);
}
}
template<>
SDRBASE_API void DecimatorsFI<true>::decimate4_inf(SampleVector::iterator* it, const float* buf, qint32 nbIAndQ)
{
float xreal, yimag;
for (int pos = 0; pos < nbIAndQ - 7; pos += 8)
{
xreal = (buf[pos+0] - buf[pos+3] + buf[pos+7] - buf[pos+4]);
yimag = (buf[pos+1] - buf[pos+5] + buf[pos+2] - buf[pos+6]);
(**it).setReal(xreal * SDR_RX_SCALED);
(**it).setImag(yimag * SDR_RX_SCALED);
++(*it);
}
}
template<>
SDRBASE_API void DecimatorsFI<false>::decimate4_inf(SampleVector::iterator* it, const float* buf, qint32 nbIAndQ)
{
float xreal, yimag;
for (int pos = 0; pos < nbIAndQ - 7; pos += 8)
{
xreal = (buf[pos+1] - buf[pos+5] + buf[pos+2] - buf[pos+6]);
yimag = (buf[pos+0] - buf[pos+3] + buf[pos+7] - buf[pos+4]);
(**it).setReal(xreal * SDR_RX_SCALED);
(**it).setImag(yimag * SDR_RX_SCALED);
++(*it);
}
}
template<>
SDRBASE_API void DecimatorsFI<true>::decimate4_sup(SampleVector::iterator* it, const float* buf, qint32 nbIAndQ)
{
// Sup (USB):
// x y x y x y x y / x -> 1,-2,-5,6 / y -> -0,-3,4,7
// [ rotate: 1, 0, -2, 3, -5, -4, 6, -7]
// Inf (LSB):
// x y x y x y x y / x -> 0,-3,-4,7 / y -> 1,2,-5,-6
// [ rotate: 0, 1, -3, 2, -4, -5, 7, -6]
float xreal, yimag;
for (int pos = 0; pos < nbIAndQ - 7; pos += 8)
{
xreal = (buf[pos+1] - buf[pos+2] - buf[pos+5] + buf[pos+6]);
yimag = (- buf[pos+0] - buf[pos+3] + buf[pos+4] + buf[pos+7]);
(**it).setReal(xreal * SDR_RX_SCALED);
(**it).setImag(yimag * SDR_RX_SCALED);
++(*it);
}
}
template<>
SDRBASE_API void DecimatorsFI<false>::decimate4_sup(SampleVector::iterator* it, const float* buf, qint32 nbIAndQ)
{
// Sup (USB):
// x y x y x y x y / x -> 1,-2,-5,6 / y -> -0,-3,4,7
// [ rotate: 1, 0, -2, 3, -5, -4, 6, -7]
// Inf (LSB):
// x y x y x y x y / x -> 0,-3,-4,7 / y -> 1,2,-5,-6
// [ rotate: 0, 1, -3, 2, -4, -5, 7, -6]
float xreal, yimag;
for (int pos = 0; pos < nbIAndQ - 7; pos += 8)
{
xreal = (- buf[pos+0] - buf[pos+3] + buf[pos+4] + buf[pos+7]);
yimag = (buf[pos+1] - buf[pos+2] - buf[pos+5] + buf[pos+6]);
(**it).setReal(xreal * SDR_RX_SCALED);
(**it).setImag(yimag * SDR_RX_SCALED);
++(*it);
}
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,41 @@
///////////////////////////////////////////////////////////////////////////////////
// Copyright (C) 2012 maintech GmbH, Otto-Hahn-Str. 15, 97204 Hoechberg, Germany //
// written by Christian Daniel //
// Copyright (C) 2015-2019 Edouard Griffiths, F4EXB <f4exb06@gmail.com> //
// //
// This program 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 as version 3 of the License, or //
// (at your option) any later version. //
// //
// This program 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 V3 for more details. //
// //
// You should have received a copy of the GNU General Public License //
// along with this program. If not, see <http://www.gnu.org/licenses/>. //
///////////////////////////////////////////////////////////////////////////////////
#include "devicesamplemimo.h"
DeviceSampleMIMO::DeviceSampleMIMO() :
m_guiMessageQueue(nullptr)
{
connect(&m_inputMessageQueue, SIGNAL(messageEnqueued()), this, SLOT(handleInputMessages()));
}
DeviceSampleMIMO::~DeviceSampleMIMO() = default;
void DeviceSampleMIMO::handleInputMessages()
{
Message* message;
while ((message = m_inputMessageQueue.pop()) != nullptr)
{
if (handleMessage(*message))
{
delete message;
}
}
}

View File

@@ -0,0 +1,176 @@
///////////////////////////////////////////////////////////////////////////////////
// Copyright (C) 2012 maintech GmbH, Otto-Hahn-Str. 15, 97204 Hoechberg, Germany //
// written by Christian Daniel //
// Copyright (C) 2015-2021 Edouard Griffiths, F4EXB <f4exb06@gmail.com> //
// //
// This program 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 as version 3 of the License, or //
// (at your option) any later version. //
// //
// This program 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 V3 for more details. //
// //
// You should have received a copy of the GNU General Public License //
// along with this program. If not, see <http://www.gnu.org/licenses/>. //
///////////////////////////////////////////////////////////////////////////////////
#ifndef SDRBASE_DSP_DEVICESAMPLEMIMO_H_
#define SDRBASE_DSP_DEVICESAMPLEMIMO_H_
#include <vector>
#include "samplemififo.h"
#include "samplemofifo.h"
#include "util/message.h"
#include "util/messagequeue.h"
#include "export.h"
namespace SWGSDRangel
{
class SWGDeviceSettings;
class SWGDeviceState;
class SWGDeviceReport;
class SWGDeviceActions;
}
class SDRBASE_API DeviceSampleMIMO : public QObject {
Q_OBJECT
public:
enum MIMOType //!< Type of MIMO
{
MIMOAsynchronous, //!< All streams are asynchronous (false MIMO)
MIMOHalfSynchronous, //!< MI + MO (synchronous inputs on one side and synchronous outputs on the other side)
MIMOFullSynchronous, //!< True MIMO (all streams synchronous)
};
typedef enum {
FC_POS_INFRA = 0,
FC_POS_SUPRA,
FC_POS_CENTER
} fcPos_t;
DeviceSampleMIMO();
virtual ~DeviceSampleMIMO();
virtual void destroy() = 0;
virtual void init() = 0; //!< initializations to be done when all collaborating objects are created and possibly connected
virtual bool startRx() = 0;
virtual void stopRx() = 0;
virtual bool startTx() = 0;
virtual void stopTx() = 0;
virtual QByteArray serialize() const = 0;
virtual bool deserialize(const QByteArray& data) = 0;
virtual const QString& getDeviceDescription() const = 0;
virtual int getSinkSampleRate(int index) const = 0; //!< Sample rate exposed by the sink at index
virtual void setSinkSampleRate(int sampleRate, int index) = 0; //!< For when the sink sample rate is set externally
virtual quint64 getSinkCenterFrequency(int index) const = 0; //!< Center frequency exposed by the sink at index
virtual void setSinkCenterFrequency(qint64 centerFrequency, int index) = 0;
virtual int getSourceSampleRate(int index) const = 0; //!< Sample rate exposed by the source at index
virtual void setSourceSampleRate(int sampleRate, int index) = 0; //!< For when the source sample rate is set externally
virtual quint64 getSourceCenterFrequency(int index) const = 0; //!< Center frequency exposed by the source at index
virtual void setSourceCenterFrequency(qint64 centerFrequency, int index) = 0;
virtual quint64 getMIMOCenterFrequency() const = 0; //!< Unique center frequency for preset identification or any unique reference
virtual unsigned int getMIMOSampleRate() const = 0; //!< Unique sample rate for any unique reference
virtual bool handleMessage(const Message& message) = 0;
virtual int webapiSettingsGet(
SWGSDRangel::SWGDeviceSettings& response,
QString& errorMessage)
{
(void) response;
errorMessage = "Not implemented";
return 501;
}
virtual int webapiSettingsPutPatch(
bool force, //!< true to force settings = put
const QStringList& deviceSettingsKeys,
SWGSDRangel::SWGDeviceSettings& response,
QString& errorMessage)
{
(void) force;
(void) deviceSettingsKeys;
(void) response;
errorMessage = "Not implemented";
return 501;
}
virtual int webapiRunGet(
int subsystemIndex,
SWGSDRangel::SWGDeviceState& response,
QString& errorMessage)
{
(void) response;
(void) subsystemIndex;
errorMessage = "Not implemented";
return 501;
}
virtual int webapiRun(bool run,
int subsystemIndex,
SWGSDRangel::SWGDeviceState& response,
QString& errorMessage)
{
(void) run;
(void) subsystemIndex;
(void) response;
errorMessage = "Not implemented";
return 501;
}
virtual int webapiReportGet(
SWGSDRangel::SWGDeviceReport& response,
QString& errorMessage)
{
(void) response;
errorMessage = "Not implemented";
return 501;
}
virtual int webapiActionsPost(
const QStringList& deviceActionsKeys,
SWGSDRangel::SWGDeviceActions& actions,
QString& errorMessage)
{
(void) deviceActionsKeys;
(void) actions;
errorMessage = "Not implemented";
return 501;
}
MIMOType getMIMOType() const { return m_mimoType; }
MessageQueue *getInputMessageQueue() { return &m_inputMessageQueue; }
virtual void setMessageQueueToGUI(MessageQueue *queue) = 0; // pure virtual so that child classes must have to deal with this
MessageQueue *getMessageQueueToGUI() { return m_guiMessageQueue; }
unsigned int getNbSourceFifos() const { return m_sampleMOFifo.getNbStreams(); } //!< Get the number of Tx FIFOs
unsigned int getNbSinkFifos() const { return m_sampleMIFifo.getNbStreams(); } //!< Get the number of Rx FIFOs
SampleMIFifo* getSampleMIFifo() { return &m_sampleMIFifo; }
SampleMOFifo* getSampleMOFifo() { return &m_sampleMOFifo; }
// Streams and FIFOs are in opposed source/sink type whick makes it confusing when stream direction is involved:
// Rx: source stream -> sink FIFO -> channel sinks
// Tx: sink stream <- source FIFO <- channel sources
unsigned int getNbSourceStreams() const { return m_sampleMIFifo.getNbStreams(); } //!< Commodity function same as getNbSinkFifos (Rx or source streams)
unsigned int getNbSinkStreams() const { return m_sampleMOFifo.getNbStreams(); } //!< Commodity function same as getNbSourceFifos (Tx or sink streams)
protected slots:
void handleInputMessages();
protected:
MIMOType m_mimoType;
SampleMIFifo m_sampleMIFifo; //!< Multiple Input FIFO
SampleMOFifo m_sampleMOFifo; //!< Multiple Output FIFO
MessageQueue m_inputMessageQueue; //!< Input queue to the sink
MessageQueue *m_guiMessageQueue; //!< Input message queue to the GUI
};
#endif // SDRBASE_DSP_DEVICESAMPLEMIMO_H_

View File

@@ -0,0 +1,113 @@
///////////////////////////////////////////////////////////////////////////////////
// Copyright (C) 2012 maintech GmbH, Otto-Hahn-Str. 15, 97204 Hoechberg, Germany //
// written by Christian Daniel //
// Copyright (C) 2016-2017, 2019 Edouard Griffiths, F4EXB <f4exb06@gmail.com> //
// //
// This program 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 as version 3 of the License, or //
// (at your option) any later version. //
// //
// This program 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 V3 for more details. //
// //
// You should have received a copy of the GNU General Public License //
// along with this program. If not, see <http://www.gnu.org/licenses/>. //
///////////////////////////////////////////////////////////////////////////////////
#include <QDebug>
#include "dsp/devicesamplestatic.h"
#include "dsp/devicesamplesink.h"
DeviceSampleSink::DeviceSampleSink() :
m_sampleSourceFifo(1<<19),
m_guiMessageQueue(0)
{
connect(&m_inputMessageQueue, SIGNAL(messageEnqueued()), this, SLOT(handleInputMessages()));
}
DeviceSampleSink::~DeviceSampleSink()
{
}
void DeviceSampleSink::handleInputMessages()
{
Message* message;
while ((message = m_inputMessageQueue.pop()) != 0)
{
if (handleMessage(*message))
{
delete message;
}
}
}
qint64 DeviceSampleSink::calculateDeviceCenterFrequency(
quint64 centerFrequency,
qint64 transverterDeltaFrequency,
int log2Interp,
fcPos_t fcPos,
quint32 devSampleRate,
bool transverterMode)
{
return DeviceSampleStatic::calculateSinkDeviceCenterFrequency(
centerFrequency,
transverterDeltaFrequency,
log2Interp,
(DeviceSampleStatic::fcPos_t) fcPos,
devSampleRate,
transverterMode
);
}
qint64 DeviceSampleSink::calculateCenterFrequency(
quint64 deviceCenterFrequency,
qint64 transverterDeltaFrequency,
int log2Interp,
fcPos_t fcPos,
quint32 devSampleRate,
bool transverterMode)
{
return DeviceSampleStatic::calculateSinkCenterFrequency(
deviceCenterFrequency,
transverterDeltaFrequency,
log2Interp,
(DeviceSampleStatic::fcPos_t) fcPos,
devSampleRate,
transverterMode
);
}
/**
* log2Interp = 0: no shift
*
* log2Interp = 1: middle of side band (inf or sup: 1/2)
* ^ | ^
* | inf | inf | sup | sup |
*
* log2Interp = 2: middle of far side half side band (inf, inf or sup, sup: 1/2 + 1/4)
* ^ | ^
* | inf | inf | sup | sup |
*
* log2Interp = 3: inf, inf, sup or sup, sup, inf: 1/2 + 1/4 - 1/8 = 5/8
* log2Interp = 4: inf, inf, sup, inf or sup, sup, inf, sup: 1/2 + 1/4 - 1/8 + 1/16 = 11/16
* log2Interp = 5: inf, inf, sup, inf, sup or sup, sup, inf, sup, inf: 1/2 + 1/4 - 1/8 + 1/16 - 1/32 = 21/32
* log2Interp = 6: inf, sup, inf, sup, inf, sup or sup, inf, sup, inf, sup, inf: 1/2 - 1/4 + 1/8 -1/16 + 1/32 - 1/64 = 21/64
*
*/
qint32 DeviceSampleSink::calculateFrequencyShift(
int log2Interp,
fcPos_t fcPos,
quint32 devSampleRate)
{
return DeviceSampleStatic::calculateSinkFrequencyShift(
log2Interp,
(DeviceSampleStatic::fcPos_t) fcPos,
devSampleRate
);
}

View File

@@ -0,0 +1,162 @@
///////////////////////////////////////////////////////////////////////////////////
// Copyright (C) 2012 maintech GmbH, Otto-Hahn-Str. 15, 97204 Hoechberg, Germany //
// written by Christian Daniel //
// Copyright (C) 2015-2021 Edouard Griffiths, F4EXB <f4exb06@gmail.com> //
// //
// This program 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 as version 3 of the License, or //
// (at your option) any later version. //
// //
// This program 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 V3 for more details. //
// //
// You should have received a copy of the GNU General Public License //
// along with this program. If not, see <http://www.gnu.org/licenses/>. //
///////////////////////////////////////////////////////////////////////////////////
#ifndef SDRBASE_DSP_DEVICESAMPLESINK_H_
#define SDRBASE_DSP_DEVICESAMPLESINK_H_
#include <QtGlobal>
#include "samplesourcefifo.h"
#include "util/message.h"
#include "util/messagequeue.h"
#include "export.h"
namespace SWGSDRangel
{
class SWGDeviceSettings;
class SWGDeviceState;
class SWGDeviceReport;
class SWGDeviceActions;
}
class SDRBASE_API DeviceSampleSink : public QObject {
Q_OBJECT
public:
typedef enum {
FC_POS_INFRA = 0,
FC_POS_SUPRA,
FC_POS_CENTER
} fcPos_t;
DeviceSampleSink();
virtual ~DeviceSampleSink();
virtual void destroy() = 0;
virtual void init() = 0; //!< initializations to be done when all collaborating objects are created and possibly connected
virtual bool start() = 0;
virtual void stop() = 0;
virtual QByteArray serialize() const = 0;
virtual bool deserialize(const QByteArray& data) = 0;
virtual const QString& getDeviceDescription() const = 0;
virtual int getSampleRate() const = 0; //!< Sample rate exposed by the sink
virtual void setSampleRate(int sampleRate) = 0; //!< For when the sink sample rate is set externally
virtual quint64 getCenterFrequency() const = 0; //!< Center frequency exposed by the sink
virtual void setCenterFrequency(qint64 centerFrequency) = 0;
virtual bool handleMessage(const Message& message) = 0;
virtual int webapiSettingsGet(
SWGSDRangel::SWGDeviceSettings& response,
QString& errorMessage)
{
(void) response;
errorMessage = "Not implemented";
return 501;
}
virtual int webapiSettingsPutPatch(
bool force, //!< true to force settings = put
const QStringList& deviceSettingsKeys,
SWGSDRangel::SWGDeviceSettings& response,
QString& errorMessage)
{
(void) force;
(void) deviceSettingsKeys;
(void) response;
errorMessage = "Not implemented";
return 501;
}
virtual int webapiRunGet(
SWGSDRangel::SWGDeviceState& response,
QString& errorMessage)
{
(void) response;
errorMessage = "Not implemented";
return 501;
}
virtual int webapiRun(bool run,
SWGSDRangel::SWGDeviceState& response,
QString& errorMessage)
{
(void) run;
(void) response;
errorMessage = "Not implemented";
return 501;
}
virtual int webapiReportGet(
SWGSDRangel::SWGDeviceReport& response,
QString& errorMessage)
{
(void) response;
errorMessage = "Not implemented";
return 501;
}
virtual int webapiActionsPost(
const QStringList& deviceActionsKeys,
SWGSDRangel::SWGDeviceActions& actions,
QString& errorMessage)
{
(void) deviceActionsKeys;
(void) actions;
errorMessage = "Not implemented";
return 501;
}
MessageQueue *getInputMessageQueue() { return &m_inputMessageQueue; }
virtual void setMessageQueueToGUI(MessageQueue *queue) = 0; // pure virtual so that child classes must have to deal with this
MessageQueue *getMessageQueueToGUI() { return m_guiMessageQueue; }
SampleSourceFifo* getSampleFifo() { return &m_sampleSourceFifo; }
static qint64 calculateDeviceCenterFrequency(
quint64 centerFrequency,
qint64 transverterDeltaFrequency,
int log2Interp,
fcPos_t fcPos,
quint32 devSampleRate,
bool transverterMode = false);
static qint64 calculateCenterFrequency(
quint64 deviceCenterFrequency,
qint64 transverterDeltaFrequency,
int log2Interp,
fcPos_t fcPos,
quint32 devSampleRate,
bool transverterMode = false);
static qint32 calculateFrequencyShift(
int log2Interp,
fcPos_t fcPos,
quint32 devSampleRate);
protected slots:
void handleInputMessages();
protected:
SampleSourceFifo m_sampleSourceFifo;
MessageQueue m_inputMessageQueue; //!< Input queue to the sink
MessageQueue *m_guiMessageQueue; //!< Input message queue to the GUI
};
#endif /* SDRBASE_DSP_DEVICESAMPLESINK_H_ */

View File

@@ -0,0 +1,115 @@
///////////////////////////////////////////////////////////////////////////////////
// Copyright (C) 2012 maintech GmbH, Otto-Hahn-Str. 15, 97204 Hoechberg, Germany //
// written by Christian Daniel //
// Copyright (C) 2016-2019 Edouard Griffiths, F4EXB <f4exb06@gmail.com> //
// //
// This program 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 as version 3 of the License, or //
// (at your option) any later version. //
// //
// This program 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 V3 for more details. //
// //
// You should have received a copy of the GNU General Public License //
// along with this program. If not, see <http://www.gnu.org/licenses/>. //
///////////////////////////////////////////////////////////////////////////////////
#include <QDebug>
#include "dsp/devicesamplestatic.h"
#include "dsp/devicesamplesource.h"
DeviceSampleSource::DeviceSampleSource() :
m_guiMessageQueue(0)
{
connect(&m_inputMessageQueue, SIGNAL(messageEnqueued()), this, SLOT(handleInputMessages()));
}
DeviceSampleSource::~DeviceSampleSource()
{
}
void DeviceSampleSource::handleInputMessages()
{
Message* message;
while ((message = m_inputMessageQueue.pop()) != 0)
{
if (handleMessage(*message))
{
delete message;
}
}
}
qint64 DeviceSampleSource::calculateDeviceCenterFrequency(
quint64 centerFrequency,
qint64 transverterDeltaFrequency,
int log2Decim,
fcPos_t fcPos,
quint32 devSampleRate,
FrequencyShiftScheme frequencyShiftScheme,
bool transverterMode)
{
return DeviceSampleStatic::calculateSourceDeviceCenterFrequency(
centerFrequency,
transverterDeltaFrequency,
log2Decim,
(DeviceSampleStatic::fcPos_t) fcPos,
devSampleRate,
(DeviceSampleStatic::FrequencyShiftScheme) frequencyShiftScheme,
transverterMode
);
}
qint64 DeviceSampleSource::calculateCenterFrequency(
quint64 deviceCenterFrequency,
qint64 transverterDeltaFrequency,
int log2Decim,
fcPos_t fcPos,
quint32 devSampleRate,
FrequencyShiftScheme frequencyShiftScheme,
bool transverterMode)
{
return DeviceSampleStatic::calculateSourceCenterFrequency(
deviceCenterFrequency,
transverterDeltaFrequency,
log2Decim,
(DeviceSampleStatic::fcPos_t) fcPos,
devSampleRate,
(DeviceSampleStatic::FrequencyShiftScheme) frequencyShiftScheme,
transverterMode
);
}
/**
* log2Decim = 0: no shift
*
* n = log2Decim <= 2: fc = +/- 1/2^(n-1)
* center
* | ^ |
* | inf | sup |
* ^ ^
*
* n = log2Decim > 2: fc = +/- 1/2^n
* center
* | ^ |
* | |inf| | |sup| |
* ^ ^
*/
qint32 DeviceSampleSource::calculateFrequencyShift(
int log2Decim,
fcPos_t fcPos,
quint32 devSampleRate,
FrequencyShiftScheme frequencyShiftScheme)
{
return DeviceSampleStatic::calculateSourceFrequencyShift(
log2Decim,
(DeviceSampleStatic::fcPos_t) fcPos,
devSampleRate,
(DeviceSampleStatic::FrequencyShiftScheme) frequencyShiftScheme
);
}

View File

@@ -0,0 +1,178 @@
///////////////////////////////////////////////////////////////////////////////////
// Copyright (C) 2012 maintech GmbH, Otto-Hahn-Str. 15, 97204 Hoechberg, Germany //
// written by Christian Daniel //
// Copyright (C) 2015-2020 Edouard Griffiths, F4EXB <f4exb06@gmail.com> //
// //
// This program 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 as version 3 of the License, or //
// (at your option) any later version. //
// //
// This program 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 V3 for more details. //
// //
// You should have received a copy of the GNU General Public License //
// along with this program. If not, see <http://www.gnu.org/licenses/>. //
///////////////////////////////////////////////////////////////////////////////////
#ifndef INCLUDE_SAMPLESOURCE_H
#define INCLUDE_SAMPLESOURCE_H
#include <QtGlobal>
#include <QByteArray>
#include "samplesinkfifo.h"
#include "util/message.h"
#include "util/messagequeue.h"
#include "export.h"
namespace SWGSDRangel
{
class SWGDeviceSettings;
class SWGDeviceState;
class SWGDeviceReport;
class SWGDeviceActions;
}
class SDRBASE_API DeviceSampleSource : public QObject {
Q_OBJECT
public:
typedef enum {
FC_POS_INFRA = 0,
FC_POS_SUPRA,
FC_POS_CENTER
} fcPos_t;
typedef enum {
FSHIFT_STD = 0, // Standard Rx independent
FSHIFT_TXSYNC // Follows same scheme as Tx
} FrequencyShiftScheme;
DeviceSampleSource();
virtual ~DeviceSampleSource();
virtual void destroy() = 0;
virtual void init() = 0; //!< initializations to be done when all collaborating objects are created and possibly connected
virtual bool start() = 0;
virtual void stop() = 0;
virtual QByteArray serialize() const = 0;
virtual bool deserialize(const QByteArray& data) = 0;
virtual const QString& getDeviceDescription() const = 0;
virtual int getSampleRate() const = 0; //!< Sample rate exposed by the source
virtual void setSampleRate(int sampleRate) = 0; //!< For when the source sample rate is set externally
virtual quint64 getCenterFrequency() const = 0; //!< Center frequency exposed by the source
virtual void setCenterFrequency(qint64 centerFrequency) = 0;
virtual bool handleMessage(const Message& message) = 0;
virtual int webapiSettingsGet(
SWGSDRangel::SWGDeviceSettings& response,
QString& errorMessage)
{
(void) response;
errorMessage = "Not implemented";
return 501;
}
virtual int webapiSettingsPutPatch(
bool force, //!< true to force settings = put
const QStringList& deviceSettingsKeys,
SWGSDRangel::SWGDeviceSettings& response,
QString& errorMessage)
{
(void) force;
(void) deviceSettingsKeys;
(void) response;
errorMessage = "Not implemented";
return 501;
}
virtual int webapiRunGet(
SWGSDRangel::SWGDeviceState& response,
QString& errorMessage)
{
(void) response;
errorMessage = "Not implemented";
return 501;
}
virtual int webapiRun(bool run,
SWGSDRangel::SWGDeviceState& response,
QString& errorMessage)
{
(void) run;
(void) response;
errorMessage = "Not implemented";
return 501;
}
virtual int webapiReportGet(
SWGSDRangel::SWGDeviceReport& response,
QString& errorMessage)
{
(void) response;
errorMessage = "Not implemented";
return 501;
}
virtual int webapiActionsPost(
const QStringList& deviceSettingsKeys,
SWGSDRangel::SWGDeviceActions& actions,
QString& errorMessage)
{
(void) deviceSettingsKeys;
(void) actions;
errorMessage = "Not implemented";
return 501;
}
MessageQueue *getInputMessageQueue() { return &m_inputMessageQueue; }
virtual void setMessageQueueToGUI(MessageQueue *queue) = 0; // pure virtual so that child classes must have to deal with this
MessageQueue *getMessageQueueToGUI() { return m_guiMessageQueue; }
SampleSinkFifo* getSampleFifo() { return &m_sampleFifo; }
static qint64 calculateDeviceCenterFrequency(
quint64 centerFrequency,
qint64 transverterDeltaFrequency,
int log2Decim,
fcPos_t fcPos,
quint32 devSampleRate,
FrequencyShiftScheme frequencyShiftScheme,
bool transverterMode = false
);
static qint64 calculateCenterFrequency(
quint64 deviceCenterFrequency,
qint64 transverterDeltaFrequency,
int log2Decim,
fcPos_t fcPos,
quint32 devSampleRate,
FrequencyShiftScheme frequencyShiftScheme,
bool transverterMode = false
);
static qint32 calculateFrequencyShift(
int log2Decim,
fcPos_t fcPos,
quint32 devSampleRate,
FrequencyShiftScheme frequencyShiftScheme
);
protected slots:
void handleInputMessages();
signals:
void positionChanged(float latitude, float longitude, float altitude);
void directionChanged(bool isotropic, float azimuth, float elevation);
protected:
SampleSinkFifo m_sampleFifo;
MessageQueue m_inputMessageQueue; //!< Input queue to the source
MessageQueue *m_guiMessageQueue; //!< Input message queue to the GUI
};
#endif // INCLUDE_SAMPLESOURCE_H

View File

@@ -0,0 +1,247 @@
///////////////////////////////////////////////////////////////////////////////////
// Copyright (C) 2012 maintech GmbH, Otto-Hahn-Str. 15, 97204 Hoechberg, Germany //
// written by Christian Daniel //
// Copyright (C) 2016-2019 Edouard Griffiths, F4EXB <f4exb06@gmail.com> //
// //
// This program 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 as version 3 of the License, or //
// (at your option) any later version. //
// //
// This program 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 V3 for more details. //
// //
// You should have received a copy of the GNU General Public License //
// along with this program. If not, see <http://www.gnu.org/licenses/>. //
///////////////////////////////////////////////////////////////////////////////////
#include <QDebug>
#include "devicesamplestatic.h"
int64_t DeviceSampleStatic::calculateSourceDeviceCenterFrequency(
uint64_t centerFrequency,
int64_t transverterDeltaFrequency,
int log2Decim,
fcPos_t fcPos,
uint32_t devSampleRate,
FrequencyShiftScheme frequencyShiftScheme,
bool transverterMode)
{
int64_t deviceCenterFrequency = centerFrequency;
deviceCenterFrequency -= transverterMode ? transverterDeltaFrequency : 0;
deviceCenterFrequency = deviceCenterFrequency < 0 ? 0 : deviceCenterFrequency;
int64_t f_img = deviceCenterFrequency;
deviceCenterFrequency -= calculateSourceFrequencyShift(log2Decim, fcPos, devSampleRate, frequencyShiftScheme);
f_img -= 2*calculateSourceFrequencyShift(log2Decim, fcPos, devSampleRate, frequencyShiftScheme);
qDebug() << "DeviceSampleStatic::calculateSourceDeviceCenterFrequency:"
<< " frequencyShiftScheme: " << frequencyShiftScheme
<< " desired center freq: " << centerFrequency << " Hz"
<< " device center freq: " << deviceCenterFrequency << " Hz"
<< " device sample rate: " << devSampleRate << "S/s"
<< " Actual sample rate: " << devSampleRate/(1<<log2Decim) << "S/s"
<< " center freq position code: " << fcPos
<< " image frequency: " << f_img << "Hz";
return deviceCenterFrequency;
}
int64_t DeviceSampleStatic::calculateSourceCenterFrequency(
uint64_t deviceCenterFrequency,
int64_t transverterDeltaFrequency,
int log2Decim,
fcPos_t fcPos,
uint32_t devSampleRate,
FrequencyShiftScheme frequencyShiftScheme,
bool transverterMode)
{
qint64 centerFrequency = deviceCenterFrequency;
centerFrequency += calculateSourceFrequencyShift(log2Decim, fcPos, devSampleRate, frequencyShiftScheme);
centerFrequency += transverterMode ? transverterDeltaFrequency : 0;
centerFrequency = centerFrequency < 0 ? 0 : centerFrequency;
qDebug() << "DeviceSampleStatic::calculateSourceCenterFrequency:"
<< " frequencyShiftScheme: " << frequencyShiftScheme
<< " desired center freq: " << centerFrequency << " Hz"
<< " device center freq: " << deviceCenterFrequency << " Hz"
<< " device sample rate: " << devSampleRate << "S/s"
<< " Actual sample rate: " << devSampleRate/(1<<log2Decim) << "S/s"
<< " center freq position code: " << fcPos;
return centerFrequency;
}
/**
* log2Decim = 0: no shift
*
* n = log2Decim <= 2: fc = +/- 1/2^(n-1)
* center
* | ^ |
* | inf | sup |
* ^ ^
*
* n = log2Decim > 2: fc = +/- 1/2^n
* center
* | ^ |
* | |inf| | |sup| |
* ^ ^
*/
int DeviceSampleStatic::calculateSourceFrequencyShift(
int log2Decim,
fcPos_t fcPos,
uint32_t devSampleRate,
FrequencyShiftScheme frequencyShiftScheme)
{
if (frequencyShiftScheme == FSHIFT_STD)
{
if (log2Decim == 0) { // no shift at all
return 0;
} else if (log2Decim < 3) {
if (fcPos == FC_POS_INFRA) { // shift in the square next to center frequency
return -(devSampleRate / (1<<(log2Decim+1)));
} else if (fcPos == FC_POS_SUPRA) {
return devSampleRate / (1<<(log2Decim+1));
} else {
return 0;
}
} else {
if (fcPos == FC_POS_INFRA) { // shift centered in the square next to center frequency
return -(devSampleRate / (1<<(log2Decim)));
} else if (fcPos == FC_POS_SUPRA) {
return devSampleRate / (1<<(log2Decim));
} else {
return 0;
}
}
}
else // frequencyShiftScheme == FSHIFT_TXSYNC
{
if (fcPos == FC_POS_CENTER) {
return 0;
}
int sign = fcPos == FC_POS_INFRA ? -1 : 1;
int halfSampleRate = devSampleRate / 2; // fractions are relative to sideband thus based on half the sample rate
if (log2Decim == 0) {
return 0;
} else if (log2Decim == 1) {
return sign * (halfSampleRate / 2); // inf or sup: 1/2
} else if (log2Decim == 2) {
return sign * ((halfSampleRate * 3) / 4); // inf, inf or sup, sup: 1/2 + 1/4
} else if (log2Decim == 3) {
return sign * ((halfSampleRate * 5) / 8); // inf, inf, sup or sup, sup, inf: 1/2 + 1/4 - 1/8 = 5/8
} else if (log2Decim == 4) {
return sign * ((halfSampleRate * 11) / 16); // inf, inf, sup, inf or sup, sup, inf, sup: 1/2 + 1/4 - 1/8 + 1/16 = 11/16
} else if (log2Decim == 5) {
return sign * ((halfSampleRate * 21) / 32); // inf, inf, sup, inf, sup or sup, sup, inf, sup, inf: 1/2 + 1/4 - 1/8 + 1/16 - 1/32 = 21/32
} else if (log2Decim == 6) {
return sign * ((halfSampleRate * 21) / 64); // inf, sup, inf, sup, inf, sup or sup, inf, sup, inf, sup, inf: 1/2 - 1/4 + 1/8 -1/16 + 1/32 - 1/64 = 21/64
} else {
return 0;
}
}
}
int64_t DeviceSampleStatic::calculateSinkDeviceCenterFrequency(
uint64_t centerFrequency,
int64_t transverterDeltaFrequency,
int log2Interp,
fcPos_t fcPos,
uint32_t devSampleRate,
bool transverterMode)
{
int64_t deviceCenterFrequency = centerFrequency;
deviceCenterFrequency -= transverterMode ? transverterDeltaFrequency : 0;
deviceCenterFrequency = deviceCenterFrequency < 0 ? 0 : deviceCenterFrequency;
int64_t f_img = deviceCenterFrequency;
deviceCenterFrequency -= calculateSinkFrequencyShift(log2Interp, fcPos, devSampleRate);
f_img -= 2*calculateSinkFrequencyShift(log2Interp, fcPos, devSampleRate);
qDebug() << "DeviceSampleStatic::calculateSinkDeviceCenterFrequency:"
<< " desired center freq: " << centerFrequency << " Hz"
<< " device center freq: " << deviceCenterFrequency << " Hz"
<< " device sample rate: " << devSampleRate << "S/s"
<< " Actual sample rate: " << devSampleRate/(1<<log2Interp) << "S/s"
<< " center freq position code: " << fcPos
<< " image frequency: " << f_img << "Hz";
return deviceCenterFrequency;
}
int64_t DeviceSampleStatic::calculateSinkCenterFrequency(
uint64_t deviceCenterFrequency,
int64_t transverterDeltaFrequency,
int log2Interp,
fcPos_t fcPos,
uint32_t devSampleRate,
bool transverterMode)
{
int64_t centerFrequency = deviceCenterFrequency;
centerFrequency += calculateSinkFrequencyShift(log2Interp, fcPos, devSampleRate);
centerFrequency += transverterMode ? transverterDeltaFrequency : 0;
centerFrequency = centerFrequency < 0 ? 0 : centerFrequency;
qDebug() << "DeviceSampleStatic::calculateSinkCenterFrequency:"
<< " desired center freq: " << centerFrequency << " Hz"
<< " device center freq: " << deviceCenterFrequency << " Hz"
<< " device sample rate: " << devSampleRate << "S/s"
<< " Actual sample rate: " << devSampleRate/(1<<log2Interp) << "S/s"
<< " center freq position code: " << fcPos;
return centerFrequency;
}
/**
* log2Interp = 0: no shift
*
* log2Interp = 1: middle of side band (inf or sup: 1/2)
* ^ | ^
* | inf | inf | sup | sup |
*
* log2Interp = 2: middle of far side half side band (inf, inf or sup, sup: 1/2 + 1/4)
* ^ | ^
* | inf | inf | sup | sup |
*
* log2Interp = 3: inf, inf, sup or sup, sup, inf: 1/2 + 1/4 - 1/8 = 5/8
* log2Interp = 4: inf, inf, sup, inf or sup, sup, inf, sup: 1/2 + 1/4 - 1/8 + 1/16 = 11/16
* log2Interp = 5: inf, inf, sup, inf, sup or sup, sup, inf, sup, inf: 1/2 + 1/4 - 1/8 + 1/16 - 1/32 = 21/32
* log2Interp = 6: inf, sup, inf, sup, inf, sup or sup, inf, sup, inf, sup, inf: 1/2 - 1/4 + 1/8 -1/16 + 1/32 - 1/64 = 21/64
*
*/
int DeviceSampleStatic::calculateSinkFrequencyShift(
int log2Interp,
fcPos_t fcPos,
uint32_t devSampleRate)
{
if (fcPos == FC_POS_CENTER) {
return 0;
}
int sign = fcPos == FC_POS_INFRA ? -1 : 1;
int halfSampleRate = devSampleRate / 2; // fractions are relative to sideband thus based on half the sample rate
if (log2Interp == 0) {
return 0;
} else if (log2Interp == 1) {
return sign * (halfSampleRate / 2);
} else if (log2Interp == 2) {
return sign * ((halfSampleRate * 3) / 4);
} else if (log2Interp == 3) {
return sign * ((halfSampleRate * 5) / 8);
} else if (log2Interp == 4) {
return sign * ((halfSampleRate * 11) / 16);
} else if (log2Interp == 5) {
return sign * ((halfSampleRate * 21) / 32);
} else if (log2Interp == 6) {
return sign * ((halfSampleRate * 21) / 64);
} else {
return 0;
}
}

View File

@@ -0,0 +1,83 @@
///////////////////////////////////////////////////////////////////////////////////
// Copyright (C) 2019-2020 Edouard Griffiths, F4EXB <f4exb06@gmail.com> //
// //
// This program 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 as version 3 of the License, or //
// (at your option) any later version. //
// //
// This program 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 V3 for more details. //
// //
// You should have received a copy of the GNU General Public License //
// along with this program. If not, see <http://www.gnu.org/licenses/>. //
///////////////////////////////////////////////////////////////////////////////////
#include <stdint.h>
#include "export.h"
class SDRBASE_API DeviceSampleStatic
{
public:
typedef enum {
FC_POS_INFRA = 0,
FC_POS_SUPRA,
FC_POS_CENTER
} fcPos_t;
typedef enum {
FSHIFT_STD = 0, // Standard Rx independent
FSHIFT_TXSYNC // Follows same scheme as Tx
} FrequencyShiftScheme;
static int64_t calculateSourceDeviceCenterFrequency(
uint64_t centerFrequency,
int64_t transverterDeltaFrequency,
int log2Decim,
fcPos_t fcPos,
uint32_t devSampleRate,
FrequencyShiftScheme frequencyShiftScheme,
bool transverterMode = false
);
static int64_t calculateSourceCenterFrequency(
uint64_t deviceCenterFrequency,
int64_t transverterDeltaFrequency,
int log2Decim,
fcPos_t fcPos,
uint32_t devSampleRate,
FrequencyShiftScheme frequencyShiftScheme,
bool transverterMode = false
);
static int calculateSourceFrequencyShift(
int log2Decim,
fcPos_t fcPos,
uint32_t devSampleRate,
FrequencyShiftScheme frequencyShiftScheme
);
static int64_t calculateSinkDeviceCenterFrequency(
uint64_t centerFrequency,
int64_t transverterDeltaFrequency,
int log2Interp,
fcPos_t fcPos,
uint32_t devSampleRate,
bool transverterMode = false);
static int64_t calculateSinkCenterFrequency(
uint64_t deviceCenterFrequency,
int64_t transverterDeltaFrequency,
int log2Interp,
fcPos_t fcPos,
uint32_t devSampleRate,
bool transverterMode = false);
static int calculateSinkFrequencyShift(
int log2Interp,
fcPos_t fcPos,
uint32_t devSampleRate);
};

View File

@@ -0,0 +1,336 @@
///////////////////////////////////////////////////////////////////////////////////
// Copyright (C) 2012 maintech GmbH, Otto-Hahn-Str. 15, 97204 Hoechberg, Germany //
// written by Christian Daniel //
// Copyright (C) 2015-2020, 2023 Edouard Griffiths, F4EXB <f4exb06@gmail.com> //
// Copyright (C) 2023 Jon Beniston, M7RCE <jon@beniston.com> //
// //
// This program 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 as version 3 of the License, or //
// (at your option) any later version. //
// //
// This program 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 V3 for more details. //
// //
// You should have received a copy of the GNU General Public License //
// along with this program. If not, see <http://www.gnu.org/licenses/>. //
///////////////////////////////////////////////////////////////////////////////////
#include <array>
#include <QString>
#include <QDebug>
#include "dsp/hbfilterchainconverter.h"
#include "downchannelizer.h"
DownChannelizer::DownChannelizer(ChannelSampleSink* sampleSink) :
m_filterChainSetMode(false),
m_sampleSink(sampleSink),
m_basebandSampleRate(0),
m_requestedOutputSampleRate(0),
m_requestedCenterFrequency(0),
m_channelSampleRate(0),
m_channelFrequencyOffset(0),
m_log2Decim(0),
m_filterChainHash(0)
{
}
DownChannelizer::~DownChannelizer()
{
freeFilterChain();
}
void DownChannelizer::feed(const SampleVector::const_iterator& begin, const SampleVector::const_iterator& end)
{
if (m_sampleSink == 0)
{
m_sampleBuffer.clear();
return;
}
if (m_filterStages.size() == 0) // optimization when no downsampling is done anyway
{
m_sampleSink->feed(begin, end);
}
else
{
for (SampleVector::const_iterator sample = begin; sample != end; ++sample)
{
Sample s(*sample);
FilterStages::iterator stage = m_filterStages.begin();
for (; stage != m_filterStages.end(); ++stage)
{
#ifndef SDR_RX_SAMPLE_24BIT
s.m_real /= 2; // avoid saturation on 16 bit samples
s.m_imag /= 2;
#endif
if (!(*stage)->work(&s)) {
break;
}
}
if(stage == m_filterStages.end())
{
#ifdef SDR_RX_SAMPLE_24BIT
s.m_real /= (1<<(m_filterStages.size())); // on 32 bit samples there is enough headroom to just divide the final result
s.m_imag /= (1<<(m_filterStages.size()));
#endif
m_sampleBuffer.push_back(s);
}
}
m_sampleSink->feed(m_sampleBuffer.begin(), m_sampleBuffer.end());
m_sampleBuffer.clear();
}
}
void DownChannelizer::setChannelization(int requestedSampleRate, qint64 requestedCenterFrequency)
{
if (requestedSampleRate < 0)
{
qWarning("DownChannelizer::setChannelization: wrong sample rate requested: %d", requestedSampleRate);
return;
}
m_requestedOutputSampleRate = requestedSampleRate;
m_requestedCenterFrequency = requestedCenterFrequency;
applyChannelization();
}
void DownChannelizer::setBasebandSampleRate(int basebandSampleRate, bool decim)
{
m_basebandSampleRate = basebandSampleRate;
if (decim) {
applyDecimation();
} else {
applyChannelization();
}
}
void DownChannelizer::applyChannelization()
{
m_filterChainSetMode = false;
if (m_basebandSampleRate == 0)
{
qDebug() << "DownChannelizer::applyChannelization: aborting (in=0)"
<< " in (baseband):" << m_basebandSampleRate
<< " req:" << m_requestedOutputSampleRate
<< " out (channel):" << m_channelSampleRate
<< " fc:" << m_channelFrequencyOffset;
return;
}
freeFilterChain();
m_channelFrequencyOffset = createFilterChain(
m_basebandSampleRate / -2, m_basebandSampleRate / 2,
m_requestedCenterFrequency - m_requestedOutputSampleRate / 2, m_requestedCenterFrequency + m_requestedOutputSampleRate / 2);
m_channelSampleRate = m_basebandSampleRate / (1 << m_filterStages.size());
qDebug() << "DownChannelizer::applyChannelization done:"
<< " nb stages:" << m_filterStages.size()
<< " in (baseband):" << m_basebandSampleRate
<< " req:" << m_requestedOutputSampleRate
<< " out (channel):" << m_channelSampleRate
<< " fc:" << m_channelFrequencyOffset;
}
void DownChannelizer::setDecimation(unsigned int log2Decim, unsigned int filterChainHash)
{
m_log2Decim = log2Decim;
m_filterChainHash = filterChainHash;
applyDecimation();
}
void DownChannelizer::applyDecimation()
{
m_filterChainSetMode = true;
std::vector<unsigned int> stageIndexes;
m_channelFrequencyOffset = m_basebandSampleRate * HBFilterChainConverter::convertToIndexes(m_log2Decim, m_filterChainHash, stageIndexes);
m_requestedCenterFrequency = m_channelFrequencyOffset;
freeFilterChain();
m_channelFrequencyOffset = m_basebandSampleRate * setFilterChain(stageIndexes);
m_channelSampleRate = m_basebandSampleRate / (1 << m_filterStages.size());
m_requestedOutputSampleRate = m_channelSampleRate;
qDebug() << "DownChannelizer::applyDecimation:"
<< " m_log2Decim:" << m_log2Decim
<< " m_filterChainHash:" << m_filterChainHash
<< " out:" << m_basebandSampleRate
<< " in:" << m_channelSampleRate
<< " fc:" << m_channelFrequencyOffset;
}
#ifdef SDR_RX_SAMPLE_24BIT
DownChannelizer::FilterStage::FilterStage(Mode mode) :
m_filter(new IntHalfbandFilterEO<qint64, qint64, DOWNCHANNELIZER_HB_FILTER_ORDER, true>),
m_workFunction(0),
m_mode(mode),
m_sse(true)
{
switch(mode) {
case ModeCenter:
m_workFunction = &IntHalfbandFilterEO<qint64, qint64, DOWNCHANNELIZER_HB_FILTER_ORDER, true>::workDecimateCenter;
break;
case ModeLowerHalf:
m_workFunction = &IntHalfbandFilterEO<qint64, qint64, DOWNCHANNELIZER_HB_FILTER_ORDER, true>::workDecimateLowerHalf;
break;
case ModeUpperHalf:
m_workFunction = &IntHalfbandFilterEO<qint64, qint64, DOWNCHANNELIZER_HB_FILTER_ORDER, true>::workDecimateUpperHalf;
break;
}
}
#else
DownChannelizer::FilterStage::FilterStage(Mode mode) :
m_filter(new IntHalfbandFilterEO<qint32, qint32, DOWNCHANNELIZER_HB_FILTER_ORDER, true>),
m_workFunction(0),
m_mode(mode),
m_sse(true)
{
switch(mode) {
case ModeCenter:
m_workFunction = &IntHalfbandFilterEO<qint32, qint32, DOWNCHANNELIZER_HB_FILTER_ORDER, true>::workDecimateCenter;
break;
case ModeLowerHalf:
m_workFunction = &IntHalfbandFilterEO<qint32, qint32, DOWNCHANNELIZER_HB_FILTER_ORDER, true>::workDecimateLowerHalf;
break;
case ModeUpperHalf:
m_workFunction = &IntHalfbandFilterEO<qint32, qint32, DOWNCHANNELIZER_HB_FILTER_ORDER, true>::workDecimateUpperHalf;
break;
}
}
#endif
DownChannelizer::FilterStage::~FilterStage()
{
delete m_filter;
}
Real DownChannelizer::channelMinSpace(Real sigStart, Real sigEnd, Real chanStart, Real chanEnd)
{
Real leftSpace = chanStart - sigStart;
Real rightSpace = sigEnd - chanEnd;
return std::min(leftSpace, rightSpace);
}
Real DownChannelizer::createFilterChain(Real sigStart, Real sigEnd, Real chanStart, Real chanEnd)
{
Real sigBw = sigEnd - sigStart;
Real chanBw = chanEnd - chanStart;
Real rot = sigBw / 4;
std::array<Real, 3> filterMinSpaces; // Array of left, center and right filter min spaces respectively
filterMinSpaces[0] = channelMinSpace(sigStart, sigStart + sigBw / 2.0, chanStart, chanEnd);
filterMinSpaces[1] = channelMinSpace(sigStart + rot, sigEnd - rot, chanStart, chanEnd);
filterMinSpaces[2] = channelMinSpace(sigEnd - sigBw / 2.0f, sigEnd, chanStart, chanEnd);
auto maxIt = std::max_element(filterMinSpaces.begin(), filterMinSpaces.end());
int maxIndex = maxIt - filterMinSpaces.begin();
Real maxValue = *maxIt;
qDebug("DownChannelizer::createFilterChain: Signal [%.1f, %.1f] (BW %.1f) Channel [%.1f, %.1f] (BW %.1f) Selected: %d (fit %.1f)",
sigStart, sigEnd, sigBw, chanStart, chanEnd, chanBw, maxIndex, maxValue);
if ((sigStart < sigEnd) && (chanStart < chanEnd) && (maxValue >= chanBw/8.0))
{
if (maxIndex == 0)
{
m_filterStages.push_back(new FilterStage(FilterStage::ModeLowerHalf));
return createFilterChain(sigStart, sigStart + sigBw / 2.0, chanStart, chanEnd);
}
if (maxIndex == 1)
{
m_filterStages.push_back(new FilterStage(FilterStage::ModeCenter));
return createFilterChain(sigStart + rot, sigEnd - rot, chanStart, chanEnd);
}
if (maxIndex == 2)
{
m_filterStages.push_back(new FilterStage(FilterStage::ModeUpperHalf));
return createFilterChain(sigEnd - sigBw / 2.0f, sigEnd, chanStart, chanEnd);
}
}
Real ofs = ((chanEnd - chanStart) / 2.0 + chanStart) - ((sigEnd - sigStart) / 2.0 + sigStart);
qDebug("DownChannelizer::createFilterChain: -> complete (final BW %.1f, frequency offset %.1f)", sigBw, ofs);
return ofs;
}
double DownChannelizer::setFilterChain(const std::vector<unsigned int>& stageIndexes)
{
// filters are described from lower to upper level but the chain is constructed the other way round
std::vector<unsigned int>::const_reverse_iterator rit = stageIndexes.rbegin();
double ofs = 0.0, ofs_stage = 0.25;
// Each index is a base 3 number with 0 = low, 1 = center, 2 = high
// Functions at upper level will convert a number to base 3 to describe the filter chain. Common converting
// algorithms will go from LSD to MSD. This explains the reverse order.
for (; rit != stageIndexes.rend(); ++rit)
{
if (*rit == 0)
{
m_filterStages.push_back(new FilterStage(FilterStage::ModeLowerHalf));
ofs -= ofs_stage;
qDebug("DownChannelizer::setFilterChain: lower half: ofs: %f", ofs);
}
else if (*rit == 1)
{
m_filterStages.push_back(new FilterStage(FilterStage::ModeCenter));
qDebug("DownChannelizer::setFilterChain: center: ofs: %f", ofs);
}
else if (*rit == 2)
{
m_filterStages.push_back(new FilterStage(FilterStage::ModeUpperHalf));
ofs += ofs_stage;
qDebug("DownChannelizer::setFilterChain: upper half: ofs: %f", ofs);
}
}
return ofs;
}
void DownChannelizer::freeFilterChain()
{
for(FilterStages::iterator it = m_filterStages.begin(); it != m_filterStages.end(); ++it)
delete *it;
m_filterStages.clear();
}
void DownChannelizer::debugFilterChain()
{
qDebug("DownChannelizer::debugFilterChain: %lu stages", m_filterStages.size());
for(FilterStages::iterator it = m_filterStages.begin(); it != m_filterStages.end(); ++it)
{
switch ((*it)->m_mode)
{
case FilterStage::ModeCenter:
qDebug("DownChannelizer::debugFilterChain: center %s", (*it)->m_sse ? "sse" : "no_sse");
break;
case FilterStage::ModeLowerHalf:
qDebug("DownChannelizer::debugFilterChain: lower %s", (*it)->m_sse ? "sse" : "no_sse");
break;
case FilterStage::ModeUpperHalf:
qDebug("DownChannelizer::debugFilterChain: upper %s", (*it)->m_sse ? "sse" : "no_sse");
break;
default:
qDebug("DownChannelizer::debugFilterChain: none %s", (*it)->m_sse ? "sse" : "no_sse");
break;
}
}
}

View File

@@ -0,0 +1,97 @@
///////////////////////////////////////////////////////////////////////////////////
// Copyright (C) 2012 maintech GmbH, Otto-Hahn-Str. 15, 97204 Hoechberg, Germany //
// written by Christian Daniel //
// Copyright (C) 2015-2020, 2023 Edouard Griffiths, F4EXB <f4exb06@gmail.com> //
// //
// This program 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 as version 3 of the License, or //
// (at your option) any later version. //
// //
// This program 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 V3 for more details. //
// //
// You should have received a copy of the GNU General Public License //
// along with this program. If not, see <http://www.gnu.org/licenses/>. //
///////////////////////////////////////////////////////////////////////////////////
#ifndef SDRBASE_DSP_DOWNCHANNELIZER_H
#define SDRBASE_DSP_DOWNCHANNELIZER_H
#include <list>
#include <vector>
#include "export.h"
#include "dsp/inthalfbandfiltereo.h"
#include "channelsamplesink.h"
#define DOWNCHANNELIZER_HB_FILTER_ORDER 48
class SDRBASE_API DownChannelizer : public ChannelSampleSink {
public:
DownChannelizer(ChannelSampleSink* sampleSink);
virtual ~DownChannelizer();
virtual void feed(const SampleVector::const_iterator& begin, const SampleVector::const_iterator& end);
void setDecimation(unsigned int log2Decim, unsigned int filterChainHash); //!< Define channelizer with decimation factor and filter chain definition
void setChannelization(int requestedSampleRate, qint64 requestedCenterFrequency); //!< Define channelizer with requested sample rate and center frequency (shift in the baseband)
void setBasebandSampleRate(int basebandSampleRate, bool decim = false); //!< decim: true => use direct decimation false => use channel configuration
int getBasebandSampleRate() const { return m_basebandSampleRate; }
int getChannelSampleRate() const { return m_channelSampleRate; }
int getChannelFrequencyOffset() const { return m_channelFrequencyOffset; }
protected:
struct FilterStage {
enum Mode {
ModeCenter,
ModeLowerHalf,
ModeUpperHalf
};
#ifdef SDR_RX_SAMPLE_24BIT
typedef bool (IntHalfbandFilterEO<qint64, qint64, DOWNCHANNELIZER_HB_FILTER_ORDER, true>::*WorkFunction)(Sample* s);
IntHalfbandFilterEO<qint64, qint64, DOWNCHANNELIZER_HB_FILTER_ORDER, true>* m_filter;
#else
typedef bool (IntHalfbandFilterEO<qint32, qint32, DOWNCHANNELIZER_HB_FILTER_ORDER, true>::*WorkFunction)(Sample* s);
IntHalfbandFilterEO<qint32, qint32, DOWNCHANNELIZER_HB_FILTER_ORDER, true>* m_filter;
#endif
WorkFunction m_workFunction;
Mode m_mode;
bool m_sse;
FilterStage(Mode mode);
~FilterStage();
bool work(Sample* sample)
{
return (m_filter->*m_workFunction)(sample);
}
};
typedef std::list<FilterStage*> FilterStages;
FilterStages m_filterStages;
bool m_filterChainSetMode;
ChannelSampleSink* m_sampleSink; //!< Demodulator
int m_basebandSampleRate;
int m_requestedOutputSampleRate;
int m_requestedCenterFrequency;
int m_channelSampleRate;
int m_channelFrequencyOffset;
unsigned int m_log2Decim;
unsigned int m_filterChainHash;
SampleVector m_sampleBuffer;
void applyChannelization();
void applyDecimation();
static Real channelMinSpace(Real sigStart, Real sigEnd, Real chanStart, Real chanEnd);
Real createFilterChain(Real sigStart, Real sigEnd, Real chanStart, Real chanEnd);
double setFilterChain(const std::vector<unsigned int>& stageIndexes);
void freeFilterChain();
void debugFilterChain();
};
#endif // SDRBASE_DSP_DOWNCHANNELIZER_H

View File

@@ -0,0 +1,48 @@
///////////////////////////////////////////////////////////////////////////////////
// Copyright (C) 2012 maintech GmbH, Otto-Hahn-Str. 15, 97204 Hoechberg, Germany //
// written by Christian Daniel //
// Copyright (C) 2015-2016, 2018-2019, 2022 Edouard Griffiths, F4EXB <f4exb06@gmail.com> //
// //
// This program 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 as version 3 of the License, or //
// (at your option) any later version. //
// //
// This program 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 V3 for more details. //
// //
// You should have received a copy of the GNU General Public License //
// along with this program. If not, see <http://www.gnu.org/licenses/>. //
///////////////////////////////////////////////////////////////////////////////////
#include "dsp/dspcommands.h"
MESSAGE_CLASS_DEFINITION(DSPAcquisitionInit, Message)
MESSAGE_CLASS_DEFINITION(DSPAcquisitionStart, Message)
MESSAGE_CLASS_DEFINITION(DSPAcquisitionStop, Message)
MESSAGE_CLASS_DEFINITION(DSPGenerationInit, Message)
MESSAGE_CLASS_DEFINITION(DSPGenerationStart, Message)
MESSAGE_CLASS_DEFINITION(DSPGenerationStop, Message)
MESSAGE_CLASS_DEFINITION(DSPGetSourceDeviceDescription, Message)
MESSAGE_CLASS_DEFINITION(DSPGetSinkDeviceDescription, Message)
MESSAGE_CLASS_DEFINITION(DSPGetErrorMessage, Message)
MESSAGE_CLASS_DEFINITION(DSPSetSource, Message)
MESSAGE_CLASS_DEFINITION(DSPSetSink, Message)
MESSAGE_CLASS_DEFINITION(DSPAddBasebandSampleSink, Message)
MESSAGE_CLASS_DEFINITION(DSPAddSpectrumSink, Message)
MESSAGE_CLASS_DEFINITION(DSPAddBasebandSampleSource, Message)
MESSAGE_CLASS_DEFINITION(DSPRemoveBasebandSampleSink, Message)
MESSAGE_CLASS_DEFINITION(DSPRemoveSpectrumSink, Message)
MESSAGE_CLASS_DEFINITION(DSPRemoveBasebandSampleSource, Message)
MESSAGE_CLASS_DEFINITION(DSPAddAudioSink, Message)
MESSAGE_CLASS_DEFINITION(DSPRemoveAudioSink, Message)
MESSAGE_CLASS_DEFINITION(DSPConfigureCorrection, Message)
MESSAGE_CLASS_DEFINITION(DSPEngineReport, Message)
MESSAGE_CLASS_DEFINITION(DSPConfigureScopeVis, Message)
MESSAGE_CLASS_DEFINITION(DSPSignalNotification, Message)
MESSAGE_CLASS_DEFINITION(DSPMIMOSignalNotification, Message)
MESSAGE_CLASS_DEFINITION(DSPConfigureChannelizer, Message)
MESSAGE_CLASS_DEFINITION(DSPConfigureAudio, Message)
MESSAGE_CLASS_DEFINITION(DSPPushMbeFrame, Message)

View File

@@ -0,0 +1,400 @@
///////////////////////////////////////////////////////////////////////////////////
// Copyright (C) 2012 maintech GmbH, Otto-Hahn-Str. 15, 97204 Hoechberg, Germany //
// written by Christian Daniel //
// Copyright (C) 2015-2016, 2018-2019, 2022-2023 Edouard Griffiths, F4EXB <f4exb06@gmail.com> //
// //
// This program 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 as version 3 of the License, or //
// (at your option) any later version. //
// //
// This program 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 V3 for more details. //
// //
// You should have received a copy of the GNU General Public License //
// along with this program. If not, see <http://www.gnu.org/licenses/>. //
///////////////////////////////////////////////////////////////////////////////////
#ifndef INCLUDE_DSPCOMMANDS_H
#define INCLUDE_DSPCOMMANDS_H
#include <QString>
#include "dsp/dsptypes.h"
#include "util/message.h"
#include "export.h"
class DeviceSampleSource;
class BasebandSampleSink;
class DeviceSampleSink;
class BasebandSampleSource;
class AudioFifo;
class SDRBASE_API DSPAcquisitionInit : public Message {
MESSAGE_CLASS_DECLARATION
};
class SDRBASE_API DSPAcquisitionStart : public Message {
MESSAGE_CLASS_DECLARATION
};
class SDRBASE_API DSPAcquisitionStop : public Message {
MESSAGE_CLASS_DECLARATION
};
class SDRBASE_API DSPGenerationInit : public Message {
MESSAGE_CLASS_DECLARATION
};
class SDRBASE_API DSPGenerationStart : public Message {
MESSAGE_CLASS_DECLARATION
};
class SDRBASE_API DSPGenerationStop : public Message {
MESSAGE_CLASS_DECLARATION
};
class SDRBASE_API DSPGetSourceDeviceDescription : public Message {
MESSAGE_CLASS_DECLARATION
public:
void setDeviceDescription(const QString& text) { m_deviceDescription = text; }
const QString& getDeviceDescription() const { return m_deviceDescription; }
private:
QString m_deviceDescription;
};
class SDRBASE_API DSPGetSinkDeviceDescription : public Message {
MESSAGE_CLASS_DECLARATION
public:
void setDeviceDescription(const QString& text) { m_deviceDescription = text; }
const QString& getDeviceDescription() const { return m_deviceDescription; }
private:
QString m_deviceDescription;
};
class SDRBASE_API DSPGetErrorMessage : public Message {
MESSAGE_CLASS_DECLARATION
public:
void setErrorMessage(const QString& text) { m_errorMessage = text; }
const QString& getErrorMessage() const { return m_errorMessage; }
private:
QString m_errorMessage;
};
class SDRBASE_API DSPSetSource : public Message {
MESSAGE_CLASS_DECLARATION
public:
DSPSetSource(DeviceSampleSource* sampleSource) : Message(), m_sampleSource(sampleSource) { }
DeviceSampleSource* getSampleSource() const { return m_sampleSource; }
private:
DeviceSampleSource* m_sampleSource;
};
class SDRBASE_API DSPSetSink : public Message {
MESSAGE_CLASS_DECLARATION
public:
DSPSetSink(DeviceSampleSink* sampleSink) : Message(), m_sampleSink(sampleSink) { }
DeviceSampleSink* getSampleSink() const { return m_sampleSink; }
private:
DeviceSampleSink* m_sampleSink;
};
class SDRBASE_API DSPAddBasebandSampleSink : public Message {
MESSAGE_CLASS_DECLARATION
public:
DSPAddBasebandSampleSink(BasebandSampleSink* sampleSink) : Message(), m_sampleSink(sampleSink) { }
BasebandSampleSink* getSampleSink() const { return m_sampleSink; }
private:
BasebandSampleSink* m_sampleSink;
};
class SDRBASE_API DSPAddSpectrumSink : public Message {
MESSAGE_CLASS_DECLARATION
public:
DSPAddSpectrumSink(BasebandSampleSink* sampleSink) : Message(), m_sampleSink(sampleSink) { }
BasebandSampleSink* getSampleSink() const { return m_sampleSink; }
private:
BasebandSampleSink* m_sampleSink;
};
class SDRBASE_API DSPAddBasebandSampleSource : public Message {
MESSAGE_CLASS_DECLARATION
public:
DSPAddBasebandSampleSource(BasebandSampleSource* sampleSource) : Message(), m_sampleSource(sampleSource) { }
BasebandSampleSource* getSampleSource() const { return m_sampleSource; }
private:
BasebandSampleSource* m_sampleSource;
};
class SDRBASE_API DSPRemoveBasebandSampleSink : public Message {
MESSAGE_CLASS_DECLARATION
public:
DSPRemoveBasebandSampleSink(BasebandSampleSink* sampleSink, bool deleting) : Message(), m_sampleSink(sampleSink), m_deleting(deleting) { }
BasebandSampleSink* getSampleSink() const { return m_sampleSink; }
bool getDeleting() const { return m_deleting; }
private:
BasebandSampleSink* m_sampleSink;
bool m_deleting;
};
class SDRBASE_API DSPRemoveSpectrumSink : public Message {
MESSAGE_CLASS_DECLARATION
public:
DSPRemoveSpectrumSink(BasebandSampleSink* sampleSink) : Message(), m_sampleSink(sampleSink) { }
BasebandSampleSink* getSampleSink() const { return m_sampleSink; }
private:
BasebandSampleSink* m_sampleSink;
};
class SDRBASE_API DSPRemoveBasebandSampleSource : public Message {
MESSAGE_CLASS_DECLARATION
public:
DSPRemoveBasebandSampleSource(BasebandSampleSource* sampleSource, bool deleting) : Message(), m_sampleSource(sampleSource), m_deleting(deleting) { }
BasebandSampleSource* getSampleSource() const { return m_sampleSource; }
bool getDeleting() const { return m_deleting; }
private:
BasebandSampleSource* m_sampleSource;
bool m_deleting;
};
class SDRBASE_API DSPAddAudioSink : public Message {
MESSAGE_CLASS_DECLARATION
public:
DSPAddAudioSink(AudioFifo* audioFifo) : Message(), m_audioFifo(audioFifo) { }
AudioFifo* getAudioFifo() const { return m_audioFifo; }
private:
AudioFifo* m_audioFifo;
};
class SDRBASE_API DSPRemoveAudioSink : public Message {
MESSAGE_CLASS_DECLARATION
public:
DSPRemoveAudioSink(AudioFifo* audioFifo) : Message(), m_audioFifo(audioFifo) { }
AudioFifo* getAudioFifo() const { return m_audioFifo; }
private:
AudioFifo* m_audioFifo;
};
class SDRBASE_API DSPConfigureCorrection : public Message {
MESSAGE_CLASS_DECLARATION
public:
DSPConfigureCorrection(bool dcOffsetCorrection, bool iqImbalanceCorrection) :
Message(),
m_dcOffsetCorrection(dcOffsetCorrection),
m_iqImbalanceCorrection(iqImbalanceCorrection)
{ }
bool getDCOffsetCorrection() const { return m_dcOffsetCorrection; }
bool getIQImbalanceCorrection() const { return m_iqImbalanceCorrection; }
private:
bool m_dcOffsetCorrection;
bool m_iqImbalanceCorrection;
};
class SDRBASE_API DSPEngineReport : public Message {
MESSAGE_CLASS_DECLARATION
public:
DSPEngineReport(int sampleRate, quint64 centerFrequency) :
Message(),
m_sampleRate(sampleRate),
m_centerFrequency(centerFrequency)
{ }
int getSampleRate() const { return m_sampleRate; }
quint64 getCenterFrequency() const { return m_centerFrequency; }
private:
int m_sampleRate;
quint64 m_centerFrequency;
};
class SDRBASE_API DSPConfigureScopeVis : public Message {
MESSAGE_CLASS_DECLARATION
public:
DSPConfigureScopeVis(int triggerChannel, Real triggerLevelHigh, Real triggerLevelLow) :
Message(),
m_triggerChannel(triggerChannel),
m_triggerLevelHigh(triggerLevelHigh),
m_triggerLevelLow(triggerLevelLow)
{ }
int getTriggerChannel() const { return m_triggerChannel; }
Real getTriggerLevelHigh() const { return m_triggerLevelHigh; }
Real getTriggerLevelLow() const { return m_triggerLevelLow; }
private:
int m_triggerChannel;
Real m_triggerLevelHigh;
Real m_triggerLevelLow;
};
class SDRBASE_API DSPSignalNotification : public Message {
MESSAGE_CLASS_DECLARATION
public:
DSPSignalNotification(int samplerate, qint64 centerFrequency, bool realElseComplex = false) :
Message(),
m_sampleRate(samplerate),
m_centerFrequency(centerFrequency),
m_realElseComplex(realElseComplex)
{ }
int getSampleRate() const { return m_sampleRate; }
qint64 getCenterFrequency() const { return m_centerFrequency; }
bool getRealElseComplex() const { return m_realElseComplex; }
private:
int m_sampleRate;
qint64 m_centerFrequency;
bool m_realElseComplex;
};
class SDRBASE_API DSPMIMOSignalNotification : public Message {
MESSAGE_CLASS_DECLARATION
public:
DSPMIMOSignalNotification(int samplerate, qint64 centerFrequency, bool sourceOrSink, unsigned int index, bool realElseComplex = false) :
Message(),
m_sampleRate(samplerate),
m_centerFrequency(centerFrequency),
m_realElseComplex(realElseComplex),
m_sourceOrSink(sourceOrSink),
m_index(index)
{ }
int getSampleRate() const { return m_sampleRate; }
qint64 getCenterFrequency() const { return m_centerFrequency; }
bool getRealElseComplex() const { return m_realElseComplex; }
bool getSourceOrSink() const { return m_sourceOrSink; }
unsigned int getIndex() const { return m_index; }
private:
int m_sampleRate;
qint64 m_centerFrequency;
bool m_realElseComplex;
bool m_sourceOrSink;
unsigned int m_index;
};
class SDRBASE_API DSPConfigureChannelizer : public Message {
MESSAGE_CLASS_DECLARATION
public:
DSPConfigureChannelizer(int sampleRate, int centerFrequency) :
Message(),
m_sampleRate(sampleRate),
m_centerFrequency(centerFrequency)
{ }
int getSampleRate() const { return m_sampleRate; }
int getCenterFrequency() const { return m_centerFrequency; }
private:
int m_sampleRate;
int m_centerFrequency;
};
class SDRBASE_API DSPConfigureAudio : public Message {
MESSAGE_CLASS_DECLARATION
public:
enum AudioType
{
AudioInput,
AudioOutput
};
DSPConfigureAudio(int sampleRate, AudioType audioType) :
m_sampleRate(sampleRate),
m_autioType(audioType)
{ }
int getSampleRate() const { return m_sampleRate; }
AudioType getAudioType() const { return m_autioType; }
private:
int m_sampleRate;
AudioType m_autioType;
};
class SDRBASE_API DSPPushMbeFrame : public Message {
MESSAGE_CLASS_DECLARATION
public:
DSPPushMbeFrame(
const unsigned char *mbeFrame,
int mbeRateIndex,
int mbeVolumeIndex,
unsigned char channels,
bool useHP,
int upsampling,
AudioFifo *audioFifo
) :
Message(),
m_mbeFrame(mbeFrame),
m_mbeRateIndex(mbeRateIndex),
m_mbeVolumeIndex(mbeVolumeIndex),
m_channels(channels),
m_useHP(useHP),
m_upsampling(upsampling),
m_audioFifo(audioFifo)
{ }
const unsigned char * getMbeFrame() const { return m_mbeFrame; }
int getMbeRateIndex() const { return m_mbeRateIndex; }
int getMbeVolumeIndex() const { return m_mbeVolumeIndex; }
unsigned char getChannels() const { return m_channels; }
bool getUseHP() const { return m_useHP; }
int getUpsampling() const { return m_upsampling; }
AudioFifo *getAudioFifo() const { return m_audioFifo; }
private:
const unsigned char *m_mbeFrame;
int m_mbeRateIndex;
int m_mbeVolumeIndex;
unsigned char m_channels;
bool m_useHP;
int m_upsampling;
AudioFifo *m_audioFifo;
};
#endif // INCLUDE_DSPCOMMANDS_H

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,369 @@
///////////////////////////////////////////////////////////////////////////////////
// Copyright (C) 2019-2020, 2023 Edouard Griffiths, F4EXB <f4exb06@gmail.com> //
// Copyright (C) 2022 Jon Beniston, M7RCE <jon@beniston.com> //
// //
// This program 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 as version 3 of the License, or //
// (at your option) any later version. //
// //
// This program 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 V3 for more details. //
// //
// You should have received a copy of the GNU General Public License //
// along with this program. If not, see <http://www.gnu.org/licenses/>. //
///////////////////////////////////////////////////////////////////////////////////
#ifndef SDRBASE_DSP_DSPDEVICEMIMOENGINE_H_
#define SDRBASE_DSP_DSPDEVICEMIMOENGINE_H_
#include <QObject>
#include "dsp/dsptypes.h"
#include "util/message.h"
#include "util/messagequeue.h"
#include "util/movingaverage.h"
#include "util/incrementalvector.h"
#include "export.h"
class DeviceSampleMIMO;
class BasebandSampleSink;
class BasebandSampleSource;
class MIMOChannel;
class SDRBASE_API DSPDeviceMIMOEngine : public QObject {
Q_OBJECT
public:
class SetSampleMIMO : public Message {
MESSAGE_CLASS_DECLARATION
public:
explicit SetSampleMIMO(DeviceSampleMIMO* sampleMIMO) : Message(), m_sampleMIMO(sampleMIMO) { }
DeviceSampleMIMO* getSampleMIMO() const { return m_sampleMIMO; }
private:
DeviceSampleMIMO* m_sampleMIMO;
};
class AddBasebandSampleSource : public Message {
MESSAGE_CLASS_DECLARATION
public:
AddBasebandSampleSource(BasebandSampleSource* sampleSource, unsigned int index) :
Message(),
m_sampleSource(sampleSource),
m_index(index)
{ }
BasebandSampleSource* getSampleSource() const { return m_sampleSource; }
unsigned int getIndex() const { return m_index; }
private:
BasebandSampleSource* m_sampleSource;
unsigned int m_index;
};
class RemoveBasebandSampleSource : public Message {
MESSAGE_CLASS_DECLARATION
public:
RemoveBasebandSampleSource(BasebandSampleSource* sampleSource, unsigned int index, bool deleting) :
Message(),
m_sampleSource(sampleSource),
m_index(index),
m_deleting(deleting)
{ }
BasebandSampleSource* getSampleSource() const { return m_sampleSource; }
unsigned int getIndex() const { return m_index; }
bool getDeleting() const { return m_deleting; }
private:
BasebandSampleSource* m_sampleSource;
unsigned int m_index;
bool m_deleting;
};
class AddMIMOChannel : public Message {
MESSAGE_CLASS_DECLARATION
public:
explicit AddMIMOChannel(MIMOChannel* channel) :
Message(),
m_channel(channel)
{ }
MIMOChannel* getChannel() const { return m_channel; }
private:
MIMOChannel* m_channel;
};
class RemoveMIMOChannel : public Message {
MESSAGE_CLASS_DECLARATION
public:
explicit RemoveMIMOChannel(MIMOChannel* channel) :
Message(),
m_channel(channel)
{ }
MIMOChannel* getChannel() const { return m_channel; }
private:
MIMOChannel* m_channel;
};
class AddBasebandSampleSink : public Message {
MESSAGE_CLASS_DECLARATION
public:
AddBasebandSampleSink(BasebandSampleSink* sampleSink, unsigned int index) :
Message(),
m_sampleSink(sampleSink),
m_index(index)
{ }
BasebandSampleSink* getSampleSink() const { return m_sampleSink; }
unsigned int getIndex() const { return m_index; }
private:
BasebandSampleSink* m_sampleSink;
unsigned int m_index;
};
class RemoveBasebandSampleSink : public Message {
MESSAGE_CLASS_DECLARATION
public:
RemoveBasebandSampleSink(BasebandSampleSink* sampleSink, unsigned int index) :
Message(),
m_sampleSink(sampleSink),
m_index(index)
{ }
BasebandSampleSink* getSampleSink() const { return m_sampleSink; }
unsigned int getIndex() const { return m_index; }
private:
BasebandSampleSink* m_sampleSink;
unsigned int m_index;
};
class AddSpectrumSink : public Message {
MESSAGE_CLASS_DECLARATION
public:
explicit AddSpectrumSink(BasebandSampleSink* sampleSink) : Message(), m_sampleSink(sampleSink) { }
BasebandSampleSink* getSampleSink() const { return m_sampleSink; }
private:
BasebandSampleSink* m_sampleSink;
};
class RemoveSpectrumSink : public Message {
MESSAGE_CLASS_DECLARATION
public:
explicit RemoveSpectrumSink(BasebandSampleSink* sampleSink) : Message(), m_sampleSink(sampleSink) { }
BasebandSampleSink* getSampleSink() const { return m_sampleSink; }
private:
BasebandSampleSink* m_sampleSink;
};
class GetErrorMessage : public Message {
MESSAGE_CLASS_DECLARATION
public:
explicit GetErrorMessage(unsigned int subsystemIndex) :
m_subsystemIndex(subsystemIndex)
{}
void setErrorMessage(const QString& text) { m_errorMessage = text; }
int getSubsystemIndex() const { return m_subsystemIndex; }
const QString& getErrorMessage() const { return m_errorMessage; }
private:
int m_subsystemIndex;
QString m_errorMessage;
};
class GetMIMODeviceDescription : public Message {
MESSAGE_CLASS_DECLARATION
public:
void setDeviceDescription(const QString& text) { m_deviceDescription = text; }
const QString& getDeviceDescription() const { return m_deviceDescription; }
private:
QString m_deviceDescription;
};
class ConfigureCorrection : public Message {
MESSAGE_CLASS_DECLARATION
public:
ConfigureCorrection(bool dcOffsetCorrection, bool iqImbalanceCorrection, unsigned int index) :
Message(),
m_dcOffsetCorrection(dcOffsetCorrection),
m_iqImbalanceCorrection(iqImbalanceCorrection),
m_index(index)
{ }
bool getDCOffsetCorrection() const { return m_dcOffsetCorrection; }
bool getIQImbalanceCorrection() const { return m_iqImbalanceCorrection; }
unsigned int getIndex() const { return m_index; }
private:
bool m_dcOffsetCorrection;
bool m_iqImbalanceCorrection;
unsigned int m_index;
};
class SetSpectrumSinkInput : public Message {
MESSAGE_CLASS_DECLARATION
public:
SetSpectrumSinkInput(bool sourceElseSink, int index) :
m_sourceElseSink(sourceElseSink),
m_index(index)
{ }
bool getSourceElseSink() const { return m_sourceElseSink; }
int getIndex() const { return m_index; }
private:
bool m_sourceElseSink;
int m_index;
};
enum class State {
StNotStarted, //!< engine is before initialization
StIdle, //!< engine is idle
StReady, //!< engine is ready to run
StRunning, //!< engine is running
StError //!< engine is in error
};
DSPDeviceMIMOEngine(uint32_t uid, QObject* parent = nullptr);
~DSPDeviceMIMOEngine() override;
MessageQueue* getInputMessageQueue() { return &m_inputMessageQueue; }
bool initProcess(int subsystemIndex); //!< Initialize process sequence
bool startProcess(int subsystemIndex); //!< Start process sequence
void stopProcess(int subsystemIndex); //!< Stop process sequence
void setMIMO(DeviceSampleMIMO* mimo); //!< Set the sample MIMO type
DeviceSampleMIMO *getMIMO() { return m_deviceSampleMIMO; }
void setMIMOSequence(int sequence); //!< Set the sample MIMO sequence in type
uint getUID() const { return m_uid; }
void addChannelSource(BasebandSampleSource* source, int index = 0); //!< Add a channel source
void removeChannelSource(BasebandSampleSource* source, bool deleting, int index = 0); //!< Remove a channel source
void addChannelSink(BasebandSampleSink* sink, int index = 0); //!< Add a channel sink
void removeChannelSink(BasebandSampleSink* sink, int index = 0); //!< Remove a channel sink
void addMIMOChannel(MIMOChannel *channel); //!< Add a MIMO channel
void removeMIMOChannel(MIMOChannel *channel); //!< Remove a MIMO channel
void addSpectrumSink(BasebandSampleSink* spectrumSink); //!< Add a spectrum vis baseband sample sink
void removeSpectrumSink(BasebandSampleSink* spectrumSink); //!< Add a spectrum vis baseband sample sink
void setSpectrumSinkInput(bool sourceElseSink, int index);
State state(int subsystemIndex) const //!< Return DSP engine current state
{
if (subsystemIndex == 0) {
return m_stateRx;
} else if (subsystemIndex == 1) {
return m_stateTx;
} else {
return State::StNotStarted;
}
}
QString errorMessage(int subsystemIndex) const; //!< Return the current error message
QString deviceDescription() const; //!< Return the device description
void configureCorrections(bool dcOffsetCorrection, bool iqImbalanceCorrection, int isource); //!< Configure source DSP corrections
private:
struct SourceCorrection
{
bool m_dcOffsetCorrection = false;
bool m_iqImbalanceCorrection = false;
double m_iOffset = 0;
double m_qOffset = 0;
int m_iRange = 1 << 16;
int m_qRange = 1 << 16;
int m_imbalance = 65536;
MovingAverageUtil<int32_t, int64_t, 1024> m_iBeta;
MovingAverageUtil<int32_t, int64_t, 1024> m_qBeta;
#if IMBALANCE_INT
// Fixed point DC + IQ corrections
MovingAverageUtil<int64_t, int64_t, 128> m_avgII;
MovingAverageUtil<int64_t, int64_t, 128> m_avgIQ;
MovingAverageUtil<int64_t, int64_t, 128> m_avgPhi;
MovingAverageUtil<int64_t, int64_t, 128> m_avgII2;
MovingAverageUtil<int64_t, int64_t, 128> m_avgQQ2;
MovingAverageUtil<int64_t, int64_t, 128> m_avgAmp;
#else
// Floating point DC + IQ corrections
MovingAverageUtil<float, double, 128> m_avgII;
MovingAverageUtil<float, double, 128> m_avgIQ;
MovingAverageUtil<float, double, 128> m_avgII2;
MovingAverageUtil<float, double, 128> m_avgQQ2;
MovingAverageUtil<double, double, 128> m_avgPhi;
MovingAverageUtil<double, double, 128> m_avgAmp;
#endif
SourceCorrection()
{
m_iBeta.reset();
m_qBeta.reset();
m_avgAmp.reset();
m_avgII.reset();
m_avgII2.reset();
m_avgIQ.reset();
m_avgPhi.reset();
m_avgQQ2.reset();
m_iBeta.reset();
m_qBeta.reset();
}
};
uint32_t m_uid; //!< unique ID
State m_stateRx;
State m_stateTx;
QString m_errorMessageRx;
QString m_errorMessageTx;
QString m_deviceDescription;
DeviceSampleMIMO* m_deviceSampleMIMO;
int m_sampleMIMOSequence;
MessageQueue m_inputMessageQueue; //<! Input message queue. Post here.
using BasebandSampleSinks = std::list<BasebandSampleSink *>;
std::vector<BasebandSampleSinks> m_basebandSampleSinks; //!< ancillary sample sinks on main thread (per input stream)
std::map<int, bool> m_rxRealElseComplex; //!< map of real else complex indicators for device sources (by input stream)
using BasebandSampleSources = std::list<BasebandSampleSource *>;
std::vector<BasebandSampleSources> m_basebandSampleSources; //!< channel sample sources (per output stream)
std::map<int, bool> m_txRealElseComplex; //!< map of real else complex indicators for device sinks (by input stream)
std::vector<IncrementalVector<Sample>> m_sourceSampleBuffers;
std::vector<IncrementalVector<Sample>> m_sourceZeroBuffers;
unsigned int m_sumIndex; //!< channel index when summing channels
using MIMOChannels = std::list<MIMOChannel *>;
MIMOChannels m_mimoChannels; //!< MIMO channels
std::vector<SourceCorrection> m_sourcesCorrections;
BasebandSampleSink *m_spectrumSink; //!< The spectrum sink
bool m_spectrumInputSourceElseSink; //!< Source else sink stream to be used as spectrum sink input
unsigned int m_spectrumInputIndex; //!< Index of the stream to be used as spectrum sink input
void workSampleSinkFifos(); //!< transfer samples of all sink streams (sync mode)
void workSampleSinkFifo(unsigned int streamIndex); //!< transfer samples of one sink stream (async mode)
void workSamplesSink(const SampleVector::const_iterator& vbegin, const SampleVector::const_iterator& vend, unsigned int streamIndex);
void workSampleSourceFifos(); //!< transfer samples of all source streams (sync mode)
void workSampleSourceFifo(unsigned int streamIndex); //!< transfer samples of one source stream (async mode)
void workSamplesSource(SampleVector& data, unsigned int iBegin, unsigned int iEnd, unsigned int streamIndex);
State gotoIdle(int subsystemIndex); //!< Go to the idle state
State gotoInit(int subsystemIndex); //!< Go to the acquisition init state from idle
State gotoRunning(int subsystemIndex); //!< Go to the running state from ready state
State gotoError(int subsystemIndex, const QString& errorMsg); //!< Go to an error state
void setStateRx(State state);
void setStateTx(State state);
void handleSetMIMO(DeviceSampleMIMO* mimo); //!< Manage MIMO device setting
void iqCorrections(SampleVector::iterator begin, SampleVector::iterator end, int isource, bool imbalanceCorrection);
bool handleMessage(const Message& cmd);
private slots:
void handleDataRxSync(); //!< Handle data when Rx samples have to be processed synchronously
void handleDataRxAsync(int streamIndex); //!< Handle data when Rx samples have to be processed asynchronously
void handleDataTxSync(); //!< Handle data when Tx samples have to be processed synchronously
void handleDataTxAsync(int streamIndex); //!< Handle data when Tx samples have to be processed asynchronously
void handleInputMessages(); //!< Handle input message queue
signals:
void stateChanged();
void acquisitionStopped();
void sampleSet();
void generationStopped();
void spectrumSinkRemoved();
};
#endif // SDRBASE_DSP_DSPDEVICEMIMOENGINE_H_

View File

@@ -0,0 +1,526 @@
///////////////////////////////////////////////////////////////////////////////////
// Copyright (C) 2016-2023 Edouard Griffiths, F4EXB <f4exb06@gmail.com> //
// Copyright (C) 2022 Jon Beniston, M7RCE <jon@beniston.com> //
// //
// This program 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 as version 3 of the License, or //
// (at your option) any later version. //
// //
// This program 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 V3 for more details. //
// //
// You should have received a copy of the GNU General Public License //
// along with this program. If not, see <http://www.gnu.org/licenses/>. //
///////////////////////////////////////////////////////////////////////////////////
#include <stdio.h>
#include <QDebug>
#include <QThread>
#include "dspdevicesinkengine.h"
#include "dsp/basebandsamplesource.h"
#include "dsp/basebandsamplesink.h"
#include "dsp/devicesamplesink.h"
#include "dsp/dspcommands.h"
DSPDeviceSinkEngine::DSPDeviceSinkEngine(uint32_t uid, QObject* parent) :
QObject(parent),
m_uid(uid),
m_state(State::StNotStarted),
m_deviceSampleSink(nullptr),
m_sampleSinkSequence(0),
m_basebandSampleSources(),
m_spectrumSink(nullptr),
m_sampleRate(0),
m_centerFrequency(0),
m_realElseComplex(false)
{
setState(State::StIdle);
connect(&m_inputMessageQueue, SIGNAL(messageEnqueued()), this, SLOT(handleInputMessages()), Qt::QueuedConnection);
}
DSPDeviceSinkEngine::~DSPDeviceSinkEngine()
{
qDebug("DSPDeviceSinkEngine::~DSPDeviceSinkEngine");
}
void DSPDeviceSinkEngine::setState(State state)
{
if (m_state != state)
{
m_state = state;
emit stateChanged();
}
}
bool DSPDeviceSinkEngine::initGeneration()
{
qDebug() << "DSPDeviceSinkEngine::initGeneration";
auto *cmd = new DSPGenerationInit();
getInputMessageQueue()->push(cmd);
return true;
}
bool DSPDeviceSinkEngine::startGeneration()
{
qDebug() << "DSPDeviceSinkEngine::startGeneration";
auto *cmd = new DSPGenerationStart();
getInputMessageQueue()->push(cmd);
return true;
}
void DSPDeviceSinkEngine::stopGeneration()
{
qDebug() << "DSPDeviceSinkEngine::stopGeneration";
auto *cmd = new DSPGenerationStop();
getInputMessageQueue()->push(cmd);
}
void DSPDeviceSinkEngine::setSink(DeviceSampleSink* sink)
{
qDebug() << "DSPDeviceSinkEngine::setSink";
m_deviceSampleSink = sink;
auto *cmd = new DSPSetSink(sink);
getInputMessageQueue()->push(cmd);
}
void DSPDeviceSinkEngine::setSinkSequence(int sequence)
{
qDebug("DSPDeviceSinkEngine::setSinkSequence: seq: %d", sequence);
m_sampleSinkSequence = sequence;
}
void DSPDeviceSinkEngine::addChannelSource(BasebandSampleSource* source)
{
qDebug() << "DSPDeviceSinkEngine::addChannelSource: " << source->getSourceName().toStdString().c_str();
auto *cmd = new DSPAddBasebandSampleSource(source);
getInputMessageQueue()->push(cmd);
}
void DSPDeviceSinkEngine::removeChannelSource(BasebandSampleSource* source, bool deleting)
{
qDebug() << "DSPDeviceSinkEngine::removeChannelSource: " << source->getSourceName().toStdString().c_str();
auto *cmd = new DSPRemoveBasebandSampleSource(source, deleting);
getInputMessageQueue()->push(cmd);
}
void DSPDeviceSinkEngine::addSpectrumSink(BasebandSampleSink* spectrumSink)
{
qDebug() << "DSPDeviceSinkEngine::addSpectrumSink: " << spectrumSink->getSinkName().toStdString().c_str();
m_spectrumSink = spectrumSink;
}
void DSPDeviceSinkEngine::removeSpectrumSink(BasebandSampleSink* spectrumSink)
{
qDebug() << "DSPDeviceSinkEngine::removeSpectrumSink: " << spectrumSink->getSinkName().toStdString().c_str();
auto *cmd = new DSPRemoveSpectrumSink(spectrumSink);
getInputMessageQueue()->push(cmd);
}
QString DSPDeviceSinkEngine::errorMessage() const
{
qDebug() << "DSPDeviceSinkEngine::errorMessage";
return m_errorMessage;
}
QString DSPDeviceSinkEngine::sinkDeviceDescription() const
{
qDebug() << "DSPDeviceSinkEngine::sinkDeviceDescription";
return m_deviceDescription;
}
void DSPDeviceSinkEngine::workSampleFifo()
{
SampleSourceFifo *sourceFifo = m_deviceSampleSink->getSampleFifo();
if (!sourceFifo) {
return;
}
SampleVector& data = sourceFifo->getData();
unsigned int iPart1Begin;
unsigned int iPart1End;
unsigned int iPart2Begin;
unsigned int iPart2End;
unsigned int remainder = sourceFifo->remainder();
while ((remainder > 0) && (m_inputMessageQueue.size() == 0))
{
sourceFifo->write(remainder, iPart1Begin, iPart1End, iPart2Begin, iPart2End);
if (iPart1Begin != iPart1End) {
workSamples(data, iPart1Begin, iPart1End);
}
if (iPart2Begin != iPart2End) {
workSamples(data, iPart2Begin, iPart2End);
}
remainder = sourceFifo->remainder();
}
}
void DSPDeviceSinkEngine::workSamples(SampleVector& data, unsigned int iBegin, unsigned int iEnd)
{
unsigned int nbSamples = iEnd - iBegin;
SampleVector::iterator begin = data.begin() + iBegin;
if (m_basebandSampleSources.size() == 0)
{
m_sourceZeroBuffer.allocate(nbSamples, Sample{0,0});
std::copy(
m_sourceZeroBuffer.m_vector.begin(),
m_sourceZeroBuffer.m_vector.begin() + nbSamples,
data.begin() + iBegin
);
}
else if (m_basebandSampleSources.size() == 1)
{
BasebandSampleSource *source = m_basebandSampleSources.front();
source->pull(begin, nbSamples);
}
else
{
m_sourceSampleBuffer.allocate(nbSamples);
auto sBegin = m_sourceSampleBuffer.m_vector.begin();
BasebandSampleSources::const_iterator srcIt = m_basebandSampleSources.begin();
BasebandSampleSource *source = *srcIt;
source->pull(begin, nbSamples);
srcIt++;
m_sumIndex = 1;
for (; srcIt != m_basebandSampleSources.end(); ++srcIt, m_sumIndex++)
{
source = *srcIt;
source->pull(sBegin, nbSamples);
std::transform(
sBegin,
sBegin + nbSamples,
data.begin() + iBegin,
data.begin() + iBegin,
[this](const Sample& a, const Sample& b) -> Sample {
FixReal den = m_sumIndex + 1; // at each stage scale sum by n/n+1 and input by 1/n+1
FixReal nom = m_sumIndex; // so that final sum is scaled by N (number of channels)
FixReal x = a.real()/den + nom*(b.real()/den);
FixReal y = a.imag()/den + nom*(b.imag()/den);
return Sample{x, y};
}
);
}
}
// possibly feed data to spectrum sink
if (m_spectrumSink) {
m_spectrumSink->feed(data.begin() + iBegin, data.begin() + iEnd, m_realElseComplex);
}
}
// notStarted -> idle -> init -> running -+
// ^ |
// +-----------------------+
DSPDeviceSinkEngine::State DSPDeviceSinkEngine::gotoIdle()
{
qDebug() << "DSPDeviceSinkEngine::gotoIdle";
switch(m_state) {
case State::StNotStarted:
return State::StNotStarted;
case State::StIdle:
case State::StError:
return State::StIdle;
case State::StReady:
case State::StRunning:
break;
}
if (!m_deviceSampleSink) {
return State::StIdle;
}
// stop everything
m_deviceSampleSink->stop();
for(BasebandSampleSources::const_iterator it = m_basebandSampleSources.begin(); it != m_basebandSampleSources.end(); it++)
{
qDebug() << "DSPDeviceSinkEngine::gotoIdle: stopping " << (*it)->getSourceName().toStdString().c_str();
(*it)->stop();
}
m_deviceDescription.clear();
m_sampleRate = 0;
return State::StIdle;
}
DSPDeviceSinkEngine::State DSPDeviceSinkEngine::gotoInit()
{
switch(m_state) {
case State::StNotStarted:
return State::StNotStarted;
case State::StRunning:
return State::StRunning;
case State::StReady:
return State::StReady;
case State::StIdle:
case State::StError:
break;
}
if (!m_deviceSampleSink) {
return gotoError("DSPDeviceSinkEngine::gotoInit: No sample source configured");
}
// init: pass sample rate and center frequency to all sample rate and/or center frequency dependent sinks and wait for completion
m_deviceDescription = m_deviceSampleSink->getDeviceDescription();
m_centerFrequency = m_deviceSampleSink->getCenterFrequency();
m_sampleRate = m_deviceSampleSink->getSampleRate();
qDebug() << "DSPDeviceSinkEngine::gotoInit: "
<< " m_deviceDescription: " << m_deviceDescription.toStdString().c_str()
<< " sampleRate: " << m_sampleRate
<< " centerFrequency: " << m_centerFrequency;
DSPSignalNotification notif(m_sampleRate, m_centerFrequency);
for (BasebandSampleSources::const_iterator it = m_basebandSampleSources.begin(); it != m_basebandSampleSources.end(); ++it)
{
qDebug() << "DSPDeviceSinkEngine::gotoInit: initializing " << (*it)->getSourceName().toStdString().c_str();
(*it)->pushMessage(new DSPSignalNotification(notif));
}
if (m_spectrumSink) {
m_spectrumSink->pushMessage(new DSPSignalNotification(notif));
}
// pass data to listeners
if (m_deviceSampleSink->getMessageQueueToGUI())
{
auto* rep = new DSPSignalNotification(notif); // make a copy for the output queue
m_deviceSampleSink->getMessageQueueToGUI()->push(rep);
}
return State::StReady;
}
DSPDeviceSinkEngine::State DSPDeviceSinkEngine::gotoRunning()
{
qDebug() << "DSPDeviceSinkEngine::gotoRunning";
switch(m_state)
{
case State::StNotStarted:
return State::StNotStarted;
case State::StIdle:
return State::StIdle;
case State::StRunning:
return State::StRunning;
case State::StReady:
case State::StError:
break;
}
if (!m_deviceSampleSink) {
return gotoError("DSPDeviceSinkEngine::gotoRunning: No sample source configured");
}
qDebug() << "DSPDeviceSinkEngine::gotoRunning: " << m_deviceDescription.toStdString().c_str() << " started";
// Start everything
if (!m_deviceSampleSink->start()) {
return gotoError("DSPDeviceSinkEngine::gotoRunning: Could not start sample sink");
}
for(BasebandSampleSources::const_iterator it = m_basebandSampleSources.begin(); it != m_basebandSampleSources.end(); it++)
{
qDebug() << "DSPDeviceSinkEngine::gotoRunning: starting " << (*it)->getSourceName().toStdString().c_str();
(*it)->start();
}
if (m_spectrumSink)
{
m_spectrumSink->start();
}
qDebug() << "DSPDeviceSinkEngine::gotoRunning: input message queue pending: " << m_inputMessageQueue.size();
return State::StRunning;
}
DSPDeviceSinkEngine::State DSPDeviceSinkEngine::gotoError(const QString& errorMessage)
{
qDebug() << "DSPDeviceSinkEngine::gotoError";
m_errorMessage = errorMessage;
m_deviceDescription.clear();
setState(State::StError);
return State::StError;
}
void DSPDeviceSinkEngine::handleSetSink(const DeviceSampleSink*)
{
if (!m_deviceSampleSink) { // Early leave
return;
}
qDebug("DSPDeviceSinkEngine::handleSetSink: set %s", qPrintable(m_deviceSampleSink->getDeviceDescription()));
QObject::connect(
m_deviceSampleSink->getSampleFifo(),
&SampleSourceFifo::dataRead,
this,
&DSPDeviceSinkEngine::handleData,
Qt::QueuedConnection
);
}
void DSPDeviceSinkEngine::handleData()
{
if (m_state == State::StRunning) {
workSampleFifo();
}
}
bool DSPDeviceSinkEngine::handleMessage(const Message& message)
{
if (DSPSignalNotification::match(message))
{
auto& notif = (const DSPSignalNotification&) message;
// update DSP values
m_sampleRate = notif.getSampleRate();
m_centerFrequency = notif.getCenterFrequency();
m_realElseComplex = notif.getRealElseComplex();
qDebug() << "DSPDeviceSinkEngine::handleInputMessages: DSPSignalNotification:"
<< " m_sampleRate: " << m_sampleRate
<< " m_centerFrequency: " << m_centerFrequency
<< " m_realElseComplex" << m_realElseComplex;
// forward source changes to sources with immediate execution
for(BasebandSampleSources::const_iterator it = m_basebandSampleSources.begin(); it != m_basebandSampleSources.end(); it++)
{
auto *rep = new DSPSignalNotification(notif); // make a copy
qDebug() << "DSPDeviceSinkEngine::handleInputMessages: forward message to " << (*it)->getSourceName().toStdString().c_str();
(*it)->pushMessage(rep);
}
// forward changes to listeners on DSP output queue
if (m_deviceSampleSink)
{
MessageQueue *guiMessageQueue = m_deviceSampleSink->getMessageQueueToGUI();
qDebug("DSPDeviceSinkEngine::handleInputMessages: DSPSignalNotification: guiMessageQueue: %p", guiMessageQueue);
if (guiMessageQueue)
{
auto *rep = new DSPSignalNotification(notif); // make a copy for the output queue
guiMessageQueue->push(rep);
}
}
return true;
}
// From synchronous messages
if (DSPGenerationInit::match(message))
{
setState(gotoIdle());
if(m_state == State::StIdle) {
setState(gotoInit()); // State goes ready if init is performed
}
return true;
}
else if (DSPGenerationStart::match(message))
{
if(m_state == State::StReady) {
setState(gotoRunning());
}
return true;
}
else if (DSPGenerationStop::match(message))
{
setState(gotoIdle());
emit generationStopped();
return true;
}
else if (DSPSetSink::match(message))
{
const auto& cmd = (const DSPSetSink&) message;
handleSetSink(cmd.getSampleSink());
emit sampleSet();
return true;
}
else if (DSPRemoveSpectrumSink::match(message))
{
auto& cmd = (const DSPRemoveSpectrumSink&) message;
BasebandSampleSink* spectrumSink = cmd.getSampleSink();
if(m_state == State::StRunning) {
spectrumSink->stop();
}
m_spectrumSink = nullptr;
emit spectrumSinkRemoved();
return true;
}
else if (DSPAddBasebandSampleSource::match(message))
{
auto& cmd = (const DSPAddBasebandSampleSource&) message;
BasebandSampleSource* source = cmd.getSampleSource();
m_basebandSampleSources.push_back(source);
auto *notif = new DSPSignalNotification(m_sampleRate, m_centerFrequency);
source->pushMessage(notif);
if (m_state == State::StRunning) {
source->start();
}
return true;
}
else if (DSPRemoveBasebandSampleSource::match(message))
{
auto& cmd = (const DSPRemoveBasebandSampleSource&) message;
BasebandSampleSource* source = cmd.getSampleSource();
bool deleting = cmd.getDeleting();
if (!deleting && (m_state == State::StRunning)) {
source->stop();
}
m_basebandSampleSources.remove(source);
return true;
}
return false;
}
void DSPDeviceSinkEngine::handleInputMessages()
{
Message* message;
while ((message = m_inputMessageQueue.pop()) != nullptr)
{
qDebug("DSPDeviceSinkEngine::handleInputMessages: message: %s", message->getIdentifier());
if (handleMessage(*message)) {
delete message;
}
}
}

View File

@@ -0,0 +1,134 @@
///////////////////////////////////////////////////////////////////////////////////
// Copyright (C) 2012 maintech GmbH, Otto-Hahn-Str. 15, 97204 Hoechberg, Germany //
// written by Christian Daniel //
// Copyright (C) 2014 John Greb <hexameron@spam.no> //
// Copyright (C) 2015-2019, 2023 Edouard Griffiths, F4EXB <f4exb06@gmail.com> //
// Copyright (C) 2022 Jon Beniston, M7RCE <jon@beniston.com> //
// //
// This program 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 as version 3 of the License, or //
// (at your option) any later version. //
// //
// This program 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 V3 for more details. //
// //
// You should have received a copy of the GNU General Public License //
// along with this program. If not, see <http://www.gnu.org/licenses/>. //
///////////////////////////////////////////////////////////////////////////////////
#ifndef SDRBASE_DSP_DSPDEVICESINKENGINE_H_
#define SDRBASE_DSP_DSPDEVICESINKENGINE_H_
#include <QObject>
#include <QTimer>
#include <QMutex>
#include <QWaitCondition>
#include <stdint.h>
#include <list>
#include <map>
#include "dsp/dsptypes.h"
#include "util/messagequeue.h"
#include "util/incrementalvector.h"
#include "export.h"
class DeviceSampleSink;
class BasebandSampleSource;
class BasebandSampleSink;
class SDRBASE_API DSPDeviceSinkEngine : public QObject {
Q_OBJECT
public:
enum class State {
StNotStarted, //!< engine is before initialization
StIdle, //!< engine is idle
StReady, //!< engine is ready to run
StRunning, //!< engine is running
StError //!< engine is in error
};
DSPDeviceSinkEngine(uint32_t uid, QObject* parent = nullptr);
~DSPDeviceSinkEngine() final;
uint32_t getUID() const { return m_uid; }
MessageQueue* getInputMessageQueue() { return &m_inputMessageQueue; }
bool initGeneration(); //!< Initialize generation sequence
bool startGeneration(); //!< Start generation sequence
void stopGeneration(); //!< Stop generation sequence
void setSink(DeviceSampleSink* sink); //!< Set the sample sink type
DeviceSampleSink *getSink() { return m_deviceSampleSink; }
void setSinkSequence(int sequence); //!< Set the sample sink sequence in type
void addChannelSource(BasebandSampleSource* source); //!< Add a baseband sample source
void removeChannelSource(BasebandSampleSource* source, bool deleting); //!< Remove a baseband sample source
void addSpectrumSink(BasebandSampleSink* spectrumSink); //!< Add a spectrum vis baseband sample sink
void removeSpectrumSink(BasebandSampleSink* spectrumSink); //!< Add a spectrum vis baseband sample sink
State state() const { return m_state; } //!< Return DSP engine current state
QString errorMessage() const; //!< Return the current error message
QString sinkDeviceDescription() const; //!< Return the sink device description
private:
uint32_t m_uid; //!< unique ID
MessageQueue m_inputMessageQueue; //<! Input message queue. Post here.
State m_state;
QString m_errorMessage;
QString m_deviceDescription;
DeviceSampleSink* m_deviceSampleSink;
int m_sampleSinkSequence;
using BasebandSampleSources = std::list<BasebandSampleSource *>;
BasebandSampleSources m_basebandSampleSources; //!< baseband sample sources within main thread (usually file input)
BasebandSampleSink *m_spectrumSink;
IncrementalVector<Sample> m_sourceSampleBuffer;
IncrementalVector<Sample> m_sourceZeroBuffer;
uint32_t m_sampleRate;
quint64 m_centerFrequency;
bool m_realElseComplex;
unsigned int m_sumIndex; //!< channel index when summing channels
void workSampleFifo(); //!< transfer samples from baseband sources to sink if in running state
void workSamples(SampleVector& data, unsigned int iBegin, unsigned int iEnd);
State gotoIdle(); //!< Go to the idle state
State gotoInit(); //!< Go to the acquisition init state from idle
State gotoRunning(); //!< Go to the running state from ready state
State gotoError(const QString& errorMsg); //!< Go to an error state
void setState(State state);
void handleSetSink(const DeviceSampleSink* sink); //!< Manage sink setting
bool handleMessage(const Message& cmd);
private slots:
void handleData(); //!< Handle data when samples have to be written to the sample FIFO
void handleInputMessages(); //!< Handle input message queue
signals:
void stateChanged();
void generationStopped();
void sampleSet();
void spectrumSinkRemoved();
};
#endif /* SDRBASE_DSP_DSPDEVICESINKENGINE_H_ */

View File

@@ -0,0 +1,659 @@
///////////////////////////////////////////////////////////////////////////////////
// Copyright (C) 2012 maintech GmbH, Otto-Hahn-Str. 15, 97204 Hoechberg, Germany //
// written by Christian Daniel //
// Copyright (C) 2014 John Greb <hexameron@spam.no> //
// Copyright (C) 2015-2019, 2022-2023 Edouard Griffiths, F4EXB <f4exb06@gmail.com> //
// Copyright (C) 2022 Jon Beniston, M7RCE <jon@beniston.com> //
// //
// This program 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 as version 3 of the License, or //
// (at your option) any later version. //
// //
// This program 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 V3 for more details. //
// //
// You should have received a copy of the GNU General Public License //
// along with this program. If not, see <http://www.gnu.org/licenses/>. //
///////////////////////////////////////////////////////////////////////////////////
#include "dspdevicesourceengine.h"
#include <dsp/basebandsamplesink.h>
#include <dsp/devicesamplesource.h>
#include <stdio.h>
#include <QDebug>
#include "dsp/dspcommands.h"
#include "samplesinkfifo.h"
DSPDeviceSourceEngine::DSPDeviceSourceEngine(uint uid, QObject* parent) :
QObject(parent),
m_uid(uid),
m_state(State::StNotStarted),
m_deviceSampleSource(nullptr),
m_sampleSourceSequence(0),
m_basebandSampleSinks(),
m_sampleRate(0),
m_centerFrequency(0),
m_realElseComplex(false),
m_dcOffsetCorrection(false),
m_iqImbalanceCorrection(false),
m_iOffset(0),
m_qOffset(0),
m_iRange(1 << 16),
m_qRange(1 << 16),
m_imbalance(65536)
{
setState(State::StIdle);
connect(&m_inputMessageQueue, SIGNAL(messageEnqueued()), this, SLOT(handleInputMessages()), Qt::QueuedConnection);
}
DSPDeviceSourceEngine::~DSPDeviceSourceEngine()
{
qDebug("DSPDeviceSourceEngine::~DSPDeviceSourceEngine");
}
void DSPDeviceSourceEngine::setState(State state)
{
if (m_state != state)
{
m_state = state;
emit stateChanged();
}
}
bool DSPDeviceSourceEngine::initAcquisition() const
{
qDebug("DSPDeviceSourceEngine::initAcquisition (dummy)");
return true;
}
bool DSPDeviceSourceEngine::startAcquisition()
{
qDebug("DSPDeviceSourceEngine::startAcquisition");
auto *cmd = new DSPAcquisitionStart();
getInputMessageQueue()->push(cmd);
return true;
}
void DSPDeviceSourceEngine::stopAcquistion()
{
qDebug("DSPDeviceSourceEngine::stopAcquistion");
auto *cmd = new DSPAcquisitionStop();
getInputMessageQueue()->push(cmd);
if (m_dcOffsetCorrection) {
qDebug("DC offset:%f,%f", m_iOffset, m_qOffset);
}
}
void DSPDeviceSourceEngine::setSource(DeviceSampleSource* source)
{
qDebug("DSPDeviceSourceEngine::setSource");
auto *cmd = new DSPSetSource(source);
getInputMessageQueue()->push(cmd);
}
void DSPDeviceSourceEngine::setSourceSequence(int sequence)
{
qDebug("DSPDeviceSourceEngine::setSourceSequence: seq: %d", sequence);
m_sampleSourceSequence = sequence;
}
void DSPDeviceSourceEngine::addSink(BasebandSampleSink* sink)
{
qDebug() << "DSPDeviceSourceEngine::addSink: " << sink->getSinkName().toStdString().c_str();
auto *cmd = new DSPAddBasebandSampleSink(sink);
getInputMessageQueue()->push(cmd);
}
void DSPDeviceSourceEngine::removeSink(BasebandSampleSink* sink, bool deleting)
{
qDebug() << "DSPDeviceSourceEngine::removeSink: " << sink->getSinkName().toStdString().c_str();
auto *cmd = new DSPRemoveBasebandSampleSink(sink, deleting);
getInputMessageQueue()->push(cmd);
}
void DSPDeviceSourceEngine::configureCorrections(bool dcOffsetCorrection, bool iqImbalanceCorrection)
{
qDebug("DSPDeviceSourceEngine::configureCorrections");
auto *cmd = new DSPConfigureCorrection(dcOffsetCorrection, iqImbalanceCorrection);
getInputMessageQueue()->push(cmd);
}
QString DSPDeviceSourceEngine::errorMessage() const
{
qDebug("DSPDeviceSourceEngine::errorMessage");
return m_errorMessage;
}
QString DSPDeviceSourceEngine::sourceDeviceDescription() const
{
qDebug("DSPDeviceSourceEngine::sourceDeviceDescription");
return m_deviceDescription;
}
void DSPDeviceSourceEngine::iqCorrections(SampleVector::iterator begin, SampleVector::iterator end, bool imbalanceCorrection)
{
for(SampleVector::iterator it = begin; it < end; it++)
{
m_iBeta(it->real());
m_qBeta(it->imag());
if (imbalanceCorrection)
{
#if IMBALANCE_INT
// acquisition
int64_t xi = (it->m_real - (int32_t) m_iBeta) << 5;
int64_t xq = (it->m_imag - (int32_t) m_qBeta) << 5;
// phase imbalance
m_avgII((xi*xi)>>28); // <I", I">
m_avgIQ((xi*xq)>>28); // <I", Q">
if ((int64_t) m_avgII != 0)
{
int64_t phi = (((int64_t) m_avgIQ)<<28) / (int64_t) m_avgII;
m_avgPhi(phi);
}
int64_t corrPhi = (((int64_t) m_avgPhi) * xq) >> 28; //(m_avgPhi.asDouble()/16777216.0) * ((double) xq);
int64_t yi = xi - corrPhi;
int64_t yq = xq;
// amplitude I/Q imbalance
m_avgII2((yi*yi)>>28); // <I, I>
m_avgQQ2((yq*yq)>>28); // <Q, Q>
if ((int64_t) m_avgQQ2 != 0)
{
int64_t a = (((int64_t) m_avgII2)<<28) / (int64_t) m_avgQQ2;
Fixed<int64_t, 28> fA(Fixed<int64_t, 28>::internal(), a);
Fixed<int64_t, 28> sqrtA = sqrt((Fixed<int64_t, 28>) fA);
m_avgAmp(sqrtA.as_internal());
}
int64_t zq = (((int64_t) m_avgAmp) * yq) >> 28;
it->m_real = yi >> 5;
it->m_imag = zq >> 5;
#else
// DC correction and conversion
float xi = (float) (it->m_real - (int32_t) m_iBeta) / SDR_RX_SCALEF;
float xq = (float) (it->m_imag - (int32_t) m_qBeta) / SDR_RX_SCALEF;
// phase imbalance
m_avgII(xi*xi); // <I", I">
m_avgIQ(xi*xq); // <I", Q">
if (m_avgII.asDouble() != 0) {
m_avgPhi(m_avgIQ.asDouble()/m_avgII.asDouble());
}
const float& yi = xi; // the in phase remains the reference
float yq = xq - (float) m_avgPhi.asDouble()*xi;
// amplitude I/Q imbalance
m_avgII2(yi*yi); // <I, I>
m_avgQQ2(yq*yq); // <Q, Q>
if (m_avgQQ2.asDouble() != 0) {
m_avgAmp(sqrt(m_avgII2.asDouble() / m_avgQQ2.asDouble()));
}
// final correction
const float& zi = yi; // the in phase remains the reference
auto zq = (float) (m_avgAmp.asDouble() * yq);
// convert and store
it->m_real = (FixReal) (zi * SDR_RX_SCALEF);
it->m_imag = (FixReal) (zq * SDR_RX_SCALEF);
#endif
}
else
{
// DC correction only
it->m_real -= (int32_t) m_iBeta;
it->m_imag -= (int32_t) m_qBeta;
}
}
}
void DSPDeviceSourceEngine::dcOffset(SampleVector::iterator begin, SampleVector::iterator end)
{
// sum and correct in one pass
for(SampleVector::iterator it = begin; it < end; it++)
{
m_iBeta(it->real());
m_qBeta(it->imag());
it->m_real -= (int32_t) m_iBeta;
it->m_imag -= (int32_t) m_qBeta;
}
}
void DSPDeviceSourceEngine::imbalance(SampleVector::iterator begin, SampleVector::iterator end)
{
int iMin = 0;
int iMax = 0;
int qMin = 0;
int qMax = 0;
// find value ranges for both I and Q
// both intervals should be same same size (for a perfect circle)
for (SampleVector::iterator it = begin; it < end; it++)
{
if (it != begin)
{
if (it->real() < iMin) {
iMin = it->real();
} else if (it->real() > iMax) {
iMax = it->real();
}
if (it->imag() < qMin) {
qMin = it->imag();
} else if (it->imag() > qMax) {
qMax = it->imag();
}
}
else
{
iMin = it->real();
iMax = it->real();
qMin = it->imag();
qMax = it->imag();
}
}
// sliding average (el cheapo again)
m_iRange = (m_iRange * 15 + (iMax - iMin)) >> 4;
m_qRange = (m_qRange * 15 + (qMax - qMin)) >> 4;
// calculate imbalance on 32 bit full scale
if(m_qRange != 0) {
m_imbalance = ((uint)m_iRange << (32-SDR_RX_SAMP_SZ)) / (uint)m_qRange;
}
// correct imbalance and convert back to sample size
for(SampleVector::iterator it = begin; it < end; it++) {
it->m_imag = (it->m_imag * m_imbalance) >> (32-SDR_RX_SAMP_SZ);
}
}
void DSPDeviceSourceEngine::work()
{
SampleSinkFifo* sampleFifo = m_deviceSampleSource->getSampleFifo();
std::size_t samplesDone = 0;
bool positiveOnly = m_realElseComplex;
while ((sampleFifo->fill() > 0) && (m_inputMessageQueue.size() == 0) && (samplesDone < m_sampleRate))
{
SampleVector::iterator part1begin;
SampleVector::iterator part1end;
SampleVector::iterator part2begin;
SampleVector::iterator part2end;
std::size_t count = sampleFifo->readBegin(sampleFifo->fill(), &part1begin, &part1end, &part2begin, &part2end);
// first part of FIFO data
if (part1begin != part1end)
{
// correct stuff
if (m_dcOffsetCorrection) {
iqCorrections(part1begin, part1end, m_iqImbalanceCorrection);
}
// feed data to direct sinks
for (BasebandSampleSinks::const_iterator it = m_basebandSampleSinks.begin(); it != m_basebandSampleSinks.end(); ++it) {
(*it)->feed(part1begin, part1end, positiveOnly);
}
}
// second part of FIFO data (used when block wraps around)
if(part2begin != part2end)
{
// correct stuff
if (m_dcOffsetCorrection) {
iqCorrections(part2begin, part2end, m_iqImbalanceCorrection);
}
// feed data to direct sinks
for (BasebandSampleSinks::const_iterator it = m_basebandSampleSinks.begin(); it != m_basebandSampleSinks.end(); it++) {
(*it)->feed(part2begin, part2end, positiveOnly);
}
}
// adjust FIFO pointers
sampleFifo->readCommit((unsigned int) count);
samplesDone += count;
}
}
// notStarted -> idle -> init -> running -+
// ^ |
// +-----------------------+
DSPDeviceSourceEngine::State DSPDeviceSourceEngine::gotoIdle()
{
qDebug("DSPDeviceSourceEngine::gotoIdle");
switch(m_state) {
case State::StNotStarted:
return State::StNotStarted;
case State::StIdle:
case State::StError:
return State::StIdle;
case State::StReady:
case State::StRunning:
break;
}
if (!m_deviceSampleSource) {
return State::StIdle;
}
// stop everything
m_deviceSampleSource->stop();
for(BasebandSampleSinks::const_iterator it = m_basebandSampleSinks.begin(); it != m_basebandSampleSinks.end(); it++)
{
(*it)->stop();
}
m_deviceDescription.clear();
m_sampleRate = 0;
return State::StIdle;
}
DSPDeviceSourceEngine::State DSPDeviceSourceEngine::gotoInit()
{
switch(m_state) {
case State::StNotStarted:
return State::StNotStarted;
case State::StRunning:
return State::StRunning;
case State::StReady:
return State::StReady;
case State::StIdle:
case State::StError:
break;
}
if (!m_deviceSampleSource) {
return gotoError("No sample source configured");
}
// init: pass sample rate and center frequency to all sample rate and/or center frequency dependent sinks and wait for completion
m_iOffset = 0;
m_qOffset = 0;
m_iRange = 1 << 16;
m_qRange = 1 << 16;
m_deviceDescription = m_deviceSampleSource->getDeviceDescription();
m_centerFrequency = m_deviceSampleSource->getCenterFrequency();
m_sampleRate = m_deviceSampleSource->getSampleRate();
qDebug() << "DSPDeviceSourceEngine::gotoInit: "
<< " m_deviceDescription: " << m_deviceDescription.toStdString().c_str()
<< " sampleRate: " << m_sampleRate
<< " centerFrequency: " << m_centerFrequency;
for (BasebandSampleSinks::const_iterator it = m_basebandSampleSinks.begin(); it != m_basebandSampleSinks.end(); ++it)
{
auto *notif = new DSPSignalNotification(m_sampleRate, m_centerFrequency);
qDebug() << "DSPDeviceSourceEngine::gotoInit: initializing " << (*it)->getSinkName().toStdString().c_str();
(*it)->pushMessage(notif);
}
// pass data to listeners
if (m_deviceSampleSource->getMessageQueueToGUI())
{
auto *rep = new DSPSignalNotification(m_sampleRate, m_centerFrequency);
m_deviceSampleSource->getMessageQueueToGUI()->push(rep);
}
return State::StReady;
}
DSPDeviceSourceEngine::State DSPDeviceSourceEngine::gotoRunning()
{
qDebug("DSPDeviceSourceEngine::gotoRunning");
switch(m_state)
{
case State::StNotStarted:
return State::StNotStarted;
case State::StIdle:
return State::StIdle;
case State::StRunning:
return State::StRunning;
case State::StReady:
case State::StError:
break;
}
if (!m_deviceSampleSource) {
return gotoError("DSPDeviceSourceEngine::gotoRunning: No sample source configured");
}
qDebug() << "DSPDeviceSourceEngine::gotoRunning: " << m_deviceDescription.toStdString().c_str() << " started";
// Start everything
if (!m_deviceSampleSource->start()) {
return gotoError("Could not start sample source");
}
for(BasebandSampleSinks::const_iterator it = m_basebandSampleSinks.begin(); it != m_basebandSampleSinks.end(); it++)
{
qDebug() << "DSPDeviceSourceEngine::gotoRunning: starting " << (*it)->getSinkName().toStdString().c_str();
(*it)->start();
}
qDebug() << "DSPDeviceSourceEngine::gotoRunning:input message queue pending: " << m_inputMessageQueue.size();
return State::StRunning;
}
DSPDeviceSourceEngine::State DSPDeviceSourceEngine::gotoError(const QString& errorMessage)
{
qDebug() << "DSPDeviceSourceEngine::gotoError: " << errorMessage;
m_errorMessage = errorMessage;
m_deviceDescription.clear();
setState(State::StError);
return State::StError;
}
void DSPDeviceSourceEngine::handleSetSource(DeviceSampleSource* source)
{
gotoIdle();
m_deviceSampleSource = source;
if (m_deviceSampleSource)
{
qDebug("DSPDeviceSourceEngine::handleSetSource: set %s", qPrintable(source->getDeviceDescription()));
connect(m_deviceSampleSource->getSampleFifo(), SIGNAL(dataReady()), this, SLOT(handleData()), Qt::QueuedConnection);
}
else
{
qDebug("DSPDeviceSourceEngine::handleSetSource: set none");
}
}
void DSPDeviceSourceEngine::handleData()
{
if(m_state == State::StRunning)
{
work();
}
}
bool DSPDeviceSourceEngine::handleMessage(const Message& message)
{
if (DSPConfigureCorrection::match(message))
{
auto& conf = (const DSPConfigureCorrection&) message;
m_iqImbalanceCorrection = conf.getIQImbalanceCorrection();
if (m_dcOffsetCorrection != conf.getDCOffsetCorrection())
{
m_dcOffsetCorrection = conf.getDCOffsetCorrection();
m_iOffset = 0;
m_qOffset = 0;
}
if (m_iqImbalanceCorrection != conf.getIQImbalanceCorrection())
{
m_iqImbalanceCorrection = conf.getIQImbalanceCorrection();
m_iRange = 1 << 16;
m_qRange = 1 << 16;
m_imbalance = 65536;
}
m_avgAmp.reset();
m_avgII.reset();
m_avgII2.reset();
m_avgIQ.reset();
m_avgPhi.reset();
m_avgQQ2.reset();
m_iBeta.reset();
m_qBeta.reset();
return true;
}
else if (DSPSignalNotification::match(message))
{
auto& notif = (const DSPSignalNotification&) message;
// update DSP values
m_sampleRate = notif.getSampleRate();
m_centerFrequency = notif.getCenterFrequency();
m_realElseComplex = notif.getRealElseComplex();
qDebug() << "DSPDeviceSourceEngine::handleInputMessages: DSPSignalNotification:"
<< " m_sampleRate: " << m_sampleRate
<< " m_centerFrequency: " << m_centerFrequency;
// forward source changes to channel sinks with immediate execution (no queuing)
for(BasebandSampleSinks::const_iterator it = m_basebandSampleSinks.begin(); it != m_basebandSampleSinks.end(); it++)
{
auto* rep = new DSPSignalNotification(notif); // make a copy
qDebug() << "DSPDeviceSourceEngine::handleInputMessages: forward message to " << (*it)->getSinkName().toStdString().c_str();
(*it)->pushMessage(rep);
}
// forward changes to source GUI input queue
if (m_deviceSampleSource)
{
MessageQueue *guiMessageQueue = m_deviceSampleSource->getMessageQueueToGUI();
qDebug("DSPDeviceSourceEngine::handleInputMessages: DSPSignalNotification: guiMessageQueue: %p", guiMessageQueue);
if (guiMessageQueue)
{
auto* rep = new DSPSignalNotification(notif); // make a copy for the source GUI
guiMessageQueue->push(rep);
}
}
return true;
}
// was in handleSynchronousMessages:
else if (DSPAcquisitionInit::match(message))
{
return true; // discard
}
else if (DSPAcquisitionStart::match(message))
{
setState(gotoIdle());
if(m_state == State::StIdle) {
setState(gotoInit()); // State goes ready if init is performed
}
if(m_state == State::StReady) {
setState(gotoRunning());
}
return true;
}
else if (DSPAcquisitionStop::match(message))
{
setState(gotoIdle());
emit acquistionStopped();
return true;
}
else if (DSPSetSource::match(message))
{
auto cmd = (const DSPSetSource&) message;
handleSetSource(cmd.getSampleSource());
emit sampleSet();
return true;
}
else if (DSPAddBasebandSampleSink::match(message))
{
auto cmd = (const DSPAddBasebandSampleSink&) message;
BasebandSampleSink* sink = cmd.getSampleSink();
m_basebandSampleSinks.push_back(sink);
// initialize sample rate and center frequency in the sink:
auto *msg = new DSPSignalNotification(m_sampleRate, m_centerFrequency);
sink->pushMessage(msg);
// start the sink:
if(m_state == State::StRunning) {
sink->start();
}
return true;
}
else if (DSPRemoveBasebandSampleSink::match(message))
{
auto cmd = (const DSPRemoveBasebandSampleSink&) message;
BasebandSampleSink* sink = cmd.getSampleSink();
bool deleting = cmd.getDeleting();
// Don't dereference sink if deleting, as it may have already been deleted
if (!deleting && (m_state == State::StRunning)) {
sink->stop();
}
m_basebandSampleSinks.remove(sink);
emit sinkRemoved();
return true;
}
return false;
}
void DSPDeviceSourceEngine::handleInputMessages()
{
Message* message;
while ((message = m_inputMessageQueue.pop()) != nullptr)
{
qDebug("DSPDeviceSourceEngine::handleInputMessages: message: %s", message->getIdentifier());
if (handleMessage(*message)) {
delete message;
}
}
}

View File

@@ -0,0 +1,151 @@
///////////////////////////////////////////////////////////////////////////////////
// Copyright (C) 2012 maintech GmbH, Otto-Hahn-Str. 15, 97204 Hoechberg, Germany //
// written by Christian Daniel //
// Copyright (C) 2014 John Greb <hexameron@spam.no> //
// Copyright (C) 2015-2019, 2023 Edouard Griffiths, F4EXB <f4exb06@gmail.com> //
// Copyright (C) 2022 Jon Beniston, M7RCE <jon@beniston.com> //
// //
// This program 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 as version 3 of the License, or //
// (at your option) any later version. //
// //
// This program 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 V3 for more details. //
// //
// You should have received a copy of the GNU General Public License //
// along with this program. If not, see <http://www.gnu.org/licenses/>. //
///////////////////////////////////////////////////////////////////////////////////
#ifndef INCLUDE_DSPDEVICEENGINE_H
#define INCLUDE_DSPDEVICEENGINE_H
#include <QObject>
#include <QTimer>
#include <QMutex>
#include <QWaitCondition>
#include "dsp/dsptypes.h"
#include "util/messagequeue.h"
#include "export.h"
#include "util/movingaverage.h"
class DeviceSampleSource;
class BasebandSampleSink;
class SDRBASE_API DSPDeviceSourceEngine : public QObject {
Q_OBJECT
public:
enum class State {
StNotStarted, //!< engine is before initialization
StIdle, //!< engine is idle
StReady, //!< engine is ready to run
StRunning, //!< engine is running
StError //!< engine is in error
};
DSPDeviceSourceEngine(uint uid, QObject* parent = nullptr);
~DSPDeviceSourceEngine() final;
uint getUID() const { return m_uid; }
MessageQueue* getInputMessageQueue() { return &m_inputMessageQueue; }
bool initAcquisition() const; //!< Initialize acquisition sequence
bool startAcquisition(); //!< Start acquisition sequence
void stopAcquistion(); //!< Stop acquisition sequence
void setSource(DeviceSampleSource* source); //!< Set the sample source type
void setSourceSequence(int sequence); //!< Set the sample source sequence in type
DeviceSampleSource *getSource() { return m_deviceSampleSource; }
void addSink(BasebandSampleSink* sink); //!< Add a sample sink
void removeSink(BasebandSampleSink* sink, bool deleting); //!< Remove a sample sink
void configureCorrections(bool dcOffsetCorrection, bool iqImbalanceCorrection); //!< Configure DSP corrections
State state() const { return m_state; } //!< Return DSP engine current state
QString errorMessage() const; //!< Return the current error message
QString sourceDeviceDescription() const; //!< Return the source device description
private:
uint m_uid; //!< unique ID
MessageQueue m_inputMessageQueue; //<! Input message queue. Post here.
State m_state;
QString m_errorMessage;
QString m_deviceDescription;
DeviceSampleSource* m_deviceSampleSource;
int m_sampleSourceSequence;
using BasebandSampleSinks = std::list<BasebandSampleSink *>;
BasebandSampleSinks m_basebandSampleSinks; //!< sample sinks within main thread (usually spectrum, file output)
uint m_sampleRate;
quint64 m_centerFrequency;
bool m_realElseComplex;
bool m_dcOffsetCorrection;
bool m_iqImbalanceCorrection;
double m_iOffset;
double m_qOffset;
MovingAverageUtil<int32_t, int64_t, 1024> m_iBeta;
MovingAverageUtil<int32_t, int64_t, 1024> m_qBeta;
#if IMBALANCE_INT
// Fixed point DC + IQ corrections
MovingAverageUtil<int64_t, int64_t, 128> m_avgII;
MovingAverageUtil<int64_t, int64_t, 128> m_avgIQ;
MovingAverageUtil<int64_t, int64_t, 128> m_avgPhi;
MovingAverageUtil<int64_t, int64_t, 128> m_avgII2;
MovingAverageUtil<int64_t, int64_t, 128> m_avgQQ2;
MovingAverageUtil<int64_t, int64_t, 128> m_avgAmp;
#else
// Floating point DC + IQ corrections
MovingAverageUtil<float, double, 128> m_avgII;
MovingAverageUtil<float, double, 128> m_avgIQ;
MovingAverageUtil<float, double, 128> m_avgII2;
MovingAverageUtil<float, double, 128> m_avgQQ2;
MovingAverageUtil<double, double, 128> m_avgPhi;
MovingAverageUtil<double, double, 128> m_avgAmp;
#endif
qint32 m_iRange;
qint32 m_qRange;
qint32 m_imbalance;
void iqCorrections(SampleVector::iterator begin, SampleVector::iterator end, bool imbalanceCorrection);
void dcOffset(SampleVector::iterator begin, SampleVector::iterator end);
void imbalance(SampleVector::iterator begin, SampleVector::iterator end);
void work(); //!< transfer samples from source to sinks if in running state
State gotoIdle(); //!< Go to the idle state
State gotoInit(); //!< Go to the acquisition init state from idle
State gotoRunning(); //!< Go to the running state from ready state
State gotoError(const QString& errorMsg); //!< Go to an error state
void setState(State state);
void handleSetSource(DeviceSampleSource* source); //!< Manage source setting
bool handleMessage(const Message& cmd);
private slots:
void handleData(); //!< Handle data when samples from source FIFO are ready to be processed
void handleInputMessages(); //!< Handle input message queue
signals:
void stateChanged();
void acquistionStopped();
void sampleSet();
void sinkRemoved();
};
#endif // INCLUDE_DSPDEVICEENGINE_H

View File

@@ -0,0 +1,235 @@
///////////////////////////////////////////////////////////////////////////////////
// Copyright (C) 2012 maintech GmbH, Otto-Hahn-Str. 15, 97204 Hoechberg, Germany //
// written by Christian Daniel //
// Copyright (C) 2014 John Greb <hexameron@spam.no> //
// Copyright (C) 2015-2020, 2022 Edouard Griffiths, F4EXB <f4exb06@gmail.com> //
// //
// This program 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 as version 3 of the License, or //
// (at your option) any later version. //
// //
// This program 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 V3 for more details. //
// //
// You should have received a copy of the GNU General Public License //
// along with this program. If not, see <http://www.gnu.org/licenses/>. //
///////////////////////////////////////////////////////////////////////////////////
#include <QGlobalStatic>
#include <QThread>
#include "dsp/dspengine.h"
#include "dsp/dspdevicesourceengine.h"
#include "dsp/dspdevicesinkengine.h"
#include "dsp/dspdevicemimoengine.h"
#include "dsp/fftfactory.h"
DSPEngine::DSPEngine() :
m_deviceSourceEnginesUIDSequence(0),
m_deviceSinkEnginesUIDSequence(0),
m_deviceMIMOEnginesUIDSequence(0),
m_audioInputDeviceIndex(-1), // default device
m_audioOutputDeviceIndex(-1), // default device
m_fftFactory(nullptr)
{
m_dvSerialSupport = false;
m_mimoSupport = false;
m_masterTimer.start(50);
}
DSPEngine::~DSPEngine()
{
auto it = m_deviceSourceEngines.begin();
while (it != m_deviceSourceEngines.end())
{
delete *it;
++it;
}
if (m_fftFactory) {
delete m_fftFactory;
}
}
Q_GLOBAL_STATIC(DSPEngine, dspEngine)
DSPEngine *DSPEngine::instance()
{
return dspEngine;
}
DSPDeviceSourceEngine *DSPEngine::addDeviceSourceEngine()
{
auto *deviceSourceEngine = new DSPDeviceSourceEngine(m_deviceSourceEnginesUIDSequence);
auto *deviceThread = new QThread();
m_deviceSourceEnginesUIDSequence++;
m_deviceSourceEngines.push_back(deviceSourceEngine);
m_deviceEngineReferences.push_back(DeviceEngineReference{0, m_deviceSourceEngines.back(), nullptr, nullptr, deviceThread});
deviceSourceEngine->moveToThread(deviceThread);
QObject::connect(
deviceThread,
&QThread::finished,
deviceThread,
&QThread::deleteLater
);
deviceThread->start();
return deviceSourceEngine;
}
void DSPEngine::removeLastDeviceSourceEngine()
{
if (!m_deviceSourceEngines.empty())
{
const DSPDeviceSourceEngine *lastDeviceEngine = m_deviceSourceEngines.back();
m_deviceSourceEngines.pop_back();
for (int i = 0; i < m_deviceEngineReferences.size(); i++)
{
if (m_deviceEngineReferences[i].m_deviceSourceEngine == lastDeviceEngine)
{
QThread* deviceThread = m_deviceEngineReferences[i].m_thread;
deviceThread->exit();
deviceThread->wait();
m_deviceEngineReferences.removeAt(i);
break;
}
}
}
}
DSPDeviceSinkEngine *DSPEngine::addDeviceSinkEngine()
{
auto *deviceSinkEngine = new DSPDeviceSinkEngine(m_deviceSinkEnginesUIDSequence);
auto *deviceThread = new QThread();
m_deviceSinkEnginesUIDSequence++;
m_deviceSinkEngines.push_back(deviceSinkEngine);
m_deviceEngineReferences.push_back(DeviceEngineReference{1, nullptr, m_deviceSinkEngines.back(), nullptr, deviceThread});
deviceSinkEngine->moveToThread(deviceThread);
QObject::connect(
deviceThread,
&QThread::finished,
deviceThread,
&QThread::deleteLater
);
deviceThread->start();
return deviceSinkEngine;
}
void DSPEngine::removeLastDeviceSinkEngine()
{
if (!m_deviceSinkEngines.empty())
{
const DSPDeviceSinkEngine *lastDeviceEngine = m_deviceSinkEngines.back();
m_deviceSinkEngines.pop_back();
for (int i = 0; i < m_deviceEngineReferences.size(); i++)
{
if (m_deviceEngineReferences[i].m_deviceSinkEngine == lastDeviceEngine)
{
QThread* deviceThread = m_deviceEngineReferences[i].m_thread;
deviceThread->exit();
deviceThread->wait();
m_deviceEngineReferences.removeAt(i);
break;
}
}
}
}
DSPDeviceMIMOEngine *DSPEngine::addDeviceMIMOEngine()
{
auto *deviceMIMOEngine = new DSPDeviceMIMOEngine(m_deviceMIMOEnginesUIDSequence);
auto *deviceThread = new QThread();
m_deviceMIMOEnginesUIDSequence++;
m_deviceMIMOEngines.push_back(deviceMIMOEngine);
m_deviceEngineReferences.push_back(DeviceEngineReference{2, nullptr, nullptr, m_deviceMIMOEngines.back(), deviceThread});
deviceMIMOEngine->moveToThread(deviceThread);
QObject::connect(
deviceThread,
&QThread::finished,
deviceThread,
&QThread::deleteLater
);
deviceThread->start();
return deviceMIMOEngine;
}
void DSPEngine::removeLastDeviceMIMOEngine()
{
if (!m_deviceMIMOEngines.empty())
{
const DSPDeviceMIMOEngine *lastDeviceEngine = m_deviceMIMOEngines.back();
m_deviceMIMOEngines.pop_back();
for (int i = 0; i < m_deviceEngineReferences.size(); i++)
{
if (m_deviceEngineReferences[i].m_deviceMIMOEngine == lastDeviceEngine)
{
QThread* deviceThread = m_deviceEngineReferences[i].m_thread;
deviceThread->exit();
deviceThread->wait();
m_deviceEngineReferences.removeAt(i);
break;
}
}
}
}
QThread *DSPEngine::getDeviceEngineThread(int deviceIndex)
{
if (deviceIndex >= m_deviceEngineReferences.size()) {
return nullptr;
} else {
return m_deviceEngineReferences[deviceIndex].m_thread;
}
}
void DSPEngine::removeDeviceEngineAt(int deviceIndex)
{
if (deviceIndex >= m_deviceEngineReferences.size()) {
return;
}
if (m_deviceEngineReferences[deviceIndex].m_deviceEngineType == 0) // source
{
DSPDeviceSourceEngine *deviceEngine = m_deviceEngineReferences[deviceIndex].m_deviceSourceEngine;
m_deviceEngineReferences[deviceIndex].m_thread->exit();
m_deviceSourceEngines.removeAll(deviceEngine);
}
else if (m_deviceEngineReferences[deviceIndex].m_deviceEngineType == 1) // sink
{
DSPDeviceSinkEngine *deviceEngine = m_deviceEngineReferences[deviceIndex].m_deviceSinkEngine;
m_deviceEngineReferences[deviceIndex].m_thread->exit();
m_deviceSinkEngines.removeAll(deviceEngine);
}
else if (m_deviceEngineReferences[deviceIndex].m_deviceEngineType == 2) // MIMO
{
DSPDeviceMIMOEngine *deviceEngine = m_deviceEngineReferences[deviceIndex].m_deviceMIMOEngine;
m_deviceEngineReferences[deviceIndex].m_thread->exit();
m_deviceMIMOEngines.removeAll(deviceEngine);
}
m_deviceEngineReferences.removeAt(deviceIndex);
}
void DSPEngine::createFFTFactory(const QString& fftWisdomFileName)
{
m_fftFactory = new FFTFactory(fftWisdomFileName);
}
void DSPEngine::preAllocateFFTs()
{
m_fftFactory->preallocate(7, 10, 1, 0); // pre-acllocate forward FFT only 1 per size from 128 to 1024
}

View File

@@ -0,0 +1,103 @@
///////////////////////////////////////////////////////////////////////////////////
// Copyright (C) 2012 maintech GmbH, Otto-Hahn-Str. 15, 97204 Hoechberg, Germany //
// written by Christian Daniel //
// Copyright (C) 2014 John Greb <hexameron@spam.no> //
// Copyright (C) 2015-2020, 2022 Edouard Griffiths, F4EXB <f4exb06@gmail.com> //
// //
// This program 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 as version 3 of the License, or //
// (at your option) any later version. //
// //
// This program 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 V3 for more details. //
// //
// You should have received a copy of the GNU General Public License //
// along with this program. If not, see <http://www.gnu.org/licenses/>. //
///////////////////////////////////////////////////////////////////////////////////
#ifndef INCLUDE_DSPENGINE_H
#define INCLUDE_DSPENGINE_H
#include <QObject>
#include <QTimer>
#include <vector>
#include "audio/audiodevicemanager.h"
#include "export.h"
class DSPDeviceSourceEngine;
class DSPDeviceSinkEngine;
class DSPDeviceMIMOEngine;
class FFTFactory;
class QThread;
class SDRBASE_API DSPEngine : public QObject {
Q_OBJECT
public:
DSPEngine();
~DSPEngine();
static DSPEngine *instance();
unsigned int getDefaultAudioSampleRate() const { return AudioDeviceManager::m_defaultAudioSampleRate; }
DSPDeviceSourceEngine *addDeviceSourceEngine();
void removeLastDeviceSourceEngine();
DSPDeviceSinkEngine *addDeviceSinkEngine();
void removeLastDeviceSinkEngine();
DSPDeviceMIMOEngine *addDeviceMIMOEngine();
void removeLastDeviceMIMOEngine();
QThread *getDeviceEngineThread(int deviceIndex);
void removeDeviceEngineAt(int deviceIndex);
AudioDeviceManager *getAudioDeviceManager() { return &m_audioDeviceManager; }
uint32_t getDeviceSourceEnginesNumber() const { return m_deviceSourceEngines.size(); }
DSPDeviceSourceEngine *getDeviceSourceEngineByIndex(unsigned int deviceIndex) { return m_deviceSourceEngines[deviceIndex]; }
uint32_t getDeviceSinkEnginesNumber() const { return m_deviceSinkEngines.size(); }
DSPDeviceSinkEngine *getDeviceSinkEngineByIndex(unsigned int deviceIndex) { return m_deviceSinkEngines[deviceIndex]; }
uint32_t getDeviceMIMOEnginesNumber() const { return m_deviceMIMOEngines.size(); }
DSPDeviceMIMOEngine *getDeviceMIMOEngineByIndex(unsigned int deviceIndex) { return m_deviceMIMOEngines[deviceIndex]; }
const QTimer& getMasterTimer() const { return m_masterTimer; }
void setMIMOSupport(bool mimoSupport) { m_mimoSupport = mimoSupport; }
bool getMIMOSupport() const { return m_mimoSupport; }
void createFFTFactory(const QString& fftWisdomFileName);
void preAllocateFFTs();
FFTFactory *getFFTFactory() { return m_fftFactory; }
private:
struct DeviceEngineReference
{
int m_deviceEngineType; //!< 0: Rx, 1: Tx, 2: MIMO
DSPDeviceSourceEngine *m_deviceSourceEngine;
DSPDeviceSinkEngine *m_deviceSinkEngine;
DSPDeviceMIMOEngine *m_deviceMIMOEngine;
QThread *m_thread;
};
QList<DSPDeviceSourceEngine*> m_deviceSourceEngines;
unsigned int m_deviceSourceEnginesUIDSequence;
QList<DSPDeviceSinkEngine*> m_deviceSinkEngines;
unsigned int m_deviceSinkEnginesUIDSequence;
QList<DSPDeviceMIMOEngine*> m_deviceMIMOEngines;
unsigned int m_deviceMIMOEnginesUIDSequence;
QList<DeviceEngineReference> m_deviceEngineReferences;
AudioDeviceManager m_audioDeviceManager;
int m_audioInputDeviceIndex;
int m_audioOutputDeviceIndex;
QTimer m_masterTimer;
bool m_dvSerialSupport;
bool m_mimoSupport;
FFTFactory *m_fftFactory;
};
#endif // INCLUDE_DSPENGINE_H

View File

@@ -0,0 +1,102 @@
///////////////////////////////////////////////////////////////////////////////////
// Copyright (C) 2012 maintech GmbH, Otto-Hahn-Str. 15, 97204 Hoechberg, Germany //
// written by Christian Daniel //
// Copyright (C) 2015-2019, 2021 Edouard Griffiths, F4EXB <f4exb06@gmail.com> //
// //
// This program 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 as version 3 of the License, or //
// (at your option) any later version. //
// //
// This program 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 V3 for more details. //
// //
// You should have received a copy of the GNU General Public License //
// along with this program. If not, see <http://www.gnu.org/licenses/>. //
///////////////////////////////////////////////////////////////////////////////////
#ifndef INCLUDE_DSPTYPES_H
#define INCLUDE_DSPTYPES_H
#include <complex>
#include <vector>
#include <stdint.h>
#ifdef SDR_RX_SAMPLE_24BIT
#define SDR_RX_SAMP_SZ 24 // internal fixed arithmetic sample size
#define SDR_RX_SCALEF 8388608.0f
#define SDR_RX_SCALED 8388608.0
typedef qint32 FixReal;
#else
#define SDR_RX_SAMP_SZ 16 // internal fixed arithmetic sample size
#define SDR_RX_SCALEF 32768.0f
#define SDR_RX_SCALED 32768.0
typedef int16_t FixReal;
#endif
#define SDR_TX_SAMP_SZ 16
#define SDR_TX_SCALEF 32768.0f
#define SDR_TX_SCALED 32768.0
typedef float Real;
typedef std::complex<Real> Complex;
typedef std::vector<Complex> ComplexVector;
#pragma pack(push, 1)
struct Sample
{
Sample() : m_real(0), m_imag(0) {}
Sample(FixReal real) : m_real(real), m_imag(0) {}
Sample(FixReal real, FixReal imag) : m_real(real), m_imag(imag) {}
Sample(const Sample& other) : m_real(other.m_real), m_imag(other.m_imag) {}
inline Sample& operator=(const Sample& other) { m_real = other.m_real; m_imag = other.m_imag; return *this; }
inline Sample& operator+=(const Sample& other) { m_real += other.m_real; m_imag += other.m_imag; return *this; }
inline Sample& operator-=(const Sample& other) { m_real -= other.m_real; m_imag -= other.m_imag; return *this; }
inline Sample& operator/=(const unsigned int& divisor) { m_real /= divisor; m_imag /= divisor; return *this; }
inline void setReal(FixReal v) { m_real = v; }
inline void setImag(FixReal v) { m_imag = v; }
inline FixReal real() const { return m_real; }
inline FixReal imag() const { return m_imag; }
FixReal m_real;
FixReal m_imag;
};
struct FSample
{
FSample() : m_real(0), m_imag(0) {}
FSample(Real real) : m_real(real), m_imag(0) {}
FSample(Real real, Real imag) : m_real(real), m_imag(imag) {}
FSample(const FSample& other) : m_real(other.m_real), m_imag(other.m_imag) {}
inline FSample& operator=(const FSample& other) { m_real = other.m_real; m_imag = other.m_imag; return *this; }
inline FSample& operator+=(const FSample& other) { m_real += other.m_real; m_imag += other.m_imag; return *this; }
inline FSample& operator-=(const FSample& other) { m_real -= other.m_real; m_imag -= other.m_imag; return *this; }
inline FSample& operator/=(const Real& divisor) { m_real /= divisor; m_imag /= divisor; return *this; }
inline void setReal(Real v) { m_real = v; }
inline void setImag(Real v) { m_imag = v; }
inline Real real() const { return m_real; }
inline Real imag() const { return m_imag; }
Real m_real;
Real m_imag;
};
struct AudioSample {
int16_t l;
int16_t r;
};
#pragma pack(pop)
typedef std::vector<Sample> SampleVector;
typedef std::vector<FSample> FSampleVector;
typedef std::vector<AudioSample> AudioVector;
#endif // INCLUDE_DSPTYPES_H

View File

@@ -0,0 +1,128 @@
///////////////////////////////////////////////////////////////////////////////////
// Copyright (C) 2018-2020 Edouard Griffiths, F4EXB <f4exb06@gmail.com> //
// //
// FFT based cross correlation. Uses FFTW/Kiss engine. //
// //
// This program 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 as version 3 of the License, or //
// (at your option) any later version. //
// //
// This program 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 V3 for more details. //
// //
// You should have received a copy of the GNU General Public License //
// along with this program. If not, see <http://www.gnu.org/licenses/>. //
///////////////////////////////////////////////////////////////////////////////////
#include <algorithm>
#include "dsp/dspengine.h"
#include "dsp/fftfactory.h"
#include "dsp/fftengine.h"
#include "fftcorr.h"
void fftcorr::init_fft()
{
FFTFactory *fftFactory = DSPEngine::instance()->getFFTFactory();
fftASequence = fftFactory->getEngine(flen, false, &fftA);
fftBSequence = fftFactory->getEngine(flen, false, &fftB);
fftInvASequence = fftFactory->getEngine(flen, true, &fftInvA);
m_window.create(FFTWindow::Hanning, flen);
dataA = new cmplx[flen];
dataB = new cmplx[flen];
dataBj = new cmplx[flen];
dataP = new cmplx[flen];
std::fill(dataA, dataA+flen, 0);
std::fill(dataB, dataB+flen, 0);
inptrA = 0;
inptrB = 0;
outptr = 0;
}
fftcorr::fftcorr(int len) :
flen(len),
flen2(len>>1),
fftA(nullptr),
fftB(nullptr),
fftInvA(nullptr),
fftASequence(0),
fftBSequence(0),
fftInvASequence(0)
{
init_fft();
}
fftcorr::~fftcorr()
{
FFTFactory *fftFactory = DSPEngine::instance()->getFFTFactory();
fftFactory->releaseEngine(flen, false, fftASequence);
fftFactory->releaseEngine(flen, false, fftBSequence);
fftFactory->releaseEngine(flen, true, fftInvASequence);
delete[] dataA;
delete[] dataB;
delete[] dataBj;
delete[] dataP;
}
int fftcorr::run(const cmplx& inA, const cmplx* inB, cmplx **out)
{
dataA[inptrA++] = inA;
if (inB) {
dataB[inptrB++] = *inB;
}
if (inptrA < flen2) {
return 0;
}
m_window.apply(dataA, fftA->in());
fftA->transform();
if (inB)
{
m_window.apply(dataB, fftB->in());
fftB->transform();
}
if (inB) {
std::transform(fftB->out(), fftB->out()+flen, dataBj, [](const cmplx& c) -> cmplx { return std::conj(c); });
} else {
std::transform(fftA->out(), fftA->out()+flen, dataBj, [](const cmplx& c) -> cmplx { return std::conj(c); });
}
std::transform(fftA->out(), fftA->out()+flen, dataBj, fftInvA->in(), [](const cmplx& a, const cmplx& b) -> cmplx { return a*b; });
fftInvA->transform();
std::copy(fftInvA->out(), fftInvA->out()+flen, dataP);
std::fill(dataA, dataA+flen, 0);
inptrA = 0;
if (inB)
{
std::fill(dataB, dataB+flen, 0);
inptrB = 0;
}
*out = dataP;
return flen2;
}
const fftcorr::cmplx& fftcorr::run(const cmplx& inA, const cmplx* inB)
{
cmplx *dummy;
if (run(inA, inB, &dummy)) {
outptr = 0;
}
return dataP[outptr++];
}

View File

@@ -0,0 +1,60 @@
///////////////////////////////////////////////////////////////////////////////////
// Copyright (C) 2018-2020 Edouard Griffiths, F4EXB <f4exb06@gmail.com> //
// //
// FFT based cross correlation. Uses FFTW/Kiss engine. //
// //
// This program 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 as version 3 of the License, or //
// (at your option) any later version. //
// //
// This program 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 V3 for more details. //
// //
// You should have received a copy of the GNU General Public License //
// along with this program. If not, see <http://www.gnu.org/licenses/>. //
///////////////////////////////////////////////////////////////////////////////////
#ifndef SDRBASE_DSP_FFTCORR2_H_
#define SDRBASE_DSP_FFTCORR2_H_
#include <complex>
#include "dsp/fftwindow.h"
#include "export.h"
class FFTEngine;
class SDRBASE_API fftcorr {
public:
typedef std::complex<float> cmplx;
fftcorr(int len);
~fftcorr();
int run(const cmplx& inA, const cmplx* inB, cmplx **out); //!< if inB = 0 then run auto-correlation
const cmplx& run(const cmplx& inA, const cmplx* inB);
private:
void init_fft();
int flen; //!< FFT length
int flen2; //!< half FFT length
FFTEngine *fftA;
FFTEngine *fftB;
FFTEngine *fftInvA;
unsigned int fftASequence;
unsigned int fftBSequence;
unsigned int fftInvASequence;
FFTWindow m_window;
cmplx *dataA; // from A input
cmplx *dataB; // from B input
cmplx *dataBj; // conjugate of B
cmplx *dataP; // product of A with conjugate of B
int inptrA;
int inptrB;
int outptr;
};
#endif /* SDRBASE_DSP_FFTCORR2_H_ */

View File

@@ -0,0 +1,118 @@
///////////////////////////////////////////////////////////////////////////////////
// Copyright (C) 2012 maintech GmbH, Otto-Hahn-Str. 15, 97204 Hoechberg, Germany //
// written by Christian Daniel //
// Copyright (C) 2017-2018, 2020 Edouard Griffiths, F4EXB <f4exb06@gmail.com> //
// Copyright (C) 2023 Jon Beniston, M7RCE <jon@beniston.com> //
// //
// This program 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 as version 3 of the License, or //
// (at your option) any later version. //
// //
// This program 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 V3 for more details. //
// //
// You should have received a copy of the GNU General Public License //
// along with this program. If not, see <http://www.gnu.org/licenses/>. //
///////////////////////////////////////////////////////////////////////////////////
#include <QDebug>
#include "dsp/fftengine.h"
#ifdef USE_KISSFFT
#include "dsp/kissengine.h"
#endif
#ifdef USE_FFTW
#include "dsp/fftwengine.h"
#endif
#ifdef VKFFT_BACKEND
#if VKFFT_BACKEND==0
#include "dsp/vulkanvkfftengine.h"
#elif VKFFT_BACKEND==1
#include "dsp/cudavkfftengine.h"
#endif
#endif
QStringList FFTEngine::m_allAvailableEngines;
FFTEngine::~FFTEngine()
{
}
FFTEngine* FFTEngine::create(const QString& fftWisdomFileName, const QString& preferredEngine)
{
QStringList allNames = getAllNames();
QString engine;
if (allNames.size() == 0)
{
// No engines available
qCritical("FFTEngine::create: no engine built");
return nullptr;
}
else if (!preferredEngine.isEmpty() && allNames.contains(preferredEngine))
{
// Use the preferred engine
engine = preferredEngine;
}
else
{
// Use first available
engine = allNames[0];
}
qDebug("FFTEngine::create: using %s engine", qPrintable(engine));
#ifdef VKFFT_BACKEND
#if VKFFT_BACKEND==0
if (engine == VulkanvkFFTEngine::m_name) {
return new VulkanvkFFTEngine();
}
#endif
#if VKFFT_BACKEND==1
if (engine == CUDAvkFFTEngine::m_name) {
return new CUDAvkFFTEngine();
}
#endif
#endif
#ifdef USE_FFTW
if (engine == FFTWEngine::m_name) {
return new FFTWEngine(fftWisdomFileName);
}
#endif
#ifdef USE_KISSFFT
if (engine == KissEngine::m_name) {
return new KissEngine;
}
#endif
return nullptr;
}
QStringList FFTEngine::getAllNames()
{
if (m_allAvailableEngines.size() == 0)
{
#ifdef USE_FFTW
m_allAvailableEngines.append(FFTWEngine::m_name);
#endif
#ifdef USE_KISSFFT
m_allAvailableEngines.append(KissEngine::m_name);
#endif
#ifdef VKFFT_BACKEND
#if VKFFT_BACKEND==0
VulkanvkFFTEngine vulkanvkFFT;
if (vulkanvkFFT.isAvailable()) {
m_allAvailableEngines.append(vulkanvkFFT.getName());
}
#elif VKFFT_BACKEND==1
CUDAvkFFTEngine cudavkFFT;
if (cudavkFFT.isAvailable()) {
m_allAvailableEngines.append(cudavkFFT.getName());
}
#endif
#endif
}
return m_allAvailableEngines;
}

View File

@@ -0,0 +1,53 @@
///////////////////////////////////////////////////////////////////////////////////
// Copyright (C) 2012 maintech GmbH, Otto-Hahn-Str. 15, 97204 Hoechberg, Germany //
// written by Christian Daniel //
// Copyright (C) 2015-2016, 2018, 2020 Edouard Griffiths, F4EXB <f4exb06@gmail.com> //
// Copyright (C) 2023 Jon Beniston, M7RCE <jon@beniston.com> //
// //
// This program 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 as version 3 of the License, or //
// (at your option) any later version. //
// //
// This program 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 V3 for more details. //
// //
// You should have received a copy of the GNU General Public License //
// along with this program. If not, see <http://www.gnu.org/licenses/>. //
///////////////////////////////////////////////////////////////////////////////////
#ifndef INCLUDE_FFTENGINE_H
#define INCLUDE_FFTENGINE_H
#include <QString>
#include "dsp/dsptypes.h"
#include "export.h"
class SDRBASE_API FFTEngine {
public:
virtual ~FFTEngine();
virtual void configure(int n, bool inverse) = 0;
virtual void transform() = 0;
virtual Complex* in() = 0;
virtual Complex* out() = 0;
virtual void setReuse(bool reuse) = 0;
static FFTEngine* create(const QString& fftWisdomFileName, const QString& preferredEngine="");
virtual bool isAvailable() { return true; } // Is this FFT engine available to be used?
virtual QString getName() const = 0; // Get the name of this FFT engine
static QStringList getAllNames(); // Get names of all available FFT engines
private:
static QStringList m_allAvailableEngines;
};
#endif // INCLUDE_FFTENGINE_H

View File

@@ -0,0 +1,153 @@
///////////////////////////////////////////////////////////////////////////////////
// Copyright (C) 2020 Edouard Griffiths, F4EXB <f4exb06@gmail.com> //
// Copyright (C) 2022 Jiří Pinkava <jiri.pinkava@rossum.ai> //
// Copyright (C) 2023 Jon Beniston, M7RCE <jon@beniston.com> //
// //
// This program 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 as version 3 of the License, or //
// (at your option) any later version. //
// //
// This program 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 V3 for more details. //
// //
// You should have received a copy of the GNU General Public License //
// along with this program. If not, see <http://www.gnu.org/licenses/>. //
///////////////////////////////////////////////////////////////////////////////////
#include <QMutexLocker>
#include "fftfactory.h"
#include "maincore.h"
FFTFactory::FFTFactory(const QString& fftwWisdomFileName) :
m_fftwWisdomFileName(fftwWisdomFileName)
{}
FFTFactory::~FFTFactory()
{
qDebug("FFTFactory::~FFTFactory: deleting FFTs");
for (auto mIt = m_fftEngineBySize.begin(); mIt != m_fftEngineBySize.end(); ++mIt)
{
for (auto eIt = mIt->second.begin(); eIt != mIt->second.end(); ++eIt) {
delete eIt->m_engine;
}
}
}
void FFTFactory::preallocate(
unsigned int minLog2Size,
unsigned int maxLog2Size,
unsigned int numberFFT,
unsigned int numberInvFFT)
{
if (minLog2Size <= maxLog2Size)
{
for (unsigned int log2Size = minLog2Size; log2Size <= maxLog2Size; log2Size++)
{
unsigned int fftSize = 1<<log2Size;
m_fftEngineBySize.insert(std::pair<unsigned int, std::vector<AllocatedEngine>>(fftSize, std::vector<AllocatedEngine>()));
m_invFFTEngineBySize.insert(std::pair<unsigned int, std::vector<AllocatedEngine>>(fftSize, std::vector<AllocatedEngine>()));
std::vector<AllocatedEngine>& fftEngines = m_fftEngineBySize[fftSize];
std::vector<AllocatedEngine>& invFFTEngines = m_invFFTEngineBySize[fftSize];
for (unsigned int i = 0; i < numberFFT; i++)
{
fftEngines.push_back(AllocatedEngine());
fftEngines.back().m_engine = FFTEngine::create(m_fftwWisdomFileName);
fftEngines.back().m_engine->setReuse(false);
fftEngines.back().m_engine->configure(fftSize, false);
}
for (unsigned int i = 0; i < numberInvFFT; i++)
{
invFFTEngines.push_back(AllocatedEngine());
invFFTEngines.back().m_engine = FFTEngine::create(m_fftwWisdomFileName);
fftEngines.back().m_engine->setReuse(false);
invFFTEngines.back().m_engine->configure(fftSize, true);
}
}
}
}
unsigned int FFTFactory::getEngine(unsigned int fftSize, bool inverse, FFTEngine **engine, const QString& preferredEngine)
{
QMutexLocker mutexLocker(&m_mutex);
std::map<unsigned int, std::vector<AllocatedEngine>>& enginesBySize = inverse ?
m_invFFTEngineBySize : m_fftEngineBySize;
// If no preferred engine requested, use user preference
QString requestedEngine = preferredEngine;
if (requestedEngine.isEmpty())
{
const MainSettings& mainSettings = MainCore::instance()->getSettings();
requestedEngine = mainSettings.getFFTEngine();
}
if (enginesBySize.find(fftSize) == enginesBySize.end())
{
qDebug("FFTFactory::getEngine: new FFT %s size: %u", (inverse ? "inv" : "fwd"), fftSize);
enginesBySize.insert(std::pair<unsigned int, std::vector<AllocatedEngine>>(fftSize, std::vector<AllocatedEngine>()));
std::vector<AllocatedEngine>& engines = enginesBySize[fftSize];
engines.push_back(AllocatedEngine());
engines.back().m_inUse = true;
engines.back().m_engine = FFTEngine::create(m_fftwWisdomFileName, requestedEngine);
engines.back().m_engine->setReuse(false);
engines.back().m_engine->configure(fftSize, inverse);
*engine = engines.back().m_engine;
return 0;
}
else
{
unsigned int i = 0;
// Look for existing engine of requested size and type not currently in use
for (; i < enginesBySize[fftSize].size(); i++)
{
if (!enginesBySize[fftSize][i].m_inUse && (enginesBySize[fftSize][i].m_engine->getName() == requestedEngine)) {
break;
}
}
if (i < enginesBySize[fftSize].size())
{
qDebug("FFTFactory::getEngine: reuse engine: %u FFT %s size: %u", i, (inverse ? "inv" : "fwd"), fftSize);
enginesBySize[fftSize][i].m_inUse = true;
*engine = enginesBySize[fftSize][i].m_engine;
return i;
}
else
{
std::vector<AllocatedEngine>& engines = enginesBySize[fftSize];
qDebug("FFTFactory::getEngine: create engine: %lu FFT %s size: %u", engines.size(), (inverse ? "inv" : "fwd"), fftSize);
engines.push_back(AllocatedEngine());
engines.back().m_inUse = true;
engines.back().m_engine = FFTEngine::create(m_fftwWisdomFileName, requestedEngine);
engines.back().m_engine->setReuse(false);
engines.back().m_engine->configure(fftSize, inverse);
*engine = engines.back().m_engine;
return engines.size() - 1;
}
}
}
void FFTFactory::releaseEngine(unsigned int fftSize, bool inverse, unsigned int engineSequence)
{
QMutexLocker mutexLocker(&m_mutex);
std::map<unsigned int, std::vector<AllocatedEngine>>& enginesBySize = inverse ?
m_invFFTEngineBySize : m_fftEngineBySize;
if (enginesBySize.find(fftSize) != enginesBySize.end())
{
std::vector<AllocatedEngine>& engines = enginesBySize[fftSize];
if (engineSequence < engines.size())
{
qDebug("FFTFactory::releaseEngine: engineSequence: %u FFT %s size: %u",
engineSequence, (inverse ? "inv" : "fwd"), fftSize);
engines[engineSequence].m_inUse = false;
}
}
}

View File

@@ -0,0 +1,61 @@
///////////////////////////////////////////////////////////////////////////////////
// Copyright (C) 2012 maintech GmbH, Otto-Hahn-Str. 15, 97204 Hoechberg, Germany //
// written by Christian Daniel //
// Copyright (C) 2016-2020 Edouard Griffiths, F4EXB <f4exb06@gmail.com> //
// Copyright (C) 2022 Jiří Pinkava <jiri.pinkava@rossum.ai> //
// Copyright (C) 2023 Jon Beniston, M7RCE <jon@beniston.com> //
// //
// This program 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 as version 3 of the License, or //
// (at your option) any later version. //
// //
// This program 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 V3 for more details. //
// //
// You should have received a copy of the GNU General Public License //
// along with this program. If not, see <http://www.gnu.org/licenses/>. //
///////////////////////////////////////////////////////////////////////////////////
#ifndef _SDRBASE_FFTWFACTORY_H
#define _SDRBASE_FFTWFACTORY_H
#include <map>
#include <vector>
#include <QRecursiveMutex>
#include <QString>
#include "export.h"
#include "fftengine.h"
class SDRBASE_API FFTFactory {
public:
FFTFactory(const QString& fftwWisdomFileName);
~FFTFactory();
void preallocate(unsigned int minLog2Size, unsigned int maxLog2Size, unsigned int numberFFT, unsigned int numberInvFFT);
unsigned int getEngine(unsigned int fftSize, bool inverse, FFTEngine **engine, const QString& preferredEngine=""); //!< returns an engine sequence
void releaseEngine(unsigned int fftSize, bool inverse, unsigned int engineSequence);
private:
struct AllocatedEngine
{
FFTEngine *m_engine;
bool m_inUse;
AllocatedEngine() :
m_engine(nullptr),
m_inUse(false)
{}
};
QString m_fftwWisdomFileName;
std::map<unsigned int, std::vector<AllocatedEngine>> m_fftEngineBySize;
std::map<unsigned int, std::vector<AllocatedEngine>> m_invFFTEngineBySize;
QRecursiveMutex m_mutex;
};
#endif // _SDRBASE_FFTWFACTORY_H

View File

@@ -0,0 +1,690 @@
///////////////////////////////////////////////////////////////////////////////////////
// Copyright (C) 2012 maintech GmbH, Otto-Hahn-Str. 15, 97204 Hoechberg, Germany //
// written by Christian Daniel //
// Copyright (C) 2014-2015 John Greb <hexameron@spam.no> //
// Copyright (C) 2015, 2017-2018, 2020, 2022-2023 Edouard Griffiths, F4EXB <f4exb06@gmail.com> //
// Copyright (C) 2020 Kacper Michajłow <kasper93@gmail.com> //
// //
// This program 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 as version 3 of the License, or //
// (at your option) any later version. //
// //
// This program 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 V3 for more details. //
// //
// You should have received a copy of the GNU General Public License //
// along with this program. If not, see <http://www.gnu.org/licenses/>. //
///////////////////////////////////////////////////////////////////////////////////////
// ----------------------------------------------------------------------------
// fftfilt.cxx -- Fast convolution Overlap-Add filter
//
// Filter implemented using overlap-add FFT convolution method
// h(t) characterized by Windowed-Sinc impulse response
//
// Reference:
// "The Scientist and Engineer's Guide to Digital Signal Processing"
// by Dr. Steven W. Smith, http://www.dspguide.com
// Chapters 16, 18 and 21
//
// Copyright (C) 2006-2008 Dave Freese, W1HKJ
//
// This file is part of fldigi.
//
// Fldigi 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.
//
// Fldigi 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 fldigi. If not, see <http://www.gnu.org/licenses/>.
//
// Augmented with more filter types
// Copyright (C) 2015-2022 Edouard Griffiths, F4EXB
// ----------------------------------------------------------------------------
#include <memory.h>
#include <algorithm>
#include <iostream>
#include <fstream>
#include <cstdlib>
#include <cmath>
#include <typeinfo>
#include <array>
#include <stdio.h>
#include <sys/types.h>
#include <dsp/misc.h>
#include <dsp/fftfilt.h>
//------------------------------------------------------------------------------
// initialize the filter
// create forward and reverse FFTs
//------------------------------------------------------------------------------
// Only need a single instance of g_fft, used for both forward and reverse
void fftfilt::init_filter()
{
flen2 = flen >> 1;
fft = new g_fft<float>(flen);
filter = new cmplx[flen];
filterOpp = new cmplx[flen];
data = new cmplx[flen];
output = new cmplx[flen2];
ovlbuf = new cmplx[flen2];
std::fill(filter, filter + flen, cmplx{0, 0});
std::fill(filterOpp, filterOpp + flen, cmplx{0, 0});
std::fill(data, data + flen , cmplx{0, 0});
std::fill(output, output + flen2, cmplx{0, 0});
std::fill(ovlbuf, ovlbuf + flen2, cmplx{0, 0});
inptr = 0;
}
//------------------------------------------------------------------------------
// fft filter
// f1 < f2 ==> band pass filter
// f1 > f2 ==> band reject filter
// f1 == 0 ==> low pass filter
// f2 == 0 ==> high pass filter
//------------------------------------------------------------------------------
fftfilt::fftfilt(int len) :
m_noiseReduction(len)
{
flen = len;
pass = 0;
window = 0;
m_dnr = false;
init_filter();
}
fftfilt::fftfilt(float f1, float f2, int len) :
m_noiseReduction(len)
{
flen = len;
pass = 0;
window = 0;
m_dnr = false;
init_filter();
create_filter(f1, f2);
}
fftfilt::fftfilt(float f2, int len) :
m_noiseReduction(len)
{
flen = len;
pass = 0;
window = 0;
m_dnr = false;
init_filter();
create_dsb_filter(f2);
}
fftfilt::~fftfilt()
{
if (fft) delete fft;
if (filter) delete [] filter;
if (filterOpp) delete [] filterOpp;
if (data) delete [] data;
if (output) delete [] output;
if (ovlbuf) delete [] ovlbuf;
}
void fftfilt::create_filter(float f1, float f2, FFTWindow::Function wf)
{
// initialize the filter to zero
std::fill(filter, filter + flen, cmplx{0, 0});
// create the filter shape coefficients by fft
bool b_lowpass, b_highpass;
b_lowpass = (f2 != 0);
b_highpass = (f1 != 0);
for (int i = 0; i < flen2; i++) {
filter[i] = 0;
// lowpass @ f2
if (b_lowpass)
filter[i] += fsinc(f2, i, flen2);
// highighpass @ f1
if (b_highpass)
filter[i] -= fsinc(f1, i, flen2);
}
// highpass is delta[flen2/2] - h(t)
if (b_highpass && f2 < f1)
filter[flen2 / 2] += 1;
FFTWindow fwin;
fwin.create(wf, flen2);
fwin.apply(filter);
// for (int i = 0; i < flen2; i++)
// filter[i] *= _blackman(i, flen2);
fft->ComplexFFT(filter); // filter was expressed in the time domain (impulse response)
// normalize the output filter for unity gain
float scale = 0, mag;
for (int i = 0; i < flen2; i++) {
mag = abs(filter[i]);
if (mag > scale) scale = mag;
}
if (scale != 0) {
for (int i = 0; i < flen; i++)
filter[i] /= scale;
}
}
void fftfilt::create_filter(const std::vector<std::pair<float, float>>& limits, bool pass, FFTWindow::Function wf)
{
std::vector<int> canvasNeg(flen2, pass ? 0 : 1); // initialize the negative frequencies filter canvas
std::vector<int> canvasPos(flen2, pass ? 0 : 1); // initialize the positive frequencies filter canvas
std::fill(filter, filter + flen, cmplx{0, 0}); // initialize the positive filter to zero
std::fill(filterOpp, filterOpp + flen, cmplx{0, 0}); // initialize the negative filter to zero
for (const auto& fs : limits)
{
const float& f1 = fs.first + 0.5;
const float& w = fs.second > 0.0 ? fs.second : 0.0;
const float& f2 = f1 + w;
for (int i = 0; i < flen; i++)
{
if (pass) // pass
{
if ((i >= f1*flen) && (i <= f2*flen))
{
if (i < flen2) {
canvasNeg[flen2-1-i] = 1;
} else {
canvasPos[i-flen2] = 1;
}
}
}
else // reject
{
if ((i >= f1*flen) && (i <= f2*flen))
{
if (i < flen2) {
canvasNeg[flen2-1-i] = 0;
} else {
canvasPos[i-flen2] = 0;
}
}
}
}
}
std::vector<std::pair<int,int>> indexesNegList;
std::vector<std::pair<int,int>> indexesPosList;
int cn = 0;
int cp = 0;
int defaultSecond = pass ? 0 : flen2 - 1;
for (int i = 0; i < flen2; i++)
{
if ((canvasNeg[i] == 1) && (cn == 0)) {
indexesNegList.push_back(std::pair<int,int>{i, defaultSecond});
}
if ((canvasNeg[i] == 0) && (cn == 1)) {
indexesNegList.back().second = i;
}
if ((canvasPos[i] == 1) && (cp == 0)) {
indexesPosList.push_back(std::pair<int,int>{i, defaultSecond});
}
if ((canvasPos[i] == 0) && (cp == 1)) {
indexesPosList.back().second = i;
}
cn = canvasNeg[i];
cp = canvasPos[i];
}
for (const auto& indexes : indexesPosList)
{
const float f1 = indexes.first / (float) flen;
const float f2 = indexes.second / (float) flen;
for (int i = 0; i < flen2; i++)
{
if (f2 != 0) {
filter[i] += fsinc(f2, i, flen2);
}
if (f1 != 0) {
filter[i] -= fsinc(f1, i, flen2);
}
}
if (f2 == 0 && f1 != 0) {
filter[flen2 / 2] += 1;
}
}
for (const auto& indexes : indexesNegList)
{
const float f1 = indexes.first / (float) flen;
const float f2 = indexes.second / (float) flen;
for (int i = 0; i < flen2; i++)
{
if (f2 != 0) {
filterOpp[i] += fsinc(f2, i, flen2);
}
if (f1 != 0) {
filterOpp[i] -= fsinc(f1, i, flen2);
}
}
if (f2 == 0 && f1 != 0) {
filterOpp[flen2 / 2] += 1;
}
}
FFTWindow fwin;
fwin.create(wf, flen2);
fwin.apply(filter);
fwin.apply(filterOpp);
fft->ComplexFFT(filter); // filter was expressed in the time domain (impulse response)
fft->ComplexFFT(filterOpp); // filter was expressed in the time domain (impulse response)
float scalen = 0, scalep = 0, magn, magp; // normalize the output filter for unity gain
for (int i = 0; i < flen2; i++)
{
magp = abs(filter[i]);
if (magp > scalep) {
scalep = magp;
}
magn = abs(filterOpp[i]);
if (magn > scalen) {
scalen = magn;
}
}
if (scalep != 0)
{
std::for_each(
filter,
filter + flen,
[scalep](fftfilt::cmplx& s) { s /= scalep; }
);
}
if (scalen != 0)
{
std::for_each(
filterOpp,
filterOpp + flen,
[scalen](fftfilt::cmplx& s) { s /= scalen; }
);
}
}
// Double the size of FFT used for equivalent SSB filter or assume FFT is half the size of the one used for SSB
void fftfilt::create_dsb_filter(float f2, FFTWindow::Function wf)
{
// initialize the filter to zero
std::fill(filter, filter + flen, cmplx{0, 0});
for (int i = 0; i < flen2; i++) {
filter[i] = fsinc(f2, i, flen2);
// filter[i] *= _blackman(i, flen2);
}
FFTWindow fwin;
fwin.create(wf, flen2);
fwin.apply(filter);
fft->ComplexFFT(filter); // filter was expressed in the time domain (impulse response)
// normalize the output filter for unity gain
float scale = 0, mag;
for (int i = 0; i < flen2; i++) {
mag = abs(filter[i]);
if (mag > scale) scale = mag;
}
if (scale != 0) {
for (int i = 0; i < flen; i++)
filter[i] /= scale;
}
}
// Double the size of FFT used for equivalent SSB filter or assume FFT is half the size of the one used for SSB
// used with runAsym for in band / opposite band asymmetrical filtering. Can be used for vestigial sideband modulation.
void fftfilt::create_asym_filter(float fopp, float fin, FFTWindow::Function wf)
{
// in band
// initialize the filter to zero
std::fill(filter, filter + flen, cmplx{0, 0});
for (int i = 0; i < flen2; i++) {
filter[i] = fsinc(fin, i, flen2);
// filter[i] *= _blackman(i, flen2);
}
FFTWindow fwin;
fwin.create(wf, flen2);
fwin.apply(filter);
fft->ComplexFFT(filter); // filter was expressed in the time domain (impulse response)
// normalize the output filter for unity gain
float scale = 0, mag;
for (int i = 0; i < flen2; i++) {
mag = abs(filter[i]);
if (mag > scale) scale = mag;
}
if (scale != 0) {
for (int i = 0; i < flen; i++)
filter[i] /= scale;
}
// opposite band
// initialize the filter to zero
std::fill(filterOpp, filterOpp + flen, cmplx{0, 0});
for (int i = 0; i < flen2; i++) {
filterOpp[i] = fsinc(fopp, i, flen2);
// filterOpp[i] *= _blackman(i, flen2);
}
fwin.apply(filterOpp);
fft->ComplexFFT(filterOpp); // filter was expressed in the time domain (impulse response)
// normalize the output filter for unity gain
scale = 0;
for (int i = 0; i < flen2; i++) {
mag = abs(filterOpp[i]);
if (mag > scale) scale = mag;
}
if (scale != 0) {
for (int i = 0; i < flen; i++)
filterOpp[i] /= scale;
}
}
// This filter is constructed directly from frequency domain response. Run with runFilt.
void fftfilt::create_rrc_filter(float fb, float a)
{
std::fill(filter, filter+flen, 0);
for (int i = 0; i < flen; i++) {
filter[i] = frrc(fb, a, i, flen);
}
// normalize the output filter for unity gain
float scale = 0, mag;
for (int i = 0; i < flen; i++)
{
mag = abs(filter[i]);
if (mag > scale) {
scale = mag;
}
}
if (scale != 0)
{
for (int i = 0; i < flen; i++) {
filter[i] /= scale;
}
}
}
// test bypass
int fftfilt::noFilt(const cmplx & in, cmplx **out)
{
data[inptr++] = in;
if (inptr < flen2)
return 0;
inptr = 0;
*out = data;
return flen2;
}
// Filter with fast convolution (overlap-add algorithm).
int fftfilt::runFilt(const cmplx & in, cmplx **out)
{
data[inptr++] = in;
if (inptr < flen2)
return 0;
inptr = 0;
fft->ComplexFFT(data);
for (int i = 0; i < flen; i++)
data[i] *= filter[i];
fft->InverseComplexFFT(data);
for (int i = 0; i < flen2; i++) {
output[i] = ovlbuf[i] + data[i];
ovlbuf[i] = data[flen2 + i];
}
std::fill(data, data + flen , cmplx{0, 0});
*out = output;
return flen2;
}
// Second version for single sideband
int fftfilt::runSSB(const cmplx & in, cmplx **out, bool usb, bool getDC)
{
data[inptr++] = in;
if (inptr < flen2)
return 0;
inptr = 0;
fft->ComplexFFT(data);
// get or reject DC component
data[0] = getDC ? data[0]*filter[0] : 0;
m_noiseReduction.setScheme(m_dnrScheme);
m_noiseReduction.init();
// Discard frequencies for ssb
if (usb)
{
for (int i = 1; i < flen2; i++)
{
data[i] *= filter[i];
data[flen2 + i] = 0;
if (m_dnr)
{
m_noiseReduction.push(data[i], i);
m_noiseReduction.push(data[flen2 + i], flen2 + i);
}
}
}
else
{
for (int i = 1; i < flen2; i++)
{
data[i] = 0;
data[flen2 + i] *= filter[flen2 + i];
if (m_dnr)
{
m_noiseReduction.push(data[i], i);
m_noiseReduction.push(data[flen2 + i], flen2 + i);
}
}
}
if (m_dnr)
{
m_noiseReduction.m_aboveAvgFactor = m_dnrAboveAvgFactor;
m_noiseReduction.m_sigmaFactor = m_dnrSigmaFactor;
m_noiseReduction.m_nbPeaks = m_dnrNbPeaks;
m_noiseReduction.calc();
for (int i = 0; i < flen; i++)
{
if (m_noiseReduction.cut(i)) {
data[i] = 0;
}
}
}
// in-place FFT: freqdata overwritten with filtered timedata
fft->InverseComplexFFT(data);
// overlap and add
for (int i = 0; i < flen2; i++) {
output[i] = ovlbuf[i] + data[i];
ovlbuf[i] = data[i+flen2];
}
std::fill(data, data + flen , cmplx{0, 0});
*out = output;
return flen2;
}
// Version for double sideband. You have to double the FFT size used for SSB.
int fftfilt::runDSB(const cmplx & in, cmplx **out, bool getDC)
{
data[inptr++] = in;
if (inptr < flen2)
return 0;
inptr = 0;
fft->ComplexFFT(data);
for (int i = 0; i < flen2; i++) {
data[i] *= filter[i];
data[flen2 + i] *= filter[flen2 + i];
}
// get or reject DC component
data[0] = getDC ? data[0] : 0;
// in-place FFT: freqdata overwritten with filtered timedata
fft->InverseComplexFFT(data);
// overlap and add
for (int i = 0; i < flen2; i++) {
output[i] = ovlbuf[i] + data[i];
ovlbuf[i] = data[i+flen2];
}
std::fill(data, data + flen , cmplx{0, 0});
*out = output;
return flen2;
}
// Version for asymmetrical sidebands. You have to double the FFT size used for SSB.
int fftfilt::runAsym(const cmplx & in, cmplx **out, bool usb)
{
data[inptr++] = in;
if (inptr < flen2)
return 0;
inptr = 0;
fft->ComplexFFT(data);
data[0] *= filter[0]; // always keep DC
if (usb)
{
for (int i = 1; i < flen2; i++)
{
data[i] *= filter[i]; // usb
data[flen2 + i] *= filterOpp[flen2 + i]; // lsb is the opposite
}
}
else
{
for (int i = 1; i < flen2; i++)
{
data[i] *= filterOpp[i]; // usb is the opposite
data[flen2 + i] *= filter[flen2 + i]; // lsb
}
}
// in-place FFT: freqdata overwritten with filtered timedata
fft->InverseComplexFFT(data);
// overlap and add
for (int i = 0; i < flen2; i++) {
output[i] = ovlbuf[i] + data[i];
ovlbuf[i] = data[i+flen2];
}
std::fill(data, data + flen , cmplx{0, 0});
*out = output;
return flen2;
}
/* Sliding FFT from Fldigi */
struct sfft::vrot_bins_pair {
cmplx vrot;
cmplx bins;
} ;
sfft::sfft(int len)
{
vrot_bins = new vrot_bins_pair[len];
delay = new cmplx[len];
fftlen = len;
first = 0;
last = len - 1;
ptr = 0;
double phi = 0.0, tau = 2.0 * M_PI/ len;
k2 = 1.0;
for (int i = 0; i < len; i++) {
vrot_bins[i].vrot = cmplx( K1 * cos (phi), K1 * sin (phi) );
phi += tau;
delay[i] = vrot_bins[i].bins = 0.0;
k2 *= K1;
}
}
sfft::~sfft()
{
delete [] vrot_bins;
delete [] delay;
}
// Sliding FFT, cmplx input, cmplx output
// FFT is computed for each value from first to last
// Values are not stable until more than "len" samples have been processed.
void sfft::run(const cmplx& input)
{
cmplx & de = delay[ptr];
const cmplx z( input.real() - k2 * de.real(), input.imag() - k2 * de.imag());
de = input;
if (++ptr >= fftlen)
ptr = 0;
for (vrot_bins_pair *itr = vrot_bins + first, *end = vrot_bins + last; itr != end ; ++itr)
itr->bins = (itr->bins + z) * itr->vrot;
}
// Copies the frequencies to a pointer.
void sfft::fetch(float *result)
{
for (vrot_bins_pair *itr = vrot_bins, *end = vrot_bins + last; itr != end; ++itr, ++result)
*result = itr->bins.real() * itr->bins.real()
+ itr->bins.imag() * itr->bins.imag();
}

View File

@@ -0,0 +1,153 @@
///////////////////////////////////////////////////////////////////////////////////////
// Copyright (C) 2012 maintech GmbH, Otto-Hahn-Str. 15, 97204 Hoechberg, Germany //
// written by Christian Daniel //
// Copyright (C) 2014-2015 John Greb <hexameron@spam.no> //
// Copyright (C) 2015-2018, 2022-2023 Edouard Griffiths, F4EXB <f4exb06@gmail.com> //
// Copyright (C) 2020 Kacper Michajłow <kasper93@gmail.com> //
// //
// This program 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 as version 3 of the License, or //
// (at your option) any later version. //
// //
// This program 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 V3 for more details. //
// //
// You should have received a copy of the GNU General Public License //
// along with this program. If not, see <http://www.gnu.org/licenses/>. //
///////////////////////////////////////////////////////////////////////////////////////
/*
* Filters from Fldigi.
*/
#ifndef _FFTFILT_H
#define _FFTFILT_H
#include <complex>
#include <cmath>
#include "gfft.h"
#include "fftwindow.h"
#include "fftnr.h"
#include "export.h"
//----------------------------------------------------------------------
class SDRBASE_API fftfilt {
enum {NONE, BLACKMAN, HAMMING, HANNING};
public:
typedef std::complex<float> cmplx;
fftfilt(int len);
fftfilt(float f1, float f2, int len);
fftfilt(float f2, int len);
~fftfilt();
// f1 < f2 ==> bandpass
// f1 > f2 ==> band reject
void create_filter(float f1, float f2, FFTWindow::Function wf = FFTWindow::Blackman);
void create_filter(const std::vector<std::pair<float, float>>& limits, bool pass = true, FFTWindow::Function wf = FFTWindow::Blackman);
void create_filter(const std::vector<std::pair<float, float>>& limits, bool pass = true); //!< Windowless version
void create_dsb_filter(float f2, FFTWindow::Function wf = FFTWindow::Blackman);
void create_asym_filter(float fopp, float fin, FFTWindow::Function wf = FFTWindow::Blackman); //!< two different filters for in band and opposite band
void create_rrc_filter(float fb, float a); //!< root raised cosine. fb is half the band pass
int noFilt(const cmplx& in, cmplx **out);
int runFilt(const cmplx& in, cmplx **out);
int runSSB(const cmplx& in, cmplx **out, bool usb, bool getDC = true);
int runDSB(const cmplx& in, cmplx **out, bool getDC = true);
int runAsym(const cmplx & in, cmplx **out, bool usb); //!< Asymmetrical filtering can be used for vestigial sideband
void setDNR(bool dnr) { m_dnr = dnr; }
void setDNRScheme(FFTNoiseReduction::Scheme scheme) { m_dnrScheme = scheme; }
void setDNRAboveAvgFactor(float aboveAvgFactor) { m_dnrAboveAvgFactor = aboveAvgFactor; }
void setDNRSigmaFactor(float sigmaFactor) { m_dnrSigmaFactor = sigmaFactor; }
void setDNRNbPeaks(int nbPeaks) { m_dnrNbPeaks = nbPeaks; }
void setDNRAlpha(float alpha) { m_noiseReduction.setAlpha(alpha); }
protected:
// helper class for FFT based noise reduction
int flen;
int flen2;
g_fft<float> *fft;
cmplx *filter;
cmplx *filterOpp;
cmplx *data;
cmplx *ovlbuf;
cmplx *output;
int inptr;
int pass;
int window;
bool m_dnr;
FFTNoiseReduction::Scheme m_dnrScheme;
float m_dnrAboveAvgFactor; //!< above average factor
float m_dnrSigmaFactor; //!< sigma multiplicator for average + std deviation
int m_dnrNbPeaks; //!< number of peaks (peaks scheme)
FFTNoiseReduction m_noiseReduction;
inline float fsinc(float fc, int i, int len)
{
int len2 = len/2;
return (i == len2) ? 2.0 * fc:
sin(2 * M_PI * fc * (i - len2)) / (M_PI * (i - len2));
}
inline float _blackman(int i, int len)
{
return (0.42 -
0.50 * cos(2.0 * M_PI * i / len) +
0.08 * cos(4.0 * M_PI * i / len));
}
/** RRC function in the frequency domain. Zero frequency is on the sides with first half in positive frequencies
* and second half in negative frequencies */
inline cmplx frrc(float fb, float a, int i, int len)
{
float x = i/(float)len; // normalize to [0..1]
x = 0.5-fabs(x-0.5); // apply symmetry: now both halves overlap near 0
float tr = fb*a; // half the transition zone
if (x < fb-tr)
{
return 1.0; // in band
}
else if (x < fb+tr) // transition
{
float y = ((x-(fb-tr)) / (2.0*tr))*M_PI;
return (cos(y) + 1.0f)/2.0f;
}
else
{
return 0.0; // out of band
}
}
void init_filter();
void init_dsb_filter();
};
/* Sliding FFT filter from Fldigi */
class SDRBASE_API sfft {
#define K1 0.99999
public:
typedef std::complex<float> cmplx;
sfft(int len);
~sfft();
void run(const cmplx& input);
void fetch(float *result);
private:
int fftlen;
int first;
int last;
int ptr;
struct vrot_bins_pair;
vrot_bins_pair *vrot_bins;
cmplx *delay;
float k2;
};
#endif

View File

@@ -0,0 +1,148 @@
///////////////////////////////////////////////////////////////////////////////////
// Copyright (C) 2023 Edouard Griffiths, F4EXB <f4exb06@gmail.com> //
// //
// Helper class for noise reduction //
// //
// This program 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 as version 3 of the License, or //
// (at your option) any later version. //
// //
// This program 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 V3 for more details. //
// //
// You should have received a copy of the GNU General Public License //
// along with this program. If not, see <http://www.gnu.org/licenses/>. //
///////////////////////////////////////////////////////////////////////////////////
#include <algorithm>
#include <numeric>
#include <QDebug>
#include "fftnr.h"
FFTNoiseReduction::FFTNoiseReduction(int len) :
m_flen(len)
{
m_scheme = SchemeAverage;
m_mags = new float[m_flen];
m_tmp = new float[m_flen];
m_aboveAvgFactor = 1.0;
m_sigmaFactor = 1.0;
m_nbPeaks = m_flen;
}
FFTNoiseReduction::~FFTNoiseReduction()
{
delete[] m_mags;
delete[] m_tmp;
}
void FFTNoiseReduction::init()
{
std::fill(m_mags, m_mags + m_flen, 0);
std::fill(m_tmp, m_tmp + m_flen, 0);
m_magAvg = 0;
}
void FFTNoiseReduction::push(cmplx data, int index)
{
m_mags[index] = std::abs(data);
if ((m_scheme == SchemeAverage) || (m_scheme == SchemeAvgStdDev)) {
m_magAvg += m_mags[index];
}
}
void FFTNoiseReduction::calc()
{
if (m_scheme == SchemeAverage)
{
m_magAvg /= m_flen;
m_magAvg = m_expFilter.push(m_magAvg);
}
if (m_scheme == SchemeAvgStdDev)
{
m_magAvg /= m_flen;
auto variance_func = [this](float accumulator, const float& val) {
return accumulator + ((val - m_magAvg)*(val - m_magAvg) / (m_flen - 1));
};
float var = std::accumulate(m_mags, m_mags + m_flen, 0.0, variance_func);
m_magThr = (m_sigmaFactor/2.0)*std::sqrt(var) + m_magAvg;
m_magThr = m_expFilter.push(m_magThr);
}
else if (m_scheme == SchemePeaks)
{
std::copy(m_mags, m_mags + m_flen, m_tmp);
std::sort(m_tmp, m_tmp + m_flen);
m_magThr = m_tmp[m_flen - m_nbPeaks];
}
}
bool FFTNoiseReduction::cut(int index)
{
if (m_scheme == SchemeAverage)
{
return m_mags[index] < m_aboveAvgFactor * m_magAvg;
}
else if ((m_scheme == SchemePeaks) || (m_scheme == SchemeAvgStdDev))
{
return m_mags[index] < m_magThr;
}
return false;
}
void FFTNoiseReduction::setScheme(Scheme scheme)
{
if (m_scheme != scheme) {
m_expFilter.reset();
}
m_scheme = scheme;
}
FFTNoiseReduction::ExponentialFilter::ExponentialFilter()
{
m_alpha = 1.0;
m_init = true;
}
float FFTNoiseReduction::ExponentialFilter::push(float newValue)
{
if (m_init)
{
m_prev = newValue;
m_init = false;
}
if (m_alpha == 1.0)
{
m_prev = newValue;
return newValue;
}
else
{
float result = m_alpha*m_prev + (1.0 - m_alpha)*newValue;
m_prev = result;
return result;
}
}
void FFTNoiseReduction::ExponentialFilter::reset()
{
m_init = true;
}
void FFTNoiseReduction::ExponentialFilter::setAlpha(float alpha)
{
m_alpha = alpha < 0.0f ? 0.0f : alpha > 1.0f ? 1.0f : alpha;
qDebug("FFTNoiseReduction::ExponentialFilter::setAlpha: %f", m_alpha);
m_init = true;
}

View File

@@ -0,0 +1,72 @@
///////////////////////////////////////////////////////////////////////////////////
// Copyright (C) 2023 Edouard Griffiths, F4EXB <f4exb06@gmail.com> //
// //
// Helper class for noise reduction //
// //
// This program 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 as version 3 of the License, or //
// (at your option) any later version. //
// //
// This program 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 V3 for more details. //
// //
// You should have received a copy of the GNU General Public License //
// along with this program. If not, see <http://www.gnu.org/licenses/>. //
///////////////////////////////////////////////////////////////////////////////////
#ifndef _FFTNR_H
#define _FFTNR_H
#include <complex>
#include "export.h"
class SDRBASE_API FFTNoiseReduction {
public:
typedef std::complex<float> cmplx;
enum Scheme {
SchemeAverage,
SchemeAvgStdDev,
SchemePeaks
};
FFTNoiseReduction(int len);
~FFTNoiseReduction();
void init(); //!< call before start of initial FFT scan
void push(cmplx data, int index); //!< Push FFT bin during initial FFT scan
void calc(); //!< calculate after initial FFT scan
bool cut(int index); //!< true if bin is to be zeroed else false (during second FFT scan)
void setAlpha(float alpha) { m_expFilter.setAlpha(alpha); }
void setScheme(Scheme scheme);
float m_aboveAvgFactor; //!< above average factor
float m_sigmaFactor; //!< sigma multiplicator for average + std deviation
int m_nbPeaks; //!< number of peaks (peaks scheme)
private:
class ExponentialFilter {
public:
ExponentialFilter();
float push(float newValue);
void reset();
void setAlpha(float alpha);
private:
bool m_init;
float m_alpha;
float m_prev;
};
Scheme m_scheme;
int m_flen; //!< FFT length
float *m_mags; //!< magnitudes (PSD)
float *m_tmp; //!< temporary buffer
float m_magAvg; //!< average of magnitudes
float m_magThr; //!< magnitude threshold (peaks scheme)
ExponentialFilter m_expFilter; //!< exponential filter for parameter smoothing
};
#endif

View File

@@ -0,0 +1,127 @@
///////////////////////////////////////////////////////////////////////////////////
// Copyright (C) 2012 maintech GmbH, Otto-Hahn-Str. 15, 97204 Hoechberg, Germany //
// written by Christian Daniel //
// Copyright (C) 2020 Edouard Griffiths, F4EXB <f4exb06@gmail.com> //
// Copyright (C) 2023 Jon Beniston, M7RCE <jon@beniston.com> //
// //
// This program 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 as version 3 of the License, or //
// (at your option) any later version. //
// //
// This program 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 V3 for more details. //
// //
// You should have received a copy of the GNU General Public License //
// along with this program. If not, see <http://www.gnu.org/licenses/>. //
///////////////////////////////////////////////////////////////////////////////////
#include <QDebug>
#include <QElapsedTimer>
#include "dsp/fftwengine.h"
#include "util/profiler.h"
FFTWEngine::FFTWEngine(const QString& fftWisdomFileName) :
m_fftWisdomFileName(fftWisdomFileName),
m_plans(),
m_currentPlan(nullptr),
m_reuse(true)
{
}
FFTWEngine::~FFTWEngine()
{
freeAll();
}
const QString FFTWEngine::m_name = "FFTW";
QString FFTWEngine::getName() const
{
return m_name;
}
void FFTWEngine::configure(int n, bool inverse)
{
if (m_reuse)
{
for (Plans::const_iterator it = m_plans.begin(); it != m_plans.end(); ++it)
{
if (((*it)->n == n) && ((*it)->inverse == inverse))
{
m_currentPlan = *it;
return;
}
}
}
m_currentPlan = new Plan;
m_currentPlan->n = n;
m_currentPlan->inverse = inverse;
m_currentPlan->in = (fftwf_complex*)fftwf_malloc(sizeof(fftwf_complex) * n);
m_currentPlan->out = (fftwf_complex*)fftwf_malloc(sizeof(fftwf_complex) * n);
QElapsedTimer t;
t.start();
m_globalPlanMutex.lock();
if (m_fftWisdomFileName.size() > 0)
{
int rc = fftwf_import_wisdom_from_filename(m_fftWisdomFileName.toStdString().c_str());
if (rc == 0) { // that's an error (undocumented)
qInfo("FFTWEngine::configure: importing from FFTW wisdom file: '%s' failed", qPrintable(m_fftWisdomFileName));
} else {
qDebug("FFTWEngine::configure: successfully imported from FFTW wisdom file: '%s'", qPrintable(m_fftWisdomFileName));
}
}
else
{
qDebug("FFTWEngine::configure: no FFTW wisdom file");
}
m_currentPlan->plan = fftwf_plan_dft_1d(n, m_currentPlan->in, m_currentPlan->out, inverse ? FFTW_BACKWARD : FFTW_FORWARD, FFTW_PATIENT);
m_globalPlanMutex.unlock();
qDebug("FFT: creating FFTW plan (n=%d,%s) took %lld ms", n, inverse ? "inverse" : "forward", t.elapsed());
m_plans.push_back(m_currentPlan);
}
void FFTWEngine::transform()
{
PROFILER_START()
if(m_currentPlan != NULL)
fftwf_execute(m_currentPlan->plan);
PROFILER_STOP(QString("%1 %2").arg(getName()).arg(m_currentPlan->n))
}
Complex* FFTWEngine::in()
{
if(m_currentPlan != NULL)
return reinterpret_cast<Complex*>(m_currentPlan->in);
else return NULL;
}
Complex* FFTWEngine::out()
{
if(m_currentPlan != NULL)
return reinterpret_cast<Complex*>(m_currentPlan->out);
else return NULL;
}
QMutex FFTWEngine::m_globalPlanMutex;
void FFTWEngine::freeAll()
{
for(Plans::iterator it = m_plans.begin(); it != m_plans.end(); ++it) {
fftwf_destroy_plan((*it)->plan);
fftwf_free((*it)->in);
fftwf_free((*it)->out);
delete *it;
}
m_plans.clear();
}

View File

@@ -0,0 +1,66 @@
///////////////////////////////////////////////////////////////////////////////////
// Copyright (C) 2012 maintech GmbH, Otto-Hahn-Str. 15, 97204 Hoechberg, Germany //
// written by Christian Daniel //
// Copyright (C) 2015-2016, 2018, 2020 Edouard Griffiths, F4EXB <f4exb06@gmail.com> //
// Copyright (C) 2023 Jon Beniston, M7RCE <jon@beniston.com> //
// //
// This program 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 as version 3 of the License, or //
// (at your option) any later version. //
// //
// This program 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 V3 for more details. //
// //
// You should have received a copy of the GNU General Public License //
// along with this program. If not, see <http://www.gnu.org/licenses/>. //
///////////////////////////////////////////////////////////////////////////////////
#ifndef INCLUDE_FFTWENGINE_H
#define INCLUDE_FFTWENGINE_H
#include <QMutex>
#include <QString>
#include <fftw3.h>
#include <list>
#include "dsp/fftengine.h"
#include "export.h"
class SDRBASE_API FFTWEngine : public FFTEngine {
public:
FFTWEngine(const QString& fftWisdomFileName);
virtual ~FFTWEngine();
virtual void configure(int n, bool inverse);
virtual void transform();
virtual Complex* in();
virtual Complex* out();
virtual void setReuse(bool reuse) { m_reuse = reuse; }
QString getName() const override;
static const QString m_name;
protected:
static QMutex m_globalPlanMutex;
QString m_fftWisdomFileName;
struct Plan {
int n;
bool inverse;
fftwf_plan plan;
fftwf_complex* in;
fftwf_complex* out;
};
typedef std::list<Plan*> Plans;
Plans m_plans;
Plan* m_currentPlan;
bool m_reuse;
void freeAll();
};
#endif // INCLUDE_FFTWENGINE_H

View File

@@ -0,0 +1,129 @@
///////////////////////////////////////////////////////////////////////////////////
// Copyright (C) 2012 maintech GmbH, Otto-Hahn-Str. 15, 97204 Hoechberg, Germany //
// written by Christian Daniel //
// Copyright (C) 2019-2020, 2022 Edouard Griffiths, F4EXB <f4exb06@gmail.com> //
// //
// This program 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 as version 3 of the License, or //
// (at your option) any later version. //
// //
// This program 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 V3 for more details. //
// //
// You should have received a copy of the GNU General Public License //
// along with this program. If not, see <http://www.gnu.org/licenses/>. //
///////////////////////////////////////////////////////////////////////////////////
#include "dsp/fftwindow.h"
FFTWindow::FFTWindow() :
m_kaiserAlpha(M_PI) // first sidelobe at < -70dB
{
m_kaiserI0Alpha = zeroethOrderBessel(m_kaiserAlpha);
}
void FFTWindow::setKaiserAlpha(Real alpha)
{
m_kaiserAlpha = alpha;
m_kaiserI0Alpha = zeroethOrderBessel(m_kaiserAlpha);
}
void FFTWindow::setKaiserBeta(Real beta)
{
m_kaiserAlpha = beta / M_PI;
m_kaiserI0Alpha = zeroethOrderBessel(m_kaiserAlpha);
}
void FFTWindow::create(Function function, int n)
{
Real (*wFunc)(Real n, Real i);
m_window.clear();
if (function == Kaiser) // Kaiser special case
{
for(int i = 0; i < n; i++) {
m_window.push_back(kaiser(n, i));
}
return;
}
switch (function) {
case Flattop:
wFunc = flatTop;
break;
case Bartlett:
wFunc = bartlett;
break;
case BlackmanHarris:
wFunc = blackmanHarris;
break;
case Hamming:
wFunc = hamming;
break;
case Hanning:
wFunc = hanning;
break;
case Blackman:
wFunc = blackman;
break;
case BlackmanHarris7:
wFunc = blackmanHarris7;
break;
case Rectangle:
default:
wFunc = rectangle;
break;
}
for(int i = 0; i < n; i++) {
m_window.push_back(wFunc(n, i));
}
}
void FFTWindow::apply(const std::vector<Real>& in, std::vector<Real>* out)
{
for(size_t i = 0; i < m_window.size(); i++) {
(*out)[i] = in[i] * m_window[i];
}
}
void FFTWindow::apply(const std::vector<Complex>& in, std::vector<Complex>* out)
{
for(size_t i = 0; i < m_window.size(); i++) {
(*out)[i] = in[i] * m_window[i];
}
}
void FFTWindow::apply(std::vector<Complex>& in)
{
for(size_t i = 0; i < m_window.size(); i++) {
in[i] *= m_window[i];
}
}
void FFTWindow::apply(const Complex* in, Complex* out)
{
for(size_t i = 0; i < m_window.size(); i++) {
out[i] = in[i] * m_window[i];
}
}
void FFTWindow::apply(Complex* in)
{
for(size_t i = 0; i < m_window.size(); i++) {
in[i] *= m_window[i];
}
}

View File

@@ -0,0 +1,146 @@
///////////////////////////////////////////////////////////////////////////////////
// Copyright (C) 2012 maintech GmbH, Otto-Hahn-Str. 15, 97204 Hoechberg, Germany //
// written by Christian Daniel //
// Copyright (C) 2015-2020, 2022 Edouard Griffiths, F4EXB <f4exb06@gmail.com> //
// Copyright (C) 2020 Kacper Michajłow <kasper93@gmail.com> //
// //
// This program 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 as version 3 of the License, or //
// (at your option) any later version. //
// //
// This program 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 V3 for more details. //
// //
// You should have received a copy of the GNU General Public License //
// along with this program. If not, see <http://www.gnu.org/licenses/>. //
///////////////////////////////////////////////////////////////////////////////////
#ifndef INCLUDE_FFTWINDOW_H
#define INCLUDE_FFTWINDOW_H
#include <vector>
#include <cmath>
#include "dsp/dsptypes.h"
#include "export.h"
class SDRBASE_API FFTWindow {
public:
enum Function {
Bartlett,
BlackmanHarris,
Flattop,
Hamming,
Hanning,
Rectangle,
Kaiser,
Blackman,
BlackmanHarris7
};
FFTWindow();
void create(Function function, int n);
void apply(const std::vector<Real>& in, std::vector<Real>* out);
void apply(const std::vector<Complex>& in, std::vector<Complex>* out);
void apply(std::vector<Complex>& in);
void apply(const Complex* in, Complex* out);
void apply(Complex* in);
void setKaiserAlpha(Real alpha); //!< set the Kaiser window alpha factor (default 2.15)
void setKaiserBeta(Real beta); //!< set the Kaiser window beta factor = pi * alpha
private:
std::vector<float> m_window;
Real m_kaiserAlpha; //!< alpha factor for Kaiser window
Real m_kaiserI0Alpha; //!< zeroethOrderBessel of alpha above
static inline Real flatTop(Real n, Real i)
{
// correction ?
return 1.0 - 1.93 * cos((2.0 * M_PI * i) / n) + 1.29 * cos((4.0 * M_PI * i) / n) - 0.388 * cos((6.0 * M_PI * i) / n) + 0.03222 * cos((8.0 * M_PI * i) / n);
}
static inline Real bartlett(Real n, Real i)
{
// amplitude correction = 2.0
return (2.0 / (n - 1.0)) * ( (n - 1.0) / 2.0 - fabs(i - (n - 1.0) / 2.0)) * 2.0;
}
static inline Real blackmanHarris(Real n, Real i) // 4 term Blackman-Harris
{
// amplitude correction = 2.79
return (0.35875
- 0.48829 * cos((2.0 * M_PI * i) / n)
+ 0.14128 * cos((4.0 * M_PI * i) / n)
- 0.01168 * cos((6.0 * M_PI * i) / n)) * 2.79;
}
static inline Real blackmanHarris7(Real n, Real i) // 7 term Blackman-Harris
{
return (0.27105
- 0.43330 * cos((2.0 * M_PI * i) / n)
+ 0.21812 * cos((4.0 * M_PI * i) / n)
- 0.065925 * cos((6.0 * M_PI * i) / n)
+ 0.010812 * cos((8.0 * M_PI * i) / n)
- 0.00077658 * cos((10.0 * M_PI * i) / n)
+ 0.000013887 * cos((12.0 * M_PI * i) / n)) * 3.72;
}
static inline Real blackman(Real n, Real i) // 3 term Blackman
{
return (0.42438
- 0.49734 * cos(2.0 * M_PI * i / n)
+ 0.078279 * cos(4.0 * M_PI * i / n)) * 2.37;
}
static inline Real hamming(Real n, Real i)
{
// amplitude correction = 1.855, energy correction = 1.586
return (0.54 - 0.46 * cos((2.0 * M_PI * i) / n)) * 1.855;
}
static inline Real hanning(Real n, Real i)
{
// amplitude correction = 2.0, energy correction = 1.633
return (0.5 - 0.5 * cos((2.0 * M_PI * i) / n)) * 2.0;
}
static inline Real rectangle(Real, Real)
{
return 1.0;
}
// https://raw.githubusercontent.com/johnglover/simpl/master/src/loris/KaiserWindow.C
inline Real kaiser(Real n, Real i)
{
Real K = ((2.0*i) / n) - 1.0;
Real arg = sqrt(1.0 - (K*K));
return zeroethOrderBessel(m_kaiserAlpha*arg) / m_kaiserI0Alpha;
}
static inline Real zeroethOrderBessel( Real x )
{
const Real eps = 0.000001;
// initialize the series term for m=0 and the result
Real besselValue = 0;
Real term = 1;
Real m = 0;
// accumulate terms as long as they are significant
while(term > eps * besselValue)
{
besselValue += term;
// update the term
++m;
term *= (x*x) / (4*m*m);
}
return besselValue;
}
};
#endif // INCLUDE_FFTWINDOWS_H

View File

@@ -0,0 +1,250 @@
///////////////////////////////////////////////////////////////////////////////////
// Copyright (C) 2015-2021 Edouard Griffiths, F4EXB <f4exb06@gmail.com> //
// Copyright (C) 2018 beta-tester <alpha-beta-release@gmx.net> //
// Copyright (C) 2020 Felix Schneider <felix@fx-schneider.de> //
// Copyright (C) 2021, 2023 Jon Beniston, M7RCE <jon@beniston.com> //
// Copyright (C) 2021 Andreas Baulig <free.geronimo@hotmail.de> //
// Copyright (C) 2021 Christoph Berg <myon@debian.org> //
// Copyright (C) 2022 CRD716 <crd716@gmail.com> //
// Copyright (C) 2022 Jiří Pinkava <jiri.pinkava@rossum.ai> //
// //
// This program 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 as version 3 of the License, or //
// (at your option) any later version. //
// //
// This program 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 V3 for more details. //
// //
// You should have received a copy of the GNU General Public License //
// along with this program. If not, see <http://www.gnu.org/licenses/>. //
///////////////////////////////////////////////////////////////////////////////////
#include <boost/crc.hpp>
#include <boost/cstdint.hpp>
#include <QDebug>
#include <QDateTime>
#include "dsp/dspcommands.h"
#include "util/message.h"
#include "filerecord.h"
FileRecord::FileRecord(quint32 sampleRate, quint64 centerFrequency) :
FileRecordInterface(),
m_fileBase("test"),
m_sampleRate(sampleRate),
m_centerFrequency(centerFrequency),
m_recordOn(false),
m_recordStart(false),
m_byteCount(0),
m_msShift(0)
{
setObjectName("FileRecord");
}
FileRecord::FileRecord(const QString& fileBase) :
FileRecordInterface(),
m_fileBase(fileBase),
m_sampleRate(0),
m_centerFrequency(0),
m_recordOn(false),
m_recordStart(false),
m_byteCount(0)
{
setObjectName("FileRecord");
}
FileRecord::~FileRecord()
{
stopRecording();
}
void FileRecord::setFileName(const QString& fileBase)
{
if (!m_recordOn)
{
m_fileBase = fileBase;
}
}
void FileRecord::genUniqueFileName(uint deviceUID, int istream)
{
if (istream < 0) {
setFileName(QString("rec%1_%2.sdriq").arg(deviceUID).arg(QDateTime::currentDateTimeUtc().toString("yyyy-MM-ddTHH_mm_ss_zzz")));
} else {
setFileName(QString("rec%1_%2_%3.sdriq").arg(deviceUID).arg(istream).arg(QDateTime::currentDateTimeUtc().toString("yyyy-MM-ddTHH_mm_ss_zzz")));
}
}
void FileRecord::feed(const SampleVector::const_iterator& begin, const SampleVector::const_iterator& end, bool positiveOnly)
{
QMutexLocker mutexLocker(&m_mutex);
(void) positiveOnly;
// if no recording is active, send the samples to /dev/null
if(!m_recordOn)
return;
if (begin < end) // if there is something to put out
{
if (m_recordStart)
{
writeHeader();
m_recordStart = false;
}
m_sampleFile.write(reinterpret_cast<const char*>(&*(begin)), (end - begin)*sizeof(Sample));
m_byteCount += end - begin;
}
}
void FileRecord::start()
{
}
void FileRecord::stop()
{
stopRecording();
}
bool FileRecord::startRecording()
{
QMutexLocker mutexLocker(&m_mutex);
if (m_recordOn) {
stopRecording();
}
#ifdef ANDROID
if (!m_sampleFile.isOpen())
#else
if (!m_sampleFile.is_open())
#endif
{
qDebug() << "FileRecord::startRecording";
#ifdef ANDROID
// FIXME: No idea how to write to a file where the filename doesn't come from the file picker
m_currentFileName = m_fileBase + ".sdriq";
m_sampleFile.setFileName(m_currentFileName);
if (!m_sampleFile.open(QIODevice::ReadWrite))
{
qWarning() << "FileRecord::startRecording: failed to open file: " << m_currentFileName << " error " << m_sampleFile.error();
return false;
}
#else
m_currentFileName = m_fileBase + "." + QDateTime::currentDateTimeUtc().toString("yyyy-MM-ddTHH_mm_ss_zzz") + ".sdriq"; // Don't use QString::arg on Android, as filename can contain %2
m_sampleFile.open(m_currentFileName.toStdString().c_str(), std::ios::binary);
if (!m_sampleFile.is_open())
{
qWarning() << "FileRecord::startRecording: failed to open file: " << m_currentFileName;
return false;
}
#endif
m_recordOn = true;
m_recordStart = true;
m_byteCount = 0;
}
return true;
}
bool FileRecord::stopRecording()
{
QMutexLocker mutexLocker(&m_mutex);
#ifdef ANDROID
if (m_sampleFile.isOpen())
#else
if (m_sampleFile.is_open())
#endif
{
qDebug() << "FileRecord::stopRecording";
m_sampleFile.close();
m_recordOn = false;
m_recordStart = false;
#ifdef ANDROID
#else
if (m_sampleFile.bad())
{
qWarning() << "FileRecord::stopRecording: an error occurred while writing to " << m_currentFileName;
return false;
}
#endif
}
return true;
}
bool FileRecord::handleMessage(const Message& message)
{
if (DSPSignalNotification::match(message))
{
QMutexLocker mutexLocker(&m_mutex);
DSPSignalNotification& notif = (DSPSignalNotification&) message;
quint32 sampleRate = notif.getSampleRate();
qint64 centerFrequency = notif.getCenterFrequency();
qDebug() << "FileRecord::handleMessage: DSPSignalNotification: inputSampleRate: " << sampleRate
<< " centerFrequency: " << centerFrequency;
if (m_recordOn && (m_sampleRate != sampleRate)) {
startRecording();
}
m_sampleRate = sampleRate;
m_centerFrequency = centerFrequency;
return true;
}
else
{
return false;
}
}
void FileRecord::writeHeader()
{
Header header;
header.sampleRate = m_sampleRate;
header.centerFrequency = m_centerFrequency;
qint64 ts = QDateTime::currentMSecsSinceEpoch();
header.startTimeStamp = (quint64)(ts + m_msShift);
header.sampleSize = SDR_RX_SAMP_SZ;
header.filler = 0;
writeHeader(m_sampleFile, header);
}
bool FileRecord::readHeader(std::ifstream& sampleFile, Header& header)
{
sampleFile.read((char *) &header, sizeof(Header));
boost::crc_32_type crc32;
crc32.process_bytes(&header, 28);
return header.crc32 == crc32.checksum();
}
bool FileRecord::readHeader(QFile& sampleFile, Header& header)
{
sampleFile.read((char *) &header, sizeof(Header));
boost::crc_32_type crc32;
crc32.process_bytes(&header, 28);
return header.crc32 == crc32.checksum();
}
void FileRecord::writeHeader(std::ofstream& sampleFile, Header& header)
{
boost::crc_32_type crc32;
crc32.process_bytes(&header, 28);
header.crc32 = crc32.checksum();
sampleFile.write((const char *) &header, sizeof(Header));
}
void FileRecord::writeHeader(QFile& sampleFile, Header& header)
{
boost::crc_32_type crc32;
crc32.process_bytes(&header, 28);
header.crc32 = crc32.checksum();
sampleFile.write((const char *) &header, sizeof(Header));
}

View File

@@ -0,0 +1,97 @@
///////////////////////////////////////////////////////////////////////////////////
// Copyright (C) 2015-2016, 2018-2020 Edouard Griffiths, F4EXB <f4exb06@gmail.com> //
// Copyright (C) 2018 beta-tester <alpha-beta-release@gmx.net> //
// Copyright (C) 2021, 2023 Jon Beniston, M7RCE <jon@beniston.com> //
// Copyright (C) 2022 CRD716 <crd716@gmail.com> //
// Copyright (C) 2022 Jiří Pinkava <jiri.pinkava@rossum.ai> //
// //
// This program 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 as version 3 of the License, or //
// (at your option) any later version. //
// //
// This program 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 V3 for more details. //
// //
// You should have received a copy of the GNU General Public License //
// along with this program. If not, see <http://www.gnu.org/licenses/>. //
///////////////////////////////////////////////////////////////////////////////////
#ifndef INCLUDE_FILERECORD_H
#define INCLUDE_FILERECORD_H
#include <QFile>
#include <dsp/basebandsamplesink.h>
#include <string>
#include <iostream>
#include <fstream>
#include <ctime>
#include "dsp/filerecordinterface.h"
#include "export.h"
class Message;
class SDRBASE_API FileRecord : public FileRecordInterface {
public:
#pragma pack(push, 1)
struct Header
{
quint32 sampleRate;
quint64 centerFrequency;
quint64 startTimeStamp;
quint32 sampleSize;
quint32 filler;
quint32 crc32;
};
#pragma pack(pop)
FileRecord(quint32 sampleRate=0, quint64 centerFrequency=0);
FileRecord(const QString& fileBase);
virtual ~FileRecord();
quint64 getByteCount() const { return m_byteCount; }
void setMsShift(qint64 shift) { m_msShift = shift; }
const QString& getCurrentFileName() { return m_currentFileName; }
void genUniqueFileName(uint deviceUID, int istream = -1);
virtual void feed(const SampleVector::const_iterator& begin, const SampleVector::const_iterator& end, bool positiveOnly);
virtual void start();
virtual void stop();
virtual bool handleMessage(const Message& message);
virtual void setFileName(const QString& fileBase);
virtual bool startRecording();
virtual bool stopRecording();
virtual bool isRecording() const { return m_recordOn; }
static bool readHeader(std::ifstream& samplefile, Header& header); //!< returns true if CRC checksum is correct else false
static bool readHeader(QFile& samplefile, Header& header); //!< returns true if CRC checksum is correct else false
static void writeHeader(std::ofstream& samplefile, Header& header);
static void writeHeader(QFile& samplefile, Header& header);
private:
QString m_fileBase;
quint32 m_sampleRate;
quint64 m_centerFrequency;
bool m_recordOn;
bool m_recordStart;
#ifdef ANDROID
QFile m_sampleFile;
#else
std::ofstream m_sampleFile;
#endif
QString m_currentFileName;
quint64 m_byteCount;
qint64 m_msShift;
QRecursiveMutex m_mutex;
void writeHeader();
};
#endif // INCLUDE_FILERECORD_H

View File

@@ -0,0 +1,88 @@
///////////////////////////////////////////////////////////////////////////////////
// Copyright (C) 2012 maintech GmbH, Otto-Hahn-Str. 15, 97204 Hoechberg, Germany //
// written by Christian Daniel //
// Copyright (C) 2015-2020 Edouard Griffiths, F4EXB <f4exb06@gmail.com> //
// Copyright (C) 2021, 2023 Jon Beniston, M7RCE <jon@beniston.com> //
// //
// File recorder in SigMF format single channel for SI plugins //
// //
// This program 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 as version 3 of the License, or //
// (at your option) any later version. //
// //
// This program 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 V3 for more details. //
// //
// You should have received a copy of the GNU General Public License //
// along with this program. If not, see <http://www.gnu.org/licenses/>. //
///////////////////////////////////////////////////////////////////////////////////
#include <QDateTime>
#include <QFileInfo>
#include "filerecordinterface.h"
FileRecordInterface::FileRecordInterface()
{
connect(&m_inputMessageQueue, SIGNAL(messageEnqueued()), this, SLOT(handleInputMessages()));
}
FileRecordInterface::~FileRecordInterface()
{}
QString FileRecordInterface::genUniqueFileName(unsigned int deviceUID, int istream)
{
if (istream < 0) {
return QString("rec%1.%2.sdriq").arg(deviceUID).arg(QDateTime::currentDateTimeUtc().toString("yyyy-MM-ddTHH_mm_ss_zzz"));
} else {
return QString("rec%1_%2.%3.sdriq").arg(deviceUID).arg(istream).arg(QDateTime::currentDateTimeUtc().toString("yyyy-MM-ddTHH_mm_ss_zzz"));
}
}
FileRecordInterface::RecordType FileRecordInterface::guessTypeFromFileName(const QString& fileName, QString& fileBase)
{
QFileInfo fileInfo(fileName);
QString extension = fileInfo.suffix();
fileBase = fileName;
if (!extension.isEmpty())
{
fileBase.chop(extension.size() + 1);
if (extension == "sdriq")
{
return RecordTypeSdrIQ;
}
else if (extension == "sigmf-meta")
{
return RecordTypeSigMF;
}
else if (extension == "wav")
{
return RecordTypeWav;
}
else
{
return RecordTypeUndefined;
}
}
else
{
return RecordTypeUndefined;
}
}
void FileRecordInterface::handleInputMessages()
{
Message* message;
while ((message = m_inputMessageQueue.pop()) != 0)
{
if (handleMessage(*message))
{
delete message;
}
}
}

View File

@@ -0,0 +1,79 @@
///////////////////////////////////////////////////////////////////////////////////
// Copyright (C) 2012 maintech GmbH, Otto-Hahn-Str. 15, 97204 Hoechberg, Germany //
// written by Christian Daniel //
// Copyright (C) 2015-2020 Edouard Griffiths, F4EXB <f4exb06@gmail.com> //
// Copyright (C) 2021 Jon Beniston, M7RCE <jon@beniston.com> //
// //
// File recorder in SigMF format single channel for SI plugins //
// //
// This program 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 as version 3 of the License, or //
// (at your option) any later version. //
// //
// This program 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 V3 for more details. //
// //
// You should have received a copy of the GNU General Public License //
// along with this program. If not, see <http://www.gnu.org/licenses/>. //
///////////////////////////////////////////////////////////////////////////////////
#ifndef INCLUDE_FILERECORD_INTERFACE_H
#define INCLUDE_FILERECORD_INTERFACE_H
#include <QString>
#include <QObject>
#include "dsp/dsptypes.h"
#include "util/message.h"
#include "util/messagequeue.h"
#include "export.h"
class SDRBASE_API FileRecordInterface : public QObject {
Q_OBJECT
public:
enum RecordType
{
RecordTypeUndefined = 0,
RecordTypeSdrIQ,
RecordTypeSigMF,
RecordTypeWav
};
FileRecordInterface();
virtual ~FileRecordInterface();
virtual void start() = 0;
virtual void stop() = 0;
virtual void feed(const SampleVector::const_iterator& begin, const SampleVector::const_iterator& end, bool positiveOnly) = 0;
virtual bool handleMessage(const Message& cmd) = 0; //!< Processing of a message. Returns true if message has actually been processed
MessageQueue *getInputMessageQueue() { return &m_inputMessageQueue; } //!< Get the queue for asynchronous inbound communication
virtual void setMessageQueueToGUI(MessageQueue *queue) { m_guiMessageQueue = queue; }
MessageQueue *getMessageQueueToGUI() { return m_guiMessageQueue; }
virtual void setFileName(const QString &filename) = 0;
virtual const QString& getCurrentFileName() = 0;
virtual bool startRecording() = 0;
virtual bool stopRecording() = 0;
virtual bool isRecording() const = 0;
virtual void setMsShift(qint64 msShift) = 0;
virtual int getBytesPerSample() { return sizeof(Sample); };
static QString genUniqueFileName(unsigned int deviceUID, int istream = -1);
static RecordType guessTypeFromFileName(const QString& fileName, QString& fileBase);
protected:
MessageQueue m_inputMessageQueue; //!< Queue for asynchronous inbound communication
MessageQueue *m_guiMessageQueue; //!< Input message queue to the GUI
protected slots:
void handleInputMessages();
};
#endif // INCLUDE_FILERECORD_INTERFACE_H

View File

@@ -0,0 +1,52 @@
///////////////////////////////////////////////////////////////////////////////////
// Copyright (C) 2012 maintech GmbH, Otto-Hahn-Str. 15, 97204 Hoechberg, Germany //
// written by Christian Daniel //
// Copyright (C) 2015-2019 Edouard Griffiths, F4EXB <f4exb06@gmail.com> //
// Copyright (C) 2015 John Greb <hexameron@spam.no> //
// //
// This program 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 as version 3 of the License, or //
// (at your option) any later version. //
// //
// This program 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 V3 for more details. //
// //
// You should have received a copy of the GNU General Public License //
// along with this program. If not, see <http://www.gnu.org/licenses/>. //
///////////////////////////////////////////////////////////////////////////////////
#include "filtermbe.h"
const float MBEAudioInterpolatorFilter::m_lpa[3] = {1.0, 1.392667E+00, -5.474446E-01};
const float MBEAudioInterpolatorFilter::m_lpb[3] = {3.869430E-02, 7.738860E-02, 3.869430E-02};
const float MBEAudioInterpolatorFilter::m_hpa[3] = {1.000000e+00, 1.667871e+00, -7.156964e-01};
const float MBEAudioInterpolatorFilter::m_hpb[3] = {8.459039e-01, -1.691760e+00, 8.459039e-01};
MBEAudioInterpolatorFilter::MBEAudioInterpolatorFilter() :
m_filterLP(m_lpa, m_lpb),
m_filterHP(m_hpa, m_hpb),
m_useHP(false)
{
}
MBEAudioInterpolatorFilter::~MBEAudioInterpolatorFilter()
{}
float MBEAudioInterpolatorFilter::run(const float& sample)
{
return m_useHP ? m_filterLP.run(m_filterHP.run(sample)) : m_filterLP.run(sample);
}
float MBEAudioInterpolatorFilter::runHP(const float& sample)
{
return m_filterHP.run(sample);
}
float MBEAudioInterpolatorFilter::runLP(const float& sample)
{
return m_filterLP.run(sample);
}

View File

@@ -0,0 +1,91 @@
///////////////////////////////////////////////////////////////////////////////////
// Copyright (C) 2012 maintech GmbH, Otto-Hahn-Str. 15, 97204 Hoechberg, Germany //
// written by Christian Daniel //
// Copyright (C) 2015-2019 Edouard Griffiths, F4EXB <f4exb06@gmail.com> //
// Copyright (C) 2015 John Greb <hexameron@spam.no> //
// //
// This program 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 as version 3 of the License, or //
// (at your option) any later version. //
// //
// This program 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 V3 for more details. //
// //
// You should have received a copy of the GNU General Public License //
// along with this program. If not, see <http://www.gnu.org/licenses/>. //
///////////////////////////////////////////////////////////////////////////////////
#ifndef SDRBASE_DSP_FILTERMBE_H_
#define SDRBASE_DSP_FILTERMBE_H_
/**
* Uses the generic IIR filter internally
*
* Low pass / High pass:
*
* This is a 2 pole Chebyshev (recursive) filter using coefficients found in table 20-1 (low pass)
* or table 20-2 (high pass) of http://www.analog.com/media/en/technical-documentation/dsp-book/dsp_book_Ch20.pdf
*
* For low pass fc = 0.075
* For high oass fc = 0.01
*
* Convention taken here exchanges A and B coefficients as shown in this image:
* https://cdn.mikroe.com/ebooks/img/8/2016/02/digital-filter-design-chapter-03-image-2-9.gif
* So A applies to Y and B to X
*
* At the interpolated sampling frequency of 48 kHz the -3 dB corner is at 48 * .075 = 3.6 kHz which is perfect for voice
* The high pass has a 3 dB corner of 48 * 0.01 = 0.48 kHz
*
* Low pass:
*
* b0 = 3.869430E-02 (a0 = 1.0)
* b1 = 7.738860E-02 a1 = 1.392667E+00
* b2 = 3.869430E-02 a2 = -5.474446E-01
*
* High pass:
*
* b0 = 9.567529E-01 (a0 = 1.0)
* b1 = -1.913506E+00 a1 = 1.911437E+00
* b2 = 9.567529E-01 a2 = -9.155749E-01
*
* given x[n] is the new input sample and y[n] the returned output sample:
*
* y[n] = b0*x[n] + b1*x[n] + b2*x[n] + a1*y[n-1] + a2*y[n-2]
*
* This one works directly with floats
*
*
*/
#include "iirfilter.h"
#include "export.h"
class SDRBASE_API MBEAudioInterpolatorFilter
{
public:
MBEAudioInterpolatorFilter();
~MBEAudioInterpolatorFilter();
void useHP(bool useHP) { m_useHP = useHP; }
bool usesHP() const { return m_useHP; }
float run(const float& sample);
float runHP(const float& sample);
float runLP(const float& sample);
private:
IIRFilter<float, 2> m_filterLP;
IIRFilter<float, 2> m_filterHP;
bool m_useHP;
// low pass coefficients
static const float m_lpa[3];
static const float m_lpb[3];
// band pass coefficients
static const float m_hpa[3];
static const float m_hpb[3];
};
#endif /* SDRBASE_DSP_FILTERMBE_H_ */

View File

@@ -0,0 +1,88 @@
///////////////////////////////////////////////////////////////////////////////////
// Copyright (C) 2012 maintech GmbH, Otto-Hahn-Str. 15, 97204 Hoechberg, Germany //
// written by Christian Daniel //
// Copyright (C) 2014 John Greb <hexameron@spam.no> //
// Copyright (C) 2015, 2019 Edouard Griffiths, F4EXB <f4exb06@gmail.com> //
// //
// This program 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 as version 3 of the License, or //
// (at your option) any later version. //
// //
// This program 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 V3 for more details. //
// //
// You should have received a copy of the GNU General Public License //
// along with this program. If not, see <http://www.gnu.org/licenses/>. //
///////////////////////////////////////////////////////////////////////////////////
#include <QDebug>
#include "dsp/filterrc.h"
// Construct 1st order low-pass IIR filter.
LowPassFilterRC::LowPassFilterRC(Real timeconst) :
m_timeconst(timeconst),
m_y1(0)
{
m_a1 = - exp(-1/m_timeconst);
m_b0 = 1 + m_a1;
}
// Reconfigure
void LowPassFilterRC::configure(Real timeconst)
{
m_timeconst = timeconst;
m_y1 = 0;
m_a1 = - exp(-1/m_timeconst);
m_b0 = 1 + m_a1;
qDebug() << "LowPassFilterRC::configure: t: " << m_timeconst
<< " a1: " << m_a1
<< " b0: " << m_b0;
}
// Process samples.
void LowPassFilterRC::process(const Real& sample_in, Real& sample_out)
{
/*
* Continuous domain:
* H(s) = 1 / (1 - s * timeconst)
*
* Discrete domain:
* H(z) = (1 - exp(-1/timeconst)) / (1 - exp(-1/timeconst) / z)
*/
m_y1 = (sample_in * m_b0) - (m_y1 * m_a1);
sample_out = m_y1;
}
// Construct 1st order high-pass IIR filter.
HighPassFilterRC::HighPassFilterRC(Real timeconst) :
m_timeconst(timeconst),
m_y1(0)
{
m_a1 = 1 - exp(-1/m_timeconst);
m_b0 = 1 + m_a1;
}
// Reconfigure
void HighPassFilterRC::configure(Real timeconst)
{
m_timeconst = timeconst;
m_y1 = 0;
m_a1 = 1 - exp(-1/m_timeconst);
m_b0 = 1 + m_a1;
qDebug() << "HighPassFilterRC::configure: t: " << m_timeconst
<< " a1: " << m_a1
<< " b0: " << m_b0;
}
// Process samples.
void HighPassFilterRC::process(const Real& sample_in, Real& sample_out)
{
m_y1 = (sample_in * m_b0) - (m_y1 * m_a1);
sample_out = m_y1;
}

View File

@@ -0,0 +1,80 @@
///////////////////////////////////////////////////////////////////////////////////
// Copyright (C) 2012 maintech GmbH, Otto-Hahn-Str. 15, 97204 Hoechberg, Germany //
// written by Christian Daniel //
// Copyright (C) 2015-2016, 2018-2019 Edouard Griffiths, F4EXB <f4exb06@gmail.com> //
// //
// This program 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 as version 3 of the License, or //
// (at your option) any later version. //
// //
// This program 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 V3 for more details. //
// //
// You should have received a copy of the GNU General Public License //
// along with this program. If not, see <http://www.gnu.org/licenses/>. //
///////////////////////////////////////////////////////////////////////////////////
#ifndef INCLUDE_DSP_FILTERRC_H_
#define INCLUDE_DSP_FILTERRC_H_
#include "dsp/dsptypes.h"
#include "export.h"
/** First order low-pass IIR filter for real-valued signals. */
class SDRBASE_API LowPassFilterRC
{
public:
/**
* Construct 1st order low-pass IIR filter.
*
* timeconst :: RC time constant in seconds (1 / (2 * PI * cutoff_freq)
*/
LowPassFilterRC(Real timeconst);
/**
* Reconfigure filter with new time constant
*/
void configure(Real timeconst);
/** Process samples. */
void process(const Real& sample_in, Real& sample_out);
private:
Real m_timeconst;
Real m_y1;
Real m_a1;
Real m_b0;
};
/** First order high-pass IIR filter for real-valued signals. */
class SDRBASE_API HighPassFilterRC
{
public:
/**
* Construct 1st order high-pass IIR filter.
*
* timeconst :: RC time constant in seconds (1 / (2 * PI * cutoff_freq)
*/
HighPassFilterRC(Real timeconst);
/**
* Reconfigure filter with new time constant
*/
void configure(Real timeconst);
/** Process samples. */
void process(const Real& sample_in, Real& sample_out);
private:
Real m_timeconst;
Real m_y1;
Real m_a1;
Real m_b0;
};
#endif /* INCLUDE_DSP_FILTERRC_H_ */

View File

@@ -0,0 +1,73 @@
///////////////////////////////////////////////////////////////////////////////////
// Copyright (C) 2012 maintech GmbH, Otto-Hahn-Str. 15, 97204 Hoechberg, Germany //
// written by Christian Daniel //
// Copyright (C) 2015-2017, 2019-2020 Edouard Griffiths, F4EXB <f4exb06@gmail.com> //
// Copyright (C) 2020 Kacper Michajłow <kasper93@gmail.com> //
// //
// This program 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 as version 3 of the License, or //
// (at your option) any later version. //
// //
// This program 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 V3 for more details. //
// //
// You should have received a copy of the GNU General Public License //
// along with this program. If not, see <http://www.gnu.org/licenses/>. //
///////////////////////////////////////////////////////////////////////////////////
#include "firfilter.h"
#include <stdio.h>
namespace FirFilterGenerators
{
void generateLowPassFilter(int nTaps, double sampleRate, double cutoff, std::vector<Real> &taps)
{
if (!(nTaps & 1))
{
printf("Filter has to have an odd number of taps\n");
nTaps++;
}
double Wc = (2.0 * M_PI * cutoff) / sampleRate;
int halfTaps = nTaps / 2 + 1;
taps.resize(halfTaps);
for (int i = 0; i < halfTaps; ++i)
{
if (i == halfTaps - 1)
{
taps[i] = Wc / M_PI;
}
else
{
int n = i - (nTaps - 1) / 2;
taps[i] = sin(n * Wc) / (n * M_PI);
}
}
// Blackman window
for (int i = 0; i < halfTaps; i++)
{
int n = i - (nTaps - 1) / 2;
taps[i] *= 0.42 + 0.5 * cos((2.0 * M_PI * n) / nTaps) + 0.08 * cos((4.0 * M_PI * n) / nTaps);
}
Real sum = 0;
size_t i;
for (i = 0; i < taps.size() - 1; ++i) {
sum += taps[i] * 2.0;
}
sum += taps[i];
for (i = 0; i < taps.size(); ++i) {
taps[i] /= sum;
}
}
}

View File

@@ -0,0 +1,144 @@
///////////////////////////////////////////////////////////////////////////////////
// Copyright (C) 2020 Edouard Griffiths, F4EXB <f4exb06@gmail.com> //
// Copyright (C) 2020 Kacper Michajłow <kasper93@gmail.com> //
// Copyright (C) 2021, 2023 Jon Beniston, M7RCE <jon@beniston.com> //
// //
// This program 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 as version 3 of the License, or //
// (at your option) any later version. //
// //
// This program 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 V3 for more details. //
// //
// You should have received a copy of the GNU General Public License //
// along with this program. If not, see <http://www.gnu.org/licenses/>. //
///////////////////////////////////////////////////////////////////////////////////
#pragma once
#include <cmath>
#include <cstdio>
#include "dsp/dsptypes.h"
#include "dsp/basebandsamplesink.h"
#include "export.h"
namespace FirFilterGenerators
{
SDRBASE_API void generateLowPassFilter(int nTaps, double sampleRate, double cutoff, std::vector<Real> &taps);
};
template <class Type>
class FirFilter
{
public:
Type filter(Type sample)
{
Type acc = 0;
unsigned int n_samples = m_samples.size();
unsigned int n_taps = m_taps.size() - 1;
unsigned int a = m_ptr;
unsigned int b = a == n_samples - 1 ? 0 : a + 1;
m_samples[m_ptr] = sample;
for (unsigned int i = 0; i < n_taps; ++i)
{
acc += (m_samples[a] + m_samples[b]) * m_taps[i];
a = (a == 0) ? n_samples - 1 : a - 1;
b = (b == n_samples - 1) ? 0 : b + 1;
}
acc += m_samples[a] * m_taps[n_taps];
m_ptr = (m_ptr == n_samples - 1) ? 0 : m_ptr + 1;
return acc;
}
// Print taps as a Matlab vector
// To view:
// h=fvtool(filter);
// h.Fs=...
void printTaps(const char *name)
{
printf("%s = [", name);
for (int i = 0; i <= m_taps.size() - 1; ++i) {
printf("%g ", m_taps[i]);
}
for (int i = m_taps.size() - 2; i >= 0; --i) {
printf("%g ", m_taps[i]);
}
printf("];\n");
}
protected:
void init(int nTaps)
{
m_ptr = 0;
m_samples.resize(nTaps);
for (int i = 0; i < nTaps; i++) {
m_samples[i] = 0;
}
}
protected:
std::vector<Real> m_taps;
std::vector<Type> m_samples;
size_t m_ptr;
};
template <class T>
struct Lowpass : public FirFilter<T>
{
public:
void create(int nTaps, double sampleRate, double cutoff)
{
this->init(nTaps);
FirFilterGenerators::generateLowPassFilter(nTaps, sampleRate, cutoff, this->m_taps);
}
};
template <class T>
struct Bandpass : public FirFilter<T>
{
void create(int nTaps, double sampleRate, double lowCutoff, double highCutoff)
{
this->init(nTaps);
FirFilterGenerators::generateLowPassFilter(nTaps, sampleRate, highCutoff, this->m_taps);
std::vector<Real> highPass;
FirFilterGenerators::generateLowPassFilter(nTaps, sampleRate, lowCutoff, highPass);
for (size_t i = 0; i < highPass.size(); ++i) {
highPass[i] = -highPass[i];
}
highPass[highPass.size() - 1] += 1;
for (size_t i = 0; i < this->m_taps.size(); ++i) {
this->m_taps[i] = -(this->m_taps[i] + highPass[i]);
}
this->m_taps[this->m_taps.size() - 1] += 1;
}
};
template <class T>
struct Highpass : public FirFilter<T>
{
void create(int nTaps, double sampleRate, double cutoff)
{
this->init(nTaps);
FirFilterGenerators::generateLowPassFilter(nTaps, sampleRate, cutoff, this->m_taps);
for (size_t i = 0; i < this->m_taps.size(); ++i) {
this->m_taps[i] = -this->m_taps[i];
}
this->m_taps[this->m_taps.size() - 1] += 1;
}
};

View File

@@ -0,0 +1,80 @@
///////////////////////////////////////////////////////////////////////////////////
// Copyright (C) 2020 Kacper Michajłow <kasper93@gmail.com> //
// Copyright (C) 2020 Jon Beniston, M7RCE <jon@beniston.com> //
// Copyright (C) 2005,2007,2012 Free Software Foundation, Inc.
// //
// This program 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 as version 3 of the License, or //
// (at your option) any later version. //
// //
// This program 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 V3 for more details. //
// //
// You should have received a copy of the GNU General Public License //
// along with this program. If not, see <http://www.gnu.org/licenses/>. //
///////////////////////////////////////////////////////////////////////////////////
#include <cmath>
#include <QDebug>
#include "dsp/fmpreemphasis.h"
FMPreemphasis::FMPreemphasis(int sampleRate, Real tau, Real highFreq)
{
configure(sampleRate, tau, highFreq);
}
void FMPreemphasis::configure(int sampleRate, Real tau, Real highFreq)
{
// Based on: https://github.com/gnuradio/gnuradio/blob/master/gr-analog/python/analog/fm_emph.py
// Compare to freq response in https://www.mathworks.com/help/comm/ref/comm.fmbroadcastmodulator-system-object.html
// High frequency corner at which to flatten the gain
double fh = std::min((double)highFreq, 0.925 * sampleRate/2.0);
// Digital corner frequencies
double w_cl = 1.0 / tau;
double w_ch = 2.0 * M_PI * fh;
// Prewarped analog corner frequencies
double w_cla = 2.0 * sampleRate * std::tan(w_cl / (2.0 * sampleRate));
double w_cha = 2.0 * sampleRate * std::tan(w_ch / (2.0 * sampleRate));
// Resulting digital pole, zero, and gain term from the bilinear
// transformation of H(s) = (s + w_cla) / (s + w_cha) to
// H(z) = b0 (1 - z1 z^-1)/(1 - p1 z^-1)
double kl = -w_cla / (2.0 * sampleRate);
double kh = -w_cha / (2.0 * sampleRate);
double z1 = (1.0 + kl) / (1.0 - kl);
double p1 = (1.0 + kh) / (1.0 - kh);
double b0 = (1.0 - kl) / (1.0 - kh);
// Adjust with a gain, g, so 0 dB gain at DC
double g = std::abs(1.0 - p1) / (b0 * std::abs(1.0 - z1));
// Calculate IIR taps
m_b0 = (Real)(g * b0 * 1.0);
m_b1 = (Real)(g * b0 * -z1);
m_a1 = (Real)-p1;
// Zero delay line so we get reproducible results
m_z = 0;
qDebug() << "FMPreemphasis::configure: tau: " << tau
<< " sampleRate: " << sampleRate
<< " b0: " << m_b0
<< " b1: " << m_b1
<< " a1: " << m_a1;
}
Real FMPreemphasis::filter(const Real sampleIn)
{
Real sampleOut;
// See Transposed Direct form 2 - https://en.wikipedia.org/wiki/Digital_biquad_filter
sampleOut = sampleIn * m_b0 + m_z;
m_z = sampleIn * m_b1 + sampleOut * -m_a1;
return sampleOut;
}

View File

@@ -0,0 +1,58 @@
///////////////////////////////////////////////////////////////////////////////////
// Copyright (C) 2012 maintech GmbH, Otto-Hahn-Str. 15, 97204 Hoechberg, Germany //
// written by Christian Daniel //
// Copyright (C) 2015-2020 Edouard Griffiths, F4EXB <f4exb06@gmail.com> //
// Copyright (C) 2020 Jon Beniston, M7RCE <jon@beniston.com> //
// //
// This program 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 as version 3 of the License, or //
// (at your option) any later version. //
// //
// This program 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 V3 for more details. //
// //
// You should have received a copy of the GNU General Public License //
// along with this program. If not, see <http://www.gnu.org/licenses/>. //
///////////////////////////////////////////////////////////////////////////////////
#ifndef INCLUDE_DSP_FMPREEMPHASIS_H_
#define INCLUDE_DSP_FMPREEMPHASIS_H_
#include "dsp/dsptypes.h"
#include "export.h"
#define FMPREEMPHASIS_TAU_EU 50e-6f
#define FMPREEMPHASIS_TAU_US 75e-6f
/** FM preemphasis filter.
* Amplifies frequencies above ~3.2k (tau=50e-6 in EU) or ~2.1k (tau=75e-6 in US)
* at ~6dB per octave, and then flattens at 12k (highFreq).
* Frequency response:
* highFreq
* -------
* /
* /
* -------/
* 1/(2*pi*tau)
*/
class SDRBASE_API FMPreemphasis
{
public:
FMPreemphasis(int sampleRate, Real tau = FMPREEMPHASIS_TAU_EU, Real highFreq = 12000.0);
void configure(int sampleRate, Real tau = FMPREEMPHASIS_TAU_EU, Real highFreq = 12000.0);
Real filter(Real sampleIn);
private:
Real m_z; // Delay element
Real m_b0; // IIR numerator taps
Real m_b1;
Real m_a1; // IIR denominator taps
};
#endif /* INCLUDE_DSP_FMPREEMPHASIS_H_ */

View File

@@ -0,0 +1,93 @@
///////////////////////////////////////////////////////////////////////////////////
// Copyright (C) 2018-2019 Edouard Griffiths, F4EXB <f4exb06@gmail.com> //
// Copyright (C) 2020 Kacper Michajłow <kasper93@gmail.com> //
// //
// See: http://liquidsdr.org/blog/pll-howto/ //
// Fixed filter registers saturation //
// Added order for PSK locking. This brilliant idea actually comes from this //
// post: https://www.dsprelated.com/showthread/comp.dsp/36356-1.php //
// //
// This program 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 as version 3 of the License, or //
// (at your option) any later version. //
// //
// This program 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 V3 for more details. //
// //
// You should have received a copy of the GNU General Public License //
// along with this program. If not, see <http://www.gnu.org/licenses/>. //
///////////////////////////////////////////////////////////////////////////////////
#include "freqlockcomplex.h"
#include <cmath>
FreqLockComplex::FreqLockComplex() :
m_a0(0.998),
m_a1(0.002),
m_y(1.0, 0.0),
m_yRe(1.0),
m_yIm(0.0),
m_freq(0.0),
m_phi(0.0),
m_phiX0(0.0),
m_phiX1(0.0),
m_y1(0.0f)
{
}
FreqLockComplex::~FreqLockComplex()
{
}
void FreqLockComplex::reset()
{
m_y.real(1.0);
m_y.imag(0.0);
m_yRe = 1.0f;
m_yIm = 0.0f;
m_freq = 0.0f;
m_phi = 0.0f;
m_phiX0 = 0.0f;
m_phiX1 = 0.0f;
m_y1 = 0.0f;
}
void FreqLockComplex::setSampleRate(unsigned int sampleRate)
{
m_a1 = 10.0f / sampleRate; // 1 - alpha
m_a0 = 1.0f - m_a1; // alpha
reset();
}
void FreqLockComplex::feed(float re, float im)
{
m_yRe = cos(m_phi);
m_yIm = sin(m_phi);
m_y.real(m_yRe);
m_y.imag(m_yIm);
std::complex<float> x(re, im);
m_phiX0 = std::arg(x);
float eF = normalizeAngle(m_phiX0 - m_phiX1);
float fHat = m_a1*eF + m_a0*m_y1;
m_y1 = fHat;
m_freq = fHat; // correct instantaneous frequency
m_phi += fHat; // advance phase with instantaneous frequency
m_phiX1 = m_phiX0;
}
float FreqLockComplex::normalizeAngle(float angle)
{
while (angle <= -M_PI) {
angle += 2.0*M_PI;
}
while (angle > M_PI) {
angle -= 2.0*M_PI;
}
return angle;
}

View File

@@ -0,0 +1,62 @@
///////////////////////////////////////////////////////////////////////////////////
// Copyright (C) 2018-2019 Edouard Griffiths, F4EXB <f4exb06@gmail.com> //
// //
// See: http://liquidsdr.org/blog/pll-howto/ //
// Fixed filter registers saturation //
// Added order for PSK locking. This brilliant idea actually comes from this //
// post: https://www.dsprelated.com/showthread/comp.dsp/36356-1.php //
// //
// This program 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 as version 3 of the License, or //
// (at your option) any later version. //
// //
// This program 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 V3 for more details. //
// //
// You should have received a copy of the GNU General Public License //
// along with this program. If not, see <http://www.gnu.org/licenses/>. //
///////////////////////////////////////////////////////////////////////////////////
#ifndef SDRBASE_DSP_FREQLOCKCOMPLEX_H_
#define SDRBASE_DSP_FREQLOCKCOMPLEX_H_
#include "dsp/dsptypes.h"
#include "export.h"
/** General purpose Phase-locked loop using complex analytic signal input. */
class SDRBASE_API FreqLockComplex
{
public:
FreqLockComplex();
~FreqLockComplex();
void reset();
void setSampleRate(unsigned int sampleRate);
/** Feed PLL with a new signa sample */
void feed(float re, float im);
const std::complex<float>& getComplex() const { return m_y; }
float getReal() const { return m_yRe; }
float getImag() const { return m_yIm; }
float getFreq() const { return m_freq; }
private:
/** Normalize angle in radians into the [-pi,+pi] region */
static float normalizeAngle(float angle);
float m_a0;
float m_a1;
std::complex<float> m_y;
float m_yRe;
float m_yIm;
float m_freq;
float m_phi;
float m_phiX0;
float m_phiX1;
float m_y1;
};
#endif /* SDRBASE_DSP_FREQLOCKCOMPLEX_H_ */

View File

@@ -0,0 +1,122 @@
///////////////////////////////////////////////////////////////////////////////////
// Copyright (C) 2020-2021 Jon Beniston, M7RCE <jon@beniston.com> //
// Copyright (C) 2020 Kacper Michajłow <kasper93@gmail.com> //
// Copyright (C) 2015 Edouard Griffiths, F4EXB //
// //
// This program 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 as version 3 of the License, or //
// (at your option) any later version. //
// //
// This program 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 V3 for more details. //
// //
// You should have received a copy of the GNU General Public License //
// along with this program. If not, see <http://www.gnu.org/licenses/>. //
///////////////////////////////////////////////////////////////////////////////////
#ifndef INCLUDE_GAUSSIAN_H
#define INCLUDE_GAUSSIAN_H
#include <math.h>
#include "dsp/dsptypes.h"
// Standard values for bt
#define GAUSSIAN_BT_BLUETOOTH 0.5
#define GAUSSIAN_BT_GSM 0.3
#define GAUSSIAN_BT_CCSDS 0.25
#define GAUSSIAN_BT_802_15_4 0.5
#define GAUSSIAN_BT_AIS 0.5
// Gaussian low-pass filter for pulse shaping
// https://onlinelibrary.wiley.com/doi/pdf/10.1002/9780470041956.app2
// Unlike raisedcosine.h, this should be feed NRZ values rather than impulse stream, as described here:
// https://www.mathworks.com/matlabcentral/answers/107231-why-does-the-pulse-shape-generated-by-gaussdesign-differ-from-that-used-in-the-comm-gmskmodulator-ob
template <class Type> class Gaussian {
public:
Gaussian() : m_ptr(0) { }
// bt - 3dB bandwidth symbol time product
// symbolSpan - number of symbols over which the filter is spread
// samplesPerSymbol - number of samples per symbol
void create(double bt, int symbolSpan, int samplesPerSymbol)
{
int nTaps = symbolSpan * samplesPerSymbol + 1;
int i;
// check constraints
if(!(nTaps & 1)) {
qDebug("Gaussian filter has to have an odd number of taps");
nTaps++;
}
// make room
m_samples.resize(nTaps);
for(int i = 0; i < nTaps; i++)
m_samples[i] = 0;
m_ptr = 0;
m_taps.resize(nTaps / 2 + 1);
// See eq B.2 - this is alpha over Ts
double alpha_t = std::sqrt(std::log(2.0) / 2.0) / (bt);
double sqrt_pi_alpha_t = std::sqrt(M_PI) / alpha_t;
// calculate filter taps
for(i = 0; i < nTaps / 2 + 1; i++)
{
double t = (i - (nTaps / 2)) / (double)samplesPerSymbol;
// See eq B.5
m_taps[i] = sqrt_pi_alpha_t * std::exp(-std::pow(t * M_PI / alpha_t, 2.0));
}
// normalize
double sum = 0;
for(i = 0; i < (int)m_taps.size() - 1; i++)
sum += m_taps[i] * 2;
sum += m_taps[i];
for(i = 0; i < (int)m_taps.size(); i++)
m_taps[i] /= sum;
}
Type filter(Type sample)
{
Type acc = 0;
unsigned int n_samples = m_samples.size();
unsigned int n_taps = m_taps.size() - 1;
unsigned int a = m_ptr;
unsigned int b = a == n_samples - 1 ? 0 : a + 1;
m_samples[m_ptr] = sample;
for (unsigned int i = 0; i < n_taps; ++i)
{
acc += (m_samples[a] + m_samples[b]) * m_taps[i];
a = (a == 0) ? n_samples - 1 : a - 1;
b = (b == n_samples - 1) ? 0 : b + 1;
}
acc += m_samples[a] * m_taps[n_taps];
m_ptr = (m_ptr == n_samples - 1) ? 0 : m_ptr + 1;
return acc;
}
/*
void printTaps()
{
for (int i = 0; i < m_taps.size(); i++)
printf("%.4f ", m_taps[i]);
printf("\n");
}
*/
private:
std::vector<Real> m_taps;
std::vector<Type> m_samples;
unsigned int m_ptr;
};
#endif // INCLUDE_GAUSSIAN_H

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,44 @@
///////////////////////////////////////////////////////////////////////////////////
// Copyright (C) 2012 maintech GmbH, Otto-Hahn-Str. 15, 97204 Hoechberg, Germany //
// written by Christian Daniel //
// Copyright (C) 2015-2021 Edouard Griffiths, F4EXB <f4exb06@gmail.com> //
// //
// This program 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 as version 3 of the License, or //
// (at your option) any later version. //
// //
// This program 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 V3 for more details. //
// //
// You should have received a copy of the GNU General Public License //
// along with this program. If not, see <http://www.gnu.org/licenses/>. //
///////////////////////////////////////////////////////////////////////////////////
#ifndef SDRBASE_DSP_GLSCOPEINTERFACE_H_
#define SDRBASE_DSP_GLSCOPEINTERFACE_H_
#include "glscopesettings.h"
class GLScopeInterface
{
public:
GLScopeInterface() {}
virtual ~GLScopeInterface() {}
virtual void setTraces(std::vector<GLScopeSettings::TraceData>* tracesData, std::vector<float *>* traces) = 0;
virtual void newTraces(std::vector<float *>* traces, int traceIndex, std::vector<Projector::ProjectionType>* projectionTypes) = 0;
virtual void setSampleRate(int sampleRate) = 0;
virtual void setTraceSize(int trceSize, bool emitSignal = false) = 0;
virtual void setTriggerPre(uint32_t triggerPre, bool emitSignal = false) = 0;
virtual const QAtomicInt& getProcessingTraceIndex() const = 0;
virtual void setTimeBase(int timeBase) = 0;
virtual void setTimeOfsProMill(int timeOfsProMill) = 0;
virtual void setFocusedTriggerData(GLScopeSettings::TriggerData& triggerData) = 0;
virtual void setFocusedTraceIndex(uint32_t traceIndex) = 0;
virtual void setConfigChanged() = 0;
virtual void updateDisplay() = 0;
};
#endif // SDRBASE_DSP_GLSCOPEINTERFACE_H_

View File

@@ -0,0 +1,498 @@
///////////////////////////////////////////////////////////////////////////////////
// Copyright (C) 2019-2021 Edouard Griffiths, F4EXB <f4exb06@gmail.com> //
// Copyright (C) 2023 Jon Beniston, M7RCE <jon@beniston.com> //
// //
// This program 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 as version 3 of the License, or //
// (at your option) any later version. //
// //
// This program 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 V3 for more details. //
// //
// You should have received a copy of the GNU General Public License //
// along with this program. If not, see <http://www.gnu.org/licenses/>. //
///////////////////////////////////////////////////////////////////////////////////
#include <QColor>
#include "util/simpleserializer.h"
#include "glscopesettings.h"
#include "SWGGLScope.h"
const double GLScopeSettings::AMPS[27] = {
2e-1, 1e-1, 5e-2,
2e-2, 1e-2, 5e-3,
2e-3, 1e-3, 5e-4,
2e-4, 1e-4, 5e-5,
2e-5, 1e-5, 5e-6,
2e-6, 1e-6, 5e-7,
2e-7, 1e-7, 5e-8,
2e-8, 1e-8, 5e-9,
2e-9, 1e-9, 5e-10,
};
GLScopeSettings::GLScopeSettings()
{
resetToDefaults();
}
GLScopeSettings::GLScopeSettings(const GLScopeSettings& t)
{
resetToDefaults();
m_tracesData = t.m_tracesData;
m_triggersData = t.m_triggersData;
m_displayMode = t.m_displayMode;
m_traceIntensity = t.m_traceIntensity;
m_gridIntensity = t.m_gridIntensity;
m_time = t.m_time;
m_timeOfs = t.m_timeOfs;
m_traceLenMult = t.m_traceLenMult;
m_trigPre = t.m_trigPre;
}
GLScopeSettings::~GLScopeSettings()
{}
void GLScopeSettings::resetToDefaults()
{
m_displayMode = DisplayX;
m_traceIntensity = 50;
m_gridIntensity = 10;
m_time = 1;
m_timeOfs = 0;
m_traceLenMult = 1;
m_trigPre = 0;
m_freerun = true;
}
QByteArray GLScopeSettings::serialize() const
{
SimpleSerializer s(1);
// first row
s.writeS32(1, (int) m_displayMode);
s.writeS32(2, m_traceIntensity);
s.writeS32(3, m_gridIntensity);
s.writeS32(4, m_time);
// s.writeS32(5, m_timeOfs);
s.writeS32(6, m_traceLenMult);
s.writeBool(7, m_freerun);
std::vector<TraceData>::const_iterator traceDataIt = m_tracesData.begin();
unsigned int i = 0;
for (; traceDataIt != m_tracesData.end(); ++traceDataIt, i++)
{
if (20 + 16*i > 200) {
break;
}
s.writeS32(20 + 16*i, (int) traceDataIt->m_projectionType);
s.writeFloat(21 + 16*i, traceDataIt->m_amp);
s.writeFloat(22 + 16*i, traceDataIt->m_ofs);
s.writeS32(24 + 16*i, traceDataIt->m_traceDelayCoarse);
s.writeS32(25 + 16*i, traceDataIt->m_traceDelayFine);
s.writeFloat(26 + 16*i, traceDataIt->m_traceColorR);
s.writeFloat(27 + 16*i, traceDataIt->m_traceColorG);
s.writeFloat(28 + 16*i, traceDataIt->m_traceColorB);
s.writeU32(29 + 16*i, traceDataIt->m_streamIndex);
}
s.writeU32(10, i);
s.writeU32(200, m_triggersData.size());
s.writeS32(201, m_trigPre);
std::vector<TriggerData>::const_iterator triggerDataIt = m_triggersData.begin();
i = 0;
for (; triggerDataIt != m_triggersData.end(); ++triggerDataIt, i++)
{
s.writeS32(210 + 16*i, (int) triggerDataIt->m_projectionType);
s.writeS32(211 + 16*i, triggerDataIt->m_triggerRepeat);
s.writeBool(212 + 16*i, triggerDataIt->m_triggerPositiveEdge);
s.writeBool(213 + 16*i, triggerDataIt->m_triggerBothEdges);
s.writeS32(214 + 16*i, triggerDataIt->m_triggerLevelCoarse);
s.writeS32(215 + 16*i, triggerDataIt->m_triggerLevelFine);
s.writeS32(216 + 16*i, triggerDataIt->m_triggerDelayCoarse);
s.writeS32(217 + 16*i, triggerDataIt->m_triggerDelayFine);
s.writeFloat(218 + 16*i, triggerDataIt->m_triggerColorR);
s.writeFloat(219 + 16*i, triggerDataIt->m_triggerColorG);
s.writeFloat(220 + 16*i, triggerDataIt->m_triggerColorB);
s.writeU32(221 + 16*i, triggerDataIt->m_triggerHoldoff);
s.writeU32(222 + 16*i, triggerDataIt->m_streamIndex);
}
return s.final();
}
bool GLScopeSettings::deserialize(const QByteArray& data)
{
SimpleDeserializer d(data);
if(!d.isValid()) {
resetToDefaults();
return false;
}
if (d.getVersion() == 1)
{
int intValue;
uint32_t uintValue;
bool boolValue;
d.readS32(1, &intValue, (int) DisplayX);
m_displayMode = (DisplayMode) intValue;
d.readS32(2, &m_traceIntensity, 50);
d.readS32(3, &m_gridIntensity, 10);
d.readS32(4, &m_time, 1);
// d.readS32(5, &m_timeOfs, 0);
d.readS32(6, &m_traceLenMult, 1);
d.readBool(7, &m_freerun, true);
d.readS32(201, &m_trigPre, 0);
uint32_t nbTracesSaved;
d.readU32(10, &nbTracesSaved, 1);
m_tracesData.clear();
float r, g, b;
for (unsigned int iTrace = 0; iTrace < nbTracesSaved; iTrace++)
{
if (20 + 16*iTrace > 200) {
break;
}
m_tracesData.push_back(TraceData());
d.readS32(20 + 16*iTrace, &intValue, 0);
m_tracesData.back().m_projectionType = (Projector::ProjectionType) intValue;
d.readFloat(21 + 16*iTrace, &m_tracesData.back().m_amp, 1.0f);
d.readFloat(22 + 16*iTrace, &m_tracesData.back().m_ofs, 0.0f);
d.readS32(24 + 16*iTrace, &intValue, 0);
m_tracesData.back().m_traceDelayCoarse = intValue;
d.readS32(25 + 16*iTrace, &intValue, 0);
m_tracesData.back().m_traceDelayFine = intValue;
d.readFloat(26 + 16*iTrace, &r, 1.0f);
d.readFloat(27 + 16*iTrace, &g, 1.0f);
d.readFloat(28 + 16*iTrace, &b, 1.0f);
m_tracesData.back().m_traceColorR = r;
m_tracesData.back().m_traceColorG = g;
m_tracesData.back().m_traceColorB = b;
m_tracesData.back().m_traceColor.setRedF(r);
m_tracesData.back().m_traceColor.setGreenF(g);
m_tracesData.back().m_traceColor.setBlueF(b);
d.readU32(29 + 16*iTrace, &uintValue, 0);
m_tracesData.back().m_streamIndex = uintValue;
}
uint32_t nbTriggersSaved;
d.readU32(200, &nbTriggersSaved, 1);
m_triggersData.clear();
for (unsigned int iTrigger = 0; iTrigger < nbTriggersSaved; iTrigger++)
{
m_triggersData.push_back(TriggerData());
d.readS32(210 + 16*iTrigger, &intValue, 0);
m_triggersData.back().m_projectionType = (Projector::ProjectionType) intValue;
d.readS32(211 + 16*iTrigger, &intValue, 1);
m_triggersData.back().m_triggerRepeat = intValue;
d.readBool(212 + 16*iTrigger, &boolValue, true);
m_triggersData.back().m_triggerPositiveEdge = boolValue;
d.readBool(213 + 16*iTrigger, &boolValue, false);
m_triggersData.back().m_triggerBothEdges = boolValue;
d.readS32(214 + 16*iTrigger, &intValue, 1);
m_triggersData.back().m_triggerLevelCoarse = intValue;
d.readS32(215 + 16*iTrigger, &intValue, 1);
m_triggersData.back().m_triggerLevelFine = intValue;
d.readS32(216 + 16*iTrigger, &intValue, 1);
m_triggersData.back().m_triggerDelayCoarse = intValue;
d.readS32(217 + 16*iTrigger, &intValue, 1);
m_triggersData.back().m_triggerDelayFine = intValue;
d.readFloat(218 + 16*iTrigger, &r, 1.0f);
d.readFloat(219 + 16*iTrigger, &g, 1.0f);
d.readFloat(220 + 16*iTrigger, &b, 1.0f);
m_triggersData.back().m_triggerColorR = r;
m_triggersData.back().m_triggerColorG = g;
m_triggersData.back().m_triggerColorB = b;
m_triggersData.back().m_triggerColor.setRedF(r);
m_triggersData.back().m_triggerColor.setGreenF(g);
m_triggersData.back().m_triggerColor.setBlueF(b);
d.readU32(221 + 16*iTrigger, &uintValue, 1);
m_triggersData.back().m_triggerHoldoff = uintValue;
d.readU32(222 + 16*iTrigger, &uintValue, 0);
m_triggersData.back().m_streamIndex = uintValue;
}
return true;
}
else
{
resetToDefaults();
return false;
}
}
void GLScopeSettings::formatTo(SWGSDRangel::SWGObject *swgObject) const
{
SWGSDRangel::SWGGLScope *swgScope = static_cast<SWGSDRangel::SWGGLScope *>(swgObject);
swgScope->setDisplayMode(m_displayMode);
swgScope->setGridIntensity(m_gridIntensity);
swgScope->setTime(m_time);
swgScope->setTimeOfs(m_timeOfs);
swgScope->setTraceIntensity(m_traceIntensity);
swgScope->setTraceLenMult(m_traceLenMult);
swgScope->setTrigPre(m_trigPre);
// array of traces
swgScope->setTracesData(new QList<SWGSDRangel::SWGTraceData *>);
std::vector<GLScopeSettings::TraceData>::const_iterator traceIt = m_tracesData.begin();
for (; traceIt != m_tracesData.end(); ++traceIt)
{
swgScope->getTracesData()->append(new SWGSDRangel::SWGTraceData);
swgScope->getTracesData()->back()->setStreamIndex(traceIt->m_streamIndex);
swgScope->getTracesData()->back()->setAmp(traceIt->m_amp);
swgScope->getTracesData()->back()->setHasTextOverlay(traceIt->m_hasTextOverlay ? 1 : 0);
swgScope->getTracesData()->back()->setStreamIndex(traceIt->m_streamIndex);
swgScope->getTracesData()->back()->setOfs(traceIt->m_ofs);
swgScope->getTracesData()->back()->setProjectionType((int) traceIt->m_projectionType);
swgScope->getTracesData()->back()->setTextOverlay(new QString(traceIt->m_textOverlay));
swgScope->getTracesData()->back()->setTraceColor(qColorToInt(traceIt->m_traceColor));
swgScope->getTracesData()->back()->setTraceColorB(traceIt->m_traceColorB);
swgScope->getTracesData()->back()->setTraceColorG(traceIt->m_traceColorG);
swgScope->getTracesData()->back()->setTraceColorR(traceIt->m_traceColorR);
swgScope->getTracesData()->back()->setTraceDelay(traceIt->m_traceDelay);
swgScope->getTracesData()->back()->setTraceDelayCoarse(traceIt->m_traceDelayCoarse);
swgScope->getTracesData()->back()->setTraceDelayFine(traceIt->m_traceDelayFine);
swgScope->getTracesData()->back()->setTriggerDisplayLevel(traceIt->m_triggerDisplayLevel);
swgScope->getTracesData()->back()->setViewTrace(traceIt->m_viewTrace ? 1 : 0);
}
// array of triggers
swgScope->setTriggersData(new QList<SWGSDRangel::SWGTriggerData *>);
std::vector<GLScopeSettings::TriggerData>::const_iterator triggerIt = m_triggersData.begin();
for (; triggerIt != m_triggersData.end(); ++triggerIt)
{
swgScope->getTriggersData()->append(new SWGSDRangel::SWGTriggerData);
swgScope->getTriggersData()->back()->setStreamIndex(triggerIt->m_streamIndex);
swgScope->getTriggersData()->back()->setInputIndex(triggerIt->m_inputIndex);
swgScope->getTriggersData()->back()->setProjectionType((int) triggerIt->m_projectionType);
swgScope->getTriggersData()->back()->setTriggerBothEdges(triggerIt->m_triggerBothEdges ? 1 : 0);
swgScope->getTriggersData()->back()->setTriggerColor(qColorToInt(triggerIt->m_triggerColor));
swgScope->getTriggersData()->back()->setTriggerColorB(triggerIt->m_triggerColorB);
swgScope->getTriggersData()->back()->setTriggerColorG(triggerIt->m_triggerColorG);
swgScope->getTriggersData()->back()->setTriggerColorR(triggerIt->m_triggerColorR);
swgScope->getTriggersData()->back()->setTriggerDelay(triggerIt->m_triggerDelay);
swgScope->getTriggersData()->back()->setTriggerDelayCoarse(triggerIt->m_triggerDelayCoarse);
swgScope->getTriggersData()->back()->setTriggerDelayFine(triggerIt->m_triggerDelayFine);
swgScope->getTriggersData()->back()->setTriggerDelayMult(triggerIt->m_triggerDelayMult);
swgScope->getTriggersData()->back()->setTriggerHoldoff(triggerIt->m_triggerHoldoff ? 1 : 0);
swgScope->getTriggersData()->back()->setTriggerLevel(triggerIt->m_triggerLevel);
swgScope->getTriggersData()->back()->setTriggerLevelCoarse(triggerIt->m_triggerLevelCoarse);
swgScope->getTriggersData()->back()->setTriggerLevelFine(triggerIt->m_triggerLevelFine);
swgScope->getTriggersData()->back()->setTriggerPositiveEdge(triggerIt->m_triggerPositiveEdge ? 1 : 0);
swgScope->getTriggersData()->back()->setTriggerRepeat(triggerIt->m_triggerRepeat);
}
}
void GLScopeSettings::updateFrom(const QStringList& keys, const SWGSDRangel::SWGObject *swgObject)
{
SWGSDRangel::SWGGLScope *swgScope =
static_cast<SWGSDRangel::SWGGLScope *>(const_cast<SWGSDRangel::SWGObject *>(swgObject));
if (keys.contains("scopeConfig.displayMode")) {
m_displayMode = (GLScopeSettings::DisplayMode) swgScope->getDisplayMode();
}
if (keys.contains("scopeConfig.gridIntensity")) {
m_gridIntensity = swgScope->getGridIntensity();
}
if (keys.contains("scopeConfig.time")) {
m_time = swgScope->getTime();
}
if (keys.contains("scopeConfig.timeOfs")) {
m_timeOfs = swgScope->getTimeOfs();
}
if (keys.contains("scopeConfig.traceIntensity")) {
m_traceIntensity = swgScope->getTraceIntensity();
}
if (keys.contains("scopeConfig.traceLenMult")) {
m_traceLenMult = swgScope->getTraceLenMult();
}
if (keys.contains("scopeConfig.trigPre")) {
m_trigPre = swgScope->getTrigPre();
}
// traces
if (keys.contains("scopeConfig.tracesData"))
{
QList<SWGSDRangel::SWGTraceData *> *tracesData = swgScope->getTracesData();
m_tracesData.clear();
for (int i = 0; i < 10; i++) // no more than 10 traces anyway
{
if (keys.contains(QString("scopeConfig.tracesData[%1]").arg(i)))
{
SWGSDRangel::SWGTraceData *traceData = tracesData->at(i);
m_tracesData.push_back(GLScopeSettings::TraceData());
if (keys.contains(QString("scopeConfig.tracesData[%1].streamIndex").arg(i))) {
m_tracesData.back().m_streamIndex = traceData->getStreamIndex();
}
if (keys.contains(QString("scopeConfig.tracesData[%1].amp").arg(i))) {
m_tracesData.back().m_amp = traceData->getAmp();
}
if (keys.contains(QString("scopeConfig.tracesData[%1].hasTextOverlay").arg(i))) {
m_tracesData.back().m_hasTextOverlay = traceData->getHasTextOverlay() != 0;
}
if (keys.contains(QString("scopeConfig.tracesData[%1].inputIndex").arg(i))) {
m_tracesData.back().m_streamIndex = traceData->getStreamIndex();
}
if (keys.contains(QString("scopeConfig.tracesData[%1].ofs").arg(i))) {
m_tracesData.back().m_ofs = traceData->getOfs();
}
if (keys.contains(QString("scopeConfig.tracesData[%1].projectionType").arg(i))) {
m_tracesData.back().m_projectionType = (Projector::ProjectionType) traceData->getProjectionType();
}
if (keys.contains(QString("scopeConfig.tracesData[%1].traceColor").arg(i))) {
m_tracesData.back().m_traceColor = intToQColor(traceData->getTraceColor());
}
if (keys.contains(QString("scopeConfig.tracesData[%1].traceColorB").arg(i))) {
m_tracesData.back().m_traceColorB = traceData->getTraceColorB();
}
if (keys.contains(QString("scopeConfig.tracesData[%1].traceColorG").arg(i))) {
m_tracesData.back().m_traceColorG = traceData->getTraceColorG();
}
if (keys.contains(QString("scopeConfig.tracesData[%1].traceColorR").arg(i))) {
m_tracesData.back().m_traceColorR = traceData->getTraceColorR();
}
if (keys.contains(QString("scopeConfig.tracesData[%1].traceDelay").arg(i))) {
m_tracesData.back().m_traceDelay = traceData->getTraceDelay();
}
if (keys.contains(QString("scopeConfig.tracesData[%1].traceDelayCoarse").arg(i))) {
m_tracesData.back().m_traceDelayCoarse = traceData->getTraceDelayCoarse();
}
if (keys.contains(QString("scopeConfig.tracesData[%1].traceDelayFine").arg(i))) {
m_tracesData.back().m_traceDelayFine = traceData->getTraceDelayFine();
}
if (keys.contains(QString("scopeConfig.tracesData[%1].triggerDisplayLevel").arg(i))) {
m_tracesData.back().m_triggerDisplayLevel = traceData->getTriggerDisplayLevel();
}
if (keys.contains(QString("scopeConfig.tracesData[%1].viewTrace").arg(i))) {
m_tracesData.back().m_viewTrace = traceData->getViewTrace() != 0;
}
}
else
{
break;
}
}
}
// triggers
if (keys.contains("scopeConfig.triggersData"))
{
QList<SWGSDRangel::SWGTriggerData *> *triggersData = swgScope->getTriggersData();
m_triggersData.clear();
for (int i = 0; i < 10; i++) // no more than 10 triggers anyway
{
if (keys.contains(QString("scopeConfig.triggersData[%1]").arg(i)))
{
SWGSDRangel::SWGTriggerData *triggerData = triggersData->at(i);
m_triggersData.push_back(GLScopeSettings::TriggerData());
if (keys.contains(QString("scopeConfig.triggersData[%1].streamIndex").arg(i))) {
m_triggersData.back().m_streamIndex = triggerData->getStreamIndex();
}
if (keys.contains(QString("scopeConfig.triggersData[%1].inputIndex").arg(i))) {
m_triggersData.back().m_inputIndex = triggerData->getInputIndex();
}
if (keys.contains(QString("scopeConfig.triggersData[%1].projectionType").arg(i))) {
m_triggersData.back().m_projectionType = (Projector::ProjectionType) triggerData->getProjectionType();
}
if (keys.contains(QString("scopeConfig.triggersData[%1].triggerBothEdges").arg(i))) {
m_triggersData.back().m_triggerBothEdges = triggerData->getTriggerBothEdges() != 0;
}
if (keys.contains(QString("scopeConfig.tracesData[%1].triggerColor").arg(i))) {
m_tracesData.back().m_traceColor = intToQColor(triggerData->getTriggerColor());
}
if (keys.contains(QString("scopeConfig.triggersData[%1].triggerColorB").arg(i))) {
m_triggersData.back().m_triggerColorB = triggerData->getTriggerColorB();
}
if (keys.contains(QString("scopeConfig.triggersData[%1].triggerColorG").arg(i))) {
m_triggersData.back().m_triggerColorG = triggerData->getTriggerColorG();
}
if (keys.contains(QString("scopeConfig.triggersData[%1].triggerColorR").arg(i))) {
m_triggersData.back().m_triggerColorR = triggerData->getTriggerColorR();
}
if (keys.contains(QString("scopeConfig.triggersData[%1].triggerDelay").arg(i))) {
m_triggersData.back().m_triggerDelay = triggerData->getTriggerDelay();
}
if (keys.contains(QString("scopeConfig.triggersData[%1].triggerDelayCoarse").arg(i))) {
m_triggersData.back().m_triggerDelayCoarse = triggerData->getTriggerDelayCoarse();
}
if (keys.contains(QString("scopeConfig.triggersData[%1].triggerDelayFine").arg(i))) {
m_triggersData.back().m_triggerDelayFine = triggerData->getTriggerDelayFine();
}
if (keys.contains(QString("scopeConfig.triggersData[%1].triggerDelayMult").arg(i))) {
m_triggersData.back().m_triggerDelayMult = triggerData->getTriggerDelayMult();
}
if (keys.contains(QString("scopeConfig.triggersData[%1].triggerHoldoff").arg(i))) {
m_triggersData.back().m_triggerHoldoff = triggerData->getTriggerHoldoff();
}
if (keys.contains(QString("scopeConfig.triggersData[%1].triggerLevel").arg(i))) {
m_triggersData.back().m_triggerLevel = triggerData->getTriggerLevel();
}
if (keys.contains(QString("scopeConfig.triggersData[%1].triggerLevelCoarse").arg(i))) {
m_triggersData.back().m_triggerLevelCoarse = triggerData->getTriggerLevelCoarse();
}
if (keys.contains(QString("scopeConfig.triggersData[%1].triggerLevelFine").arg(i))) {
m_triggersData.back().m_triggerLevelFine = triggerData->getTriggerLevelFine();
}
if (keys.contains(QString("scopeConfig.triggersData[%1].triggerPositiveEdge").arg(i))) {
m_triggersData.back().m_triggerPositiveEdge = triggerData->getTriggerPositiveEdge() != 0;
}
if (keys.contains(QString("scopeConfig.triggersData[%1].triggerRepeat").arg(i))) {
m_triggersData.back().m_triggerRepeat = triggerData->getTriggerRepeat() != 0;
}
}
}
}
}
GLScopeSettings& GLScopeSettings::operator=(const GLScopeSettings& t)
{
// Check for self assignment
if (this != &t)
{
m_tracesData = t.m_tracesData;
m_triggersData = t.m_triggersData;
m_displayMode = t.m_displayMode;
m_traceIntensity = t.m_traceIntensity;
m_gridIntensity = t.m_gridIntensity;
m_time = t.m_time;
m_timeOfs = t.m_timeOfs;
m_traceLenMult = t.m_traceLenMult;
m_trigPre = t.m_trigPre;
}
return *this;
}
int GLScopeSettings::qColorToInt(const QColor& color)
{
return 256*256*color.blue() + 256*color.green() + color.red();
}
QColor GLScopeSettings::intToQColor(int intColor)
{
int r = intColor % 256;
int bg = intColor / 256;
int g = bg % 256;
int b = bg / 256;
return QColor(r, g, b);
}

View File

@@ -0,0 +1,191 @@
///////////////////////////////////////////////////////////////////////////////////
// Copyright (C) 2019, 2021 Edouard Griffiths, F4EXB <f4exb06@gmail.com> //
// Copyright (C) 2022-2023 Jon Beniston, M7RCE <jon@beniston.com> //
// //
// This program 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 as version 3 of the License, or //
// (at your option) any later version. //
// //
// This program 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 V3 for more details. //
// //
// You should have received a copy of the GNU General Public License //
// along with this program. If not, see <http://www.gnu.org/licenses/>. //
///////////////////////////////////////////////////////////////////////////////////
#ifndef SDRBASE_DSP_GLSCOPESETTINGS_H
#define SDRBASE_DSP_GLSCOPESETTINGS_H
#include <vector>
#include <QByteArray>
#include <QColor>
#include "export.h"
#include "dsp/dsptypes.h"
#include "dsp/projector.h"
#include "settings/serializable.h"
class SDRBASE_API GLScopeSettings : public Serializable
{
public:
enum DisplayMode // TODO: copy of GLScope::DisplayMode => unify
{
DisplayXYH,
DisplayXYV,
DisplayX,
DisplayY,
DisplayPol
};
struct TraceData // TODO: copy of ScopeVis::TraceData => unify
{
uint32_t m_streamIndex; //!< I/Q stream index
Projector::ProjectionType m_projectionType; //!< Complex to real projection type
float m_amp; //!< Amplification factor
float m_ofs; //!< Offset factor
int m_traceDelay; //!< Trace delay in number of samples
int m_traceDelayCoarse; //!< Coarse delay slider value
int m_traceDelayFine; //!< Fine delay slider value
float m_triggerDisplayLevel; //!< Displayable trigger display level in -1:+1 scale. Off scale if not displayable.
QColor m_traceColor; //!< Trace display color
float m_traceColorR; //!< Trace display color - red shortcut
float m_traceColorG; //!< Trace display color - green shortcut
float m_traceColorB; //!< Trace display color - blue shortcut
bool m_hasTextOverlay; //!< True if a text overlay has to be displayed
QString m_textOverlay; //!< Text overlay to display
bool m_viewTrace; //!< Trace visibility
TraceData()
{
resetToDefaults();
}
void setColor(QColor color)
{
m_traceColor = color;
#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
float r,g,b,a;
#else
qreal r,g,b,a;
#endif
m_traceColor.getRgbF(&r, &g, &b, &a);
m_traceColorR = r;
m_traceColorG = g;
m_traceColorB = b;
}
void resetToDefaults()
{
m_streamIndex = 0;
m_projectionType = Projector::ProjectionReal;
m_amp = 1.0f;
m_ofs = 0.0f;
m_traceDelay = 0;
m_traceDelayCoarse = 0;
m_traceDelayFine = 0;
m_triggerDisplayLevel = 2.0; // Over scale by default (2.0)
m_traceColor = QColor(255,255,64);
m_hasTextOverlay = false;
m_viewTrace = true;
setColor(m_traceColor);
}
};
struct TriggerData // TODO: copy of ScopeVis::TriggerData => unify
{
uint32_t m_streamIndex; //!< I/Q stream index
Projector::ProjectionType m_projectionType; //!< Complex to real projection type
uint32_t m_inputIndex; //!< Input or feed index this trigger is associated with
Real m_triggerLevel; //!< Level in real units
int m_triggerLevelCoarse;
int m_triggerLevelFine;
bool m_triggerPositiveEdge; //!< Trigger on the positive edge (else negative)
bool m_triggerBothEdges; //!< Trigger on both edges (else only one)
uint32_t m_triggerHoldoff; //!< Trigger holdoff in number of samples
uint32_t m_triggerDelay; //!< Delay before the trigger is kicked off in number of samples (trigger delay)
double m_triggerDelayMult; //!< Trigger delay as a multiplier of trace length
int m_triggerDelayCoarse;
int m_triggerDelayFine;
uint32_t m_triggerRepeat; //!< Number of trigger conditions before the final decisive trigger
QColor m_triggerColor; //!< Trigger line display color
float m_triggerColorR; //!< Trigger line display color - red shortcut
float m_triggerColorG; //!< Trigger line display color - green shortcut
float m_triggerColorB; //!< Trigger line display color - blue shortcut
TriggerData()
{
resetToDefaults();
}
void setColor(QColor color)
{
m_triggerColor = color;
#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
float r,g,b,a;
#else
qreal r,g,b,a;
#endif
m_triggerColor.getRgbF(&r, &g, &b, &a);
m_triggerColorR = r;
m_triggerColorG = g;
m_triggerColorB = b;
}
void resetToDefaults()
{
m_streamIndex = 0;
m_projectionType = Projector::ProjectionReal;
m_inputIndex = 0;
m_triggerLevel = 0.0f;
m_triggerLevelCoarse = 0;
m_triggerLevelFine = 0;
m_triggerPositiveEdge = true;
m_triggerBothEdges = false;
m_triggerHoldoff = 1;
m_triggerDelay = 0;
m_triggerDelayMult = 0.0;
m_triggerDelayCoarse = 0;
m_triggerDelayFine = 0;
m_triggerRepeat = 0;
m_triggerColor = QColor(0,255,0);
setColor(m_triggerColor);
}
};
DisplayMode m_displayMode;
int m_traceIntensity;
int m_gridIntensity;
int m_time;
int m_timeOfs;
int m_traceLenMult;
int m_trigPre;
bool m_freerun;
std::vector<TraceData> m_tracesData;
std::vector<TriggerData> m_triggersData;
static const double AMPS[27];
static const uint32_t m_traceChunkDefaultSize = 4800;
static const uint32_t m_maxNbTriggers = 10;
static const uint32_t m_maxNbTraces = 10;
static const uint32_t m_nbTraceMemories = 50;
static const uint32_t m_nbTraceBuffers = 2;
GLScopeSettings();
GLScopeSettings(const GLScopeSettings& t);
virtual ~GLScopeSettings();
void resetToDefaults();
virtual QByteArray serialize() const;
virtual bool deserialize(const QByteArray& data);
virtual void formatTo(SWGSDRangel::SWGObject *swgObject) const;
virtual void updateFrom(const QStringList& keys, const SWGSDRangel::SWGObject *swgObject);
GLScopeSettings& operator=(const GLScopeSettings& t);
static int qColorToInt(const QColor& color);
static QColor intToQColor(int intColor);
};
#endif // SDRBASE_DSP_GLSCOPESETTINGS_H

View File

@@ -0,0 +1,39 @@
///////////////////////////////////////////////////////////////////////////////////
// Copyright (C) 2012 maintech GmbH, Otto-Hahn-Str. 15, 97204 Hoechberg, Germany //
// written by Christian Daniel //
// Copyright (C) 2015-2021 Edouard Griffiths, F4EXB <f4exb06@gmail.com> //
// //
// This program 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 as version 3 of the License, or //
// (at your option) any later version. //
// //
// This program 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 V3 for more details. //
// //
// You should have received a copy of the GNU General Public License //
// along with this program. If not, see <http://www.gnu.org/licenses/>. //
///////////////////////////////////////////////////////////////////////////////////
#ifndef SDRBASE_DSP_GLSPECTRUMINTERFACE_H_
#define SDRBASE_DSP_GLSPECTRUMINTERFACE_H_
#include <vector>
#include "dsp/dsptypes.h"
class GLSpectrumInterface
{
public:
GLSpectrumInterface() {}
virtual ~GLSpectrumInterface() {}
virtual void newSpectrum(const Real* spectrum, int nbBins, int fftSize)
{
(void) spectrum;
(void) nbBins;
(void) fftSize;
}
};
#endif // SDRBASE_DSP_GLSPECTRUMINTERFACE_H_

Some files were not shown because too many files have changed in this diff Show More