WSJT-X/Modulator.cpp
Bill Somerville 4a32b0bef6 Add facility to disallow CAT commands while transmitting.
Some rigs either do not honour some CAT commands while transmitting or
interpret them  incorrectly. To  deal with this  a settings  option to
allow TX  frequency changes while  transmitting has been added  with a
default value of off.

Any UI actions that directly or indirectly change the TX frequency are
guarded according to this new option. As well as this band changes and
use of  the +2kHz check box  are disabled and guarded  respectively in
transmit mode.

Mode changes via the menu are now disabled while transmitting.

When TX frequency changes are allowed; frequency changes are correctly
implemented while in tune mode.

Double clicking  decodes while  transmitting now  correctly regenerate
and change the message sent on the fly.

git-svn-id: svn+ssh://svn.code.sf.net/p/wsjt/wsjt/branches/wsjtx@4349 ab8295b8-cf94-4d9e-aec4-7959e3be5d79
2014-09-24 17:25:19 +00:00

297 lines
8.6 KiB
C++

#include "Modulator.hpp"
#include <limits>
#include <qmath.h>
#include <QDateTime>
#include <QDebug>
#include "mainwindow.h"
#include "moc_Modulator.cpp"
extern float gran(); // Noise generator (for tests only)
#define RAMP_INCREMENT 64 // MUST be an integral factor of 2^16
#if defined (WSJT_SOFT_KEYING)
# define SOFT_KEYING WSJT_SOFT_KEYING
#else
# define SOFT_KEYING 1
#endif
double const Modulator::m_twoPi = 2.0 * 3.141592653589793238462;
// float wpm=20.0;
// unsigned m_nspd=1.2*48000.0/wpm;
// m_nspd=3072; //18.75 WPM
unsigned const Modulator::m_nspd = 2048 + 512; // 22.5 WPM
Modulator::Modulator (unsigned frameRate, unsigned periodLengthInSeconds, QObject * parent)
: AudioDevice {parent}
, m_stream {nullptr}
, 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_cwLevel {false}
{
qsrand (QDateTime::currentMSecsSinceEpoch()); // Initialize random
// seed
}
void Modulator::start (unsigned symbolsLength, double framesPerSymbol, unsigned frequency, double toneSpacing, SoundOutput * stream, Channel channel, bool synchronize, double dBSNR)
{
Q_ASSERT (stream);
// Time according to this computer which becomes our base time
qint64 ms0 = QDateTime::currentMSecsSinceEpoch() % 86400000;
// qDebug () << "Modulator: Using soft keying for CW is " << SOFT_KEYING;;
if (m_state != Idle)
{
stop ();
}
m_quickClose = false;
m_symbolsLength = symbolsLength;
m_isym0 = std::numeric_limits<unsigned>::max (); // Arbitrary big
// number
m_frequency0 = 0.;
m_addNoise = dBSNR < 0.;
m_nsps = framesPerSymbol;
m_frequency = frequency;
m_amp = std::numeric_limits<qint16>::max ();
m_toneSpacing = toneSpacing;
// noise generator parameters
if (m_addNoise) {
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;
}
unsigned mstr = ms0 % (1000 * m_period); // ms in period
m_ic = (mstr / 1000) * m_frameRate; // we start exactly N seconds
// into period where N is the next whole second
m_silentFrames = 0;
// calculate number of silent frames to send
if (synchronize && !m_tuning) {
m_silentFrames = m_ic + m_frameRate - (mstr * m_frameRate / 1000);
}
// qDebug () << "Modulator: starting at " << m_ic / m_frameRate << " sec, sending " << m_silentFrames << " silent frames";
initialize (QIODevice::ReadOnly, channel);
Q_EMIT stateChanged ((m_state = (synchronize && m_silentFrames) ?
Synchronizing : Active));
m_stream = stream;
if (m_stream)
{
m_stream->restart (this);
}
}
void Modulator::tune (bool newState)
{
m_tuning = newState;
if (!m_tuning)
{
stop (true);
}
}
void Modulator::stop (bool quick)
{
m_quickClose = quick;
close ();
}
void Modulator::close ()
{
if (m_stream)
{
if (m_quickClose)
{
m_stream->reset ();
}
else
{
m_stream->stop ();
}
}
if (m_state != Idle)
{
Q_EMIT stateChanged ((m_state = Idle));
}
AudioDevice::close ();
}
qint64 Modulator::readData (char * data, qint64 maxSize)
{
static int j0=-1;
static double toneFrequency0;
double toneFrequency;
if(maxSize==0) return 0;
Q_ASSERT (!(maxSize % qint64 (bytesPerFrame ()))); // no torn frames
Q_ASSERT (isOpen ());
qint64 numFrames (maxSize / bytesPerFrame ());
qint16 * samples (reinterpret_cast<qint16 *> (data));
qint16 * end (samples + numFrames * (bytesPerFrame () / sizeof (qint16)));
qint64 framesGenerated (0);
// qDebug () << "Modulator: " << numFrames << " requested, m_ic = " << m_ic << ", tune mode is " << m_tuning;
// qDebug() << "C" << maxSize << numFrames << bytesPerFrame();
switch (m_state)
{
case Synchronizing:
{
if (m_silentFrames) { // send silence up to first second
framesGenerated = qMin (m_silentFrames, numFrames);
for ( ; samples != end; samples = load (0, samples)) { // silence
}
m_silentFrames -= framesGenerated;
return framesGenerated * bytesPerFrame ();
}
Q_EMIT stateChanged ((m_state = Active));
m_cwLevel = false;
m_ramp = 0; // prepare for CW wave shaping
}
// fall through
case Active:
{
unsigned isym (m_tuning ? 0 : m_ic / (4.0 * m_nsps)); // Actual fsample=48000
if (isym >= m_symbolsLength && icw[0] > 0) { // start CW condition
// Output the CW ID
m_dphi = m_twoPi * m_frequency / m_frameRate;
unsigned const ic0 = m_symbolsLength * 4 * m_nsps;
unsigned j (0);
while (samples != end) {
j = (m_ic - ic0) / m_nspd + 1; // symbol of this sample
bool level {bool (icw[j])};
m_phi += m_dphi;
if (m_phi > m_twoPi) m_phi -= m_twoPi;
qint16 sample ((SOFT_KEYING ? qAbs (m_ramp - 1) :
(m_ramp ? 32767 : 0)) * qSin (m_phi));
if (int (j) <= icw[0] && j < NUM_CW_SYMBOLS) // stop condition
{
samples = load (postProcessSample (sample), samples);
++framesGenerated;
++m_ic;
}
else
{
Q_EMIT stateChanged ((m_state = Idle));
return framesGenerated * bytesPerFrame ();
}
// adjust ramp
if ((m_ramp != 0 && m_ramp != std::numeric_limits<qint16>::min ()) || level != m_cwLevel)
{
// either ramp has terminated at max/min or direction
// has changed
m_ramp += RAMP_INCREMENT; // ramp
}
// if (m_cwLevel != level)
// {
// qDebug () << "@m_ic:" << m_ic << "icw[" << j << "] =" << icw[j] << "@" << framesGenerated << "in numFrames:" << numFrames;
// }
m_cwLevel = level;
}
return framesGenerated * bytesPerFrame ();
}
double const baud (12000.0 / m_nsps);
// fade out parameters (no fade out for tuning)
unsigned const i0 = m_tuning ? 999 * m_nsps :
(m_symbolsLength - 0.017) * 4.0 * m_nsps;
unsigned const i1 = m_tuning ? 999 * m_nsps :
m_symbolsLength * 4.0 * m_nsps;
for (unsigned i = 0; i < numFrames && m_ic <= i1; ++i) {
isym = m_tuning ? 0 : m_ic / (4.0 * m_nsps); //Actual fsample=48000
if (isym != m_isym0 || m_frequency != m_frequency0) {
// qDebug () << "@m_ic:" << m_ic << "itone[" << isym << "] =" << itone[isym] << "@" << i << "in numFrames:" << numFrames;
if(m_toneSpacing==0.0) {
toneFrequency0=m_frequency + itone[isym]*baud;
} else {
toneFrequency0=m_frequency + itone[isym]*m_toneSpacing;
}
m_dphi = m_twoPi * toneFrequency0 / m_frameRate;
m_isym0 = isym;
m_frequency0 = m_frequency;
}
int j=m_ic/480;
if(m_fSpread>0.0 and j!=j0) {
float x1=(float)rand()/RAND_MAX;
float x2=(float)rand()/RAND_MAX;
toneFrequency = toneFrequency0 + 0.5*m_fSpread*(x1+x2-1.0);
m_dphi = m_twoPi * toneFrequency / m_frameRate;
j0=j;
}
m_phi += m_dphi;
if (m_phi > m_twoPi) m_phi -= m_twoPi;
if (m_ic > i0) m_amp = 0.98 * m_amp;
if (m_ic > i1) m_amp = 0.0;
samples = load (postProcessSample (m_amp * qSin (m_phi)), samples);
++framesGenerated;
++m_ic;
}
if (m_amp == 0.0) { // TODO G4WJS: compare double with zero might not be wise
if (icw[0] == 0) {
// no CW ID to send
Q_EMIT stateChanged ((m_state = Idle));
return framesGenerated * bytesPerFrame ();
}
m_phi = 0.0;
}
// done for this chunk - continue on next call
return framesGenerated * bytesPerFrame ();
}
// fall through
case Idle:
break;
}
Q_ASSERT (Idle == m_state);
return 0;
}
qint16 Modulator::postProcessSample (qint16 sample) const
{
if (m_addNoise) { // Test frame, we'll add noise
qint32 s = m_fac * (gran () + sample * m_snr / 32768.0);
if (s > std::numeric_limits<qint16>::max ()) {
s = std::numeric_limits<qint16>::max ();
}
if (s < std::numeric_limits<qint16>::min ()) {
s = std::numeric_limits<qint16>::min ();
}
sample = s;
}
return sample;
}