mirror of
https://github.com/saitohirga/WSJT-X.git
synced 2025-06-19 23:12:51 -04:00
Static storage variables that should have been class members are made so. This ensures that if they are used as initialization one time switches then they will operate correctly when their class instantiated more than once. This now happoens for most classes due to the configurations switching facility which destroys all windows and re-instantiates them. git-svn-id: svn+ssh://svn.code.sf.net/p/wsjt/wsjt/branches/wsjtx@6661 ab8295b8-cf94-4d9e-aec4-7959e3be5d79
306 lines
8.6 KiB
C++
306 lines
8.6 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
|
|
unsigned const Modulator::m_nspd;
|
|
|
|
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
|
|
if(m_bFastMode) m_ic=0;
|
|
// into period where N is the next whole second
|
|
|
|
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(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
|
|
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=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 ();
|
|
}
|
|
|
|
double const baud (12000.0 / m_nsps);
|
|
// fade out parameters (no fade out for tuning)
|
|
unsigned int i0,i1;
|
|
if(m_tuning) {
|
|
i0=9999*m_nsps;
|
|
i1=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;
|
|
}
|