#include "Modulator.hpp" #include #include #include #include #include #include "commons.h" #include "DriftingDateTime.h" #include "JS8Submode.hpp" #include "mainwindow.h" #include "soundout.h" #include "moc_Modulator.cpp" namespace { constexpr double TAU = 2 * M_PI; constexpr auto MS_PER_DAY = 86400000; } Modulator::Modulator(unsigned frameRate, QObject * parent) : AudioDevice {parent} , m_frameRate {frameRate} {} void Modulator::start(double const frequency, int const submode, SoundOutput * const stream, Channel const channel) { Q_ASSERT (stream); if (isIdle()) stop(); m_quickClose = false; m_isym0 = std::numeric_limits::max(); // big number m_frequency0 = 0.; m_phi = 0.; m_nsps = JS8::Submode::symbolSamples(submode); m_frequency = frequency; m_amp = std::numeric_limits::max(); m_toneSpacing = RX_SAMPLE_RATE / m_nsps; m_silentFrames = 0; m_ic = 0; // If we're not tunining, then we'll need to figure out exactly when we // should start transmitting; this will depend on the submode in play. if (!m_tuning) { // Get current time in milliseconds from the perspective of this machine. // Using the submode-specific transmit period in milliseconds, determine // the number of milliseconds that we're presently at into the nominal // transmit start time. auto const startDelayMS = JS8::Submode::startDelayMS(submode); unsigned const mstr = (DriftingDateTime::currentMSecsSinceEpoch() % MS_PER_DAY) % static_cast(1000.0 * JS8::Submode::period(submode)); // Calculate number of silent frames to send, so that audio will // start at the nominal time "delay_ms" into the Tx sequence. if (startDelayMS > mstr) { m_silentFrames = (startDelayMS - mstr) * m_frameRate / 1000; } // Adjust for late starts. if (!m_silentFrames && mstr >= startDelayMS) { m_ic = (mstr - startDelayMS) * m_frameRate / 1000; } } initialize(QIODevice::ReadOnly, channel); Q_EMIT stateChanged ((m_state = m_silentFrames ? State::Synchronizing : State::Active)); m_stream = stream; if (m_stream) { m_stream->restart(this); } else { qDebug() << "Modulator::start: no audio output stream assigned"; } } void Modulator::tune(bool const tuning) { m_tuning = tuning; if (!m_tuning) stop(true); } void Modulator::stop(bool const quickClose) { m_quickClose = quickClose; close(); } void Modulator::close() { if (m_stream) { if (m_quickClose) m_stream->reset(); else m_stream->stop(); } if (m_state != State::Idle) { Q_EMIT stateChanged ((m_state = State::Idle)); } AudioDevice::close(); } qint64 Modulator::readData(char * const data, qint64 const maxSize) { if (maxSize == 0) return 0; Q_ASSERT (!(maxSize % qint64(bytesPerFrame()))); // no torn frames Q_ASSERT (isOpen()); qint64 numFrames = maxSize / bytesPerFrame(); qint16 * samples = reinterpret_cast(data); qint16 * end = samples + numFrames * (bytesPerFrame() / sizeof(qint16)); qint64 framesGenerated = 0; switch (m_state) { case State::Synchronizing: { if (m_silentFrames) { // Send silence up to end of start delay. framesGenerated = qMin(m_silentFrames, numFrames); do { samples = load(0, samples); // silence } while (--m_silentFrames && samples != end); if (!m_silentFrames) { Q_EMIT stateChanged ((m_state = State::Active)); } } } [[fallthrough]]; case State::Active: { double const baud = 12000.0 / m_nsps; unsigned int i0; // fade out parameters, no unsigned int i1; // fade out for tuning if (m_tuning) { i1 = i0 = 9999 * m_nsps; } else { i0 = (JS8_NUM_SYMBOLS - 0.017) * 4.0 * m_nsps; i1 = JS8_NUM_SYMBOLS * 4.0 * m_nsps; } qint16 sample; while (samples != end && m_ic <= i1) { unsigned int const isym = m_tuning ? 0 : m_ic / (4.0 * m_nsps); //Actual fsample=48000 if (isym != m_isym0 || m_frequency != m_frequency0) { if (itone[0] >= 100) { m_toneFrequency0 = itone[0]; } else { if (m_toneSpacing == 0.0) m_toneFrequency0 = m_frequency + itone[isym] * baud; else m_toneFrequency0 = m_frequency + itone[isym] * m_toneSpacing; } m_dphi = TAU * m_toneFrequency0 / m_frameRate; m_isym0 = isym; m_frequency0 = m_frequency; //??? } m_phi += m_dphi; if (m_phi > TAU) m_phi -= TAU; if (m_ic > i0) m_amp = 0.98 * m_amp; if (m_ic > i1) m_amp = 0.0; sample = qRound(m_amp * qSin(m_phi)); samples = load(sample, samples); ++framesGenerated; ++m_ic; } // TODO G4WJS: compare double with zero might not be wise if (m_amp == 0.0) { Q_EMIT stateChanged ((m_state = State::Idle)); return framesGenerated * bytesPerFrame(); m_phi = 0.0; } m_frequency0 = m_frequency; // done for this chunk - continue on next call while (samples != end) // pad block with silence { samples = load(0, samples); ++framesGenerated; } return framesGenerated * bytesPerFrame(); } [[fallthrough]]; case State::Idle: break; } Q_ASSERT (State::Idle == m_state); return 0; }