FT8 demod: test with .wav files

This commit is contained in:
f4exb 2023-01-18 23:00:59 +01:00
parent d6cafa08c5
commit 0d77b37ec1
21 changed files with 311 additions and 133 deletions

View File

@ -7,6 +7,8 @@ set(demodft8_SOURCES
ft8demodbaseband.cpp
ft8demodwebapiadapter.cpp
ft8plugin.cpp
ft8buffer.cpp
ft8demodworker.cpp
)
set(demodft8_HEADERS
@ -16,6 +18,8 @@ set(demodft8_HEADERS
ft8demodbaseband.h
ft8demodwebapiadapter.h
ft8plugin.h
ft8buffer.h
ft8demodworker.h;
)
include_directories(

View File

@ -0,0 +1,51 @@
///////////////////////////////////////////////////////////////////////////////////
// Copyright (C) 2023 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/>. //
///////////////////////////////////////////////////////////////////////////////////
#include <QMutexLocker>
#include "ft8demodsettings.h"
#include "ft8buffer.h"
FT8Buffer::FT8Buffer() :
m_bufferSize(FT8DemodSettings::m_ft8SampleRate*15),
m_sampleIndex(0)
{
m_buffer = new int16_t[2*m_bufferSize];
}
FT8Buffer::~FT8Buffer()
{
delete[] m_buffer;
}
void FT8Buffer::feed(int16_t sample)
{
QMutexLocker mlock(&m_mutex);
m_buffer[m_sampleIndex] = sample;
m_buffer[m_sampleIndex + m_bufferSize] = sample;
m_sampleIndex++;
if (m_sampleIndex == m_bufferSize) {
m_sampleIndex = 0;
}
}
void FT8Buffer::getCurrentBuffer(int16_t *bufferCopy)
{
QMutexLocker mlock(&m_mutex);
std::copy(&m_buffer[m_sampleIndex], &m_buffer[m_sampleIndex + m_bufferSize], bufferCopy);
}

View File

@ -0,0 +1,41 @@
///////////////////////////////////////////////////////////////////////////////////
// Copyright (C) 2023 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_FT8DEMOD_FT8BUFFER_H
#define INCLUDE_FT8DEMOD_FT8BUFFER_H
#include <QObject>
#include <QMutex>
class FT8Buffer : public QObject
{
Q_OBJECT
public:
FT8Buffer();
~FT8Buffer();
void feed(int16_t sample);
void getCurrentBuffer(int16_t *bufferCopy);
private:
int16_t *m_buffer;
int m_bufferSize;
int m_sampleIndex;
QMutex m_mutex;
};
#endif // INCLUDE_FT8DEMOD_FT8BUFFER_H

View File

@ -242,7 +242,6 @@ void FT8Demod::applySettings(const FT8DemodSettings& settings, bool force)
<< " m_fftWindow: " << settings.m_filterBank[settings.m_filterIndex].m_fftWindow << "]"
<< " m_volume: " << settings.m_volume
<< " m_agcActive: " << settings.m_agc
<< " m_ft8SampleRate: " << settings.m_ft8SampleRate
<< " m_streamIndex: " << settings.m_streamIndex
<< " m_useReverseAPI: " << settings.m_useReverseAPI
<< " m_reverseAPIAddress: " << settings.m_reverseAPIAddress
@ -274,9 +273,6 @@ void FT8Demod::applySettings(const FT8DemodSettings& settings, bool force)
if ((m_settings.m_volume != settings.m_volume) || force) {
reverseAPIKeys.append("volume");
}
if ((settings.m_ft8SampleRate != m_settings.m_ft8SampleRate) || force) {
reverseAPIKeys.append("ft8SampleRate");
}
if ((m_settings.m_agc != settings.m_agc) || force) {
reverseAPIKeys.append("agc");
}
@ -366,7 +362,7 @@ void FT8Demod::sendSampleRateToDemodAnalyzer()
{
MainCore::MsgChannelDemodReport *msg = MainCore::MsgChannelDemodReport::create(
this,
m_settings.m_ft8SampleRate
FT8DemodSettings::m_ft8SampleRate
);
messageQueue->push(msg);
}
@ -454,9 +450,6 @@ void FT8Demod::webapiUpdateChannelSettings(
if (channelSettingsKeys.contains("title")) {
settings.m_title = *response.getFt8DemodSettings()->getTitle();
}
if (channelSettingsKeys.contains("audioDeviceName")) {
settings.m_ft8SampleRate = response.getFt8DemodSettings()->getFt8SampleRate();
}
if (channelSettingsKeys.contains("streamIndex")) {
settings.m_streamIndex = response.getFt8DemodSettings()->getStreamIndex();
}
@ -508,7 +501,6 @@ void FT8Demod::webapiFormatChannelSettings(SWGSDRangel::SWGChannelSettings& resp
response.getFt8DemodSettings()->setVolume(settings.m_volume);
response.getFt8DemodSettings()->setAgc(settings.m_agc ? 1 : 0);
response.getFt8DemodSettings()->setRgbColor(settings.m_rgbColor);
response.getFt8DemodSettings()->setFt8SampleRate(settings.m_ft8SampleRate);
if (response.getFt8DemodSettings()->getTitle()) {
*response.getFt8DemodSettings()->getTitle() = settings.m_title;
@ -685,9 +677,6 @@ void FT8Demod::webapiFormatChannelSettings(
if (channelSettingsKeys.contains("title") || force) {
swgFT8DemodSettings->setTitle(new QString(settings.m_title));
}
if (channelSettingsKeys.contains("audioDeviceName") || force) {
swgFT8DemodSettings->setFt8SampleRate(settings.m_ft8SampleRate);
}
if (channelSettingsKeys.contains("streamIndex") || force) {
swgFT8DemodSettings->setStreamIndex(settings.m_streamIndex);
}
@ -746,5 +735,4 @@ void FT8Demod::handleIndexInDeviceSetChanged(int index)
.arg(m_deviceAPI->getDeviceSetIndex())
.arg(index);
m_basebandSink->setFifoLabel(fifoLabel);
m_basebandSink->setAudioFifoLabel(fifoLabel);
}

View File

@ -16,11 +16,14 @@
///////////////////////////////////////////////////////////////////////////////////
#include <QDebug>
#include <QThread>
#include "dsp/dspengine.h"
#include "dsp/dspcommands.h"
#include "dsp/spectrumvis.h"
#include "maincore.h"
#include "ft8demodworker.h"
#include "ft8demodbaseband.h"
MESSAGE_CLASS_DEFINITION(FT8DemodBaseband::MsgConfigureFT8DemodBaseband, Message)
@ -30,9 +33,36 @@ FT8DemodBaseband::FT8DemodBaseband() :
m_messageQueueToGUI(nullptr),
m_spectrumVis(nullptr)
{
m_sampleFifo.setSize(SampleSinkFifo::getSizePolicy(48000));
qDebug("FT8DemodBaseband::FT8DemodBaseband");
m_sampleFifo.setSize(SampleSinkFifo::getSizePolicy(48000));
m_ft8WorkerBuffer = new int16_t[FT8DemodSettings::m_ft8SampleRate*15];
m_workerThread = new QThread();
m_ft8DemodWorker = new FT8DemodWorker();
m_ft8DemodWorker->moveToThread(m_workerThread);
QObject::connect(
m_workerThread,
&QThread::finished,
m_ft8DemodWorker,
&QObject::deleteLater
);
QObject::connect(
m_workerThread,
&QThread::finished,
m_ft8DemodWorker,
&QThread::deleteLater
);
QObject::connect(
this,
&FT8DemodBaseband::bufferReady,
m_ft8DemodWorker,
&FT8DemodWorker::processBuffer
);
m_workerThread->start();
QObject::connect(
&m_sampleFifo,
&SampleSinkFifo::dataReady,
@ -41,15 +71,18 @@ FT8DemodBaseband::FT8DemodBaseband() :
Qt::QueuedConnection
);
DSPEngine::instance()->getAudioDeviceManager()->addAudioSink(m_sink.getAudioFifo(), getInputMessageQueue());
m_channelSampleRate = 0;
m_sink.setFT8Buffer(&m_ft8Buffer);
connect(&m_inputMessageQueue, SIGNAL(messageEnqueued()), this, SLOT(handleInputMessages()));
connect(&MainCore::instance()->getMasterTimer(), SIGNAL(timeout()), this, SLOT(tick()));
}
FT8DemodBaseband::~FT8DemodBaseband()
{
DSPEngine::instance()->getAudioDeviceManager()->removeAudioSink(m_sink.getAudioFifo());
m_workerThread->exit();
m_workerThread->wait();
delete[] m_ft8WorkerBuffer;
}
void FT8DemodBaseband::reset()
@ -136,7 +169,7 @@ bool FT8DemodBaseband::handleMessage(const Message& cmd)
if (m_channelSampleRate != m_channelizer.getChannelSampleRate())
{
m_sink.applyFT8SampleRate(m_settings.m_ft8SampleRate); // reapply when channel sample rate changes
m_sink.applyFT8SampleRate(); // reapply when channel sample rate changes
m_channelSampleRate = m_channelizer.getChannelSampleRate();
}
@ -152,12 +185,12 @@ void FT8DemodBaseband::applySettings(const FT8DemodSettings& settings, bool forc
{
if ((settings.m_inputFrequencyOffset != m_settings.m_inputFrequencyOffset) || force)
{
m_channelizer.setChannelization(m_settings.m_ft8SampleRate, settings.m_inputFrequencyOffset);
m_channelizer.setChannelization(FT8DemodSettings::m_ft8SampleRate, settings.m_inputFrequencyOffset);
m_sink.applyChannelSettings(m_channelizer.getChannelSampleRate(), m_channelizer.getChannelFrequencyOffset());
if (m_channelSampleRate != m_channelizer.getChannelSampleRate())
{
m_sink.applyFT8SampleRate(m_settings.m_ft8SampleRate); // reapply when channel sample rate changes
m_sink.applyFT8SampleRate(); // reapply when channel sample rate changes
m_channelSampleRate = m_channelizer.getChannelSampleRate();
}
}
@ -166,32 +199,12 @@ void FT8DemodBaseband::applySettings(const FT8DemodSettings& settings, bool forc
{
if (m_spectrumVis)
{
DSPSignalNotification *msg = new DSPSignalNotification(m_settings.m_ft8SampleRate/(1<<settings.m_filterBank[settings.m_filterIndex].m_spanLog2), 0);
m_spectrumVis->getInputMessageQueue()->push(msg);
}
}
if ((settings.m_ft8SampleRate != m_settings.m_ft8SampleRate) || force)
{
m_sink.applyFT8SampleRate(settings.m_ft8SampleRate);
m_channelizer.setChannelization(settings.m_ft8SampleRate, settings.m_inputFrequencyOffset);
m_sink.applyChannelSettings(m_channelizer.getChannelSampleRate(), m_channelizer.getChannelFrequencyOffset());
if (getMessageQueueToGUI())
{
DSPConfigureAudio *msg = new DSPConfigureAudio((int) settings.m_ft8SampleRate, DSPConfigureAudio::AudioOutput);
getMessageQueueToGUI()->push(msg);
}
if (m_spectrumVis)
{
DSPSignalNotification *msg = new DSPSignalNotification(settings.m_ft8SampleRate/(1<<m_settings.m_filterBank[settings.m_filterIndex].m_spanLog2), 0);
DSPSignalNotification *msg = new DSPSignalNotification(FT8DemodSettings::m_ft8SampleRate/(1<<settings.m_filterBank[settings.m_filterIndex].m_spanLog2), 0);
m_spectrumVis->getInputMessageQueue()->push(msg);
}
}
m_sink.applySettings(settings, force);
m_settings = settings;
}
@ -206,3 +219,24 @@ void FT8DemodBaseband::setBasebandSampleRate(int sampleRate)
m_channelizer.setBasebandSampleRate(sampleRate);
m_sink.applyChannelSettings(m_channelizer.getChannelSampleRate(), m_channelizer.getChannelFrequencyOffset());
}
void FT8DemodBaseband::tick()
{
QDateTime nowUTC = QDateTime::currentDateTimeUtc();
if (nowUTC.time().second() % 15 < 14)
{
if (m_tickCount++ == 0)
{
QDateTime periodTs = nowUTC.addSecs(-15);
qDebug("FT8DemodBaseband::tick: %s", qPrintable(nowUTC.toString("yyyy-MM-dd HH:mm:ss")));
m_ft8Buffer.getCurrentBuffer(m_ft8WorkerBuffer);
emit bufferReady(m_ft8WorkerBuffer, periodTs);
periodTs = nowUTC;
}
}
else
{
m_tickCount = 0;
}
}

View File

@ -20,6 +20,7 @@
#include <QObject>
#include <QRecursiveMutex>
#include <QDateTime>
#include "dsp/samplesinkfifo.h"
#include "dsp/downchannelizer.h"
@ -28,9 +29,12 @@
#include "ft8demodsettings.h"
#include "ft8demodsink.h"
#include "ft8buffer.h"
class ChannelAPI;
class SpectrumVis;
class QThread;
class FT8DemodWorker;
class FT8DemodBaseband : public QObject
{
@ -73,7 +77,6 @@ public:
void setMessageQueueToGUI(MessageQueue *messageQueue) { m_messageQueueToGUI = messageQueue; }
void setChannel(ChannelAPI *channel);
void setFifoLabel(const QString& label) { m_sampleFifo.setLabel(label); }
void setAudioFifoLabel(const QString& label) { m_sink.setAudioFifoLabel(label); }
signals:
/**
@ -83,6 +86,7 @@ signals:
* \param numSamples Number of samples analyzed
*/
void levelChanged(qreal rmsLevel, qreal peakLevel, int numSamples);
void bufferReady(int16_t *buffer, QDateTime periodTS);
private:
SampleSinkFifo m_sampleFifo;
@ -93,6 +97,11 @@ private:
int m_channelSampleRate;
MessageQueue *m_messageQueueToGUI;
SpectrumVis *m_spectrumVis;
FT8Buffer m_ft8Buffer;
int m_tickCount;
QThread *m_workerThread;
FT8DemodWorker *m_ft8DemodWorker;
int16_t *m_ft8WorkerBuffer;
QRecursiveMutex m_mutex;
bool handleMessage(const Message& cmd);
@ -102,6 +111,7 @@ private:
private slots:
void handleInputMessages();
void handleData(); //!< Handle data when samples have to be processed
void tick();
};
#endif // INCLUDE_SSBDEMODBASEBAND_H

View File

@ -361,7 +361,7 @@ void FT8DemodGUI::applySettings(bool force)
unsigned int FT8DemodGUI::spanLog2Max()
{
unsigned int spanLog2 = 0;
for (; m_settings.m_ft8SampleRate / (1<<spanLog2) >= 1000; spanLog2++);
for (; FT8DemodSettings::m_ft8SampleRate / (1<<spanLog2) >= 1000; spanLog2++);
return spanLog2 == 0 ? 0 : spanLog2-1;
}
@ -372,10 +372,10 @@ void FT8DemodGUI::applyBandwidths(unsigned int spanLog2, bool force)
unsigned int limit = s2max < 1 ? 0 : s2max - 1;
ui->spanLog2->setMaximum(limit);
//int spanLog2 = ui->spanLog2->value();
m_spectrumRate = m_settings.m_ft8SampleRate / (1<<spanLog2);
m_spectrumRate = FT8DemodSettings::m_ft8SampleRate / (1<<spanLog2);
int bw = ui->BW->value();
int lw = ui->lowCut->value();
int bwMax = m_settings.m_ft8SampleRate / (100*(1<<spanLog2));
int bwMax = FT8DemodSettings::m_ft8SampleRate / (100*(1<<spanLog2));
int tickInterval = m_spectrumRate / 2400;
tickInterval = tickInterval == 0 ? 1 : tickInterval;

View File

@ -22,6 +22,7 @@
#include "settings/serializable.h"
#include "ft8demodsettings.h"
const int FT8DemodSettings::m_ft8SampleRate = 12000;
#ifdef SDR_RX_SAMPLE_24BIT
const int FT8DemodSettings::m_minPowerThresholdDB = -120;
const float FT8DemodSettings::m_mminPowerThresholdDBf = 120.0f;
@ -46,7 +47,6 @@ void FT8DemodSettings::resetToDefaults()
m_inputFrequencyOffset = 0;
m_rgbColor = QColor(0, 192, 255).rgb();
m_title = "FT8 Demodulator";
m_ft8SampleRate = 12000;
m_streamIndex = 0;
m_useReverseAPI = false;
m_reverseAPIAddress = "127.0.0.1";
@ -71,7 +71,6 @@ QByteArray FT8DemodSettings::serialize() const
s.writeU32(5, m_rgbColor);
s.writeBool(11, m_agc);
s.writeString(16, m_title);
s.writeS32(17, m_ft8SampleRate);
s.writeBool(18, m_useReverseAPI);
s.writeString(19, m_reverseAPIAddress);
s.writeU32(20, m_reverseAPIPort);
@ -129,7 +128,6 @@ bool FT8DemodSettings::deserialize(const QByteArray& data)
d.readU32(5, &m_rgbColor);
d.readBool(11, &m_agc, false);
d.readString(16, &m_title, "SSB Demodulator");
d.readS32(17, &m_ft8SampleRate, 12000);
d.readBool(18, &m_useReverseAPI, false);
d.readString(19, &m_reverseAPIAddress, "127.0.0.1");
d.readU32(20, &utmp, 0);

View File

@ -50,7 +50,6 @@ struct FT8DemodSettings
bool m_agc;
quint32 m_rgbColor;
QString m_title;
int m_ft8SampleRate;
int m_streamIndex; //!< MIMO channel. Not relevant when connected to SI (single Rx).
bool m_useReverseAPI;
QString m_reverseAPIAddress;
@ -76,6 +75,7 @@ struct FT8DemodSettings
QByteArray serialize() const;
bool deserialize(const QByteArray& data);
static const int m_ft8SampleRate;
static const int m_minPowerThresholdDB;
static const float m_mminPowerThresholdDBf;
};

View File

@ -31,6 +31,7 @@
#include "util/messagequeue.h"
#include "maincore.h"
#include "ft8buffer.h"
#include "ft8demodsink.h"
const int FT8DemodSink::m_ssbFftLen = 1024;
@ -66,8 +67,7 @@ FT8DemodSink::FT8DemodSink() :
m_agcActive(false),
m_audioActive(false),
m_spectrumSink(nullptr),
m_audioFifo(24000),
m_ft8SampleRate(12000),
m_ft8Buffer(nullptr),
m_levelInNbSamples(1200) // 100 ms
{
m_Bandwidth = 5000;
@ -77,8 +77,6 @@ FT8DemodSink::FT8DemodSink() :
m_channelSampleRate = 48000;
m_channelFrequencyOffset = 0;
m_audioBuffer.resize(1<<14);
m_audioBufferFill = 0;
m_undersampleCount = 0;
m_sum = 0;
@ -94,7 +92,7 @@ FT8DemodSink::FT8DemodSink() :
m_agc.setThresholdEnable(false); // no squelch
m_agc.setClamping(false); // no clamping
SSBFilter = new fftfilt(m_LowCutoff / m_ft8SampleRate, m_Bandwidth / m_ft8SampleRate, m_ssbFftLen);
SSBFilter = new fftfilt(m_LowCutoff / FT8DemodSettings::m_ft8SampleRate, m_Bandwidth / FT8DemodSettings::m_ft8SampleRate, m_ssbFftLen);
applyChannelSettings(m_channelSampleRate, m_channelFrequencyOffset, true);
applySettings(m_settings, true);
@ -178,8 +176,11 @@ void FT8DemodSink::processOneSample(Complex &ci)
Real demod = (z.real() + z.imag()) * 0.7;
qint16 sample = (qint16)(demod * m_volume);
m_audioBuffer[m_audioBufferFill].l = sample;
m_audioBuffer[m_audioBufferFill].r = sample;
if (m_ft8Buffer) {
m_ft8Buffer->feed(sample);
}
m_demodBuffer[m_demodBufferFill++] = sample;
calculateLevel(sample);
@ -209,19 +210,6 @@ void FT8DemodSink::processOneSample(Complex &ci)
m_demodBufferFill = 0;
}
++m_audioBufferFill;
if (m_audioBufferFill >= m_audioBuffer.size())
{
// uint res = m_audioFifo.write((const quint8*)&m_audioBuffer[0], m_audioBufferFill);
// if (res != m_audioBufferFill) {
// qDebug("FT8DemodSink::processOneSample: %u/%u samples written", res, m_audioBufferFill);
// }
m_audioBufferFill = 0;
}
}
if (m_spectrumSink && (m_sampleBuffer.size() != 0))
@ -248,27 +236,23 @@ void FT8DemodSink::applyChannelSettings(int channelSampleRate, int channelFreque
Real interpolatorBandwidth = (m_Bandwidth * 1.5f) > channelSampleRate ? channelSampleRate : (m_Bandwidth * 1.5f);
m_interpolator.create(16, channelSampleRate, interpolatorBandwidth, 2.0f);
m_interpolatorDistanceRemain = 0;
m_interpolatorDistance = (Real) channelSampleRate / (Real) m_ft8SampleRate;
m_interpolatorDistance = (Real) channelSampleRate / (Real) FT8DemodSettings::m_ft8SampleRate;
}
m_channelSampleRate = channelSampleRate;
m_channelFrequencyOffset = channelFrequencyOffset;
}
void FT8DemodSink::applyFT8SampleRate(int sampleRate)
void FT8DemodSink::applyFT8SampleRate()
{
qDebug("FT8DemodSink::applyFT8SampleRate: %d", sampleRate);
qDebug("FT8DemodSink::applyFT8SampleRate: %d", FT8DemodSettings::m_ft8SampleRate);
Real interpolatorBandwidth = (m_Bandwidth * 1.5f) > m_channelSampleRate ? m_channelSampleRate : (m_Bandwidth * 1.5f);
m_interpolator.create(16, m_channelSampleRate, interpolatorBandwidth, 2.0f);
m_interpolatorDistanceRemain = 0;
m_interpolatorDistance = (Real) m_channelSampleRate / (Real) sampleRate;
SSBFilter->create_filter(m_LowCutoff / (float) sampleRate, m_Bandwidth / (float) sampleRate, m_settings.m_filterBank[m_settings.m_filterIndex].m_fftWindow);
m_audioFifo.setSize(sampleRate);
m_ft8SampleRate = sampleRate;
m_levelInNbSamples = m_ft8SampleRate / 10; // 100 ms
m_interpolatorDistance = (Real) m_channelSampleRate / (Real) FT8DemodSettings::m_ft8SampleRate;
SSBFilter->create_filter(m_LowCutoff / (float) FT8DemodSettings::m_ft8SampleRate, m_Bandwidth / (float) FT8DemodSettings::m_ft8SampleRate, m_settings.m_filterBank[m_settings.m_filterIndex].m_fftWindow);
m_levelInNbSamples = FT8DemodSettings::m_ft8SampleRate / 10; // 100 ms
QList<ObjectPipe*> pipes;
MainCore::instance()->getMessagePipes().getMessagePipes(m_channel, "reportdemod", pipes);
@ -281,7 +265,7 @@ void FT8DemodSink::applyFT8SampleRate(int sampleRate)
if (messageQueue)
{
MainCore::MsgChannelDemodReport *msg = MainCore::MsgChannelDemodReport::create(m_channel, sampleRate);
MainCore::MsgChannelDemodReport *msg = MainCore::MsgChannelDemodReport::create(m_channel, FT8DemodSettings::m_ft8SampleRate);
messageQueue->push(msg);
}
}
@ -299,7 +283,6 @@ void FT8DemodSink::applySettings(const FT8DemodSettings& settings, bool force)
<< " m_fftWindow: " << settings.m_filterBank[settings.m_filterIndex].m_fftWindow << "]"
<< " m_volume: " << settings.m_volume
<< " m_agcActive: " << settings.m_agc
<< " m_ft8SampleRate: " << settings.m_ft8SampleRate
<< " m_streamIndex: " << settings.m_streamIndex
<< " m_useReverseAPI: " << settings.m_useReverseAPI
<< " m_reverseAPIAddress: " << settings.m_reverseAPIAddress
@ -337,8 +320,8 @@ void FT8DemodSink::applySettings(const FT8DemodSettings& settings, bool force)
Real interpolatorBandwidth = (m_Bandwidth * 1.5f) > m_channelSampleRate ? m_channelSampleRate : (m_Bandwidth * 1.5f);
m_interpolator.create(16, m_channelSampleRate, interpolatorBandwidth, 2.0f);
m_interpolatorDistanceRemain = 0;
m_interpolatorDistance = (Real) m_channelSampleRate / (Real) m_ft8SampleRate;
SSBFilter->create_filter(m_LowCutoff / (float) m_ft8SampleRate, m_Bandwidth / (float) m_ft8SampleRate, settings.m_filterBank[settings.m_filterIndex].m_fftWindow);
m_interpolatorDistance = (Real) m_channelSampleRate / (Real) FT8DemodSettings::m_ft8SampleRate;
SSBFilter->create_filter(m_LowCutoff / (float) FT8DemodSettings::m_ft8SampleRate, m_Bandwidth / (float) FT8DemodSettings::m_ft8SampleRate, settings.m_filterBank[settings.m_filterIndex].m_fftWindow);
}
if ((m_settings.m_volume != settings.m_volume) || force)

View File

@ -25,13 +25,13 @@
#include "dsp/interpolator.h"
#include "dsp/fftfilt.h"
#include "dsp/agc.h"
#include "audio/audiofifo.h"
#include "util/doublebufferfifo.h"
#include "ft8demodsettings.h"
class SpectrumVis;
class ChannelAPI;
class FT8Buffer;
class FT8DemodSink : public ChannelSampleSink {
public:
@ -41,15 +41,14 @@ public:
virtual void feed(const SampleVector::const_iterator& begin, const SampleVector::const_iterator& end);
void setSpectrumSink(SpectrumVis* spectrumSink) { m_spectrumSink = spectrumSink; }
void setFT8Buffer(FT8Buffer *buffer) { m_ft8Buffer = buffer; }
void applyChannelSettings(int inputSampleRate, int inputFrequencyOffset, bool force = false);
void applySettings(const FT8DemodSettings& settings, bool force = false);
void applyFT8SampleRate(int sampleRate);
void applyFT8SampleRate();
AudioFifo *getAudioFifo() { return &m_audioFifo; }
double getMagSq() const { return m_magsq; }
bool getAudioActive() const { return m_audioActive; }
void setChannel(ChannelAPI *channel) { m_channel = channel; }
void setAudioFifoLabel(const QString& label) { m_audioFifo.setLabel(label); }
void getMagSqLevels(double& avg, double& peak, int& nbSamples)
{
@ -134,10 +133,7 @@ private:
SpectrumVis* m_spectrumSink;
SampleVector m_sampleBuffer;
AudioVector m_audioBuffer;
uint m_audioBufferFill;
AudioFifo m_audioFifo;
quint32 m_ft8SampleRate;
FT8Buffer *m_ft8Buffer;
QVector<qint16> m_demodBuffer;
int m_demodBufferFill;

View File

@ -0,0 +1,53 @@
///////////////////////////////////////////////////////////////////////////////////
// Copyright (C) 2023 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/>. //
///////////////////////////////////////////////////////////////////////////////////
#include <QStandardPaths>
#include <QDir>
#include <QDateTime>
#include "dsp/wavfilerecord.h"
#include "ft8demodsettings.h"
#include "ft8demodworker.h"
FT8DemodWorker::FT8DemodWorker()
{
QString relPath = "sdrangel/ft8/save";
QDir dir(QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation));
dir.mkpath(relPath);
m_samplesPath = dir.absolutePath() + "/" + relPath;
qDebug("FT8DemodWorker::FT8DemodWorker: samples path: %s", qPrintable(m_samplesPath));
}
FT8DemodWorker::~FT8DemodWorker()
{}
void FT8DemodWorker::processBuffer(int16_t *buffer, QDateTime periodTS)
{
qDebug("FT8DemodWorker::processBuffer: %s", qPrintable(periodTS.toString("yyyy-MM-dd HH:mm:ss")));
WavFileRecord *wavFileRecord = new WavFileRecord(FT8DemodSettings::m_ft8SampleRate);
QFileInfo wfi(QDir(m_samplesPath), periodTS.toString("yyyyMMdd_HHmmss"));
QString wpath = wfi.absoluteFilePath();
qDebug("FT8DemodWorker::processBuffer: WAV file: %s.wav", qPrintable(wpath));
wavFileRecord->setFileName(wpath);
wavFileRecord->setFileBaseIsFileName(true);
wavFileRecord->setMono(true);
wavFileRecord->startRecording();
wavFileRecord->writeMono(buffer, 15*FT8DemodSettings::m_ft8SampleRate);
wavFileRecord->stopRecording();
delete wavFileRecord;
}

View File

@ -0,0 +1,38 @@
///////////////////////////////////////////////////////////////////////////////////
// Copyright (C) 2023 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_FT8DEMODWORKER_H
#define INCLUDE_FT8DEMODWORKER_H
#include <QObject>
class QDateTime;
class FT8DemodWorker : public QObject
{
Q_OBJECT
public:
FT8DemodWorker();
~FT8DemodWorker();
void processBuffer(int16_t *buffer, QDateTime periodTS);
private:
QString m_samplesPath;
};
#endif // INCLUDE_FT8DEMODWORKER_H

View File

@ -32,6 +32,7 @@
WavFileRecord::WavFileRecord(quint32 sampleRate, quint64 centerFrequency) :
FileRecordInterface(),
m_fileBase("test"),
m_fileBaseIsFileName(false),
m_sampleRate(sampleRate),
m_centerFrequency(centerFrequency),
m_recordOn(false),
@ -46,6 +47,7 @@ WavFileRecord::WavFileRecord(quint32 sampleRate, quint64 centerFrequency) :
WavFileRecord::WavFileRecord(const QString& fileBase) :
FileRecordInterface(),
m_fileBase(fileBase),
m_fileBaseIsFileName(false),
m_sampleRate(0),
m_centerFrequency(0),
m_recordOn(false),
@ -139,6 +141,18 @@ void WavFileRecord::writeMono(qint16 sample)
m_byteCount += 2;
}
void WavFileRecord::writeMono(qint16 *samples, int nbSamples)
{
if (m_recordStart)
{
writeHeader();
m_recordStart = false;
}
m_sampleFile.write(reinterpret_cast<const char*>(samples), 2*nbSamples);
m_byteCount += 2*nbSamples;
}
void WavFileRecord::start()
{
}
@ -171,7 +185,11 @@ bool WavFileRecord::startRecording()
return false;
}
#else
m_currentFileName = m_fileBase + "_" + QDateTime::currentDateTimeUtc().toString("yyyy-MM-ddTHH_mm_ss_zzz") + ".wav"; // Don't use QString::arg on Android, as filename can contain %2
if (m_fileBaseIsFileName) {
m_currentFileName = m_fileBase + ".wav";
} else {
m_currentFileName = m_fileBase + "_" + QDateTime::currentDateTimeUtc().toString("yyyy-MM-ddTHH_mm_ss_zzz") + ".wav"; // 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())
{

View File

@ -102,11 +102,13 @@ public:
virtual void feed(const SampleVector::const_iterator& begin, const SampleVector::const_iterator& end, bool positiveOnly) override;
void write(qint16 lSample, qint16 rSample); //!< write a single sample
void writeMono(qint16 sample); //!< write a single mono sample
void writeMono(qint16 *samples, int nbSamples); //!< write a buffer of mono samples
virtual void start() override;
virtual void stop() override;
virtual bool handleMessage(const Message& message) override;
virtual void setFileName(const QString& fileBase) override;
void setFileBaseIsFileName(bool fileBaseIsFileName) { m_fileBaseIsFileName = fileBaseIsFileName; }
virtual bool startRecording() override;
virtual bool stopRecording() override;
virtual bool isRecording() const override { return m_recordOn; }
@ -122,6 +124,7 @@ public:
private:
QString m_fileBase;
bool m_fileBaseIsFileName;
quint32 m_sampleRate;
quint64 m_centerFrequency;
bool m_recordOn;

View File

@ -5617,9 +5617,6 @@ margin-bottom: 20px;
"title" : {
"type" : "string"
},
"ft8SampleRate" : {
"type" : "integer"
},
"streamIndex" : {
"type" : "integer",
"description" : "MIMO channel. Not relevant when connected to SI (single Rx)."
@ -56876,7 +56873,7 @@ except ApiException as e:
</div>
<div id="generator">
<div class="content">
Generated 2023-01-15T12:10:43.505+01:00
Generated 2023-01-17T00:44:14.657+01:00
</div>
</div>
</div>

View File

@ -37,8 +37,6 @@ FT8DemodSettings:
type: integer
title:
type: string
ft8SampleRate:
type: integer
streamIndex:
description: MIMO channel. Not relevant when connected to SI (single Rx).
type: integer

View File

@ -37,8 +37,6 @@ FT8DemodSettings:
type: integer
title:
type: string
ft8SampleRate:
type: integer
streamIndex:
description: MIMO channel. Not relevant when connected to SI (single Rx).
type: integer

View File

@ -5617,9 +5617,6 @@ margin-bottom: 20px;
"title" : {
"type" : "string"
},
"ft8SampleRate" : {
"type" : "integer"
},
"streamIndex" : {
"type" : "integer",
"description" : "MIMO channel. Not relevant when connected to SI (single Rx)."
@ -56876,7 +56873,7 @@ except ApiException as e:
</div>
<div id="generator">
<div class="content">
Generated 2023-01-15T12:10:43.505+01:00
Generated 2023-01-17T00:44:14.657+01:00
</div>
</div>
</div>

View File

@ -48,8 +48,6 @@ SWGFT8DemodSettings::SWGFT8DemodSettings() {
m_rgb_color_isSet = false;
title = nullptr;
m_title_isSet = false;
ft8_sample_rate = 0;
m_ft8_sample_rate_isSet = false;
stream_index = 0;
m_stream_index_isSet = false;
use_reverse_api = 0;
@ -96,8 +94,6 @@ SWGFT8DemodSettings::init() {
m_rgb_color_isSet = false;
title = new QString("");
m_title_isSet = false;
ft8_sample_rate = 0;
m_ft8_sample_rate_isSet = false;
stream_index = 0;
m_stream_index_isSet = false;
use_reverse_api = 0;
@ -134,7 +130,6 @@ SWGFT8DemodSettings::cleanup() {
}
if(reverse_api_address != nullptr) {
delete reverse_api_address;
}
@ -183,8 +178,6 @@ SWGFT8DemodSettings::fromJsonObject(QJsonObject &pJson) {
::SWGSDRangel::setValue(&title, pJson["title"], "QString", "QString");
::SWGSDRangel::setValue(&ft8_sample_rate, pJson["ft8SampleRate"], "qint32", "");
::SWGSDRangel::setValue(&stream_index, pJson["streamIndex"], "qint32", "");
::SWGSDRangel::setValue(&use_reverse_api, pJson["useReverseAPI"], "qint32", "");
@ -249,9 +242,6 @@ SWGFT8DemodSettings::asJsonObject() {
if(title != nullptr && *title != QString("")){
toJsonValue(QString("title"), title, obj, QString("QString"));
}
if(m_ft8_sample_rate_isSet){
obj->insert("ft8SampleRate", QJsonValue(ft8_sample_rate));
}
if(m_stream_index_isSet){
obj->insert("streamIndex", QJsonValue(stream_index));
}
@ -383,16 +373,6 @@ SWGFT8DemodSettings::setTitle(QString* title) {
this->m_title_isSet = true;
}
qint32
SWGFT8DemodSettings::getFt8SampleRate() {
return ft8_sample_rate;
}
void
SWGFT8DemodSettings::setFt8SampleRate(qint32 ft8_sample_rate) {
this->ft8_sample_rate = ft8_sample_rate;
this->m_ft8_sample_rate_isSet = true;
}
qint32
SWGFT8DemodSettings::getStreamIndex() {
return stream_index;
@ -518,9 +498,6 @@ SWGFT8DemodSettings::isSet(){
if(title && *title != QString("")){
isObjectUpdated = true; break;
}
if(m_ft8_sample_rate_isSet){
isObjectUpdated = true; break;
}
if(m_stream_index_isSet){
isObjectUpdated = true; break;
}

View File

@ -75,9 +75,6 @@ public:
QString* getTitle();
void setTitle(QString* title);
qint32 getFt8SampleRate();
void setFt8SampleRate(qint32 ft8_sample_rate);
qint32 getStreamIndex();
void setStreamIndex(qint32 stream_index);
@ -139,9 +136,6 @@ private:
QString* title;
bool m_title_isSet;
qint32 ft8_sample_rate;
bool m_ft8_sample_rate_isSet;
qint32 stream_index;
bool m_stream_index_isSet;