From f076c37c19c31769db70e3b14bca99bae1c4a10e Mon Sep 17 00:00:00 2001 From: Bill Somerville Date: Sun, 6 Apr 2014 21:58:11 +0000 Subject: [PATCH] Made the soft keying CW wave shaper a CMake option (off by default). Reorganized Modulator interface so that it can control the stream it writes to. Make sure only QAudioOutput::stop is called at the end of sending rather than QAudioOutput::reset which discards pending samples. Added a quick close option to the Modulator::stop slot to discard pending buffers if required. Fix issue in CW synthesizer that was causing CW to be inverted occasionally. Made global arrays of symbols volatile because compiler waa optimizing away reads in sound thread. These global variables must go eventually as they are a multi-threading hazard. Simplified TX sequencing to remove some duplicate signals. Increased range of TX attenuator from 10dB to 30dB. This is mainly for non-Windows platforms where the attenuator isn't linearized correctly. git-svn-id: svn+ssh://svn.code.sf.net/p/wsjt/wsjt/branches/wsjtx@3985 ab8295b8-cf94-4d9e-aec4-7959e3be5d79 --- CMakeLists.txt | 1 + Configuration.cpp | 5 --- EmulateSplitTransceiver.cpp | 3 +- Modulator.cpp | 87 ++++++++++++++++++++++++------------- Modulator.hpp | 14 +++--- mainwindow.cpp | 52 ++++++++++++++-------- mainwindow.h | 9 ++-- mainwindow.ui | 5 ++- soundout.cpp | 36 +++++++++++++-- soundout.h | 11 ++++- wsjtx_config.h.in | 1 + 11 files changed, 154 insertions(+), 70 deletions(-) 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)