mirror of
				https://github.com/saitohirga/WSJT-X.git
				synced 2025-11-04 05:50:31 -05:00 
			
		
		
		
	git-svn-id: svn+ssh://svn.code.sf.net/p/wsjt/wsjt/branches/wsjtx@6734 ab8295b8-cf94-4d9e-aec4-7959e3be5d79
		
			
				
	
	
		
			304 lines
		
	
	
		
			8.5 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
			
		
		
	
	
			304 lines
		
	
	
		
			8.5 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
#include "Modulator.hpp"
 | 
						|
#include <limits>
 | 
						|
#include <qmath.h>
 | 
						|
#include <QDateTime>
 | 
						|
#include <QDebug>
 | 
						|
#include "mainwindow.h"
 | 
						|
#include "soundout.h"
 | 
						|
 | 
						|
#include "moc_Modulator.cpp"
 | 
						|
 | 
						|
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 WSJT_SOFT_KEYING
 | 
						|
#else
 | 
						|
# define SOFT_KEYING 1
 | 
						|
#endif
 | 
						|
 | 
						|
double constexpr Modulator::m_twoPi;
 | 
						|
 | 
						|
//    float wpm=20.0;
 | 
						|
//    unsigned m_nspd=1.2*48000.0/wpm;
 | 
						|
//    m_nspd=3072;                           //18.75 WPM
 | 
						|
 | 
						|
Modulator::Modulator (unsigned frameRate, unsigned periodLengthInSeconds,
 | 
						|
                      QObject * parent)
 | 
						|
  : AudioDevice {parent}
 | 
						|
  , m_quickClose {false}
 | 
						|
  , m_phi {0.0}
 | 
						|
  , m_toneSpacing {0.0}
 | 
						|
  , m_fSpread {0.0}
 | 
						|
  , m_frameRate {frameRate}
 | 
						|
  , m_period {periodLengthInSeconds}
 | 
						|
  , m_state {Idle}
 | 
						|
  , m_tuning {false}
 | 
						|
  , m_cwLevel {false}
 | 
						|
  , m_j0 {-1}
 | 
						|
  , m_toneFrequency0 {1500.0}
 | 
						|
{
 | 
						|
}
 | 
						|
 | 
						|
void Modulator::start (unsigned symbolsLength, double framesPerSymbol,
 | 
						|
                       double frequency, double toneSpacing,
 | 
						|
                       SoundOutput * stream, Channel channel,
 | 
						|
                       bool synchronize, bool fastMode, double dBSNR, int TRperiod)
 | 
						|
{
 | 
						|
  Q_ASSERT (stream);
 | 
						|
 | 
						|
  // Time according to this computer which becomes our base time
 | 
						|
  qint64 ms0 = QDateTime::currentMSecsSinceEpoch() % 86400000;
 | 
						|
 | 
						|
  if (m_state != Idle)
 | 
						|
    {
 | 
						|
      stop ();
 | 
						|
    }
 | 
						|
 | 
						|
  m_quickClose = false;
 | 
						|
 | 
						|
  m_symbolsLength = symbolsLength;
 | 
						|
  m_isym0 = std::numeric_limits<unsigned>::max (); // big number
 | 
						|
  m_frequency0 = 0.;
 | 
						|
  m_phi = 0.;
 | 
						|
  m_addNoise = dBSNR < 0.;
 | 
						|
  m_nsps = framesPerSymbol;
 | 
						|
  m_frequency = frequency;
 | 
						|
  m_amp = std::numeric_limits<qint16>::max ();
 | 
						|
  m_toneSpacing = toneSpacing;
 | 
						|
  m_bFastMode=fastMode;
 | 
						|
  m_TRperiod=TRperiod;
 | 
						|
 | 
						|
  // 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
 | 
						|
  if(m_bFastMode) m_ic=0;
 | 
						|
  // into period where N is the next whole second
 | 
						|
 | 
						|
  m_silentFrames = 0;
 | 
						|
  // calculate number of silent frames to send
 | 
						|
  if (synchronize && !m_tuning && !m_bFastMode)	{
 | 
						|
    m_silentFrames = m_ic + m_frameRate - (mstr * m_frameRate / 1000);
 | 
						|
  }
 | 
						|
  initialize (QIODevice::ReadOnly, channel);
 | 
						|
  Q_EMIT stateChanged ((m_state = (synchronize && m_silentFrames) ?
 | 
						|
                        Synchronizing : Active));
 | 
						|
  m_stream = stream;
 | 
						|
  if (m_stream) m_stream->restart (this);
 | 
						|
}
 | 
						|
 | 
						|
