WSJT-X/Modulator.cpp
Joe Taylor 1cc278cce6 1. Enable CW ID for fast modes.
2. Correct several more flaws in making GUI controls visible or invisible.


git-svn-id: svn+ssh://svn.code.sf.net/p/wsjt/wsjt/branches/wsjtx@7170 ab8295b8-cf94-4d9e-aec4-7959e3be5d79
2016-10-13 16:19:24 +00:00

319 lines
9.3 KiB
C++

#include "Modulator.hpp"
#include <limits>
#include <qmath.h>
#include <QDateTime>
#include <QDebug>
#include "mainwindow.h"
#include "soundout.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 constexpr Modulator::m_twoPi;
// float wpm=20.0;
// unsigned m_nspd=1.2*48000.0/wpm;
// m_nspd=3072; //18.75 WPM
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_cwLevel {false}
, m_j0 {-1}
, m_toneFrequency0 {1500.0}
{
}
void Modulator::start (unsigned symbolsLength, double framesPerSymbol,
double frequency, double toneSpacing,
SoundOutput * stream, Channel channel,
bool synchronize, bool fastMode, double dBSNR, int TRperiod)
{
Q_ASSERT (stream);
// Time according to this computer which becomes our base time
qint64 ms0 = QDateTime::currentMSecsSinceEpoch() % 86400000;
if (m_state != Idle)
{
stop ();
}
m_quickClose = false;
m_symbolsLength = symbolsLength;
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;
// 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
if(m_bFastMode) m_ic=0;
m_silentFrames = 0;
// calculate number of silent frames to send
if (synchronize && !m_tuning && !m_bFastMode) {
m_silentFrames = m_ic + m_frameRate - (mstr * m_frameRate / 1000);
}
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)
{
double toneFrequency=1500.0;
if(m_nsps==6) {
toneFrequency=1000.0;
m_frequency=1000.0;
m_frequency0=1000.0;
}
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);
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 int isym=0;
if(!m_tuning) isym=m_ic/(4.0*m_nsps); // Actual fsample=48000
bool slowCwId=((isym >= m_symbolsLength) && (icw[0] > 0)) && (!m_bFastMode);
bool fastCwId=false;
static bool bCwId=false;
qint64 ms = QDateTime::currentMSecsSinceEpoch();
float tsec=0.001*(ms % (1000*m_TRperiod));
if(m_bFastMode and (icw[0]>0) and (tsec>(m_TRperiod-5.0))) fastCwId=true;
if(!m_bFastMode) m_nspd=2560; // 22.5 WPM
if(slowCwId or fastCwId) { // Transmit CW ID?
m_dphi = m_twoPi*m_frequency/m_frameRate;
if(m_bFastMode and !bCwId) {
m_frequency=1500; // Set params for CW ID
m_symbolsLength=126;
m_nsps=4096.0*12000.0/11025.0;
m_ic=2246949;
m_nspd=2560; // 22.5 WPM
if(icw[0]*m_nspd/48000.0 > 4.1) m_nspd=4.1*48000/icw[0]; //Faster CW for long calls
}
bCwId=true;
unsigned 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=0;
float amp=32767.0;
if(m_ramp!=0) {
float x=qSin(float(m_phi));
if(SOFT_KEYING) {
amp=qAbs(qint32(m_ramp));
if(amp>32767.0) amp=32767.0;
}
sample=round(amp*x);
}
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
}
m_cwLevel = level;
}
return framesGenerated * bytesPerFrame ();
} else {
bCwId=false;
} //End of code for CW ID
double const baud (12000.0 / m_nsps);
// fade out parameters (no fade out for tuning)
unsigned int i0,i1;
if(m_tuning) {
i1 = i0 = (m_bFastMode ? 999999 : 9999) * m_nsps;
} else {
i0=(m_symbolsLength - 0.017) * 4.0 * m_nsps;
i1= m_symbolsLength * 4.0 * m_nsps;
}
if(m_bFastMode and !m_tuning) {
i1=m_TRperiod*48000 - 24000;
i0=i1-816;
}
for (unsigned i = 0; i < numFrames && m_ic <= i1; ++i) {
isym=0;
if(!m_tuning) 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) {
m_toneFrequency0=itone[0];
} else {
if(m_toneSpacing==0.0) {
m_toneFrequency0=m_frequency + itone[isym]*baud;
} else {
m_toneFrequency0=m_frequency + itone[isym]*m_toneSpacing;
}
}
// qDebug() << "B" << m_bFastMode << m_ic << numFrames << isym << itone[isym]
// << m_toneFrequency0 << m_nsps;
m_dphi = m_twoPi * m_toneFrequency0 / m_frameRate;
m_isym0 = isym;
m_frequency0 = m_frequency; //???
}
int j=m_ic/480;
if(m_fSpread>0.0 and j!=m_j0) {
float x1=(float)qrand()/RAND_MAX;
float x2=(float)qrand()/RAND_MAX;
toneFrequency = m_toneFrequency0 + 0.5*m_fSpread*(x1+x2-1.0);
m_dphi = m_twoPi * toneFrequency / m_frameRate;
m_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;
}
m_frequency0 = m_frequency;
// 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;
}