2018-02-08 21:28:33 -05:00
|
|
|
#include "Modulator.hpp"
|
2024-10-27 15:41:10 -07:00
|
|
|
#include <algorithm>
|
2024-10-27 15:32:42 -07:00
|
|
|
#include <cmath>
|
2018-02-08 21:28:33 -05:00
|
|
|
#include <limits>
|
2024-10-27 16:10:13 -07:00
|
|
|
#include <random>
|
2018-02-08 21:28:33 -05:00
|
|
|
#include <QDateTime>
|
|
|
|
|
#include <QDebug>
|
2024-10-27 16:03:31 -07:00
|
|
|
#include <QRandomGenerator>
|
|
|
|
|
#include <QtMath>
|
2018-03-05 14:49:51 -05:00
|
|
|
#include "commons.h"
|
2018-09-18 17:24:07 -04:00
|
|
|
#include "DriftingDateTime.h"
|
2024-10-27 16:03:31 -07:00
|
|
|
#include "mainwindow.h"
|
|
|
|
|
#include "soundout.h"
|
2018-09-18 17:24:07 -04:00
|
|
|
|
2018-02-08 21:28:33 -05:00
|
|
|
#include "moc_Modulator.cpp"
|
|
|
|
|
|
2024-09-11 17:48:01 -07:00
|
|
|
namespace
|
|
|
|
|
{
|
2024-10-27 15:32:42 -07:00
|
|
|
constexpr double TAU = 2 * M_PI;
|
2024-10-27 16:10:13 -07:00
|
|
|
|
|
|
|
|
// Noise generator, for tests only; generate gaussian random
|
|
|
|
|
// float with mean = 0 and std_dev = 1.
|
|
|
|
|
|
|
|
|
|
float
|
|
|
|
|
gran()
|
|
|
|
|
{
|
|
|
|
|
static std::normal_distribution<float> d;
|
|
|
|
|
return d(*QRandomGenerator::global());
|
|
|
|
|
}
|
|
|
|
|
|
2024-09-11 17:48:01 -07:00
|
|
|
unsigned
|
|
|
|
|
delayMS(qint32 const trPeriod)
|
|
|
|
|
{
|
|
|
|
|
switch (trPeriod)
|
|
|
|
|
{
|
|
|
|
|
case JS8A_TX_SECONDS: { return JS8A_START_DELAY_MS; }
|
|
|
|
|
case JS8B_TX_SECONDS: { return JS8B_START_DELAY_MS; }
|
|
|
|
|
case JS8C_TX_SECONDS: { return JS8C_START_DELAY_MS; }
|
|
|
|
|
case JS8E_TX_SECONDS: { return JS8E_START_DELAY_MS; }
|
|
|
|
|
case JS8I_TX_SECONDS: { return JS8I_START_DELAY_MS; }
|
|
|
|
|
default: { return 0; }
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2024-10-27 15:32:42 -07:00
|
|
|
Modulator::Modulator(unsigned frameRate,
|
|
|
|
|
unsigned periodLengthInSeconds,
|
|
|
|
|
QObject * parent)
|
|
|
|
|
: AudioDevice {parent}
|
|
|
|
|
, m_quickClose {false}
|
|
|
|
|
, m_phi {0.0}
|
|
|
|
|
, m_toneSpacing {0.0}
|
|
|
|
|
, m_fSpread {0.0}
|
|
|
|
|
, m_frameRate {frameRate}
|
|
|
|
|
, m_period {periodLengthInSeconds}
|
|
|
|
|
, m_state {Idle}
|
|
|
|
|
, m_tuning {false}
|
|
|
|
|
, m_j0 {-1}
|
2018-02-08 21:28:33 -05:00
|
|
|
, m_toneFrequency0 {1500.0}
|
|
|
|
|
{
|
|
|
|
|
}
|
|
|
|
|
|
2024-10-27 15:32:42 -07:00
|
|
|
void
|
|
|
|
|
Modulator::start(unsigned symbolsLength,
|
|
|
|
|
double framesPerSymbol,
|
|
|
|
|
double frequency,
|
|
|
|
|
double toneSpacing,
|
|
|
|
|
SoundOutput * stream,
|
|
|
|
|
Channel channel,
|
|
|
|
|
bool synchronize,
|
|
|
|
|
bool fastMode,
|
|
|
|
|
double dBSNR,
|
|
|
|
|
int TRperiod)
|
2018-02-08 21:28:33 -05:00
|
|
|
{
|
2024-09-11 20:24:00 -07:00
|
|
|
// qDebug () << "mode:" << mode << "symbolsLength:" << symbolsLength << "framesPerSymbol:" << framesPerSymbol << "frequency:" << frequency << "toneSpacing:" << toneSpacing << "channel:" << channel << "synchronize:" << synchronize << "fastMode:" << fastMode << "dBSNR:" << dBSNR << "TRperiod:" << TRperiod;
|
2018-02-08 21:28:33 -05:00
|
|
|
Q_ASSERT (stream);
|
2024-10-27 15:32:42 -07:00
|
|
|
|
|
|
|
|
// Time according to this computer which becomes our base time
|
|
|
|
|
|
2024-09-11 20:24:00 -07:00
|
|
|
qint64 const ms0 = DriftingDateTime::currentMSecsSinceEpoch() % 86400000;
|
2024-10-27 15:32:42 -07:00
|
|
|
unsigned const mstr = ms0 % int(1000.0 * m_period); // ms into the nominal Tx start time
|
2018-02-08 21:28:33 -05:00
|
|
|
|
2024-09-11 20:24:00 -07:00
|
|
|
if(m_state != Idle) stop();
|
2024-10-27 15:32:42 -07:00
|
|
|
|
|
|
|
|
m_quickClose = false;
|
2018-02-08 21:28:33 -05:00
|
|
|
m_symbolsLength = symbolsLength;
|
2024-10-27 15:32:42 -07:00
|
|
|
m_isym0 = std::numeric_limits<unsigned>::max(); // big number
|
|
|
|
|
m_frequency0 = 0.;
|
|
|
|
|
m_phi = 0.;
|
|
|
|
|
m_addNoise = dBSNR < 0.;
|
|
|
|
|
m_nsps = framesPerSymbol;
|
|
|
|
|
m_frequency = frequency;
|
|
|
|
|
m_amp = std::numeric_limits<qint16>::max();
|
|
|
|
|
m_toneSpacing = toneSpacing;
|
|
|
|
|
m_bFastMode = fastMode;
|
|
|
|
|
m_TRperiod = TRperiod;
|
2024-09-11 17:48:01 -07:00
|
|
|
|
|
|
|
|
unsigned const delay_ms = delayMS(m_TRperiod);
|
2018-02-08 21:28:33 -05:00
|
|
|
|
|
|
|
|
// noise generator parameters
|
2024-10-27 15:32:42 -07:00
|
|
|
if (m_addNoise)
|
|
|
|
|
{
|
2018-02-08 21:28:33 -05:00
|
|
|
m_snr = qPow (10.0, 0.05 * (dBSNR - 6.0));
|
|
|
|
|
m_fac = 3000.0;
|
|
|
|
|
if (m_snr > 1.0) m_fac = 3000.0 / m_snr;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
m_silentFrames = 0;
|
2024-10-27 15:32:42 -07:00
|
|
|
m_ic = 0;
|
|
|
|
|
|
2024-09-11 20:24:00 -07:00
|
|
|
if (!m_tuning && !m_bFastMode)
|
2024-10-27 15:32:42 -07:00
|
|
|
{
|
|
|
|
|
// Calculate number of silent frames to send, so that audio will
|
|
|
|
|
// start at the nominal time "delay_ms" into the Tx sequence.
|
|
|
|
|
|
|
|
|
|
if (synchronize)
|
2024-09-11 20:24:00 -07:00
|
|
|
{
|
2024-10-27 15:32:42 -07:00
|
|
|
if(delay_ms > mstr) m_silentFrames = (delay_ms - mstr) * m_frameRate / 1000;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Adjust for late starts.
|
|
|
|
|
|
|
|
|
|
if(!m_silentFrames && mstr >= delay_ms)
|
|
|
|
|
{
|
|
|
|
|
m_ic = (mstr - delay_ms) * m_frameRate / 1000;
|
2024-09-12 07:48:49 -07:00
|
|
|
}
|
2024-10-27 15:32:42 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
initialize(QIODevice::ReadOnly, channel);
|
2018-02-08 21:28:33 -05:00
|
|
|
|
|
|
|
|
Q_EMIT stateChanged ((m_state = (synchronize && m_silentFrames) ?
|
|
|
|
|
Synchronizing : Active));
|
2024-09-11 20:24:00 -07:00
|
|
|
|
|
|
|
|
// qDebug() << "delay_ms:" << delay_ms << "mstr:" << mstr << "m_silentFrames:" << m_silentFrames << "m_ic:" << m_ic << "m_state:" << m_state;
|
|
|
|
|
|
2018-02-08 21:28:33 -05:00
|
|
|
m_stream = stream;
|
2024-09-11 20:24:00 -07:00
|
|
|
if (m_stream)
|
2024-10-27 15:32:42 -07:00
|
|
|
{
|
|
|
|
|
m_stream->restart (this);
|
|
|
|
|
}
|
2024-09-11 20:24:00 -07:00
|
|
|
else
|
2024-10-27 15:32:42 -07:00
|
|
|
{
|
|
|
|
|
qDebug () << "Modulator::start: no audio output stream assigned";
|
|
|
|
|
}
|
2018-02-08 21:28:33 -05:00
|
|
|
}
|
|
|
|
|
|
2024-10-27 16:03:31 -07:00
|
|
|
void
|
|
|
|
|
Modulator::tune(bool const tuning)
|
2018-02-08 21:28:33 -05:00
|
|
|
{
|
2024-10-27 16:03:31 -07:00
|
|
|
m_tuning = tuning;
|
|
|
|
|
if (!m_tuning) stop(true);
|
2018-02-08 21:28:33 -05:00
|
|
|
}
|
|
|
|
|
|
2024-10-27 16:03:31 -07:00
|
|
|
void
|
|
|
|
|
Modulator::stop(bool const quickClose)
|
2018-02-08 21:28:33 -05:00
|
|
|
{
|
2024-10-27 16:03:31 -07:00
|
|
|
m_quickClose = quickClose;
|
|
|
|
|
close();
|
2018-02-08 21:28:33 -05:00
|
|
|
}
|
|
|
|
|
|
2024-10-27 16:03:31 -07:00
|
|
|
void
|
|
|
|
|
Modulator::close()
|
2018-02-08 21:28:33 -05:00
|
|
|
{
|
|
|
|
|
if (m_stream)
|
2024-10-27 15:32:42 -07:00
|
|
|
{
|
2024-10-27 16:03:31 -07:00
|
|
|
if (m_quickClose) m_stream->reset();
|
|
|
|
|
else m_stream->stop();
|
2024-10-27 15:32:42 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (m_state != Idle)
|
|
|
|
|
{
|
|
|
|
|
Q_EMIT stateChanged ((m_state = Idle));
|
|
|
|
|
}
|
|
|
|
|
|
2024-10-27 16:03:31 -07:00
|
|
|
AudioDevice::close();
|
2018-02-08 21:28:33 -05:00
|
|
|
}
|
|
|
|
|
|
2024-10-27 16:03:31 -07:00
|
|
|
qint64
|
|
|
|
|
Modulator::readData(char * const data,
|
|
|
|
|
qint64 const maxSize)
|
2018-02-08 21:28:33 -05:00
|
|
|
{
|
2024-10-27 16:03:31 -07:00
|
|
|
double toneFrequency = 1500.0;
|
|
|
|
|
|
|
|
|
|
if(m_nsps == 6)
|
|
|
|
|
{
|
|
|
|
|
toneFrequency = 1000.0;
|
|
|
|
|
m_frequency = 1000.0;
|
|
|
|
|
m_frequency0 = 1000.0;
|
2018-02-08 21:28:33 -05:00
|
|
|
}
|
2024-10-27 16:03:31 -07:00
|
|
|
|
|
|
|
|
if (maxSize == 0) return 0;
|
|
|
|
|
|
2018-02-08 21:28:33 -05:00
|
|
|
Q_ASSERT (!(maxSize % qint64 (bytesPerFrame ()))); // no torn frames
|
|
|
|
|
Q_ASSERT (isOpen ());
|
|
|
|
|
|
2024-10-27 16:03:31 -07:00
|
|
|
qint64 numFrames = maxSize / bytesPerFrame();
|
|
|
|
|
qint16 * samples = reinterpret_cast<qint16 *>(data);
|
|
|
|
|
qint16 * end = samples + numFrames * (bytesPerFrame() / sizeof(qint16));
|
|
|
|
|
qint64 framesGenerated = 0;
|
2018-02-08 21:28:33 -05:00
|
|
|
|
|
|
|
|
switch (m_state)
|
2024-10-27 16:03:31 -07:00
|
|
|
{
|
2018-02-08 21:28:33 -05:00
|
|
|
case Synchronizing:
|
2024-10-27 16:03:31 -07:00
|
|
|
{
|
|
|
|
|
if (m_silentFrames)
|
2018-02-08 21:28:33 -05:00
|
|
|
{
|
2024-10-27 16:03:31 -07:00
|
|
|
// Send silence up to end of start delay.
|
|
|
|
|
|
|
|
|
|
framesGenerated = qMin(m_silentFrames, numFrames);
|
|
|
|
|
|
|
|
|
|
do
|
|
|
|
|
{
|
|
|
|
|
samples = load(0, samples); // silence
|
|
|
|
|
} while (--m_silentFrames && samples != end);
|
|
|
|
|
|
|
|
|
|
if (!m_silentFrames)
|
|
|
|
|
{
|
|
|
|
|
Q_EMIT stateChanged ((m_state = Active));
|
2018-02-08 21:28:33 -05:00
|
|
|
}
|
|
|
|
|
}
|
2024-10-27 16:03:31 -07:00
|
|
|
}
|
|
|
|
|
[[fallthrough]];
|
2018-02-08 21:28:33 -05:00
|
|
|
|
|
|
|
|
case Active:
|
2024-10-27 16:03:31 -07:00
|
|
|
{
|
|
|
|
|
double const baud = 12000.0 / m_nsps;
|
|
|
|
|
unsigned int i0; // fade out parameters, no
|
|
|
|
|
unsigned int i1; // fade out for tuning
|
|
|
|
|
|
|
|
|
|
if (m_tuning)
|
|
|
|
|
{
|
|
|
|
|
i1 = i0 = (m_bFastMode ? 999999 : 9999) * m_nsps;
|
|
|
|
|
}
|
|
|
|
|
else
|
2018-02-08 21:28:33 -05:00
|
|
|
{
|
2024-10-27 16:03:31 -07:00
|
|
|
i0 = (m_symbolsLength - 0.017) * 4.0 * m_nsps;
|
|
|
|
|
i1 = m_symbolsLength * 4.0 * m_nsps;
|
|
|
|
|
}
|
2018-02-08 21:28:33 -05:00
|
|
|
|
2024-10-27 16:03:31 -07:00
|
|
|
if(m_bFastMode and !m_tuning)
|
|
|
|
|
{
|
|
|
|
|
i1 = m_TRperiod*48000 - 24000;
|
|
|
|
|
i0 = i1-816;
|
|
|
|
|
}
|
2018-02-08 21:28:33 -05:00
|
|
|
|
2024-10-27 16:03:31 -07:00
|
|
|
qint16 sample;
|
|
|
|
|
unsigned int isym;
|
2018-02-08 21:28:33 -05:00
|
|
|
|
2024-10-27 16:03:31 -07:00
|
|
|
while (samples != end && m_ic <= i1)
|
|
|
|
|
{
|
|
|
|
|
isym = 0;
|
|
|
|
|
if (!m_tuning and m_TRperiod!=3) isym=m_ic / (4.0 * m_nsps); //Actual fsample=48000
|
|
|
|
|
if (m_bFastMode) isym=isym%m_symbolsLength;
|
|
|
|
|
if (isym != m_isym0 || m_frequency != m_frequency0)
|
|
|
|
|
{
|
|
|
|
|
if (itone[0] >= 100)
|
2024-10-27 15:32:42 -07:00
|
|
|
{
|
2024-10-27 16:03:31 -07:00
|
|
|
m_toneFrequency0 = itone[0];
|
2018-02-08 21:28:33 -05:00
|
|
|
}
|
2024-10-27 16:03:31 -07:00
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
if (m_toneSpacing==0.0) m_toneFrequency0 = m_frequency + itone[isym] * baud;
|
|
|
|
|
else m_toneFrequency0 = m_frequency + itone[isym] * m_toneSpacing;
|
|
|
|
|
}
|
|
|
|
|
m_dphi = TAU * m_toneFrequency0 / m_frameRate;
|
|
|
|
|
m_isym0 = isym;
|
|
|
|
|
m_frequency0 = m_frequency; //???
|
|
|
|
|
}
|
2018-02-08 21:28:33 -05:00
|
|
|
|
2024-10-27 16:03:31 -07:00
|
|
|
int const j = m_ic / 480;
|
2024-10-09 22:05:49 -07:00
|
|
|
|
2024-10-27 16:03:31 -07:00
|
|
|
if (m_fSpread > 0.0 and j != m_j0)
|
|
|
|
|
{
|
|
|
|
|
float const x1 = QRandomGenerator::global()->generateDouble();
|
|
|
|
|
float const x2 = QRandomGenerator::global()->generateDouble();
|
|
|
|
|
toneFrequency = m_toneFrequency0 + 0.5 * m_fSpread * (x1 + x2 - 1.0);
|
|
|
|
|
m_dphi = TAU * toneFrequency / m_frameRate;
|
|
|
|
|
m_j0 = j;
|
2018-02-08 21:28:33 -05:00
|
|
|
}
|
|
|
|
|
|
2024-10-27 16:03:31 -07:00
|
|
|
m_phi += m_dphi;
|
|
|
|
|
if (m_phi > TAU) m_phi -= TAU;
|
|
|
|
|
if (m_ic > i0) m_amp = 0.98 * m_amp;
|
|
|
|
|
if (m_ic > i1) m_amp = 0.0;
|
|
|
|
|
|
|
|
|
|
sample = qRound(m_amp * qSin(m_phi));
|
|
|
|
|
samples = load(postProcessSample(sample), samples);
|
2018-02-08 21:28:33 -05:00
|
|
|
|
2024-10-27 16:03:31 -07:00
|
|
|
++framesGenerated;
|
|
|
|
|
++m_ic;
|
|
|
|
|
}
|
2024-09-11 20:24:00 -07:00
|
|
|
|
2024-10-27 16:03:31 -07:00
|
|
|
// TODO G4WJS: compare double with zero might not be wise
|
2024-09-11 20:24:00 -07:00
|
|
|
|
2024-10-27 16:03:31 -07:00
|
|
|
if (m_amp == 0.0)
|
|
|
|
|
{
|
|
|
|
|
Q_EMIT stateChanged ((m_state = Idle));
|
|
|
|
|
return framesGenerated * bytesPerFrame();
|
|
|
|
|
m_phi = 0.0;
|
2018-02-08 21:28:33 -05:00
|
|
|
}
|
|
|
|
|
|
2024-10-27 16:03:31 -07:00
|
|
|
m_frequency0 = m_frequency;
|
|
|
|
|
|
|
|
|
|
// done for this chunk - continue on next call
|
|
|
|
|
|
|
|
|
|
while (samples != end) // pad block with silence
|
|
|
|
|
{
|
|
|
|
|
samples = load(0, samples);
|
|
|
|
|
++framesGenerated;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return framesGenerated * bytesPerFrame();
|
2018-02-08 21:28:33 -05:00
|
|
|
}
|
2024-10-27 16:03:31 -07:00
|
|
|
[[fallthrough]];
|
|
|
|
|
|
|
|
|
|
case Idle:
|
|
|
|
|
break;
|
|
|
|
|
}
|
2018-02-08 21:28:33 -05:00
|
|
|
|
|
|
|
|
Q_ASSERT (Idle == m_state);
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
|
2024-10-27 15:41:10 -07:00
|
|
|
// If this is a test frame, we'll add noise.
|
|
|
|
|
|
2024-10-27 15:32:42 -07:00
|
|
|
qint16
|
|
|
|
|
Modulator::postProcessSample(qint16 sample) const
|
2018-02-08 21:28:33 -05:00
|
|
|
{
|
2024-10-27 15:32:42 -07:00
|
|
|
if (m_addNoise)
|
2024-10-27 15:41:10 -07:00
|
|
|
{
|
|
|
|
|
return std::clamp(static_cast<qint32>(m_fac * (gran() + sample * m_snr / 32768.0)),
|
|
|
|
|
static_cast<qint32>(std::numeric_limits<qint16>::min()),
|
|
|
|
|
static_cast<qint32>(std::numeric_limits<qint16>::max()));
|
2018-02-08 21:28:33 -05:00
|
|
|
}
|
2024-10-27 15:32:42 -07:00
|
|
|
|
2018-02-08 21:28:33 -05:00
|
|
|
return sample;
|
|
|
|
|
}
|