diff --git a/CMakeLists.txt b/CMakeLists.txt index e634a5e68..efb57fde2 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -24,6 +24,7 @@ option (WSJT_TRACE_CAT "Debugging option that turns on CAT diagnostics.") option (WSJT_TRACE_CAT_POLLS "Debugging option that turns on CAT diagnostics during polling.") option (WSJT_HAMLIB_TRACE "Debugging option that turns on full Hamlib internal diagnostics.") option (WSJT_STANDARD_FILE_LOCATIONS "All non-installation files located in \"Standard\" platfom specific locations." ON) +option (WSJT_SOFT_KEYING "Apply a ramp to CW keying envelope to reduce transients.") # diff --git a/Configuration.cpp b/Configuration.cpp index 0e56b67e3..e43fa8212 100644 --- a/Configuration.cpp +++ b/Configuration.cpp @@ -1891,11 +1891,6 @@ void Configuration::impl::handle_transceiver_update (TransceiverState state) Q_EMIT tx_frequency (TransceiverFactory::split_mode_none != split_mode_selected ? state.tx_frequency () : 0, true); } setup_split_ = false; - - // if (TransceiverFactory::split_mode_emulate == split_mode_selected) - // { - // state.split (true); // complete the illusion - // } } else { diff --git a/EmulateSplitTransceiver.cpp b/EmulateSplitTransceiver.cpp index ba0dbc6ee..a13e40c6b 100644 --- a/EmulateSplitTransceiver.cpp +++ b/EmulateSplitTransceiver.cpp @@ -89,8 +89,7 @@ void EmulateSplitTransceiver::handle_update (TransceiverState state) } else { - // Always emit rigs split state so clients can detect abuse. - state.split (true); + state.split (true); // override rig state #if WSJT_TRACE_CAT qDebug () << "EmulateSplitTransceiver::handle_update: signalling:" << state; diff --git a/Modulator.cpp b/Modulator.cpp index 2153a65e2..441f38e3c 100644 --- a/Modulator.cpp +++ b/Modulator.cpp @@ -12,9 +12,9 @@ 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 true +# define SOFT_KEYING WSJT_SOFT_KEYING #else -# define SOFT_KEYING false +# define SOFT_KEYING 0 #endif double const Modulator::m_twoPi = 2.0 * 3.141592653589793238462; @@ -27,6 +27,7 @@ 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_framesSent {0} , m_frameRate {frameRate} @@ -34,21 +35,27 @@ Modulator::Modulator (unsigned frameRate, unsigned periodLengthInSeconds, QObjec , m_state {Idle} , m_tuning {false} , m_muted {false} + , m_cwLevel {false} { - qsrand (QDateTime::currentMSecsSinceEpoch()); // Initialize random seed + qsrand (QDateTime::currentMSecsSinceEpoch()); // Initialize random + // seed } -void Modulator::start (unsigned symbolsLength, double framesPerSymbol, unsigned frequency, QAudioOutput * stream, Channel channel, bool synchronize, double dBSNR) +void Modulator::start (unsigned symbolsLength, double framesPerSymbol, unsigned frequency, 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 (); } - // qDebug () << "Modulator: Using soft keying for CW is " << SOFT_KEYING;; + m_quickClose = false; m_symbolsLength = symbolsLength; m_framesSent = 0; @@ -83,16 +90,22 @@ void Modulator::start (unsigned symbolsLength, double framesPerSymbol, unsigned m_stream = stream; if (m_stream) { - m_stream->start (this); + m_stream->restart (this); } } -void Modulator::stop () +void Modulator::tune (bool newState) { - if (m_stream) + m_tuning = newState; + if (!m_tuning) { - m_stream->reset (); + stop (true); } +} + +void Modulator::stop (bool quick) +{ + m_quickClose = quick; close (); } @@ -100,7 +113,14 @@ void Modulator::close () { if (m_stream) { - m_stream->stop (); + if (m_quickClose) + { + m_stream->reset (); + } + else + { + m_stream->stop (); + } } if (m_state != Idle) { @@ -138,6 +158,7 @@ qint64 Modulator::readData (char * data, qint64 maxSize) } Q_EMIT stateChanged ((m_state = Active)); + m_cwLevel = false; m_ramp = 0; // prepare for CW wave shaping } // fall through @@ -153,39 +174,41 @@ qint64 Modulator::readData (char * data, qint64 maxSize) qint64 framesGenerated (0); while (samples != end) { + j = (m_ic - ic0) / m_nspd + 1; // symbol of this sample + bool level {static_cast (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)); - j = (m_ic - ic0 - 1) / m_nspd + 1; - bool l0 (icw[j] && icw[j] <= 1); // first element treated specially as it's a count - j = (m_ic - ic0) / m_nspd + 1; - - if ((m_ramp != 0 && m_ramp != std::numeric_limits::min ()) || - !!icw[j] != l0) { - if (!!icw[j] != l0) { - Q_ASSERT (m_ramp == 0 || m_ramp == std::numeric_limits::min ()); + if (j < NUM_CW_SYMBOLS) // stop condition + { + samples = load (postProcessSample (sample), samples); + ++framesGenerated; + ++m_ic; } - m_ramp += RAMP_INCREMENT; // ramp - } - if (j < NUM_CW_SYMBOLS) { // stop condition - // if (!m_ramp && !icw[j]) - // { - // sample = 0; - // } + // adjust ramp + if ((m_ramp != 0 && m_ramp != std::numeric_limits::min ()) || level != m_cwLevel) + { + // either ramp has terminated at max/min or direction + // has changed + m_ramp += RAMP_INCREMENT; // ramp + } - samples = load (postProcessSample (sample), samples); - ++framesGenerated; - ++m_ic; - } + // if (m_cwLevel != level) + // { + // qDebug () << "@m_ic:" << m_ic << "icw[" << j << "] =" << icw[j] << "@" << framesGenerated << "in numFrames:" << numFrames; + // } + + m_cwLevel = level; } if (j > static_cast (icw[0])) { - close (); + Q_EMIT stateChanged ((m_state = Idle)); } m_framesSent += framesGenerated; @@ -202,6 +225,8 @@ qint64 Modulator::readData (char * data, qint64 maxSize) for (unsigned i = 0; i < numFrames; ++i) { isym = m_tuning ? 0 : m_ic / (4.0 * m_nsps); //Actual fsample=48000 if (isym != m_isym0) { + // 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 { @@ -251,7 +276,7 @@ qint64 Modulator::readData (char * data, qint64 maxSize) } Q_ASSERT (Idle == m_state); - close (); + //close (); return 0; } diff --git a/Modulator.hpp b/Modulator.hpp index 230e3f7ec..69e35f4cc 100644 --- a/Modulator.hpp +++ b/Modulator.hpp @@ -1,9 +1,11 @@ #ifndef MODULATOR_HPP__ #define MODULATOR_HPP__ +#include + #include "AudioDevice.hpp" -class QAudioOutput; +class SoundOutput; // // Input device that generates PCM audio frames that encode a message @@ -30,9 +32,9 @@ public: bool isActive () const {return m_state != Idle;} void setWide9(double d1, double d2) {m_toneSpacing=d1; m_fSpread=d2;} - Q_SLOT void start (unsigned symbolsLength, double framesPerSymbol, unsigned frequency, QAudioOutput *, Channel = Mono, bool synchronize = true, double dBSNR = 99.); - Q_SLOT void stop (); - Q_SLOT void tune (bool newState = true) {m_tuning = newState;} + Q_SLOT void start (unsigned symbolsLength, double framesPerSymbol, unsigned frequency, SoundOutput *, Channel = Mono, bool synchronize = true, double dBSNR = 99.); + Q_SLOT void stop (bool quick = false); + Q_SLOT void tune (bool newState = true); Q_SLOT void mute (bool newState = true) {m_muted = newState;} Q_SLOT void setFrequency (unsigned newFrequency) {m_frequency = newFrequency;} Q_SIGNAL void stateChanged (ModulatorState) const; @@ -47,7 +49,8 @@ protected: private: qint16 postProcessSample (qint16 sample) const; - QAudioOutput * m_stream; + SoundOutput * m_stream; + bool m_quickClose; unsigned m_symbolsLength; @@ -75,6 +78,7 @@ private: bool volatile m_muted; bool m_addNoise; + bool m_cwLevel; unsigned m_ic; unsigned m_isym0; qint16 m_ramp; diff --git a/mainwindow.cpp b/mainwindow.cpp index d6934f2f5..155452329 100644 --- a/mainwindow.cpp +++ b/mainwindow.cpp @@ -31,8 +31,8 @@ #include "ui_mainwindow.h" -int itone[NUM_JT65_SYMBOLS]; //Audio tones for all Tx symbols -int icw[NUM_CW_SYMBOLS]; //Dits for CW ID +int volatile itone[NUM_JT65_SYMBOLS]; //Audio tones for all Tx symbols +int volatile icw[NUM_CW_SYMBOLS]; //Dits for CW ID int outBufSize; int rc; @@ -267,30 +267,27 @@ MainWindow::MainWindow(bool multiple, QSettings * settings, QSharedMemory *shdme font.setWeight(75); ui->readFreq->setFont(font); - connect(&m_guiTimer, SIGNAL(timeout()), this, SLOT(guiUpdate())); + connect(&m_guiTimer, &QTimer::timeout, this, &MainWindow::guiUpdate); m_guiTimer.start(100); //Don't change the 100 ms! ptt0Timer = new QTimer(this); ptt0Timer->setSingleShot(true); - connect (ptt0Timer, &QTimer::timeout, &m_modulator, &Modulator::stop); - connect(ptt0Timer, SIGNAL(timeout()), this, SLOT(stopTx2())); + connect(ptt0Timer, &QTimer::timeout, this, &MainWindow::stopTx2); ptt1Timer = new QTimer(this); ptt1Timer->setSingleShot(true); - connect(ptt1Timer, SIGNAL(timeout()), this, SLOT(startTx2())); + connect(ptt1Timer, &QTimer::timeout, this, &MainWindow::startTx2); logQSOTimer = new QTimer(this); logQSOTimer->setSingleShot(true); - connect(logQSOTimer, SIGNAL(timeout()), this, SLOT(on_logQSOButton_clicked())); + connect(logQSOTimer, &QTimer::timeout, this, &MainWindow::on_logQSOButton_clicked); tuneButtonTimer= new QTimer(this); tuneButtonTimer->setSingleShot(true); - connect (tuneButtonTimer, &QTimer::timeout, &m_modulator, &Modulator::stop); - connect(tuneButtonTimer, SIGNAL(timeout()), this, - SLOT(on_stopTxButton_clicked())); + connect(tuneButtonTimer, &QTimer::timeout, this, &MainWindow::on_stopTxButton_clicked); killFileTimer = new QTimer(this); killFileTimer->setSingleShot(true); - connect(killFileTimer, SIGNAL(timeout()), this, SLOT(killFile())); + connect(killFileTimer, &QTimer::timeout, this, &MainWindow::killFile); m_auto=false; m_waterfallAvg = 1; @@ -502,7 +499,10 @@ void MainWindow::readSettings() } m_settings->beginGroup("Common"); - morse_(const_cast (m_config.my_callsign ().toLatin1().constData()),icw,&m_ncw,m_config.my_callsign ().length()); + morse_(const_cast (m_config.my_callsign ().toLatin1().constData()) + , const_cast (icw) + , &m_ncw + , m_config.my_callsign ().length()); m_mode=m_settings->value("Mode","JT9").toString(); m_modeTx=m_settings->value("ModeTx","JT9").toString(); if(m_modeTx.mid(0,3)=="JT9") ui->pbTxMode->setText("Tx JT9 @"); @@ -1484,8 +1484,20 @@ void MainWindow::guiUpdate() // ba2msg(ba,msgsent); int len1=22; int ichk=0,itext=0; - if(m_modeTx=="JT9") genjt9_(message,&ichk,msgsent,itone,&itext,len1,len1); - if(m_modeTx=="JT65") gen65_(message,&ichk,msgsent,itone,&itext,len1,len1); + if(m_modeTx=="JT9") genjt9_(message + , &ichk + , msgsent + , const_cast (itone) + , &itext + , len1 + , len1); + if(m_modeTx=="JT65") gen65_(message + , &ichk + , msgsent + , const_cast (itone) + , &itext + , len1 + , len1); msgsent[22]=0; QString t=QString::fromLatin1(msgsent); if(m_tune) t="TUNE"; @@ -2164,7 +2176,13 @@ void MainWindow::msgtype(QString t, QLineEdit* tx) //msgtype() QByteArray s=t.toUpper().toLocal8Bit(); ba2msg(s,message); int ichk=1,itext=0; - genjt9_(message,&ichk,msgsent,itone,&itext,len1,len1); + genjt9_(message + , &ichk + , msgsent + , const_cast (itone) + , &itext + , len1 + , len1); msgsent[22]=0; bool text=false; if(itext!=0) text=true; @@ -2829,11 +2847,11 @@ void MainWindow::transmit (double snr) { if (m_modeTx == "JT65") { - Q_EMIT sendMessage (NUM_JT65_SYMBOLS, 4096.0 * 12000.0 / 11025.0, m_txFreq - m_XIT, m_soundOutput.stream (), m_config.audio_output_channel (), true, snr); + Q_EMIT sendMessage (NUM_JT65_SYMBOLS, 4096.0 * 12000.0 / 11025.0, m_txFreq - m_XIT, &m_soundOutput, m_config.audio_output_channel (), true, snr); } else { - Q_EMIT sendMessage (NUM_JT9_SYMBOLS, m_nsps, m_txFreq - m_XIT, m_soundOutput.stream (), m_config.audio_output_channel (), true, snr); + Q_EMIT sendMessage (NUM_JT9_SYMBOLS, m_nsps, m_txFreq - m_XIT, &m_soundOutput, m_config.audio_output_channel (), true, snr); } } diff --git a/mainwindow.h b/mainwindow.h index 51bb8509b..48f13ebcc 100644 --- a/mainwindow.h +++ b/mainwindow.h @@ -33,8 +33,8 @@ #define NUM_CW_SYMBOLS 250 #define TX_SAMPLE_RATE 48000 -extern int itone[NUM_JT65_SYMBOLS]; //Audio tones for all Tx symbols -extern int icw[NUM_CW_SYMBOLS]; //Dits for CW ID +extern int volatile itone[NUM_JT65_SYMBOLS]; //Audio tones for all Tx symbols +extern int volatile icw[NUM_CW_SYMBOLS]; //Dits for CW ID //--------------------------------------------------------------- MainWindow @@ -48,7 +48,6 @@ class WideGraph; class LogQSO; class Transceiver; class Astro; -class QAudioOutput; class MainWindow : public QMainWindow { @@ -190,9 +189,9 @@ private: Q_SIGNAL void finished () const; Q_SIGNAL void muteAudioOutput (bool = true) const; Q_SIGNAL void transmitFrequency (unsigned) const; - Q_SIGNAL void endTransmitMessage () const; + Q_SIGNAL void endTransmitMessage (bool quick = false) const; Q_SIGNAL void tune (bool = true) const; - Q_SIGNAL void sendMessage (unsigned symbolsLength, double framesPerSymbol, unsigned frequency, QAudioOutput *, AudioDevice::Channel = AudioDevice::Mono, bool synchronize = true, double dBSNR = 99.) const; + Q_SIGNAL void sendMessage (unsigned symbolsLength, double framesPerSymbol, unsigned frequency, SoundOutput *, AudioDevice::Channel = AudioDevice::Mono, bool synchronize = true, double dBSNR = 99.) const; Q_SIGNAL void outAttenuationChanged (qreal) const; private: diff --git a/mainwindow.ui b/mainwindow.ui index 49f3e567a..87cc35fe0 100644 --- a/mainwindow.ui +++ b/mainwindow.ui @@ -2106,6 +2106,9 @@ p, li { white-space: pre-wrap; } Adjust Tx audio level + + 300 + 0 @@ -2122,7 +2125,7 @@ p, li { white-space: pre-wrap; } QSlider::TicksBelow - 0 + 10 diff --git a/soundout.cpp b/soundout.cpp index 672bb9699..9e5843e91 100644 --- a/soundout.cpp +++ b/soundout.cpp @@ -50,6 +50,8 @@ void SoundOutput::setFormat (QAudioDeviceInfo const& device, unsigned channels, { Q_ASSERT (0 < channels && channels < 3); + m_msBuffered = msBuffered; + QAudioFormat format (device.preferredFormat ()); format.setChannelCount (channels); @@ -74,6 +76,11 @@ void SoundOutput::setFormat (QAudioDeviceInfo const& device, unsigned channels, connect (m_stream.data(), &QAudioOutput::stateChanged, this, &SoundOutput::handleStateChanged); // qDebug() << "A" << m_volume << m_stream->notifyInterval(); +} + +void SoundOutput::restart (QIODevice * source) +{ + Q_ASSERT (m_stream); // // This buffer size is critical since for proper sound streaming. If @@ -88,8 +95,11 @@ void SoundOutput::setFormat (QAudioDeviceInfo const& device, unsigned channels, // we have to set this before every start on the stream because the // Windows implementation seems to forget the buffer size after a // stop. - m_stream->setBufferSize (m_stream->format().bytesForDuration((msBuffered ? msBuffered : MS_BUFFERED) * 1000)); - // qDebug() << "B" << m_stream->bufferSize() << m_stream->periodSize() << m_stream->notifyInterval(); + m_stream->setBufferSize (m_stream->format().bytesForDuration((m_msBuffered ? m_msBuffered : MS_BUFFERED) * 1000)); + // qDebug() << "B" << m_stream->bufferSize() << + // m_stream->periodSize() << m_stream->notifyInterval(); + + m_stream->start (source); } void SoundOutput::suspend () @@ -110,6 +120,24 @@ void SoundOutput::resume () } } +void SoundOutput::reset () +{ + if (m_stream) + { + m_stream->reset (); + audioError (); + } +} + +void SoundOutput::stop () +{ + if (m_stream) + { + m_stream->stop (); + audioError (); + } +} + qreal SoundOutput::attenuation () const { return -(10. * qLn (m_volume) / qLn (10.)); @@ -117,7 +145,7 @@ qreal SoundOutput::attenuation () const void SoundOutput::setAttenuation (qreal a) { - Q_ASSERT (0. <= a && a <= 99.); + Q_ASSERT (0. <= a && a <= 999.); m_volume = qPow (10., -a / 10.); // qDebug () << "SoundOut: attn = " << a << ", vol = " << m_volume; if (m_stream) @@ -137,6 +165,8 @@ void SoundOutput::resetAttenuation () void SoundOutput::handleStateChanged (QAudio::State newState) { + // qDebug () << "SoundOutput::handleStateChanged: newState:" << newState; + switch (newState) { case QAudio::IdleState: diff --git a/soundout.h b/soundout.h index 11f9df884..1e4b3a948 100644 --- a/soundout.h +++ b/soundout.h @@ -17,13 +17,21 @@ class SoundOutput Q_OBJECT; public: + SoundOutput () + : m_msBuffered {0u} + , m_volume {1.0} + { + } + qreal attenuation () const; - QAudioOutput * stream () {return m_stream.data ();} public Q_SLOTS: void setFormat (QAudioDeviceInfo const& device, unsigned channels, unsigned msBuffered = 0u); + void restart (QIODevice *); void suspend (); void resume (); + void reset (); + void stop (); void setAttenuation (qreal); /* unsigned */ void resetAttenuation (); /* to zero */ @@ -39,6 +47,7 @@ private Q_SLOTS: private: QScopedPointer m_stream; + unsigned m_msBuffered; qreal m_volume; }; diff --git a/wsjtx_config.h.in b/wsjtx_config.h.in index 222554848..0d388e77c 100644 --- a/wsjtx_config.h.in +++ b/wsjtx_config.h.in @@ -22,6 +22,7 @@ #cmakedefine01 WSJT_TRACE_CAT_POLLS #cmakedefine01 WSJT_HAMLIB_TRACE #cmakedefine01 WSJT_STANDARD_FILE_LOCATIONS +#cmakedefine01 WSJT_SOFT_KEYING #define WSJTX_STRINGIZE1(x) #x #define WSJTX_STRINGIZE(x) WSJTX_STRINGIZE1(x)