diff --git a/Detector.cpp b/Detector.cpp index bf3f4b8cb..2318e9475 100644 --- a/Detector.cpp +++ b/Detector.cpp @@ -1,93 +1,95 @@ -#include "Detector.hpp" - -#include - -#include -#include - -#include "commons.h" - -Detector::Detector (unsigned frameRate, unsigned periodLengthInSeconds, unsigned bytesPerSignal, QObject * parent) - : QIODevice (parent) - , m_frameRate (frameRate) - , m_period (periodLengthInSeconds) - , m_bytesPerSignal (bytesPerSignal) - , m_monitoring (false) - , m_starting (false) -{ - clear (); -} - -bool Detector::reset () -{ - clear (); - return QIODevice::reset (); -} - -void Detector::clear () -{ - // set index to roughly where we are in time (1s resolution) - jt9com_.kin = secondInPeriod () * m_frameRate; - - // fill buffer with zeros - std::fill (jt9com_.d2, jt9com_.d2 + sizeof (jt9com_.d2) / sizeof (jt9com_.d2[0]), 0); -} - -qint64 Detector::writeData (char const * data, qint64 maxSize) -{ - Q_ASSERT (!(maxSize % sizeof (jt9com_.d2[0]))); // no torn frames - Q_ASSERT (!(reinterpret_cast (data) % __alignof__ (frame_t))); // data is aligned as frame_t would be - - frame_t const * frames (reinterpret_cast (data)); - - qint64 framesAcceptable (sizeof (jt9com_.d2) / sizeof (jt9com_.d2[0]) - jt9com_.kin); - qint64 framesAccepted (std::min (maxSize / sizeof (jt9com_.d2[0]), framesAcceptable)); - - if (framesAccepted < maxSize / sizeof (jt9com_.d2[0])) - { - qDebug () << "dropped " << maxSize / sizeof (jt9com_.d2[0]) - framesAccepted << " frames of data on the floor!\n"; - } - - std::copy (frames, frames + framesAccepted, &jt9com_.d2[jt9com_.kin]); - - unsigned lastSignalIndex (jt9com_.kin * sizeof (jt9com_.d2[0]) / m_bytesPerSignal); - jt9com_.kin += framesAccepted; - unsigned currentSignalIndex (jt9com_.kin * sizeof (jt9com_.d2[0]) / m_bytesPerSignal); - - if (currentSignalIndex != lastSignalIndex && m_monitoring) - { - Q_EMIT bytesWritten (currentSignalIndex * m_bytesPerSignal); - } - - if (!secondInPeriod ()) - { - if (!m_starting) - { - // next samples will be in new period so wrap around to - // start of buffer - // - // we don't bother calling reset () since we expect to fill - // the whole buffer and don't need to waste cycles zeroing - jt9com_.kin = 0; - m_starting = true; - } - } - else if (m_starting) - { - m_starting = false; - } - - return maxSize; // we drop any data past the end of - // the buffer on the floor until the - // next period starts -} - -unsigned Detector::secondInPeriod () const -{ - // we take the time of the data as the following assuming no latency - // delivering it to us (not true but close enough for us) - qint64 now (QDateTime::currentMSecsSinceEpoch ()); - - unsigned secondInToday ((now % 86400000LL) / 1000); - return secondInToday % m_period; -} +#include "Detector.hpp" + +#include +#include +#include + +#include "commons.h" + +Detector::Detector (unsigned frameRate, unsigned periodLengthInSeconds, unsigned bytesPerSignal, QObject * parent) + : QIODevice (parent) + , m_frameRate (frameRate) + , m_period (periodLengthInSeconds) + , m_bytesPerSignal (bytesPerSignal) + , m_monitoring (false) + , m_starting (false) +{ + clear (); +} + +bool Detector::reset () +{ + clear (); + return QIODevice::reset (); +} + +void Detector::clear () +{ + // set index to roughly where we are in time (1ms resolution) + // qint64 now (QDateTime::currentMSecsSinceEpoch ()); + // unsigned msInPeriod ((now % 86400000LL) % (m_period * 1000)); + // jt9com_.kin = qMin ((msInPeriod * m_frameRate) / 1000, static_cast (sizeof (jt9com_.d2) / sizeof (jt9com_.d2[0]))); + jt9com_.kin = 0; + + // fill buffer with zeros + qFill (jt9com_.d2, jt9com_.d2 + sizeof (jt9com_.d2) / sizeof (jt9com_.d2[0]), 0); +} + +qint64 Detector::writeData (char const * data, qint64 maxSize) +{ + Q_ASSERT (!(maxSize % static_cast (sizeof (frame_t)))); // no torn frames + Q_ASSERT (!(reinterpret_cast (data) % __alignof__ (frame_t))); // data is aligned as frame_t would be + + frame_t const * frames (reinterpret_cast (data)); + + qint64 framesAcceptable (sizeof (jt9com_.d2) / sizeof (jt9com_.d2[0]) - jt9com_.kin); + qint64 framesAccepted (qMin (static_cast (maxSize / sizeof (jt9com_.d2[0])), framesAcceptable)); + + if (framesAccepted < static_cast (maxSize / sizeof (jt9com_.d2[0]))) + { + qDebug () << "dropped " << maxSize / sizeof (jt9com_.d2[0]) - framesAccepted << " frames of data on the floor!"; + } + + qCopy (frames, frames + framesAccepted, &jt9com_.d2[jt9com_.kin]); + + unsigned lastSignalIndex (jt9com_.kin * sizeof (jt9com_.d2[0]) / m_bytesPerSignal); + jt9com_.kin += framesAccepted; + unsigned currentSignalIndex (jt9com_.kin * sizeof (jt9com_.d2[0]) / m_bytesPerSignal); + + if (currentSignalIndex != lastSignalIndex && m_monitoring) + { + Q_EMIT bytesWritten (currentSignalIndex * m_bytesPerSignal); + } + + if (!secondInPeriod ()) + { + if (!m_starting) + { + // next samples will be in new period so wrap around to + // start of buffer + // + // we don't bother calling reset () since we expect to fill + // the whole buffer and don't need to waste cycles zeroing + jt9com_.kin = 0; + m_starting = true; + } + } + else if (m_starting) + { + m_starting = false; + } + + return maxSize; // we drop any data past the end of + // the buffer on the floor until the + // next period starts +} + +unsigned Detector::secondInPeriod () const +{ + // we take the time of the data as the following assuming no latency + // delivering it to us (not true but close enough for us) + qint64 now (QDateTime::currentMSecsSinceEpoch ()); + + unsigned secondInToday ((now % 86400000LL) / 1000); + return secondInToday % m_period; +} diff --git a/Detector.hpp b/Detector.hpp index 7ddb08121..f842e70b9 100644 --- a/Detector.hpp +++ b/Detector.hpp @@ -1,67 +1,67 @@ -#ifndef DETECTOR_HPP__ -#define DETECTOR_HPP__ - -#include - -#include - -// -// output device that distributes data in predefined chunks via a signal -// -// the underlying device for this abstraction is just the buffer that -// stores samples throughout a receiving period -// -class Detector : public QIODevice -{ - Q_OBJECT; - - Q_PROPERTY (bool monitoring READ isMonitoring WRITE setMonitoring); - -private: - Q_DISABLE_COPY (Detector); - -public: - // - // if the data buffer were not global storage and fixed size then we - // might want maximum size passed as constructor arguments - // - Detector (unsigned frameRate, unsigned periodLengthInSeconds, unsigned bytesPerSignal, QObject * parent = 0); - - bool open () - { - // we only support data consumption and want it as fast as possible - return QIODevice::open (QIODevice::WriteOnly | QIODevice::Unbuffered); - } - - bool isSequential () const - { - return true; - } - - bool isMonitoring () const {return m_monitoring;} - void setMonitoring (bool newState) {m_monitoring = newState;} - - bool reset (); - -protected: - qint64 readData (char * /* data */, qint64 /* maxSize */) - { - return -1; // we don't produce data - } - - qint64 writeData (char const * data, qint64 maxSize); - -private: - typedef int16_t frame_t; - - void clear (); // discard buffer contents - unsigned secondInPeriod () const; - - unsigned m_frameRate; - unsigned m_period; - unsigned m_bytesPerSignal; - bool m_monitoring; - bool m_starting; -}; - -#endif +#ifndef DETECTOR_HPP__ +#define DETECTOR_HPP__ + +#include + +#include + +// +// output device that distributes data in predefined chunks via a signal +// +// the underlying device for this abstraction is just the buffer that +// stores samples throughout a receiving period +// +class Detector : public QIODevice +{ + Q_OBJECT; + + Q_PROPERTY (bool monitoring READ isMonitoring WRITE setMonitoring); + +private: + Q_DISABLE_COPY (Detector); + +public: + // + // if the data buffer were not global storage and fixed size then we + // might want maximum size passed as constructor arguments + // + Detector (unsigned frameRate, unsigned periodLengthInSeconds, unsigned bytesPerSignal, QObject * parent = 0); + + bool open () + { + // we only support data consumption and want it as fast as possible + return QIODevice::open (QIODevice::WriteOnly | QIODevice::Unbuffered); + } + + bool isSequential () const + { + return true; + } + + bool isMonitoring () const {return m_monitoring;} + void setMonitoring (bool newState) {m_monitoring = newState;} + + bool reset (); + +protected: + qint64 readData (char * /* data */, qint64 /* maxSize */) + { + return -1; // we don't produce data + } + + qint64 writeData (char const * data, qint64 maxSize); + +private: + typedef qint16 frame_t; + + void clear (); // discard buffer contents + unsigned secondInPeriod () const; + + unsigned m_frameRate; + unsigned m_period; + unsigned m_bytesPerSignal; + bool m_monitoring; + bool m_starting; +}; + +#endif diff --git a/Modulator.cpp b/Modulator.cpp index c1178b6f8..30cbcf867 100644 --- a/Modulator.cpp +++ b/Modulator.cpp @@ -1,195 +1,262 @@ -#include "Modulator.hpp" - -#include -#include -#include -#include - -#include - -extern float gran(); // Noise generator (for tests only) - -double const Modulator::m_twoPi = 2.0 * 3.141592653589793238462; - -// float wpm=20.0; -// unsigned m_nspd=1.2*48000.0/wpm; -// m_nspd=3072; //18.75 WPM -unsigned const Modulator::m_nspd = 2048 + 512; // 22.5 WPM - -Modulator::Modulator (unsigned frameRate, unsigned periodLengthInSeconds, QObject * parent) - : QIODevice (parent) - , m_frameRate (frameRate) - , m_period (periodLengthInSeconds) - , m_state (Idle) - , m_phi (0.) - , m_ic (0) - , m_isym0 (std::numeric_limits::max ()) // ensure we set up first symbol tone -{ -} - -bool Modulator::open (std::vector const * symbols, std::vector const * cw, double framesPerSymbol, unsigned frequency, double dBSNR) -{ - m_symbols.reset (symbols); // take over ownership (cannot throw) - m_cw.reset (cw); // take over ownership (cannot throw) - m_addNoise = dBSNR < 0.; - m_nsps = framesPerSymbol; - m_frequency = frequency; - m_amp = std::numeric_limits::max (); - m_state = Idle; - - // noise generator parameters - if (m_addNoise) - { - m_snr = std::pow (10.0, 0.05 * (dBSNR - 6.0)); - m_fac = 3000.0; - if (m_snr > 1.0) - { - m_fac = 3000.0 / m_snr; - } - } - - return QIODevice::open (QIODevice::ReadOnly); -} - -qint64 Modulator::readData (char * data, qint64 maxSize) -{ - frame_t * frames (reinterpret_cast (data)); - unsigned numFrames (maxSize / sizeof (frame_t)); - - switch (m_state) - { - case Idle: - { - // Time according to this computer - qint64 ms = QDateTime::currentMSecsSinceEpoch() % 86400000; - unsigned mstr = ms % (1000 * m_period); - if (mstr < 1000) // send silence up to first second - { - std::fill (frames, frames + numFrames, 0); // silence - return numFrames * sizeof (frame_t); - } - m_ic = (mstr - 1000) * 48; - - std::srand (mstr); // Initialize random seed - - m_state = Active; - } - // fall through - - case Active: - { - unsigned isym (m_tuning ? 0 : m_ic / (4.0 * m_nsps)); // Actual fsample=48000 - - if (isym >= m_symbols->size () && (*m_cw)[0] > 0) - { - // Output the CW ID - m_dphi = m_twoPi * m_frequency / m_frameRate; - - unsigned const ic0 = m_symbols->size () * 4 * m_nsps; - unsigned j (0); - for (unsigned i = 0; i < numFrames; ++i) - { - m_phi += m_dphi; - if (m_phi > m_twoPi) - { - m_phi -= m_twoPi; - } - frame_t frame = std::numeric_limits::max () * std::sin (m_phi); - j = (m_ic - ic0) / m_nspd + 1; - if (!(*m_cw)[j]) - { - frame = 0; - } - - frame = postProcessFrame (frame); - - *frames++ = frame; //left - ++m_ic; - } - if (j > static_cast ((*m_cw)[0])) - { - m_state = Done; - } - return numFrames * sizeof (frame_t); - } - - double const baud (12000.0 / m_nsps); - - // fade out parameters (no fade out for tuning) - unsigned const i0 = m_tuning ? 999 * m_nsps : (m_symbols->size () - 0.017) * 4.0 * m_nsps; - unsigned const i1 = m_tuning ? 999 * m_nsps : m_symbols->size () * 4.0 * m_nsps; - - for (unsigned i = 0; i < numFrames; ++i) - { - isym = m_tuning ? 0 : m_ic / (4.0 * m_nsps); //Actual fsample=48000 - if (isym != m_isym0) - { - double toneFrequency = m_frequency + (*m_symbols)[isym] * baud; - m_dphi = m_twoPi * toneFrequency / m_frameRate; - m_isym0 = isym; - } - 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; - } - frame_t frame (m_amp * std::sin (m_phi)); - frame = postProcessFrame (frame); - *frames++ = frame; //left - ++m_ic; - } - - if (m_amp == 0.0) // TODO G4WJS: compare double with zero might not be wise - { - if ((*m_cw)[0] == 0) - { - // no CW ID to send - m_state = Done; - return numFrames * sizeof (frame_t); - } - - m_phi = 0.0; - } - - // done for this chunk - continue on next call - return numFrames * sizeof (frame_t); - } - - case Done: - break; - } - - Q_ASSERT (m_state == Done); - return 0; -} - -Modulator::frame_t Modulator::postProcessFrame (frame_t frame) const -{ - if (m_muted) // silent frame - { - return 0; - } - - if (m_addNoise) - { - int i4 = m_fac * (gran () + frame * m_snr / 32768.0); - if (i4 > std::numeric_limits::max ()) - { - i4 = std::numeric_limits::max (); - } - if (i4 < std::numeric_limits::min ()) - { - i4 = std::numeric_limits::min (); - } - frame = i4; - } - return frame; -} +#include "Modulator.hpp" + +#include + +#include +#include +#include + +#include "mainwindow.h" + +extern float gran(); // Noise generator (for tests only) + +double const Modulator::m_twoPi = 2.0 * 3.141592653589793238462; + +// float wpm=20.0; +// unsigned m_nspd=1.2*48000.0/wpm; +// m_nspd=3072; //18.75 WPM +unsigned const Modulator::m_nspd = 2048 + 512; // 22.5 WPM + +Modulator::Modulator (unsigned frameRate, unsigned periodLengthInSeconds, QObject * parent) + : QIODevice (parent) + , m_frameRate (frameRate) + , m_period (periodLengthInSeconds) + , m_framesSent (0) + , m_state (Idle) + , m_tuning (false) + , m_muted (false) + , m_phi (0.) +{ + qsrand (QDateTime::currentMSecsSinceEpoch()); // Initialize random seed +} + +void Modulator::send (unsigned symbolsLength, double framesPerSymbol, unsigned frequency, bool synchronize, double dBSNR) +{ + // Time according to this computer which becomes our base time + qint64 ms0 = QDateTime::currentMSecsSinceEpoch() % 86400000; + + m_symbolsLength = symbolsLength; + + m_framesSent = 0; + m_isym0 = std::numeric_limits::max (); // ensure we set up first symbol tone + m_addNoise = dBSNR < 0.; + m_nsps = framesPerSymbol; + m_frequency = frequency; + m_amp = std::numeric_limits::max (); + + // 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 + + m_silentFrames = 0; + if (synchronize && !m_tuning) // calculate number of silent frames to send + { + m_silentFrames = m_ic + m_frameRate - (mstr * m_frameRate / 1000); + } + + qDebug () << "Modulator: starting at " << m_ic / m_frameRate << " sec, sending " << m_silentFrames << " silent frames"; + + Q_EMIT stateChanged ((m_state = (synchronize && m_silentFrames) ? Synchronizing : Active)); +} + +qint64 Modulator::readData (char * data, qint64 maxSize) +{ + Q_ASSERT (!(maxSize % static_cast (sizeof (frame_t)))); // no torn frames + Q_ASSERT (!(reinterpret_cast (data) % __alignof__ (frame_t))); // data is aligned as frame_t would be + + frame_t * frames (reinterpret_cast (data)); + qint64 numFrames (maxSize / sizeof (frame_t)); + + qDebug () << "Modulator: " << numFrames << " requested, m_ic = " << m_ic << ", tune mode is " << m_tuning; + + switch (m_state) + { + case Synchronizing: + { + if (m_silentFrames) // send silence up to first second + { + frame_t frame; + for (unsigned c = 0; c < NUM_CHANNELS; ++c) + { + frame.channel[c] = 0; // silence + } + + numFrames = qMin (m_silentFrames, numFrames); + qFill (frames, frames + numFrames, frame); + m_silentFrames -= numFrames; + return numFrames * sizeof (frame_t); + } + + Q_EMIT stateChanged ((m_state = Active)); + } + // fall through + + case Active: + { + unsigned isym (m_tuning ? 0 : 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); + qint64 framesGenerated (0); + for (unsigned i = 0; i < numFrames; ++i) + { + m_phi += m_dphi; + if (m_phi > m_twoPi) + { + m_phi -= m_twoPi; + } + + frame_t frame; + for (unsigned c = 0; c < NUM_CHANNELS; ++c) + { + frame.channel[c] = std::numeric_limits::max () * qSin (m_phi); + } + + j = (m_ic - ic0) / m_nspd + 1; + if (j < NUM_CW_SYMBOLS) // stop condition + { + if (!icw[j]) + { + for (unsigned c = 0; c < NUM_CHANNELS; ++c) + { + frame.channel[c] = 0; + } + } + + frame = postProcessFrame (frame); + + *frames++ = frame; + ++framesGenerated; + + ++m_ic; + } + } + + if (j > static_cast (icw[0])) + { + Q_EMIT stateChanged ((m_state = Idle)); + } + + m_framesSent += framesGenerated; + return framesGenerated * sizeof (frame_t); + } + + double const baud (12000.0 / m_nsps); + + // fade out parameters (no fade out for tuning) + unsigned const i0 = m_tuning ? 999 * m_nsps : (m_symbolsLength - 0.017) * 4.0 * m_nsps; + unsigned const i1 = m_tuning ? 999 * m_nsps : m_symbolsLength * 4.0 * m_nsps; + + for (unsigned i = 0; i < numFrames; ++i) + { + isym = m_tuning ? 0 : m_ic / (4.0 * m_nsps); //Actual fsample=48000 + if (isym != m_isym0) + { + double toneFrequency = m_frequency + itone[isym] * baud; + m_dphi = m_twoPi * toneFrequency / m_frameRate; + m_isym0 = isym; + } + 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; + } + + frame_t frame; + for (unsigned c = 0; c < NUM_CHANNELS; ++c) + { + frame.channel[c] = m_amp * qSin (m_phi); + } + + frame = postProcessFrame (frame); + + *frames++ = frame; + + ++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)); + m_framesSent += numFrames; + return numFrames * sizeof (frame_t); + } + + m_phi = 0.0; + } + + // done for this chunk - continue on next call + m_framesSent += numFrames; + return numFrames * sizeof (frame_t); + } + Q_EMIT stateChanged ((m_state = Idle)); + // fall through + + case Idle: + break; + } + + Q_ASSERT (Idle == m_state); + return 0; +} + +Modulator::frame_t Modulator::postProcessFrame (frame_t frame) const +{ + if (m_muted) // silent frame + { + for (unsigned c = 0; c < NUM_CHANNELS; ++c) + { + frame.channel[c] = 0; + } + } + else if (m_addNoise) + { + qint32 f[NUM_CHANNELS]; + for (unsigned c = 0; c < NUM_CHANNELS; ++c) + { + f[c] = m_fac * (gran () + frame.channel[c] * m_snr / 32768.0); + if (f[c] > std::numeric_limits::max ()) + { + f[c] = std::numeric_limits::max (); + } + if (f[c] < std::numeric_limits::min ()) + { + f[c] = std::numeric_limits::min (); + } + } + + for (unsigned c = 0; c < NUM_CHANNELS; ++c) + { + frame.channel[c] = f[c]; + } + } + return frame; +} diff --git a/Modulator.hpp b/Modulator.hpp index 24b7a17bb..7a9f941ea 100644 --- a/Modulator.hpp +++ b/Modulator.hpp @@ -1,10 +1,13 @@ #ifndef MODULATOR_HPP__ #define MODULATOR_HPP__ -#include - #include -#include + +#ifdef UNIX +# define NUM_CHANNELS 2 +#else +# define NUM_CHANNELS 1 +#endif // // Input device that generates PCM audio frames that encode a message @@ -27,6 +30,12 @@ private: public: Modulator (unsigned frameRate, unsigned periodLengthInSeconds, QObject * parent = 0); + bool open () {return QIODevice::open (QIODevice::ReadOnly | QIODevice::Unbuffered);} + + Q_SLOT void send (unsigned symbolsLength, double framesPerSymbol, unsigned frequency, bool synchronize = true, double dBSNR = 99.); + + Q_SLOT void stop () {Q_EMIT stateChanged ((m_state = Idle));} + bool isTuning () const {return m_tuning;} Q_SLOT void tune (bool newState = true) {m_tuning = newState;} @@ -36,12 +45,11 @@ public: unsigned frequency () const {return m_frequency;} Q_SLOT void setFrequency (unsigned newFrequency) {m_frequency = newFrequency;} - bool open (std::vector const * symbols, std::vector const * cw, double framesPerSymbol, unsigned frequency, double dBSNR = 99.); + enum ModulatorState {Synchronizing, Active, Idle}; + Q_SIGNAL void stateChanged (ModulatorState); + bool isActive () const {return m_state != Idle;} - bool isSequential () const - { - return true; - } + bool isSequential () const {return true;} protected: qint64 readData (char * data, qint64 maxSize); @@ -51,12 +59,14 @@ protected: } private: - typedef short frame_t; + typedef struct + { + qint16 channel[NUM_CHANNELS]; + } frame_t; frame_t postProcessFrame (frame_t frame) const; - QScopedPointer const> m_symbols; - QScopedPointer const> m_cw; + unsigned m_symbolsLength; static double const m_twoPi; static unsigned const m_nspd; // CW ID WPM factor @@ -64,11 +74,13 @@ private: int m_frameRate; int m_period; double m_nsps; - double m_frequency; + double volatile m_frequency; double m_snr; - enum {Idle, Active, Done} m_state; - bool m_tuning; - bool m_muted; + qint64 m_silentFrames; + qint64 m_framesSent; + ModulatorState volatile m_state; + bool volatile m_tuning; + bool volatile m_muted; bool m_addNoise; double m_phi; double m_dphi; diff --git a/devsetup.cpp b/devsetup.cpp index 55535cb9c..84412864c 100644 --- a/devsetup.cpp +++ b/devsetup.cpp @@ -1,8 +1,10 @@ #include "devsetup.h" + #include #include #include #include +#include #define MAXDEVICES 100 @@ -40,7 +42,7 @@ void DevSetup::initDlg() settings.endGroup(); // - // loaad combo boxes with setup choices + // load combo boxes with setup choices // { int currentIndex = -1; @@ -78,16 +80,7 @@ void DevSetup::initDlg() ui.comboBoxSndOut->setCurrentIndex (currentIndex != -1 ? currentIndex : defaultIndex); } - connect(&p4, SIGNAL(readyReadStandardOutput()), - this, SLOT(p4ReadFromStdout())); - connect(&p4, SIGNAL(readyReadStandardError()), - this, SLOT(p4ReadFromStderr())); - connect(&p4, SIGNAL(error(QProcess::ProcessError)), - this, SLOT(p4Error())); - p4.start("rigctl -l"); - p4.waitForFinished(1000); - ui.rigComboBox->addItem(" 9998 Commander"); - ui.rigComboBox->addItem(" 9999 Ham Radio Deluxe"); + enumerateRigs (); QPalette pal(ui.myCallEntry->palette()); if(m_myCall=="") { @@ -111,7 +104,6 @@ void DevSetup::initDlg() enableWidgets(); - ui.rigComboBox->setCurrentIndex(m_rigIndex); ui.catPortComboBox->setCurrentIndex(m_catPortIndex); ui.serialRateComboBox->setCurrentIndex(m_serialRateIndex); ui.dataBitsComboBox->setCurrentIndex(m_dataBitsIndex); @@ -338,34 +330,6 @@ void DevSetup::reject() QDialog::reject(); } -void DevSetup::p4ReadFromStdout() //p4readFromStdout -{ - while(p4.canReadLine()) { - QString t(p4.readLine()); - QString t1,t2,t3; - if(t.mid(0,6)!=" Rig #") { - t1=t.mid(0,6); - t2=t.mid(8,22).trimmed(); - t3=t.mid(31,23).trimmed(); - t=t1 + " " + t2 + " " + t3; - ui.rigComboBox->addItem(t); - } - } -} - -void DevSetup::p4ReadFromStderr() //p4readFromStderr -{ - QByteArray t=p4.readAllStandardError(); - if(t.length()>0) { - msgBox(t); - } -} - -void DevSetup::p4Error() //p4rror -{ - msgBox("Error running 'rigctl -l'."); -} - void DevSetup::msgBox(QString t) //msgBox { msgBox0.setText(t); @@ -465,9 +429,7 @@ void DevSetup::on_stopBitsComboBox_activated(int index) void DevSetup::on_rigComboBox_activated(int index) { - m_rigIndex=index; - QString t=ui.rigComboBox->itemText(index); - m_rig=t.mid(0,7).toInt(); + m_rig = ui.rigComboBox->itemData (index).toInt (); enableWidgets(); } @@ -653,3 +615,36 @@ void DevSetup::on_cbXIT_toggled(bool checked) m_bXIT=checked; if(m_bSplit and m_bXIT) ui.cbSplit->setChecked(false); } + +typedef QMap RigList; + +int rigCallback (rig_caps const * caps, void * cbData) +{ + RigList * rigs = reinterpret_cast (cbData); + + QString key (QString::fromLatin1 (caps->mfg_name).trimmed () + + ' '+ QString::fromLatin1 (caps->model_name).trimmed () + // + ' '+ QString::fromLatin1 (caps->version).trimmed () + // + " (" + QString::fromLatin1 (rig_strstatus (caps->status)).trimmed () + ')' + ); + + (*rigs)[key] = caps->rig_model; + + return 1; // keep them coming +} + +void DevSetup::enumerateRigs () +{ + RigList rigs; + rig_load_all_backends (); + rig_list_foreach (rigCallback, &rigs); + + for (RigList::const_iterator r = rigs.cbegin (); r != rigs.cend (); ++r) + { + ui.rigComboBox->addItem (r.key (), r.value ()); + } + + ui.rigComboBox->addItem ("DX Lab Suite Commander", 9998); + ui.rigComboBox->addItem ("Ham Radio Deluxe", 9999); + ui.rigComboBox->setCurrentIndex (ui.rigComboBox->findData (m_rig)); +} diff --git a/devsetup.h b/devsetup.h index 6e0d52c6e..4b9ef5712 100644 --- a/devsetup.h +++ b/devsetup.h @@ -8,8 +8,12 @@ #include #include +#include + #include "rigclass.h" +int rigCallback (rig_caps const *, void *); + class DevSetup : public QDialog { Q_OBJECT @@ -67,15 +71,11 @@ public: QStringList m_antDescription; // per band antenna description QStringList m_bandDescription; // per band description - QProcess p4; QMessageBox msgBox0; public slots: void accept(); void reject(); - void p4ReadFromStdout(); - void p4ReadFromStderr(); - void p4Error(); private slots: void on_myCallEntry_editingFinished(); @@ -105,12 +105,15 @@ private slots: void on_cbXIT_toggled(bool checked); private: + void enumerateRigs (); Rig* rig; void msgBox(QString t); void setEnableAntennaDescriptions(bool enable); void enableWidgets(); void openRig(); Ui::DialogSndCard ui; + + friend int rigCallback (rig_caps const *, void *); }; extern int ptt(int nport, int ntx, int* iptt, int* nopen); diff --git a/devsetup.ui b/devsetup.ui index 049324c74..c9d4400d5 100644 --- a/devsetup.ui +++ b/devsetup.ui @@ -6,14 +6,14 @@ 0 0 - 570 - 465 + 571 + 440 588 - 557 + 522 @@ -1136,51 +1136,6 @@ - - - - - - Qt::Horizontal - - - QSizePolicy::Fixed - - - - 96 - 20 - - - - - - - - - 0 - 0 - - - - - 90 - 0 - - - - - 16777215 - 16777215 - - - - Dev Ch API Name - - - - - @@ -1852,8 +1807,8 @@ 0 0 - 308 - 505 + 510 + 449 diff --git a/lib/Makefile.linux b/lib/Makefile.linux index 38c3ac753..0fa9d1001 100644 --- a/lib/Makefile.linux +++ b/lib/Makefile.linux @@ -1,13 +1,13 @@ # Set paths EXE_DIR = ../../wsjtx_install -INCPATH = -I'/usr/include/qt4' -I'/usr/include/qt4/QtCore' +INCPATH = -I/usr/include/qt5 -I/usr/include/qt5/QtCore CC = gcc CXX = g++ FC = gfortran -FFLAGS = -O2 -fbounds-check -Wall -Wno-conversion -fno-second-underscore -CFLAGS = -I. -fbounds-check -mno-stack-arg-probe +FFLAGS = -O2 -fbounds-check -Wall -Wno-conversion -fno-second-underscore -fPIE +CFLAGS = -I. -fbounds-check -mno-stack-arg-probe -fPIE # Default rules %.o: %.c @@ -49,17 +49,17 @@ libjt9.a: $(OBJS1) OBJS2 = jt9.o jt9a.o jt9b.o jt9c.o jt9: $(OBJS2) libjt9.a - $(CXX) -o jt9 $(OBJS2) libjt9.a -lfftw3f -lgfortran -lQtCore + $(CXX) -o jt9 $(OBJS2) -L. -ljt9 -lQt5Core -lfftw3f `$(FC) -print-file-name=libgfortran.so` mkdir -p $(EXE_DIR) cp jt9 $(EXE_DIR) OBJS3 = jt9sim.o jt9sim: $(OBJS3) libjt9.a - $(FC) -o jt9sim $(OBJS3) libjt9.a + $(FC) -o jt9sim $(OBJS3) -L. -ljt9 OBJS4 = jt9code.o jt9code: $(OBJS4) libjt9.a - $(FC) -o jt9code $(OBJS4) libjt9.a + $(FC) -o jt9code $(OBJS4) -L. -ljt9 sync9.o: sync9.f90 jt9sync.f90 $(FC) $(FFLAGS) -c sync9.f90 @@ -80,7 +80,7 @@ redsync.o: redsync.f90 jt9sync.f90 $(FC) $(FFLAGS) -c redsync.f90 ipcomm.o: ipcomm.cpp - $(CXX) -c $(INCPATH) ipcomm.cpp + $(CXX) -c $(INCPATH) -fPIE ipcomm.cpp sec_midn.o: sec_midn.f90 $(FC) -c -fno-second-underscore sec_midn.f90 diff --git a/main.cpp b/main.cpp index b6abef9c9..7d40fe2e4 100644 --- a/main.cpp +++ b/main.cpp @@ -4,6 +4,7 @@ #include #endif #include +#include #include "mainwindow.h" @@ -52,5 +53,7 @@ int main(int argc, char *argv[]) // Multiple instances: Call MainWindow() with the UUID key MainWindow w(&mem_jt9, &my_key, fontSize2, fontWeight2); w.show(); + + QObject::connect (&a, SIGNAL (lastWindowClosed()), &a, SLOT (quit())); return a.exec(); } diff --git a/mainwindow.cpp b/mainwindow.cpp index e597c1983..485d244aa 100644 --- a/mainwindow.cpp +++ b/mainwindow.cpp @@ -2,9 +2,10 @@ #include "mainwindow.h" #include "ui_mainwindow.h" -#include -#include +#include #include + +#include "soundout.h" #include "devsetup.h" #include "plotter.h" #include "about.h" @@ -18,11 +19,6 @@ #include #endif -#define NUM_JT65_SYMBOLS 126 -#define NUM_JT9_SYMBOLS 85 -#define NUM_CW_SYMBOLS 250 -#define TX_SAMPLE_RATE 48000 - int itone[NUM_JT65_SYMBOLS]; //Audio tones for all Tx symbols int icw[NUM_CW_SYMBOLS]; //Dits for CW ID @@ -51,13 +47,44 @@ MainWindow::MainWindow(QSharedMemory *shdmem, QString *thekey, \ QWidget *parent) : QMainWindow(parent), ui(new Ui::MainWindow), - m_audioInputDevice (QAudioDeviceInfo::defaultInputDevice ()), // start with default m_detector (RX_SAMPLE_RATE, NTMAX / 2, 6912 / 2 * sizeof (jt9com_.d2[0]), this), + m_audioInputDevice (QAudioDeviceInfo::defaultInputDevice ()), // start with default + m_modulator (TX_SAMPLE_RATE, NTMAX / 2), m_audioOutputDevice (QAudioDeviceInfo::defaultOutputDevice ()), // start with default - m_modulator (TX_SAMPLE_RATE, NTMAX / 2, this) + m_soundOutput (&m_modulator) { ui->setupUi(this); m_detector.open (); + m_modulator.open (); + + connect (this, &MainWindow::finished, this, &MainWindow::close); + + // start sound out thread and hook up slots & signals for shutdown management + m_soundOutput.moveToThread (&m_soundOutputThread); + connect (this, &MainWindow::finished, &m_soundOutputThread, &QThread::quit); // quit thread event loop + connect (&m_soundOutputThread, &QThread::finished, &m_soundOutputThread, &QThread::deleteLater); // disposal + + // hook up sound output stream slots & signals + connect (this, &MainWindow::startAudioOutputStream, &m_soundOutput, &SoundOutput::startStream); + connect (this, &MainWindow::stopAudioOutputStream, &m_soundOutput, &SoundOutput::stopStream); + connect (&m_soundOutput, &SoundOutput::error, this, &MainWindow::showSoundOutError); + // connect (&m_soundOutput, &SoundOutput::status, this, &MainWindow::showStatusMessage); + + // hook up Modulator slots + connect (this, &MainWindow::muteAudioOutput, &m_modulator, &Modulator::mute); + connect (this, &MainWindow::transmitFrequency, &m_modulator, &Modulator::setFrequency); + connect (this, &MainWindow::endTransmitMessage, &m_modulator, &Modulator::stop); + connect (this, &MainWindow::tune, &m_modulator, &Modulator::tune); + connect ( + this + , SIGNAL (sendMessage (unsigned, double, unsigned, bool, double)) + , &m_modulator + , SLOT (send (unsigned, double, unsigned, bool, double)) + ); + + // start the sound output thread + m_soundOutputThread.start (QThread::HighPriority); + on_EraseButton_clicked(); QActionGroup* modeGroup = new QActionGroup(this); @@ -93,11 +120,7 @@ MainWindow::MainWindow(QSharedMemory *shdmem, QString *thekey, \ connect(&m_detector, &Detector::bytesWritten, this, &MainWindow::dataSink); connect(&m_soundInput, SIGNAL(error(QString)), this, SLOT(showSoundInError(QString))); - connect(&m_soundOutput, SIGNAL(error(QString)), this, - SLOT(showSoundOutError(QString))); - connect(&m_soundInput, SIGNAL(status(QString)), this, - SLOT(showStatusMessage(QString))); - // connect(&m_soundOutput, SIGNAL(status(QString)), this, + // connect(&m_soundInput, SIGNAL(status(QString)), this, // SLOT(showStatusMessage(QString))); createStatusBar(); @@ -135,9 +158,9 @@ MainWindow::MainWindow(QSharedMemory *shdmem, QString *thekey, \ font.setWeight(75); ui->readFreq->setFont(font); - QTimer *guiTimer = new QTimer(this); - connect(guiTimer, SIGNAL(timeout()), this, SLOT(guiUpdate())); - guiTimer->start(100); //Don't change the 100 ms! + connect(&m_guiTimer, SIGNAL(timeout()), this, SLOT(guiUpdate())); + m_guiTimer.start(100); //Don't change the 100 ms! + ptt0Timer = new QTimer(this); ptt0Timer->setSingleShot(true); connect(ptt0Timer, SIGNAL(timeout()), this, SLOT(stopTx2())); @@ -161,7 +184,7 @@ MainWindow::MainWindow(QSharedMemory *shdmem, QString *thekey, \ m_auto=false; m_waterfallAvg = 1; m_txFirst=false; - m_modulator.mute(false); + Q_EMIT muteAudioOutput (false); m_btxMute=false; m_btxok=false; m_restart=false; @@ -317,8 +340,8 @@ MainWindow::MainWindow(QSharedMemory *shdmem, QString *thekey, \ connect(watcher2, SIGNAL(finished()),this,SLOT(diskWriteFinished())); m_soundInput.start(m_audioInputDevice, RX_SAMPLE_RATE / 10, &m_detector); - m_modulator.setFrequency(m_txFreq - (m_bSplit || m_bXIT ? m_XIT : 0)); - m_modulator.tune(false); + Q_EMIT transmitFrequency (m_txFreq - (m_bSplit || m_bXIT ? m_XIT : 0)); + Q_EMIT muteAudioOutput (false); m_monitoring=!m_monitorStartOFF; // Start with Monitoring ON/OFF m_detector.setMonitoring(m_monitoring); m_diskData=false; @@ -366,13 +389,10 @@ MainWindow::MainWindow(QSharedMemory *shdmem, QString *thekey, \ MainWindow::~MainWindow() { writeSettings(); - m_soundOutput.stop(); - m_modulator.close(); if(!m_decoderBusy) { QFile lockFile(m_appDir + "/.lock"); lockFile.remove(); } - m_detector.close (); delete ui; } @@ -497,7 +517,7 @@ void MainWindow::readSettings() // // retrieve audio input device // - QString savedName = settings.value( "SoundInName", "default").toString(); + QString savedName = settings.value( "SoundInName").toString(); QList audioInputDevices (QAudioDeviceInfo::availableDevices (QAudio::AudioInput)); // available audio input devices for (QList::const_iterator p = audioInputDevices.begin (); p != audioInputDevices.end (); ++p) { @@ -512,7 +532,7 @@ void MainWindow::readSettings() // // retrieve audio output device // - QString savedName = settings.value("SoundOutName", "default").toString(); + QString savedName = settings.value("SoundOutName").toString(); QList audioOutputDevices (QAudioDeviceInfo::availableDevices (QAudio::AudioOutput)); // available audio output devices for (QList::const_iterator p = audioOutputDevices.begin (); p != audioOutputDevices.end (); ++p) { @@ -535,7 +555,7 @@ void MainWindow::readSettings() ui->RxFreqSpinBox->setValue(m_rxFreq); m_txFreq=settings.value("TxFreq",1500).toInt(); ui->TxFreqSpinBox->setValue(m_txFreq); - m_modulator.setFrequency(m_txFreq - (m_bSplit || m_bXIT ? m_XIT : 0)); + Q_EMIT transmitFrequency (m_txFreq - (m_bSplit || m_bXIT ? m_XIT : 0)); m_saveDecoded=ui->actionSave_decoded->isChecked(); m_saveAll=ui->actionSave_all->isChecked(); m_ndepth=settings.value("NDepth",3).toInt(); @@ -811,6 +831,7 @@ void MainWindow::on_monitorButton_clicked() //Monitor { m_monitoring=true; m_detector.setMonitoring(true); + m_soundInput.start(m_audioInputDevice, RX_SAMPLE_RATE / 10, &m_detector); m_diskData=false; } @@ -827,7 +848,7 @@ void MainWindow::on_autoButton_clicked() //Auto ui->autoButton->setStyleSheet(m_pbAutoOn_style); } else { m_btxok=false; - m_modulator.mute(); + Q_EMIT muteAudioOutput (); ui->autoButton->setStyleSheet(""); on_monitorButton_clicked(); m_repeatMsg=0; @@ -1031,6 +1052,7 @@ void MainWindow::closeEvent(QCloseEvent*) void MainWindow::OnExit() { + m_guiTimer.stop (); g_pWideGraph->saveSettings(); if(m_fname != "") killFile(); m_killAll=true; @@ -1042,13 +1064,16 @@ void MainWindow::OnExit() bool b=proc_jt9.waitForFinished(1000); if(!b) proc_jt9.kill(); quitFile.remove(); - qApp->exit(0); // Exit the event loop + + Q_EMIT finished (); + m_soundOutputThread.wait (); } void MainWindow::on_stopButton_clicked() //stopButton { m_monitoring=false; m_detector.setMonitoring(m_monitoring); + m_soundInput.stop (); m_loopall=false; } @@ -1082,6 +1107,7 @@ void MainWindow::on_actionWide_Waterfall_triggered() //Display Waterfalls SLOT(setXIT(int))); // connect(g_pWideGraph, SIGNAL(dialFreqChanged(double)),this, // SLOT(dialFreqChanged2(double))); + connect (this, &MainWindow::finished, g_pWideGraph, &WideGraph::close); } g_pWideGraph->show(); } @@ -1621,7 +1647,7 @@ void MainWindow::guiUpdate() } if(!bTxTime || m_btxMute) { m_btxok=false; - m_modulator.mute(); + Q_EMIT muteAudioOutput (); } } @@ -1719,8 +1745,9 @@ void MainWindow::guiUpdate() signalMeter->setValue(0); m_monitoring=false; m_detector.setMonitoring(false); + m_soundInput.stop (); m_btxok=true; - m_modulator.mute(false); + Q_EMIT muteAudioOutput (false); m_transmitting=true; ui->pbTxMode->setEnabled(false); if(!m_tune) { @@ -1839,7 +1866,7 @@ void MainWindow::displayTxMsg(QString t) void MainWindow::startTx2() { - if(!m_soundOutput.isRunning()) { + if (!m_modulator.isActive ()) { QString t=ui->tx6->text(); double snr=t.mid(1,5).toDouble(); if(snr>0.0 or snr < -50.0) snr=99.0; @@ -1847,8 +1874,9 @@ void MainWindow::startTx2() signalMeter->setValue(0); m_monitoring=false; m_detector.setMonitoring(false); + m_soundInput.stop (); m_btxok=true; - m_modulator.mute(false); + Q_EMIT muteAudioOutput (false); m_transmitting=true; ui->pbTxMode->setEnabled(false); } @@ -1856,8 +1884,8 @@ void MainWindow::startTx2() void MainWindow::stopTx() { - m_soundOutput.stop(); - m_modulator.close (); + Q_EMIT endTransmitMessage (); + Q_EMIT stopAudioOutputStream (); m_transmitting=false; ui->pbTxMode->setEnabled(true); g_iptt=0; @@ -1865,6 +1893,7 @@ void MainWindow::stopTx() lab1->setText(""); ptt0Timer->start(200); //Sequencer delay m_monitoring=true; + m_soundInput.start(m_audioInputDevice, RX_SAMPLE_RATE / 10, &m_detector); m_detector.setMonitoring(true); } @@ -2381,7 +2410,7 @@ void MainWindow::on_tx6_editingFinished() //tx6 edited // double snr=t.mid(1,5).toDouble(); // if(snr>0.0 or snr < -50.0) snr=99.0; - // m_soundOutput.setTxSNR(snr); + // m_modulator.setTxSNR(snr); } void MainWindow::on_dxCallEntry_textChanged(const QString &t) //dxCall changed @@ -2525,7 +2554,7 @@ void MainWindow::on_TxFreqSpinBox_valueChanged(int n) m_txFreq=n; if(g_pWideGraph!=NULL) g_pWideGraph->setTxFreq(n); if(m_lockTxFreq) ui->RxFreqSpinBox->setValue(n); - m_modulator.setFrequency(m_txFreq - (m_bSplit || m_bXIT ? m_XIT : 0)); + Q_EMIT transmitFrequency (m_txFreq - (m_bSplit || m_bXIT ? m_XIT : 0)); } void MainWindow::on_RxFreqSpinBox_valueChanged(int n) @@ -2848,7 +2877,7 @@ void MainWindow::on_tuneButton_clicked() } else { m_tune=true; m_sent73=false; - m_modulator.tune(); + Q_EMIT tune (); m_repeatMsg=0; ui->tuneButton->setStyleSheet(m_pbTune_style); } @@ -2858,11 +2887,11 @@ void MainWindow::on_stopTxButton_clicked() //Stop Tx { if(m_tune) { m_tune=false; - m_modulator.tune(m_tune); + Q_EMIT tune (m_tune); } if(m_auto) on_autoButton_clicked(); m_btxok=false; - m_modulator.mute(); + Q_EMIT muteAudioOutput (); m_repeatMsg=0; ui->tuneButton->setStyleSheet(""); } @@ -2999,7 +3028,7 @@ void MainWindow::setXIT(int n) ret=rig->setSplitFreq(MHz(m_dialFreq)+m_XIT,RIG_VFO_B); } } - m_modulator.setFrequency(m_txFreq - (m_bSplit || m_bXIT ? m_XIT : 0)); + Q_EMIT transmitFrequency (m_txFreq - (m_bSplit || m_bXIT ? m_XIT : 0)); } void MainWindow::setFreq4(int rxFreq, int txFreq) @@ -3055,19 +3084,13 @@ void MainWindow::pollRigFreq() void MainWindow::transmit (double snr) { - QScopedPointer > cw (new std::vector (NUM_CW_SYMBOLS)); - cw->assign (icw, icw + NUM_CW_SYMBOLS); // load data if (m_modeTx == "JT65") { - QScopedPointer > symbols (new std::vector (NUM_JT65_SYMBOLS)); - symbols->assign (itone, itone + NUM_JT65_SYMBOLS); // load data - m_modulator.open (symbols.take (), cw.take (), 4096.0 * 12000.0 / 11025.0, m_txFreq - (m_bSplit || m_bXIT ? m_XIT : 0), snr); + Q_EMIT sendMessage (NUM_JT65_SYMBOLS, 4096.0 * 12000.0 / 11025.0, m_txFreq - (m_bSplit || m_bXIT ? m_XIT : 0), true, snr); } else { - QScopedPointer > symbols (new std::vector (NUM_JT65_SYMBOLS)); - symbols->assign (itone, itone + NUM_JT9_SYMBOLS); // load data - m_modulator.open (symbols.take (), cw.take (), m_nsps, m_txFreq - (m_bSplit || m_bXIT ? m_XIT : 0), snr); + Q_EMIT sendMessage (NUM_JT9_SYMBOLS, m_nsps, m_txFreq - (m_bSplit || m_bXIT ? m_XIT : 0), true, snr); } - m_soundOutput.start(m_audioOutputDevice, &m_modulator); + Q_EMIT startAudioOutputStream (m_audioOutputDevice); } diff --git a/mainwindow.h b/mainwindow.h index 64eebdcac..8e0e422f8 100644 --- a/mainwindow.h +++ b/mainwindow.h @@ -5,6 +5,7 @@ #else #include #endif +#include #include #include #include @@ -24,6 +25,15 @@ #include "PSKReporter.h" #endif +#define NUM_JT65_SYMBOLS 126 +#define NUM_JT9_SYMBOLS 85 +#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 + + //--------------------------------------------------------------- MainWindow namespace Ui { class MainWindow; @@ -166,6 +176,16 @@ private slots: void on_actionTx2QSO_triggered(bool checked); void on_cbPlus2kHz_toggled(bool checked); + private: + Q_SIGNAL void startAudioOutputStream (QAudioDeviceInfo); + Q_SIGNAL void stopAudioOutputStream (); + Q_SIGNAL void finished (); + Q_SIGNAL void muteAudioOutput (bool = true); + Q_SIGNAL void transmitFrequency (unsigned); + Q_SIGNAL void endTransmitMessage (); + Q_SIGNAL void tune (bool = true); + Q_SIGNAL void sendMessage (unsigned symbolsLength, double framesPerSymbol, unsigned frequency, bool synchronize = true, double dBSNR = 99.); + private: Ui::MainWindow *ui; @@ -190,10 +210,16 @@ private: qint32 m_nutc0; qint32 m_nrx; qint32 m_hsym; - QAudioDeviceInfo m_audioInputDevice; + Detector m_detector; - QAudioDeviceInfo m_audioOutputDevice; + QAudioDeviceInfo m_audioInputDevice; + SoundInput m_soundInput; + Modulator m_modulator; + QAudioDeviceInfo m_audioOutputDevice; + SoundOutput m_soundOutput; + QThread m_soundOutputThread; + qint32 m_TRperiod; qint32 m_nsps; qint32 m_hsymStop; @@ -299,6 +325,7 @@ private: QProcess proc_jt9; + QTimer m_guiTimer; QTimer* ptt1Timer; //StartTx delay QTimer* ptt0Timer; //StopTx delay QTimer* logQSOTimer; @@ -344,8 +371,6 @@ private: QDateTime m_dateTimeQSO; - SoundInput m_soundInput; //Instantiate the audio objects - SoundOutput m_soundOutput; QSharedMemory *mem_jt9; // Multiple instances: QString *mykey_jt9; diff --git a/soundin.cpp b/soundin.cpp index 17066c97d..7ab72b6c0 100644 --- a/soundin.cpp +++ b/soundin.cpp @@ -74,7 +74,7 @@ bool SoundInput::start(QAudioDeviceInfo const& device, int framesPerBuffer, QIOD m_stream->start (sink); - qDebug () << "audio input buffer size = " << m_stream->bufferSize () << " bytes\n"; + qDebug () << "audio input buffer size = " << m_stream->bufferSize () << " bytes"; return audioError () ? false : true; } @@ -84,29 +84,29 @@ void SoundInput::handleStateChanged (QAudio::State newState) const switch (newState) { case QAudio::IdleState: - qDebug () << "SoundInput idle\n"; + qDebug () << "SoundInput idle"; Q_EMIT status (tr ("Idle")); break; case QAudio::ActiveState: - qDebug () << "SoundInput active\n"; + qDebug () << "SoundInput active"; Q_EMIT status (tr ("Receiving")); break; case QAudio::SuspendedState: - qDebug () << "SoundInput suspended\n"; + qDebug () << "SoundInput suspended"; Q_EMIT status (tr ("Suspended")); break; case QAudio::StoppedState: if (audioError ()) { - qDebug () << "SoundInput error\n"; + qDebug () << "SoundInput error"; Q_EMIT status (tr ("Error")); } else { - qDebug () << "SoundInput stopped\n"; + qDebug () << "SoundInput stopped"; Q_EMIT status (tr ("Stopped")); } break; diff --git a/soundout.cpp b/soundout.cpp index a21ffc5ab..d1f1bd7a8 100644 --- a/soundout.cpp +++ b/soundout.cpp @@ -5,6 +5,12 @@ #include #include +#if defined (WIN32) +# define MS_BUFFERED 1000 +#else +# define MS_BUFFERED 2000 +#endif + bool SoundOutput::audioError () const { bool result (true); @@ -38,56 +44,119 @@ bool SoundOutput::audioError () const return result; } -bool SoundOutput::start(QAudioDeviceInfo const& device, QIODevice * source) +SoundOutput::SoundOutput (QIODevice * source) + : m_source (source) + , m_active (false) + , m_currentDevice (QAudioDeviceInfo::defaultOutputDevice ()) { Q_ASSERT (source); - - stop(); - - QAudioFormat format (device.preferredFormat()); - format.setChannelCount (1); - format.setCodec ("audio/pcm"); - format.setSampleRate (48000); - format.setSampleType (QAudioFormat::SignedInt); - format.setSampleSize (16); - if (!format.isValid ()) - { - Q_EMIT error (tr ("Requested output audio format is not valid.")); - return false; - } - if (!device.isFormatSupported (format)) - { - Q_EMIT error (tr ("Requested output audio format is not supported on device.")); - return false; - } - - m_stream.reset (new QAudioOutput (device, format, this)); - if (audioError ()) - { - return false; - } - connect (m_stream.data(), &QAudioOutput::stateChanged, this, &SoundOutput::handleStateChanged); - - m_stream->setBufferSize(48000); - m_stream->start (source); - if (audioError ()) // start the input stream - { - return false; - } - - m_active = true; - return true; } -void SoundOutput::handleStateChanged (QAudio::State newState) const +void SoundOutput::startStream (QAudioDeviceInfo const& device) +{ + if (!m_stream || device != m_currentDevice) + { + QAudioFormat format (device.preferredFormat ()); + +#ifdef UNIX + format.setChannelCount (2); +#else + format.setChannelCount (1); +#endif + + format.setCodec ("audio/pcm"); + format.setSampleRate (48000); + format.setSampleType (QAudioFormat::SignedInt); + format.setSampleSize (16); + if (!format.isValid ()) + { + Q_EMIT error (tr ("Requested output audio format is not valid.")); + } + if (!device.isFormatSupported (format)) + { + Q_EMIT error (tr ("Requested output audio format is not supported on device.")); + } + + m_stream.reset (new QAudioOutput (device, format, this)); + audioError (); + + connect (m_stream.data(), &QAudioOutput::stateChanged, this, &SoundOutput::handleStateChanged); + + m_currentDevice = device; + } + + // + // This buffer size is critical since we are running in the GUI + // thread. If it is too short; high activity levels on the GUI can + // starve the audio buffer. On the other hand the Windows + // implementation seems to take the length of the buffer in time to + // stop the audio stream even if reset() is used. + // + // 1 seconds seems a reasonable compromise except for Windows + // where things are probably broken. + // + // 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 (MS_BUFFERED * 1000)); + m_stream->start (m_source); + audioError (); + + qDebug () << "audio output buffer size = " << m_stream->bufferSize () << " bytes"; +} + +void SoundOutput::suspend () +{ + if (m_stream && QAudio::ActiveState == m_stream->state ()) + { + m_stream->suspend (); + audioError (); + } +} + +void SoundOutput::resume () +{ + if (m_stream && QAudio::SuspendedState == m_stream->state ()) + { + m_stream->resume (); + audioError (); + } +} + +void SoundOutput::stopStream () +{ + if (m_stream) + { + m_stream->stop (); + audioError (); + } +} + +void SoundOutput::handleStateChanged (QAudio::State newState) { switch (newState) { - case QAudio::IdleState: Q_EMIT status (tr ("Idle")); break; - case QAudio::ActiveState: Q_EMIT status (tr ("Sending")); break; - case QAudio::SuspendedState: Q_EMIT status (tr ("Suspended")); break; + case QAudio::IdleState: + qDebug () << "SoundOutput: entered Idle state"; + Q_EMIT status (tr ("Idle")); + m_active = false; + break; + + case QAudio::ActiveState: + qDebug () << "SoundOutput: entered Active state"; + m_active = true; + Q_EMIT status (tr ("Sending")); + break; + + case QAudio::SuspendedState: + qDebug () << "SoundOutput: entered Suspended state"; + m_active = true; + Q_EMIT status (tr ("Suspended")); + break; case QAudio::StoppedState: + qDebug () << "SoundOutput: entered Stopped state"; + m_active = false; if (audioError ()) { Q_EMIT status (tr ("Error")); @@ -100,13 +169,10 @@ void SoundOutput::handleStateChanged (QAudio::State newState) const } } -void SoundOutput::stop() +SoundOutput::~SoundOutput () { - m_stream.reset (); - m_active = false; -} - -SoundOutput::~SoundOutput() -{ - stop (); + if (m_stream) + { + m_stream->stop (); + } } diff --git a/soundout.h b/soundout.h index 0a4318117..bdca53a3f 100644 --- a/soundout.h +++ b/soundout.h @@ -4,8 +4,9 @@ #include #include #include +#include -#include "Modulator.hpp" +class QAudioDeviceInfo; class QAudioDeviceInfo; @@ -21,32 +22,33 @@ class SoundOutput : public QObject Q_DISABLE_COPY (SoundOutput); public: - SoundOutput () - : m_active(false) - { - } + SoundOutput (QIODevice * source); ~SoundOutput (); bool isRunning() const {return m_active;} -public Q_SLOTS: - bool start(QAudioDeviceInfo const& device, QIODevice * source); - void stop(); + public Q_SLOTS: + void startStream (QAudioDeviceInfo const& device); + void suspend (); + void resume (); + void stopStream (); -Q_SIGNALS: + Q_SIGNALS: void error (QString message) const; void status (QString message) const; private: bool audioError () const; -private Q_SLOTS: - void handleStateChanged (QAudio::State) const; + private Q_SLOTS: + void handleStateChanged (QAudio::State); private: QScopedPointer m_stream; - bool m_active; + QIODevice * m_source; + bool volatile m_active; + QAudioDeviceInfo m_currentDevice; }; #endif diff --git a/wsjtx.pro b/wsjtx.pro index da1117206..1fe45a7f6 100644 --- a/wsjtx.pro +++ b/wsjtx.pro @@ -14,7 +14,6 @@ TARGET = wsjtx DESTDIR = ../wsjtx_install VERSION = 1.2 TEMPLATE = app -#DEFINES = QT4 DEFINES = QT5 win32 { @@ -82,9 +81,9 @@ FORMS += mainwindow.ui about.ui devsetup.ui widegraph.ui \ RC_FILE = wsjtx.rc unix { -LIBS += ../wsjtx/lib/libjt9.a +LIBS += -L lib -ljt9 LIBS += -lhamlib -LIBS += -lgfortran -lfftw3f +LIBS += -lfftw3f `$$F90 -print-file-name=libgfortran.so` } win32 { @@ -99,3 +98,6 @@ LIBS += libwsock32 LIBS += C:/MinGW/lib/libf95.a } + +RESOURCES += \ + wsjtx.qrc