void Modulator::tune (bool newState)
 | 
						|
{
 | 
						|
  m_tuning = newState;
 | 
						|
  if (!m_tuning) stop (true);
 | 
						|
}
 | 
						|
 | 
						|
void Modulator::stop (bool quick)
 | 
						|
{
 | 
						|
  m_quickClose = quick;
 | 
						|
  close ();
 | 
						|
}
 | 
						|
 | 
						|
void Modulator::close ()
 | 
						|
{
 | 
						|
  if (m_stream)
 | 
						|
    {
 | 
						|
      if (m_quickClose)
 | 
						|
        {
 | 
						|
          m_stream->reset ();
 | 
						|
        }
 | 
						|
      else
 | 
						|
        {
 | 
						|
          m_stream->stop ();
 | 
						|
        }
 | 
						|
    }
 | 
						|
  if (m_state != Idle)
 | 
						|
    {
 | 
						|
      Q_EMIT stateChanged ((m_state = Idle));
 | 
						|
    }
 | 
						|
  AudioDevice::close ();
 | 
						|
}
 | 
						|
 | 
						|
qint64 Modulator::readData (char * data, qint64 maxSize)
 | 
						|
{
 | 
						|
  double toneFrequency=1500.0;
 | 
						|
 | 
						|
  if(maxSize==0) return 0;
 | 
						|
  Q_ASSERT (!(maxSize % qint64 (bytesPerFrame ()))); // no torn frames
 | 
						|
  Q_ASSERT (isOpen ());
 | 
						|
 | 
						|
  qint64 numFrames (maxSize / bytesPerFrame ());
 | 
						|
  qint16 * samples (reinterpret_cast<qint16 *> (data));
 | 
						|
  qint16 * end (samples + numFrames * (bytesPerFrame () / sizeof (qint16)));
 | 
						|
  qint64 framesGenerated (0);
 | 
						|
 | 
						|
  switch (m_state)
 | 
						|
    {
 | 
						|
    case Synchronizing:
 | 
						|
      {
 | 
						|
        if (m_silentFrames)	{  // send silence up to first second
 | 
						|
          framesGenerated = qMin (m_silentFrames, numFrames);
 | 
						|
          for ( ; samples != end; samples = load (0, samples)) { // silence
 | 
						|
          }
 | 
						|
          m_silentFrames -= framesGenerated;
 | 
						|
          return framesGenerated * bytesPerFrame ();
 | 
						|
        }
 | 
						|
 | 
						|
        Q_EMIT stateChanged ((m_state = Active));
 | 
						|
        m_cwLevel = false;
 | 
						|
        m_ramp = 0;		// prepare for CW wave shaping
 | 
						|
      }
 | 
						|
      // fall through
 | 
						|
 | 
						|
    case Active:
 | 
						|
      {
 | 
						|
        unsigned int isym=0;
 | 
						|
        if(!m_tuning) isym=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);
 | 
						|
 | 
						|
          while (samples != end) {
 | 
						|
            j = (m_ic - ic0) / m_nspd + 1; // symbol of this sample
 | 
						|
            bool level {bool (icw[j])};
 | 
						|
 | 
						|
            m_phi += m_dphi;
 | 
						|
            if (m_phi > m_twoPi) m_phi -= m_twoPi;
 | 
						|
 | 
						|
            qint16 sample=0;
 | 
						|
            float amp=32767.0;
 | 
						|
            if(m_ramp!=0) {
 | 
						|
              float x=qSin(float(m_phi));
 | 
						|
              if(SOFT_KEYING) {
 | 
						|
                amp=qAbs(qint32(m_ramp));
 | 
						|
                if(amp>32767.0) amp=32767.0;
 | 
						|
              }
 | 
						|
              sample=round(amp*x);
 | 
						|
            }
 | 
						|
 | 
						|
            if (int (j) <= icw[0] && j < NUM_CW_SYMBOLS) // stop condition
 | 
						|
              {
 | 
						|
                samples = load (postProcessSample (sample), samples);
 | 
						|
                ++framesGenerated;
 | 
						|
                ++m_ic;
 | 
						|
              }
 | 
						|
            else
 | 
						|
              {
 | 
						|
                Q_EMIT stateChanged ((m_state = Idle));
 | 
						|
                return framesGenerated * bytesPerFrame ();
 | 
						|
              }
 | 
						|
 | 
						|
            // 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
 | 
						|
              }
 | 
						|
            m_cwLevel = level;
 | 
						|
          }
 | 
						|
 | 
						|
          return framesGenerated * bytesPerFrame ();
 | 
						|
        }
 | 
						|
 | 
						|
        double const baud (12000.0 / m_nsps);
 | 
						|
        // fade out parameters (no fade out for tuning)
 | 
						|
        unsigned int i0,i1;
 | 
						|
        if(m_tuning) {
 | 
						|
          i1 = i0 = (m_bFastMode ? 999999 : 9999) * m_nsps;
 | 
						|
        } else {
 | 
						|
          i0=(m_symbolsLength - 0.017) * 4.0 * m_nsps;
 | 
						|
          i1= m_symbolsLength * 4.0 * m_nsps;
 | 
						|
        }
 | 
						|
        if(m_bFastMode and !m_tuning) {
 | 
						|
          i1=m_TRperiod*48000 - 24000;
 | 
						|
          i0=i1-816;
 | 
						|
        }
 | 
						|
 | 
						|
 | 
						|
        for (unsigned i = 0; i < numFrames && m_ic <= i1; ++i) {
 | 
						|
          isym=0;
 | 
						|
          if(!m_tuning) isym=m_ic / (4.0 * m_nsps);         //Actual fsample=48000
 | 
						|
          if(m_bFastMode) isym=isym%m_symbolsLength;
 | 
						|
          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;
 | 
						|
              }
 | 
						|
            }
 | 
						|
