| 
									
										
										
										
											2018-02-08 21:28:33 -05:00
										 |  |  | #include "Modulator.hpp" | 
					
						
							|  |  |  | #include <limits> | 
					
						
							|  |  |  | #include <qmath.h> | 
					
						
							|  |  |  | #include <QDateTime> | 
					
						
							|  |  |  | #include <QDebug> | 
					
						
							|  |  |  | #include "mainwindow.h" | 
					
						
							|  |  |  | #include "soundout.h" | 
					
						
							| 
									
										
										
										
											2018-08-05 11:33:30 -04:00
										 |  |  | #include "commons.h" | 
					
						
							| 
									
										
										
										
											2018-02-08 21:28:33 -05:00
										 |  |  | 
 | 
					
						
							|  |  |  | #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; | 
					
						
							|  |  |  |   unsigned delay_ms = 1920 == m_nsps && 15 == m_period ? 500 : 1000; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   // 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 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   // round up to an exact portion of a second that allows for startup | 
					
						
							|  |  |  |   // delays | 
					
						
							|  |  |  |   m_ic = (mstr / delay_ms) * m_frameRate * delay_ms / 1000; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   if(m_bFastMode) m_ic=0; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   m_silentFrames = 0; | 
					
						
							| 
									
										
										
										
											2018-08-05 11:33:30 -04:00
										 |  |  |   // calculate number of silent frames to send, so that audio will start at | 
					
						
							|  |  |  |   // the nominal time "delay_ms" into the Tx sequence. | 
					
						
							| 
									
										
										
										
											2018-02-08 21:28:33 -05:00
										 |  |  |   if (synchronize && !m_tuning && !m_bFastMode)	{ | 
					
						
							|  |  |  |     m_silentFrames = m_ic + m_frameRate / (1000 / delay_ms) - (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(m_nsps==6) { | 
					
						
							|  |  |  |     toneFrequency=1000.0; | 
					
						
							|  |  |  |     m_frequency=1000.0; | 
					
						
							|  |  |  |     m_frequency0=1000.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; | 
					
						
							| 
									
										
										
										
											2018-08-05 11:33:30 -04:00
										 |  |  | //        qDebug() << "Mod A" << m_toneSpacing << m_ic; | 
					
						
							| 
									
										
										
										
											2018-02-08 21:28:33 -05:00
										 |  |  |         if(!m_tuning) isym=m_ic/(4.0*m_nsps);            // Actual fsample=48000 | 
					
						
							|  |  |  |         bool slowCwId=((isym >= m_symbolsLength) && (icw[0] > 0)) && (!m_bFastMode); | 
					
						
							|  |  |  |         if(m_TRperiod==3) slowCwId=false; | 
					
						
							|  |  |  |         bool fastCwId=false; | 
					
						
							|  |  |  |         static bool bCwId=false; | 
					
						
							|  |  |  |         qint64 ms = QDateTime::currentMSecsSinceEpoch(); | 
					
						
							|  |  |  |         float tsec=0.001*(ms % (1000*m_TRperiod)); | 
					
						
							|  |  |  |         if(m_bFastMode and (icw[0]>0) and (tsec>(m_TRperiod-5.0))) fastCwId=true; | 
					
						
							|  |  |  |         if(!m_bFastMode) m_nspd=2560;                 // 22.5 WPM | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         if(slowCwId or fastCwId) {     // Transmit CW ID? | 
					
						
							|  |  |  |           m_dphi = m_twoPi*m_frequency/m_frameRate; | 
					
						
							|  |  |  |           if(m_bFastMode and !bCwId) { | 
					
						
							|  |  |  |             m_frequency=1500;          // Set params for CW ID | 
					
						
							|  |  |  |             m_dphi = m_twoPi*m_frequency/m_frameRate; | 
					
						
							|  |  |  |             m_symbolsLength=126; | 
					
						
							|  |  |  |             m_nsps=4096.0*12000.0/11025.0; | 
					
						
							|  |  |  |             m_ic=2246949; | 
					
						
							|  |  |  |             m_nspd=2560;               // 22.5 WPM | 
					
						
							|  |  |  |             if(icw[0]*m_nspd/48000.0 > 4.0) m_nspd=4.0*48000.0/icw[0];  //Faster CW for long calls | 
					
						
							|  |  |  |           } | 
					
						
							|  |  |  |           bCwId=true; | 
					
						
							|  |  |  |           unsigned 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; | 
					
						
							|  |  |  |             float x=0; | 
					
						
							|  |  |  |             if(m_ramp!=0) { | 
					
						
							|  |  |  |               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(m_bFastMode) { | 
					
						
							|  |  |  |               sample=0; | 
					
						
							|  |  |  |               if(level) sample=32767.0*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 (); | 
					
						
							|  |  |  |         } else { | 
					
						
							|  |  |  |           bCwId=false; | 
					
						
							|  |  |  |         } //End of code for CW ID | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         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; | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-08-05 11:33:30 -04:00
										 |  |  |         qint16 sample; | 
					
						
							| 
									
										
										
										
											2018-02-08 21:28:33 -05:00
										 |  |  |         for (unsigned i = 0; i < numFrames && m_ic <= i1; ++i) { | 
					
						
							|  |  |  |           isym=0; | 
					
						
							|  |  |  |           if(!m_tuning and m_TRperiod!=3) 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; | 
					
						
							|  |  |  |               } | 
					
						
							|  |  |  |             } | 
					
						
							| 
									
										
										
										
											2018-08-05 11:33:30 -04:00
										 |  |  | //            qDebug() << "Mod B" << m_bFastMode << m_ic << numFrames << isym << itone[isym] | 
					
						
							| 
									
										
										
										
											2018-02-08 21:28:33 -05:00
										 |  |  | //                     << 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; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-08-05 11:33:30 -04:00
										 |  |  |           sample=qRound(m_amp*qSin(m_phi)); | 
					
						
							|  |  |  |           if(m_toneSpacing < 0) sample=qRound(m_amp*foxcom_.wave[m_ic]); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | //          if(m_ic < 100) qDebug() << "Mod C" << m_ic << m_amp << foxcom_.wave[m_ic] << sample; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |           samples = load(postProcessSample(sample), samples); | 
					
						
							| 
									
										
										
										
											2018-02-08 21:28:33 -05:00
										 |  |  |           ++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; | 
					
						
							|  |  |  | } |