diff --git a/Detector.cpp b/Detector.cpp new file mode 100644 index 000000000..bf3f4b8cb --- /dev/null +++ b/Detector.cpp @@ -0,0 +1,93 @@ +#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; +} diff --git a/Detector.hpp b/Detector.hpp new file mode 100644 index 000000000..7ddb08121 --- /dev/null +++ b/Detector.hpp @@ -0,0 +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 diff --git a/Modulator.cpp b/Modulator.cpp new file mode 100644 index 000000000..c1178b6f8 --- /dev/null +++ b/Modulator.cpp @@ -0,0 +1,195 @@ +#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; +} diff --git a/Modulator.hpp b/Modulator.hpp new file mode 100644 index 000000000..24b7a17bb --- /dev/null +++ b/Modulator.hpp @@ -0,0 +1,81 @@ +#ifndef MODULATOR_HPP__ +#define MODULATOR_HPP__ + +#include + +#include +#include + +// +// Input device that generates PCM audio frames that encode a message +// and an optional CW ID. +// +// Output can be muted while underway, preserving waveform timing when +// transmission is resumed. +// +class Modulator : public QIODevice +{ + Q_OBJECT; + + Q_PROPERTY (unsigned frequency READ frequency WRITE setFrequency); + Q_PROPERTY (bool tuning READ isTuning WRITE tune); + Q_PROPERTY (bool muted READ isMuted WRITE mute); + +private: + Q_DISABLE_COPY (Modulator); + +public: + Modulator (unsigned frameRate, unsigned periodLengthInSeconds, QObject * parent = 0); + + bool isTuning () const {return m_tuning;} + Q_SLOT void tune (bool newState = true) {m_tuning = newState;} + + bool isMuted () const {return m_muted;} + Q_SLOT void mute (bool newState = true) {m_muted = newState;} + + 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.); + + bool isSequential () const + { + return true; + } + +protected: + qint64 readData (char * data, qint64 maxSize); + qint64 writeData (char const * /* data */, qint64 /* maxSize */) + { + return -1; // we don't consume data + } + +private: + typedef short frame_t; + + frame_t postProcessFrame (frame_t frame) const; + + QScopedPointer const> m_symbols; + QScopedPointer const> m_cw; + + static double const m_twoPi; + static unsigned const m_nspd; // CW ID WPM factor + + int m_frameRate; + int m_period; + double m_nsps; + double m_frequency; + double m_snr; + enum {Idle, Active, Done} m_state; + bool m_tuning; + bool m_muted; + bool m_addNoise; + double m_phi; + double m_dphi; + double m_amp; + unsigned m_ic; + double m_fac; + unsigned m_isym0; +}; + +#endif diff --git a/mainwindow.cpp b/mainwindow.cpp index f9c8335c9..5322b7aa6 100644 --- a/mainwindow.cpp +++ b/mainwindow.cpp @@ -1,4 +1,4 @@ -//--------------------------------------------------------------- MainWindow +//---------------------------------------------------------------- MainWindow #include "mainwindow.h" #include "ui_mainwindow.h"