diff --git a/CMakeLists.txt b/CMakeLists.txt index 24e9d8580..9e3a6d1e2 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -505,6 +505,7 @@ set (wsjt_FSRCS lib/wqencode.f90 lib/fsk4hf/wspr5d.f90 lib/fsk4hf/wspr5sim.f90 + lib/fsk4hf/wspr5_wav.f90 lib/fsk4hf/wsprlfsim.f90 lib/wspr_downsample.f90 lib/zplot9.f90 diff --git a/commons.h b/commons.h index 079217c4c..fea9ed4ef 100644 --- a/commons.h +++ b/commons.h @@ -2,7 +2,7 @@ #define COMMONS_H #define NSMAX 6827 -#define NTMAX 120 +#define NTMAX 300 #define RX_SAMPLE_RATE 12000 #ifdef __cplusplus diff --git a/lib/constants.f90 b/lib/constants.f90 index 939cc3071..ea872e8ad 100644 --- a/lib/constants.f90 +++ b/lib/constants.f90 @@ -1,4 +1,4 @@ - integer, parameter :: NTMAX=120 + integer, parameter :: NTMAX=300 integer, parameter :: NMAX=NTMAX*12000 !Total sample intervals (one minute) integer, parameter :: NDMAX=NTMAX*1500 !Sample intervals at 1500 Hz rate integer, parameter :: NSMAX=6827 !Max length of saved spectra diff --git a/lib/fsk4hf/genwspr5.f90 b/lib/fsk4hf/genwspr5.f90 index e3d7a6189..1788e07bd 100644 --- a/lib/fsk4hf/genwspr5.f90 +++ b/lib/fsk4hf/genwspr5.f90 @@ -15,7 +15,7 @@ subroutine genwspr5(msg,ichk,msgsent,itone,itype) integer jd(NS+ND) integer isync(48) !Long sync vector integer ib13(13) !Barker 13 code - integer itone(NZ) + integer itone(NN) integer*8 n8 data ib13/1,1,1,1,1,-1,-1,1,1,-1,1,-1,1/ data first/.true./ diff --git a/lib/fsk4hf/getfc1w.f90 b/lib/fsk4hf/getfc1w.f90 index c57d61a87..a368420b2 100644 --- a/lib/fsk4hf/getfc1w.f90 +++ b/lib/fsk4hf/getfc1w.f90 @@ -25,7 +25,7 @@ subroutine getfc1w(c,fs,fc1,xsnr) smax=0. ipk=0 fc1=0. - ia=nint(100.0/df1) + ia=nint(190.0/df1) do i=-ia,ia f=i*df1 if(s(i).gt.smax) then diff --git a/lib/fsk4hf/wspr5_wav.f90 b/lib/fsk4hf/wspr5_wav.f90 new file mode 100644 index 000000000..aca36b5f1 --- /dev/null +++ b/lib/fsk4hf/wspr5_wav.f90 @@ -0,0 +1,48 @@ +subroutine wspr5_wav(baud,xdt,f0,itone,snrdb,iwave) + + include 'wsprlf_params.f90' + parameter (NMAX=300*12000) + integer itone(NN) + integer*2 iwave(NMAX) + real*8 twopi,dt,dphi0,dphi1,dphi,phi + real dat(NMAX) + + twopi=8.d0*atan(1.d0) + dt=1.d0/12000.d0 + + dat=0. + if(snrdb.lt.90) then + do i=1,NMAX + dat(i)=gran() !Generate gaussian noise + enddo + bandwidth_ratio=2500.0/6000.0 + sig=sqrt(2*bandwidth_ratio)*10.0**(0.05*snrdb) + else + sig=1.0 + endif + + dphi0=twopi*(f0-0.25d0*baud)*dt + dphi1=twopi*(f0+0.25d0*baud)*dt + phi=0.d0 + k=nint(xdt/dt) + do j=1,NN + dphi=dphi0 + if(itone(j).eq.1) dphi=dphi1 + if(k.eq.0) phi=-dphi + do i=1,NSPS0 + k=k+1 + phi=phi+dphi + if(phi.gt.twopi) phi=phi-twopi + xphi=phi + if(k.gt.0 .and. k.le.NMAX) dat(k)=dat(k) + sig*sin(xphi) + enddo + enddo + print*,NN,NSPS0,k + + fac=32767.0 + rms=100.0 + if(snrdb.ge.90.0) iwave=nint(fac*dat) + if(snrdb.lt.90.0) iwave=nint(rms*dat) + + return +end subroutine wspr5_wav diff --git a/lib/fsk4hf/wspr5d.f90 b/lib/fsk4hf/wspr5d.f90 index 9a32f3ef5..4ba16d088 100644 --- a/lib/fsk4hf/wspr5d.f90 +++ b/lib/fsk4hf/wspr5d.f90 @@ -3,6 +3,8 @@ program wspr5d ! Simulate characteristics of a potential "WSPR-LF" mode using LDPC (300,60) ! code, OQPSK modulation, and 5 minute T/R sequences. +! Q: Would it be better for central Sync array to use both I and Q channels? + ! Reception and Demodulation algorithm: ! 1. Compute coarse spectrum; find fc1 = approx carrier freq ! 2. Mix from fc1 to 0; LPF at +/- 0.75*R @@ -13,15 +15,14 @@ program wspr5d ! 7. Get soft bits from equalized data include 'wsprlf_params.f90' - -! Q: Would it be better for central Sync array to use both I and Q channels? - - character arg*8,message*22,cbits*50 + parameter (NMAX=300*12000) + character arg*8,message*22,cbits*50,infile*80,fname*16 complex csync(0:NZ-1) !Sync symbols only, from cbb complex c(0:NZ-1) !Complex waveform complex c1(0:NZ-1) !Complex waveform complex zz(NS+ND) !Complex symbol values (intermediate) complex z + real*8 fMHz real rxdata(ND),llr(ND) !Soft symbols real pp(2*NSPS) !Shaped pulse for OQPSK real a(5) !For twkfreq1 @@ -31,13 +32,14 @@ program wspr5d integer isync(48) !Long sync vector integer ib13(13) !Barker 13 code integer*8 n8 + integer*2 iwave(NMAX) !Generated full-length waveform integer*1 idat(7) integer*1 decoded(KK),apmask(ND),cw(ND) data ib13/1,1,1,1,1,-1,-1,1,1,-1,1,-1,1/ nargs=iargc() - if(nargs.ne.1) then - print*,'Usage: wspr5d maxn' + if(nargs.lt.2) then + print*,'Usage: wspr5d maxn file1 [file2 ...]' go to 999 endif call getarg(1,arg) @@ -82,8 +84,17 @@ program wspr5d endif enddo - do ifile=1,9999 - read(10,end=999) c + do ifile=1,nargs-1 + call getarg(ifile+1,infile) + open(10,file=infile,status='old',access='stream') + if(index(infile,'.c5').gt.0) then + read(10,end=999) fname,ntrmin,fMHz,c + close(10) + read(fname(8:11),*) nutc + else + print*,'Wrong file format?' + go to 999 + endif ! do i=0,NZ-1 ! write(40,4001) i,c(i),csync(i) !4001 format(i8,4f12.6) @@ -102,7 +113,8 @@ program wspr5d jpk=0 iaa=0 ibb=NZ-1 - do j=-20*NSPS,20*NSPS,NSPS/8 + jmax=1260 + do j=-jmax,jmax,NSPS/8 ia=j ib=NZ-1+j if(ia.lt.0) then @@ -171,8 +183,8 @@ program wspr5d call wqdecode(idat,message,itype) endif nsnr=nint(xsnr) - write(*,1110) ifile,nsnr,xdt,fc1+fc2,message -1110 format(2i5,f7.2,f7.2,2x,a22) - enddo + write(*,1110) nutc,nsnr,xdt,fc1+fc2,message +1110 format(i4.4,i5,f7.2,f7.2,2x,a22) + enddo ! ifile loop 999 end program wspr5d diff --git a/lib/fsk4hf/wspr5sim.f90 b/lib/fsk4hf/wspr5sim.f90 index f9611ecb0..0e2c6c286 100644 --- a/lib/fsk4hf/wspr5sim.f90 +++ b/lib/fsk4hf/wspr5sim.f90 @@ -1,38 +1,40 @@ program wspr5sim - include 'wsprlf_params.f90' - character*12 arg - character*22 msg,msgsent +! Generate simulated data for the 5-minute "WSPR-LF" mode. + + use wavhdr + include 'wsprlf_params.f90' !Set various constants + parameter (NMAX=300*12000) + type(hdr) h !Header for .wav file + character arg*12,fname*16 + character msg*22,msgsent*22 complex c0(0:NZ-1) complex c(0:NZ-1) - integer itone(NZ) + real*8 fMHz + integer itone(NN) + integer*2 iwave(NMAX) !Generated full-length waveform ! Get command-line argument(s) nargs=iargc() - if(nargs.ne.5) then - print*,'Usage: wspr5sim "message" f0 DT nfiles snr' - print*,'Example: wspr5sim "K1ABC FN42 30" 1500 0.0 10 -33' + if(nargs.ne.6) then + print*,'Usage: wspr5sim "message" f0 DT nwav nfiles snr' + print*,'Example: wspr5sim "K1ABC FN42 30" 50 0.0 0 10 -33' go to 999 endif - call getarg(1,msg) !Get message from command line + call getarg(1,msg) !Message to be transmitted call getarg(2,arg) - read(arg,*) f0 + read(arg,*) f0 !Freq relative to WSPR-band center (Hz) call getarg(3,arg) - read(arg,*) xdt + read(arg,*) xdt !Time offset from nominal (s) call getarg(4,arg) - read(arg,*) nfiles + read(arg,*) nwav !1 for *.wav file, 0 for *.c5 file call getarg(5,arg) - read(arg,*) snrdb - - call genwspr5(msg,ichk,msgsent,itone,itype) - - txt=NN*NSPS0/12000.0 - write(*,1000) f0,xdt,txt,snrdb,nfiles,msgsent -1000 format('f0:',f9.3,' DT:',f6.2,' txt:',f6.1,' SNR:',f6.1, & - ' nfiles:',i3,2x,a22) + read(arg,*) nfiles !Number of files + call getarg(6,arg) + read(arg,*) snrdb !SNR_2500 twopi=8.0*atan(1.0) - fs=NSPS*12000.0/NSPS0 !Sample rate + fs=12000.0/NDOWN !Sample rate dt=1.0/fs !Sample interval (s) tt=NSPS*dt !Duration of "itone" symbols (s) ts=2*NSPS*dt !Duration of OQPSK symbols (s) @@ -41,10 +43,16 @@ program wspr5sim bandwidth_ratio=2500.0/(fs/2.0) sig=sqrt(bandwidth_ratio) * 10.0**(0.05*snrdb) if(snrdb.gt.90.0) sig=1.0 - - dphi0=twopi*(f0-0.25d0*baud)*dt - dphi1=twopi*(f0+0.25d0*baud)*dt - phi=0.d0 + txt=NN*NSPS0/12000.0 + + call genwspr5(msg,ichk,msgsent,itone,itype) !Encode the message, get itone() + write(*,1000) f0,xdt,txt,snrdb,nfiles,msgsent +1000 format('f0:',f9.3,' DT:',f6.2,' txt:',f6.1,' SNR:',f6.1, & + ' nfiles:',i3,2x,a22) + + dphi0=twopi*(f0-0.25*baud)*dt + dphi1=twopi*(f0+0.25*baud)*dt + phi=0.0 c0=0. k=-1 + nint(xdt/dt) do j=1,NN @@ -62,14 +70,30 @@ program wspr5sim c0=sig*c0 !Scale to requested sig level do ifile=1,nfiles - if(snrdb.lt.90) then - do i=0,NZ-1 !Add gaussian noise at specified SNR - xnoise=gran() - ynoise=gran() - c(i)=c0(i) + cmplx(xnoise,ynoise) - enddo + if(nwav.eq.0) then + if(snrdb.lt.90) then + do i=0,NZ-1 !Add gaussian noise at specified SNR + xnoise=gran() + ynoise=gran() + c(i)=c0(i) + cmplx(xnoise,ynoise) + enddo + endif + write(fname,1100) ifile +1100 format('000000_',i4.4,'.c5') + open(10,file=fname,status='unknown',access='stream') + fMHz=10.1387d0 + nmin=5 + write(10) fname,nmin,fMHz,c + close(10) + else + call wspr5_wav(baud,xdt,f0,itone,snrdb,iwave) + h=default_header(12000,NMAX) + write(fname,1102) ifile +1102 format('000000_',i4.4,'.wav') + open(10,file=fname,status='unknown',access='stream') + write(10) h,iwave !Save the .wav file + close(10) endif - write(10) c enddo 999 end program wspr5sim diff --git a/mainwindow.cpp b/mainwindow.cpp index 16886b6a2..60874ee23 100644 --- a/mainwindow.cpp +++ b/mainwindow.cpp @@ -489,6 +489,7 @@ MainWindow::MainWindow(QDir const& temp_directory, bool multiple, ui->actionJT9_JT65->setActionGroup(modeGroup); ui->actionJT4->setActionGroup(modeGroup); ui->actionWSPR->setActionGroup(modeGroup); + ui->actionWSPR_LF->setActionGroup(modeGroup); ui->actionEcho->setActionGroup(modeGroup); ui->actionISCAT->setActionGroup(modeGroup); ui->actionMSK144->setActionGroup(modeGroup); @@ -811,6 +812,7 @@ MainWindow::MainWindow(QDir const& temp_directory, bool multiple, if(m_mode=="JT65") on_actionJT65_triggered(); if(m_mode=="JT9+JT65") on_actionJT9_JT65_triggered(); if(m_mode=="WSPR") on_actionWSPR_triggered(); + if(m_mode=="WSPR-LF") on_actionWSPR_LF_triggered(); if(m_mode=="ISCAT") on_actionISCAT_triggered(); if(m_mode=="MSK144") on_actionMSK144_triggered(); if(m_mode=="QRA64") on_actionQRA64_triggered(); @@ -879,6 +881,9 @@ MainWindow::MainWindow(QDir const& temp_directory, bool multiple, splashTimer.setSingleShot (true); splashTimer.start (20 * 1000); + if(m_config.my_callsign()=="K1JT" or m_config.my_callsign()=="K9AN" or + m_config.my_callsign()=="G4WJS") ui->actionWSPR_LF->setEnabled(true); + // this must be the last statement of constructor if (!m_valid) throw std::runtime_error {"Fatal initialization exception"}; } @@ -1101,8 +1106,8 @@ void MainWindow::fixStop() m_hsymStop=179; if(m_mode=="WSPR") { m_hsymStop=396; - } else if(m_mode=="WSPR-15") { - m_hsymStop=3090; + } else if(m_mode=="WSPR-LF") { + m_hsymStop=1030; } else if(m_mode=="Echo") { m_hsymStop=10; } else if (m_mode=="JT4"){ @@ -1536,6 +1541,7 @@ void MainWindow::on_actionSettings_triggered() //Setup Dialog if(m_config.contestMode() != bcontest0) genStdMsgs(m_rpt); } if(m_mode=="WSPR") on_actionWSPR_triggered(); + if(m_mode=="WSPR-LF") on_actionWSPR_LF_triggered(); if(m_mode=="Echo") on_actionEcho_triggered(); } @@ -2175,8 +2181,9 @@ void MainWindow::read_wav_file (QString const& fname) auto n = file.read (reinterpret_cast (dec_data.d2), std::min (max_bytes, file.size ())); int frames_read = n / bytes_per_frame; +// qDebug() << "a" << max_bytes << n << frames_read << frames_read/12000.0 << m_TRperiod; // zero unfilled remaining sample space - std::memset (&dec_data.d2[0] + n, 0, max_bytes - n); + std::memset(&dec_data.d2[frames_read],0,max_bytes - n); if (11025 == file.format ().sampleRate ()) { short sample_size = file.format ().sampleSize (); wav12_ (dec_data.d2, dec_data.d2, &frames_read, &sample_size); @@ -2793,7 +2800,8 @@ void MainWindow::guiUpdate() if(m_modeTx=="JT9") txDuration=1.0 + 85.0*m_nsps/12000.0; // JT9 if(m_modeTx=="JT65") txDuration=1.0 + 126*4096/11025.0; // JT65 if(m_mode=="QRA64") txDuration=1.0 + 84*6912/12000.0; // QRA64 - if(m_mode=="WSPR") txDuration=2.0 + 162*8192/12000.0; // WSPR + if(m_mode=="WSPR") txDuration=2.0 + 162*8192/12000.0; // WSPR + if(m_mode=="WSPR-LF") txDuration=2.0 + 412*8640/12000.0; // WSPR-LF if(m_mode=="ISCAT" or m_mode=="MSK144" or m_bFast9) { txDuration=m_TRperiod-0.25; // ISCAT, JT9-fast, MSK144 } @@ -2977,7 +2985,7 @@ void MainWindow::guiUpdate() &m_currentMessageType, len1, len1); if(m_mode=="QRA64") genqra64_(message, &ichk, msgsent, const_cast (itone), &m_currentMessageType, len1, len1); - if(m_mode.startsWith ("WSPR")) genwspr_(message, msgsent, const_cast (itone), + if(m_mode=="WSPR") genwspr_(message, msgsent, const_cast (itone), len1, len1); if(m_modeTx=="MSK144") { bool bcontest=m_config.contestMode(); @@ -4558,6 +4566,23 @@ void MainWindow::on_actionWSPR_triggered() statusChanged(); } +void MainWindow::on_actionWSPR_LF_triggered() +{ + on_actionWSPR_triggered(); + m_mode="WSPR-LF"; + switch_mode (Modes::WSPR); + m_modeTx="WSPR-LF"; + m_TRperiod=300; + m_modulator->setPeriod(m_TRperiod); // TODO - not thread safe + m_detector->setPeriod(m_TRperiod); // TODO - not thread safe + m_hsymStop=1030; + m_toneSpacing=0.5*12000.0/8640.0; + setup_status_bar (false); + ui->actionWSPR_LF->setChecked(true); + m_wideGraph->setPeriod(m_TRperiod,m_nsps); + statusChanged(); +} + void MainWindow::on_actionEcho_triggered() { on_actionJT4_triggered(); diff --git a/mainwindow.h b/mainwindow.h index bc55069a4..e2c1c2bc7 100644 --- a/mainwindow.h +++ b/mainwindow.h @@ -234,6 +234,7 @@ private slots: void networkError (QString const&); void on_ClrAvgButton_clicked(); void on_actionWSPR_triggered(); + void on_actionWSPR_LF_triggered(); void on_syncSpinBox_valueChanged(int n); void on_TxPowerComboBox_currentIndexChanged(const QString &arg1); void on_sbTxPercent_valueChanged(int n); diff --git a/mainwindow.ui b/mainwindow.ui index ec1e133de..6368fc652 100644 --- a/mainwindow.ui +++ b/mainwindow.ui @@ -2,6 +2,14 @@ MainWindow + + + 0 + 0 + 867 + 583 + + WSJT-X by K1JT @@ -2358,6 +2366,14 @@ QPushButton[state="ok"] { + + + 0 + 0 + 867 + 21 + + File @@ -2436,6 +2452,7 @@ QPushButton[state="ok"] { + @@ -2980,6 +2997,20 @@ QPushButton[state="ok"] { View phase response ... + + + true + + + false + + + WSPR-LF + + + Experimental LF/MF mode + + diff --git a/soundin.cpp b/soundin.cpp index eaa30b8e8..3da3bdd09 100644 --- a/soundin.cpp +++ b/soundin.cpp @@ -50,7 +50,7 @@ void SoundInput::start(QAudioDeviceInfo const& device, int framesPerBuffer, Audi m_sink = sink; QAudioFormat format (device.preferredFormat()); - qDebug () << "Preferred audio input format:" << format; +// qDebug () << "Preferred audio input format:" << format; format.setChannelCount (AudioDevice::Mono == channel ? 1 : 2); format.setCodec ("audio/pcm"); format.setSampleRate (12000 * downSampleFactor); @@ -65,11 +65,11 @@ void SoundInput::start(QAudioDeviceInfo const& device, int framesPerBuffer, Audi if (!device.isFormatSupported (format)) { - qDebug () << "Nearest supported audio format:" << device.nearestFormat (format); +// qDebug () << "Nearest supported audio format:" << device.nearestFormat (format); Q_EMIT error (tr ("Requested input audio format is not supported on device.")); return; } - qDebug () << "Selected audio input format:" << format; +// qDebug () << "Selected audio input format:" << format; m_stream.reset (new QAudioInput {device, format}); if (audioError ()) diff --git a/soundout.cpp b/soundout.cpp index 4295c2560..944f04e4e 100644 --- a/soundout.cpp +++ b/soundout.cpp @@ -54,7 +54,7 @@ void SoundOutput::setFormat (QAudioDeviceInfo const& device, unsigned channels, m_msBuffered = msBuffered; QAudioFormat format (device.preferredFormat ()); - qDebug () << "Preferred audio output format:" << format; +// qDebug () << "Preferred audio output format:" << format; format.setChannelCount (channels); format.setCodec ("audio/pcm"); format.setSampleRate (48000); @@ -69,7 +69,7 @@ void SoundOutput::setFormat (QAudioDeviceInfo const& device, unsigned channels, { Q_EMIT error (tr ("Requested output audio format is not supported on device.")); } - qDebug () << "Selected audio output format:" << format; +// qDebug () << "Selected audio output format:" << format; m_stream.reset (new QAudioOutput (device, format)); audioError ();