//            qDebug() << "B" << m_bFastMode << m_ic << numFrames << isym << itone[isym]
 | 
						|
//                            << m_toneFrequency0 << m_nsps;
 | 
						|
            m_dphi = m_twoPi * m_toneFrequency0 / m_frameRate;
 | 
						|
            m_isym0 = isym;
 | 
						|
            m_frequency0 = m_frequency;         //???
 | 
						|
          }
 | 
						|
 | 
						|
          int j=m_ic/480;
 | 
						|
          if(m_fSpread>0.0 and j!=m_j0) {
 | 
						|
            float x1=(float)qrand()/RAND_MAX;
 | 
						|
            float x2=(float)qrand()/RAND_MAX;
 | 
						|
            toneFrequency = m_toneFrequency0 + 0.5*m_fSpread*(x1+x2-1.0);
 | 
						|
            m_dphi = m_twoPi * toneFrequency / m_frameRate;
 | 
						|
            m_j0=j;
 | 
						|
          }
 | 
						|
 | 
						|
          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;
 | 
						|
 | 
						|
          samples = load (postProcessSample (m_amp * qSin (m_phi)), samples);
 | 
						|
          ++framesGenerated;
 | 
						|
          ++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));
 | 
						|
            return framesGenerated * bytesPerFrame ();
 | 
						|
          }
 | 
						|
          m_phi = 0.0;
 | 
						|
        }
 | 
						|
 | 
						|
        m_frequency0 = m_frequency;
 | 
						|
        // done for this chunk - continue on next call
 | 
						|
        return framesGenerated * bytesPerFrame ();
 | 
						|
      }
 | 
						|
      // fall through
 | 
						|
 | 
						|
    case Idle:
 | 
						|
      break;
 | 
						|
    }
 | 
						|
 | 
						|
  Q_ASSERT (Idle == m_state);
 | 
						|
  return 0;
 | 
						|
}
 | 
						|
 | 
						|
qint16 Modulator::postProcessSample (qint16 sample) const
 | 
						|
{
 | 
						|
  if (m_addNoise) {  // Test frame, we'll add noise
 | 
						|
    qint32 s = m_fac * (gran () + sample * m_snr / 32768.0);
 | 
						|
    if (s > std::numeric_limits<qint16>::max ()) {
 | 
						|
      s = std::numeric_limits<qint16>::max ();
 | 
						|
    }
 | 
						|
    if (s < std::numeric_limits<qint16>::min ()) {
 | 
						|
      s = std::numeric_limits<qint16>::min ();
 | 
						|
    }
 | 
						|
    sample = s;
 | 
						|
  }
 | 
						|
  return sample;
 | 
						|
}
 |