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
This commit is contained in:
Bill Somerville 2014-04-06 21:58:11 +00:00
parent dbda79e7a2
commit f076c37c19
11 changed files with 154 additions and 70 deletions

View File

@ -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.")
#

View File

@ -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
{

View File

@ -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;

View File

@ -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<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));
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<qint16>::min ()) ||
!!icw[j] != l0) {
if (!!icw[j] != l0) {
Q_ASSERT (m_ramp == 0 || m_ramp == std::numeric_limits<qint16>::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<qint16>::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<unsigned> (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;
}

View File

@ -1,9 +1,11 @@
#ifndef MODULATOR_HPP__
#define MODULATOR_HPP__
#include <QAudio>
#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;

View File

@ -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<char *> (m_config.my_callsign ().toLatin1().constData()),icw,&m_ncw,m_config.my_callsign ().length());
morse_(const_cast<char *> (m_config.my_callsign ().toLatin1().constData())
, const_cast<int *> (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<int *> (itone)
, &itext
, len1
, len1);
if(m_modeTx=="JT65") gen65_(message
, &ichk
, msgsent
, const_cast<int *> (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<int *> (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);
}
}

View File

@ -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:

View File

@ -2106,6 +2106,9 @@ p, li { white-space: pre-wrap; }
<property name="toolTip">
<string>Adjust Tx audio level</string>
</property>
<property name="maximum">
<number>300</number>
</property>
<property name="value">
<number>0</number>
</property>
@ -2122,7 +2125,7 @@ p, li { white-space: pre-wrap; }
<enum>QSlider::TicksBelow</enum>
</property>
<property name="tickInterval">
<number>0</number>
<number>10</number>
</property>
</widget>
</item>

View File

@ -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:

View File

@ -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<QAudioOutput> m_stream;
unsigned m_msBuffered;
qreal m_volume;
};

View File

@ -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)