diff --git a/CMakeLists.txt b/CMakeLists.txt index db716d464..ccb2f6d5c 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -394,6 +394,7 @@ set (wsjt_FSRCS lib/chkhist.f90 lib/chkmsg.f90 lib/chkss2.f90 + lib/ft4/clockit.f90 lib/ft8/compress.f90 lib/coord.f90 lib/db.f90 @@ -455,6 +456,7 @@ set (wsjt_FSRCS lib/ft8.f90 lib/ft8dec.f90 lib/ft8/ft8sim.f90 + lib/ft8/ft8sim_gfsk.f90 lib/gen4.f90 lib/gen65.f90 lib/gen9.f90 @@ -462,12 +464,16 @@ set (wsjt_FSRCS lib/ft8/genft8.f90 lib/genmsk_128_90.f90 lib/genmsk40.f90 + lib/ft4/genft4.f90 + lib/ft4/gen_ft4wave.f90 + lib/ft8/gen_ft8wave.f90 lib/genqra64.f90 lib/ft8/genft8refsig.f90 lib/genwspr.f90 lib/geodist.f90 lib/getlags.f90 lib/getmet4.f90 + lib/ft2/gfsk_pulse.f90 lib/graycode.f90 lib/graycode65.f90 lib/grayline.f90 @@ -506,6 +512,11 @@ set (wsjt_FSRCS lib/msk144signalquality.f90 lib/msk144sim.f90 lib/mskrtd.f90 + lib/nuttal_window.f90 + lib/ft4/ft4sim.f90 + lib/ft4/ft4sim_mult.f90 + lib/ft4/ft4_decode.f90 + lib/ft4/ft4_downsample.f90 lib/77bit/my_hash.f90 lib/wsprd/osdwspr.f90 lib/ft8/osd174_91.f90 @@ -546,8 +557,12 @@ set (wsjt_FSRCS lib/sync4.f90 lib/sync64.f90 lib/sync65.f90 + lib/ft4/getcandidates4.f90 + lib/fsk4hf/getcandidates2.f90 + lib/ft4/syncft4.f90 lib/ft8/sync8.f90 lib/ft8/sync8d.f90 + lib/ft4/sync4d.f90 lib/sync9.f90 lib/sync9f.f90 lib/sync9w.f90 @@ -1254,9 +1269,21 @@ target_link_libraries (ft8 wsjt_fort wsjt_cxx) add_executable (ft8sim lib/ft8/ft8sim.f90 wsjtx.rc) target_link_libraries (ft8sim wsjt_fort wsjt_cxx) +add_executable (ft8sim_gfsk lib/ft8/ft8sim_gfsk.f90 wsjtx.rc) +target_link_libraries (ft8sim_gfsk wsjt_fort wsjt_cxx) + add_executable (msk144sim lib/msk144sim.f90 wsjtx.rc) target_link_libraries (msk144sim wsjt_fort wsjt_cxx) +add_executable (ft4sim lib/ft4/ft4sim.f90 wsjtx.rc) +target_link_libraries (ft4sim wsjt_fort wsjt_cxx) + +add_executable (ft4sim_mult lib/ft4/ft4sim_mult.f90 wsjtx.rc) +target_link_libraries (ft4sim_mult wsjt_fort wsjt_cxx) + +add_executable (ft4d lib/ft4/ft4d.f90 wsjtx.rc) +target_link_libraries (ft4d wsjt_fort wsjt_cxx) + endif(WSJT_BUILD_UTILS) # build the main application diff --git a/Configuration.cpp b/Configuration.cpp index a937dcfcd..cce8db23c 100644 --- a/Configuration.cpp +++ b/Configuration.cpp @@ -209,6 +209,7 @@ namespace |LB|NU|YT|PEI |DC # District of Columbia |DX # anyone else + |SCC # Slovenia Contest Club contest ) )", QRegularExpression::CaseInsensitiveOption | QRegularExpression::ExtendedPatternSyntaxOption}; @@ -630,6 +631,7 @@ private: bool udpWindowToFront_; bool udpWindowRestore_; DataMode data_mode_; + bool bLowSidelobes_; bool pwrBandTxMemory_; bool pwrBandTuneMemory_; @@ -720,6 +722,7 @@ bool Configuration::accept_udp_requests () const {return m_->accept_udp_requests QString Configuration::n1mm_server_name () const {return m_->n1mm_server_name_;} auto Configuration::n1mm_server_port () const -> port_type {return m_->n1mm_server_port_;} bool Configuration::broadcast_to_n1mm () const {return m_->broadcast_to_n1mm_;} +bool Configuration::lowSidelobes() const {return m_->bLowSidelobes_;} bool Configuration::udpWindowToFront () const {return m_->udpWindowToFront_;} bool Configuration::udpWindowRestore () const {return m_->udpWindowRestore_;} Bands * Configuration::bands () {return &m_->bands_;} @@ -1289,6 +1292,8 @@ void Configuration::impl::initialize_models () ui_->udpWindowRestore->setChecked(udpWindowRestore_); ui_->calibration_intercept_spin_box->setValue (calibration_.intercept); ui_->calibration_slope_ppm_spin_box->setValue (calibration_.slope_ppm); + ui_->rbLowSidelobes->setChecked(bLowSidelobes_); + if(!bLowSidelobes_) ui_->rbMaxSensitivity->setChecked(true); if (rig_params_.ptt_port.isEmpty ()) { @@ -1480,6 +1485,7 @@ void Configuration::impl::read_settings () rig_params_.audio_source = settings_->value ("TXAudioSource", QVariant::fromValue (TransceiverFactory::TX_audio_source_front)).value (); rig_params_.ptt_port = settings_->value ("PTTport").toString (); data_mode_ = settings_->value ("DataMode", QVariant::fromValue (data_mode_none)).value (); + bLowSidelobes_ = settings_->value("LowSidelobes",true).toBool(); prompt_to_log_ = settings_->value ("PromptToLog", false).toBool (); autoLog_ = settings_->value ("AutoLog", false).toBool (); decodes_from_top_ = settings_->value ("DecodesFromTop", false).toBool (); @@ -1581,6 +1587,7 @@ void Configuration::impl::write_settings () settings_->setValue ("CATStopBits", QVariant::fromValue (rig_params_.stop_bits)); settings_->setValue ("CATHandshake", QVariant::fromValue (rig_params_.handshake)); settings_->setValue ("DataMode", QVariant::fromValue (data_mode_)); + settings_->setValue ("LowSidelobes",bLowSidelobes_); settings_->setValue ("PromptToLog", prompt_to_log_); settings_->setValue ("AutoLog", autoLog_); settings_->setValue ("DecodesFromTop", decodes_from_top_); @@ -2039,6 +2046,7 @@ void Configuration::impl::accept () watchdog_ = ui_->tx_watchdog_spin_box->value (); TX_messages_ = ui_->TX_messages_check_box->isChecked (); data_mode_ = static_cast (ui_->TX_mode_button_group->checkedId ()); + bLowSidelobes_ = ui_->rbLowSidelobes->isChecked(); save_directory_ = ui_->save_path_display_label->text (); azel_directory_ = ui_->azel_path_display_label->text (); enable_VHF_features_ = ui_->enable_VHF_features_check_box->isChecked (); diff --git a/Configuration.hpp b/Configuration.hpp index 9aa0c286b..991e126fb 100644 --- a/Configuration.hpp +++ b/Configuration.hpp @@ -137,6 +137,7 @@ public: bool twoPass() const; bool bFox() const; bool bHound() const; + bool bLowSidelobes() const; bool x2ToneSpacing() const; bool x4ToneSpacing() const; bool MyDx() const; @@ -152,6 +153,7 @@ public: port_type n1mm_server_port () const; bool valid_n1mm_info () const; bool broadcast_to_n1mm() const; + bool lowSidelobes() const; bool accept_udp_requests () const; bool udpWindowToFront () const; bool udpWindowRestore () const; diff --git a/Configuration.ui b/Configuration.ui index 9bc5f35aa..a8f36797c 100644 --- a/Configuration.ui +++ b/Configuration.ui @@ -2821,6 +2821,48 @@ Right click for insert and delete options. + + + + + 0 + 50 + + + + Waterfall spectra + + + + + 10 + 20 + 91 + 17 + + + + Low sidelobes + + + true + + + + + + 120 + 20 + 92 + 17 + + + + Most sensitive + + + + @@ -3036,13 +3078,13 @@ Right click for insert and delete options. - - + + diff --git a/Modulator.cpp b/Modulator.cpp index a7564f6cf..8811d16a3 100644 --- a/Modulator.cpp +++ b/Modulator.cpp @@ -51,10 +51,10 @@ void Modulator::start (unsigned symbolsLength, double framesPerSymbol, // Time according to this computer which becomes our base time qint64 ms0 = QDateTime::currentMSecsSinceEpoch() % 86400000; - if (m_state != Idle) - { - stop (); - } +// qDebug() << "ModStart" << symbolsLength << framesPerSymbol +// << frequency << toneSpacing; + + if(m_state != Idle) stop (); m_quickClose = false; @@ -92,6 +92,16 @@ void Modulator::start (unsigned symbolsLength, double framesPerSymbol, if (synchronize && !m_tuning && !m_bFastMode) { m_silentFrames = m_ic + m_frameRate / (1000 / delay_ms) - (mstr * (m_frameRate / 1000)); } + if((symbolsLength==103 or symbolsLength==105) and framesPerSymbol==512 + and (toneSpacing==12000.0/512.0 or toneSpacing==-2.0)) { +//### FT4 parameters + delay_ms=100; + mstr=5000; + m_ic=0; + m_silentFrames=0; + } +// qDebug() << "Mod AA" << symbolsLength << framesPerSymbol << toneSpacing; +// qDebug() << "Mod AB" << delay_ms << mstr << m_ic << m_silentFrames; initialize (QIODevice::ReadOnly, channel); Q_EMIT stateChanged ((m_state = (synchronize && m_silentFrames) ? @@ -170,7 +180,8 @@ qint64 Modulator::readData (char * data, qint64 maxSize) case Active: { unsigned int isym=0; -// qDebug() << "Mod A" << m_toneSpacing << m_ic; +// qDebug() << "Mod A" << m_toneSpacing << m_frequency << m_nsps +// << m_ic << m_symbolsLength << icw[0]; 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; @@ -289,9 +300,12 @@ qint64 Modulator::readData (char * data, qint64 maxSize) if (m_ic > i1) m_amp = 0.0; 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; +//Here's where we transmit from a precomputed wave[] array: + if(!m_tuning and (m_toneSpacing < 0)) sample=qRound(m_amp*foxcom_.wave[m_ic]); +// if(m_ic < 10) qDebug() << "Mod Tx" << m_ic << m_amp +// << foxcom_.wave[m_ic] << sample +// << m_toneSpacing; samples = load(postProcessSample(sample), samples); ++framesGenerated; @@ -309,6 +323,14 @@ qint64 Modulator::readData (char * data, qint64 maxSize) m_frequency0 = m_frequency; // done for this chunk - continue on next call +// qint64 ms1=QDateTime::currentMSecsSinceEpoch() - m_ms0; +// if(m_ic>=4*144*160) qDebug() << "Modulator finished" << m_ic << 0.001*ms1; + + while (samples != end) // pad block with silence + { + samples = load (0, samples); + ++framesGenerated; + } return framesGenerated * bytesPerFrame (); } // fall through diff --git a/Modulator.hpp b/Modulator.hpp index 493b94bc3..e8df7f2dd 100644 --- a/Modulator.hpp +++ b/Modulator.hpp @@ -33,6 +33,7 @@ public: void setSpread(double s) {m_fSpread=s;} void setTRPeriod(unsigned p) {m_period=p;} void set_nsym(int n) {m_symbolsLength=n;} + void set_ms0(qint64 ms) {m_ms0=ms;} Q_SLOT void start (unsigned symbolsLength, double framesPerSymbol, double frequency, double toneSpacing, SoundOutput *, Channel = Mono, @@ -73,6 +74,7 @@ private: double m_fSpread; qint64 m_silentFrames; + qint64 m_ms0; qint32 m_TRperiod; qint16 m_ramp; diff --git a/decodedtext.cpp b/decodedtext.cpp index 88f6bd621..bfa808173 100644 --- a/decodedtext.cpp +++ b/decodedtext.cpp @@ -51,11 +51,12 @@ DecodedText::DecodedText (QString const& the_string) QStringList DecodedText::messageWords () const { - if (is_standard_) - { - // extract up to the first four message words - return words_re.match (message_).capturedTexts (); - } + if(is_standard_) { + // extract up to the first four message words + QString t=message_; + if(t.left(4)=="TU; ") t=message_.mid(4,-1); + return words_re.match(t).capturedTexts(); + } // simple word split for free text messages auto words = message_.split (' ', QString::SkipEmptyParts); // add whole message as item 0 to mimic RE capture list diff --git a/lib/addit.f90 b/lib/addit.f90 index 48082833f..33ed5a289 100644 --- a/lib/addit.f90 +++ b/lib/addit.f90 @@ -12,18 +12,22 @@ subroutine addit(itone,nfsample,nsym,nsps,ifreq,sig,dat) dphi=0. iters=1 - if(nsym.eq.79) iters=2 + if(nsym.eq.79) iters=2 !FT8 + if(nsym.eq.103) iters=5 !FT4 + do iter=1,iters f=ifreq phi=0. ntot=nsym*tsym/dt k=12000 !Start audio at t = 1.0 s t=0. - if(nsym.eq.79) k=12000 + (iter-1)*12000*30 !Special case for FT8 + if(nsym.eq.79) k=12000 + (iter-1)*12000*30 !Special case for FT8 + if(nsym.eq.103) k=12000 + (iter-1)*12000*10 !Special case for FT4 isym0=-1 do i=1,ntot t=t+dt isym=nint(t/tsym) + 1 + if(isym.gt.nsym) exit if(isym.ne.isym0) then freq=f + itone(isym)*baud dphi=twopi*freq*dt @@ -59,7 +63,7 @@ subroutine addcw(icw,ncw,ifreq,sig,dat) phi=0. k=12000 !Start audio at t = 1.0 s t=0. - npts=60*12000 + npts=59*12000 x=0. do i=1,npts t=t+dt diff --git a/lib/allsim.f90 b/lib/allsim.f90 index 2d948b64f..59d3fa264 100644 --- a/lib/allsim.f90 +++ b/lib/allsim.f90 @@ -1,6 +1,6 @@ program allsim -! Generate simulated data for WSJT-X slow modes: JT4, JT9, JT65, QRA64, +! Generate simulated data for WSJT-X modes: JT4, JT9, JT65, FT8, FT4, QRA64, ! and WSPR. Also unmodulated carrier and 20 WPM CW. @@ -15,6 +15,7 @@ program allsim logical*1 bcontest real*4 dat(NMAX) character message*22,msgsent*22,arg*8,mygrid*6 + character*37 msg37,msgsent37 nargs=iargc() if(nargs.ne.1) then @@ -60,15 +61,20 @@ program allsim call gen4(message,0,msgsent,itone,itype) call addit(itone,11025,206,2520,1200,sig,dat) !JT4 - i3bit=0 ! ### TEMPORARY ??? ### - call genft8(message,mygrid,bcontest,i3bit,msgsent,msgbits,itone) + i3=-1 + n3=-1 + call genft8(message,i3,n3,msgsent,msgbits,itone) call addit(itone,12000,79,1920,1400,sig,dat) !FT8 + msg37=message//' ' + call genft4(msg37,0,msgsent37,itone) + call addit(itone,12000,103,512,1600,sig,dat) !FT4 + call genqra64(message,0,msgsent,itone,itype) - call addit(itone,12000,84,6912,1600,sig,dat) !QRA64 + call addit(itone,12000,84,6912,1800,sig,dat) !QRA64 call gen65(message,0,msgsent,itone,itype) - call addit(itone,11025,126,4096,1800,sig,dat) !JT65 + call addit(itone,11025,126,4096,2000,sig,dat) !JT65 iwave(1:npts)=nint(rms*dat(1:npts)) diff --git a/lib/four2a.f90 b/lib/four2a.f90 index 57c7239e1..337ca15c3 100644 --- a/lib/four2a.f90 +++ b/lib/four2a.f90 @@ -19,6 +19,7 @@ subroutine four2a(a,nfft,ndim,isign,iform) ! This version of four2a makes calls to the FFTW library to do the ! actual computations. + use fftw3 parameter (NPMAX=2100) !Max numberf of stored plans parameter (NSMALL=16384) !Max size of "small" FFTs complex a(nfft) !Array to be transformed @@ -29,7 +30,6 @@ subroutine four2a(a,nfft,ndim,isign,iform) logical found_plan data nplan/0/ !Number of stored plans common/patience/npatience,nthreads !Patience and threads for FFTW plans - include 'fftw3.f90' !FFTW definitions save plan,nplan,nn,ns,nf,nl if(nfft.lt.0) go to 999 @@ -107,7 +107,7 @@ subroutine four2a(a,nfft,ndim,isign,iform) !$omp end critical(fftw) end if enddo - + call fftwf_cleanup() nplan=0 !$omp end critical(four2a) diff --git a/lib/fsk4hf/ft2_params.f90 b/lib/fsk4hf/ft2_params.f90 new file mode 100644 index 000000000..351119b4b --- /dev/null +++ b/lib/fsk4hf/ft2_params.f90 @@ -0,0 +1,12 @@ +! LDPC (128,90) code +parameter (KK=90) !Information bits (77 + CRC13) +parameter (ND=128) !Data symbols +parameter (NS=16) !Sync symbols (2x8) +parameter (NN=NS+ND) !Total channel symbols (144) +parameter (NSPS=160) !Samples per symbol at 12000 S/s +parameter (NZ=NSPS*NN) !Samples in full 1.92 s waveform (23040) +parameter (NMAX=2.5*12000) !Samples in iwave (36,000) +parameter (NFFT1=400, NH1=NFFT1/2) !Length of FFTs for symbol spectra +parameter (NSTEP=NSPS/4) !Rough time-sync step size +parameter (NHSYM=NMAX/NSTEP-3) !Number of symbol spectra (1/4-sym steps) +parameter (NDOWN=16) !Downsample factor diff --git a/lib/fsk4hf/ft2d.f90 b/lib/fsk4hf/ft2d.f90 new file mode 100644 index 000000000..fda1f1826 --- /dev/null +++ b/lib/fsk4hf/ft2d.f90 @@ -0,0 +1,335 @@ +program ft2d + + use crc + use packjt77 + include 'ft2_params.f90' + character arg*8,message*37,c77*77,infile*80,fname*16,datetime*11 + character*37 decodes(100) + character*120 data_dir + character*90 dmsg + complex c2(0:NMAX/16-1) !Complex waveform + complex cb(0:NMAX/16-1) + complex cd(0:144*10-1) !Complex waveform + complex c1(0:9),c0(0:9) + complex ccor(0:1,144) + complex csum,cterm,cc0,cc1,csync1,csync2 + complex csync(16),csl(0:159) + real*8 fMHz + + real a(5) + real rxdata(128),llr(128) !Soft symbols + real llr2(128) + real sbits(144),sbits1(144),sbits3(144) + real ps(0:8191),psbest(0:8191) + real candidates(100,2) + real savg(NH1),sbase(NH1) + integer ihdr(11) + integer*2 iwave(NMAX) !Generated full-length waveform + integer*1 message77(77),apmask(128),cw(128) + integer*1 hbits(144),hbits1(144),hbits3(144) + integer*1 s16(16),s45(45) + logical unpk77_success + data s16/0,0,0,0,1,1,1,1,1,1,1,1,0,0,0,0/ + data s45/0,0,0,0,1,1,1,1,1,1,1,1,0,0,0,0,0,0,0,0,1,0,0,1,1,1,1,0,0,1,0,0,0,1,1,0,1,0,0,0,1,1,1,0,0/ + + fs=12000.0/NDOWN !Sample rate + dt=1/fs !Sample interval after downsample (s) + tt=NSPS*dt !Duration of "itone" symbols (s) + baud=1.0/tt !Keying rate for "itone" symbols (baud) + txt=NZ*dt !Transmission length (s) + twopi=8.0*atan(1.0) + h=0.800 !h=0.8 seems to be optimum for AWGN sensitivity (not for fading) + + dphi=twopi/2*baud*h*dt*16 ! dt*16 is samp interval after downsample + dphi0=-1*dphi + dphi1=+1*dphi + phi0=0.0 + phi1=0.0 + do i=0,9 + c1(i)=cmplx(cos(phi1),sin(phi1)) + c0(i)=cmplx(cos(phi0),sin(phi0)) + phi1=mod(phi1+dphi1,twopi) + phi0=mod(phi0+dphi0,twopi) + enddo + the=twopi*h/2.0 + cc1=cmplx(cos(the),-sin(the)) + cc0=cmplx(cos(the),sin(the)) + + k=0 + do j=1,16 + dphi1=(2*s16(j)-1)*dphi + phi1=0.0 + do i=0,9 + csl(k)=cmplx(cos(phi1),sin(phi1)) + phi1=mod(phi1+dphi1,twopi) + k=k+1 + enddo + enddo + + nargs=iargc() + if(nargs.lt.1) then + print*,'Usage: ft2d [-a ] [-f fMHz] file1 [file2 ...]' + go to 999 + endif + iarg=1 + data_dir="." + call getarg(iarg,arg) + if(arg(1:2).eq.'-a') then + call getarg(iarg+1,data_dir) + iarg=iarg+2 + endif + call getarg(iarg,arg) + if(arg(1:2).eq.'-f') then + call getarg(iarg+1,arg) + read(arg,*) fMHz + iarg=iarg+2 + endif + ncoh=1 + + do ifile=iarg,nargs + call getarg(ifile,infile) + j2=index(infile,'.wav') + open(10,file=infile,status='old',access='stream') + read(10,end=999) ihdr,iwave + read(infile(j2-4:j2-1),*) nutc + datetime=infile(j2-11:j2-1) + close(10) + candidates=0.0 + ncand=0 + call getcandidates2(iwave,375.0,3000.0,0.2,2200.0,100,savg,candidates,ncand,sbase) + ndecodes=0 + do icand=1,ncand + f0=candidates(icand,1) + xsnr=1.0 + if( f0.le.375.0 .or. f0.ge.(5000.0-375.0) ) cycle + call ft2_downsample(iwave,f0,c2) ! downsample from 160s/Symbol to 10s/Symbol + +!c2=c2/sqrt(sum(abs(c2(0:NMAX/16-1)))) +!ishift=-1 +!rccbest=-99. +!do is=0,435 +!rcc=0.0 +! do id=10,10 +! rcc=rcc+abs(sum(conjg(c2(is:is+159-id))*c2(is+id:is+159)*csl(0:159-id)*conjg(csl(id:159)))) +! enddo +! if(rcc.gt.rccbest) then +! rccbest=rcc +! ishift=is +! endif +!write(21,*) is,rcc +!enddo + +! 750 samples/second here + ibest=-1 + sybest=-99. + dfbest=-1. + do if=-30,+30 + df=if + a=0. + a(1)=-df + call twkfreq1(c2,NMAX/16,fs,a,cb) + do is=0,374 + csync1=0. + cterm=1 + do ib=1,16 +! do ib=1,45 + i1=(ib-1)*10+is + if(s16(ib).eq.1) then +! if(s45(ib).eq.1) then + csync1=csync1+sum(cb(i1:i1+9)*conjg(c1(0:9)))*cterm + cterm=cterm*cc1 + else + csync1=csync1+sum(cb(i1:i1+9)*conjg(c0(0:9)))*cterm + cterm=cterm*cc0 + endif + enddo + if(abs(csync1).gt.sybest) then + ibest=is + sybest=abs(csync1) + dfbest=df + endif + enddo + enddo + + a=0. +!dfbest=1500.0-f0 + a(1)=-dfbest + + call twkfreq1(c2,NMAX/16,fs,a,cb) + +!ibest=197 + ib=ibest + + cd=cb(ib:ib+144*10-1) + s2=sum(cd*conjg(cd))/(10*144) + cd=cd/sqrt(s2) + do nseq=1,4 + if( nseq.eq.1 ) then ! noncoherent single-symbol detection + sbits1=0.0 + do ibit=1,144 + ib=(ibit-1)*10 + ccor(1,ibit)=sum(cd(ib:ib+9)*conjg(c1(0:9))) + ccor(0,ibit)=sum(cd(ib:ib+9)*conjg(c0(0:9))) + sbits1(ibit)=abs(ccor(1,ibit))-abs(ccor(0,ibit)) + hbits1(ibit)=0 + if(sbits1(ibit).gt.0) hbits1(ibit)=1 + enddo + sbits=sbits1 + hbits=hbits1 + sbits3=sbits1 + hbits3=hbits1 + elseif( nseq.ge.2 ) then + nbit=2*nseq-1 + numseq=2**(nbit) + ps=0 + do ibit=nbit/2+1,144-nbit/2 + ps=0.0 + pmax=0.0 + do iseq=0,numseq-1 + csum=0.0 + cterm=1.0 + k=1 + do i=nbit-1,0,-1 + ibb=iand(iseq/(2**i),1) + csum=csum+ccor(ibb,ibit-(nbit/2+1)+k)*cterm + if(ibb.eq.0) cterm=cterm*cc0 + if(ibb.eq.1) cterm=cterm*cc1 + k=k+1 + enddo + ps(iseq)=abs(csum) + if( ps(iseq) .gt. pmax ) then + pmax=ps(iseq) + ibflag=1 + endif + enddo + if( ibflag .eq. 1 ) then + psbest=ps + ibflag=0 + endif + call getbitmetric(2**(nbit/2),psbest,numseq,sbits3(ibit)) + hbits3(ibit)=0 + if(sbits3(ibit).gt.0) hbits3(ibit)=1 + enddo + sbits=sbits3 + hbits=hbits3 + endif + nsync_qual=count(hbits(1:16).eq.s16) +! if(nsync_qual.lt.10) exit + rxdata=sbits(17:144) + rxav=sum(rxdata(1:128))/128.0 + rx2av=sum(rxdata(1:128)*rxdata(1:128))/128.0 + rxsig=sqrt(rx2av-rxav*rxav) + rxdata=rxdata/rxsig + sigma=0.80 + llr(1:128)=2*rxdata/(sigma*sigma) +!xllrmax=maxval(abs(llr)) +!write(*,*) ifile,icand,nseq,nsync_qual + apmask=0 +!apmask(1:29)=1 +!llr(1:29)=xllrmax*(2*s45(17:45)-1) + max_iterations=40 + do ibias=0,0 + llr2=llr + if(ibias.eq.1) llr2=llr+0.4 + if(ibias.eq.2) llr2=llr-0.4 + call bpdecode128_90(llr2,apmask,max_iterations,message77,cw,nharderror,niterations) + if(nharderror.ge.0) exit + enddo + if(sum(message77).eq.0) cycle + if( nharderror.ge.0 ) then + write(c77,'(77i1)') message77(1:77) + call unpack77(c77,1,message,unpk77_success) + idupe=0 + do i=1,ndecodes + if(decodes(i).eq.message) idupe=1 + enddo + if(idupe.eq.1) goto 888 + ndecodes=ndecodes+1 + decodes(ndecodes)=message + nsnr=nint(xsnr) + freq=f0+dfbest +1210 format(a11,2i4,f6.2,f12.7,2x,a22,i3) + write(*,1212) datetime(8:11),nsnr,ibest/750.0,freq,message,'*',nseq,nharderror,nsync_qual +1212 format(a4,i4,2x,f5.3,f11.1,2x,a22,a1,i5,i5,i5) + goto 888 + endif + enddo ! nseq +888 continue + enddo !candidate list + enddo !files + + write(*,1120) +1120 format("") + +999 end program ft2d + +subroutine getbitmetric(ib,ps,ns,xmet) + real ps(0:ns-1) + xm1=0 + xm0=0 + do i=0,ns-1 + if( iand(i/ib,1) .eq. 1 .and. ps(i) .gt. xm1 ) xm1=ps(i) + if( iand(i/ib,1) .eq. 0 .and. ps(i) .gt. xm0 ) xm0=ps(i) + enddo + xmet=xm1-xm0 + return +end subroutine getbitmetric + +subroutine downsample2(ci,f0,co) + parameter(NI=144*160,NH=NI/2,NO=NI/16) ! downsample from 200 samples per symbol to 10 + complex ci(0:NI-1),ct(0:NI-1) + complex co(0:NO-1) + fs=12000.0 + df=fs/NI + ct=ci + call four2a(ct,NI,1,-1,1) !c2c FFT to freq domain + i0=nint(f0/df) + ct=cshift(ct,i0) + co=0.0 + co(0)=ct(0) + b=8.0 + do i=1,NO/2 + arg=(i*df/b)**2 + filt=exp(-arg) + co(i)=ct(i)*filt + co(NO-i)=ct(NI-i)*filt + enddo + co=co/NO + call four2a(co,NO,1,1,1) !c2c FFT back to time domain + return +end subroutine downsample2 + +subroutine ft2_downsample(iwave,f0,c) + +! Input: i*2 data in iwave() at sample rate 12000 Hz +! Output: Complex data in c(), sampled at 1200 Hz + + include 'ft2_params.f90' + parameter (NFFT2=NMAX/16) + integer*2 iwave(NMAX) + complex c(0:NMAX/16-1) + complex c1(0:NFFT2-1) + complex cx(0:NMAX/2) + real x(NMAX) + equivalence (x,cx) + + BW=4.0*75 + df=12000.0/NMAX + x=iwave + call four2a(x,NMAX,1,-1,0) !r2c FFT to freq domain + ibw=nint(BW/df) + i0=nint(f0/df) + c1=0. + c1(0)=cx(i0) + do i=1,NFFT2/2 + arg=(i-1)*df/bw + win=exp(-arg*arg) + c1(i)=cx(i0+i)*win + c1(NFFT2-i)=cx(i0-i)*win + enddo + c1=c1/NFFT2 + call four2a(c1,NFFT2,1,1,1) !c2c FFT back to time domain + c=c1(0:NMAX/16-1) + return +end subroutine ft2_downsample + diff --git a/lib/fsk4hf/ft2sim.f90 b/lib/fsk4hf/ft2sim.f90 new file mode 100644 index 000000000..ea0518a52 --- /dev/null +++ b/lib/fsk4hf/ft2sim.f90 @@ -0,0 +1,154 @@ +program ft2sim + +! Generate simulated signals for experimental "FT2" mode + + use wavhdr + use packjt77 + include 'ft2_params.f90' !Set various constants + parameter (NWAVE=NN*NSPS) + type(hdr) h !Header for .wav file + character arg*12,fname*17 + character msg37*37,msgsent37*37 + character c77*77 + complex c0(0:NMAX-1) + complex c(0:NMAX-1) + real wave(NMAX) + real dphi(0:NMAX-1) + real pulse(480) + integer itone(NN) + integer*1 msgbits(77) + integer*2 iwave(NMAX) !Generated full-length waveform + +! Get command-line argument(s) + nargs=iargc() + if(nargs.ne.8) then + print*,'Usage: ft2sim "message" f0 DT fdop del width nfiles snr' + print*,'Examples: ft2sim "K1ABC W9XYZ EN37" 1500.0 0.0 0.1 1.0 0 10 -18' + print*,' ft2sim "WA9XYZ/R KA1ABC/R FN42" 1500.0 0.0 0.1 1.0 0 10 -18' + print*,' ft2sim "K1ABC RR73; W9XYZ -11" 300 0 0 0 25 1 -10' + go to 999 + endif + call getarg(1,msg37) !Message to be transmitted + call getarg(2,arg) + read(arg,*) f0 !Frequency (only used for single-signal) + call getarg(3,arg) + read(arg,*) xdt !Time offset from nominal (s) + call getarg(4,arg) + read(arg,*) fspread !Watterson frequency spread (Hz) + call getarg(5,arg) + read(arg,*) delay !Watterson delay (ms) + call getarg(6,arg) + read(arg,*) width !Filter transition width (Hz) + call getarg(7,arg) + read(arg,*) nfiles !Number of files + call getarg(8,arg) + read(arg,*) snrdb !SNR_2500 + + nfiles=abs(nfiles) + twopi=8.0*atan(1.0) + fs=12000.0 !Sample rate (Hz) + dt=1.0/fs !Sample interval (s) + hmod=0.800 !Modulation index (0.5 is MSK, 1.0 is FSK) + tt=NSPS*dt !Duration of symbols (s) + baud=1.0/tt !Keying rate (baud) + txt=NZ*dt !Transmission length (s) + + bandwidth_ratio=2500.0/(fs/2.0) + sig=sqrt(2*bandwidth_ratio) * 10.0**(0.05*snrdb) + if(snrdb.gt.90.0) sig=1.0 + txt=NN*NSPS/12000.0 + + ! Source-encode, then get itone() + i3=-1 + n3=-1 + call pack77(msg37,i3,n3,c77) + read(c77,'(77i1)') msgbits + call genft2(msg37,0,msgsent37,itone,itype) + write(*,*) + write(*,'(a9,a37,3x,a7,i1,a1,i1)') 'Message: ',msgsent37,'i3.n3: ',i3,'.',n3 + write(*,1000) f0,xdt,txt,snrdb +1000 format('f0:',f9.3,' DT:',f6.2,' TxT:',f6.1,' SNR:',f6.1) + write(*,*) + if(i3.eq.1) then + write(*,*) ' mycall hiscall hisgrid' + write(*,'(28i1,1x,i1,1x,28i1,1x,i1,1x,i1,1x,15i1,1x,3i1)') msgbits(1:77) + else + write(*,'(a14)') 'Message bits: ' + write(*,'(77i1)') msgbits + endif + write(*,*) + write(*,'(a17)') 'Channel symbols: ' + write(*,'(79i1)') itone + write(*,*) + + call sgran() + +! The filtered frequency pulse + do i=1,480 + tt=(i-240.5)/160.0 + pulse(i)=gfsk_pulse(1.0,tt) + enddo + +! Define the instantaneous frequency waveform + dphi_peak=twopi*(hmod/2.0)/real(NSPS) + dphi=0.0 + do j=1,NN + ib=(j-1)*160 + ie=ib+480-1 + dphi(ib:ie)=dphi(ib:ie)+dphi_peak*pulse*(2*itone(j)-1) + enddo + + phi=0.0 + c0=0.0 + dphi=dphi+twopi*f0*dt + do j=0,NMAX-1 + c0(j)=cmplx(cos(phi),sin(phi)) + phi=mod(phi+dphi(j),twopi) + enddo + + c0(0:159)=c0(0:159)*(1.0-cos(twopi*(/(i,i=0,159)/)/320.0) )/2.0 + c0(145*160:145*160+159)=c0(145*160:145*160+159)*(1.0+cos(twopi*(/(i,i=0,159)/)/320.0 ))/2.0 + c0(146*160:)=0. + + k=nint((xdt+0.25)/dt) + c0=cshift(c0,-k) + ia=k + + do ifile=1,nfiles + c=c0 + if(fspread.ne.0.0 .or. delay.ne.0.0) call watterson(c,NMAX,NWAVE,fs,delay,fspread) + c=sig*c + + ib=k + wave=real(c) + peak=maxval(abs(wave(ia:ib))) + nslots=1 + if(width.gt.0.0) call filt8(f0,nslots,width,wave) + + if(snrdb.lt.90) then + do i=1,NMAX !Add gaussian noise at specified SNR + xnoise=gran() + wave(i)=wave(i) + xnoise + enddo + endif + + gain=100.0 + if(snrdb.lt.90.0) then + wave=gain*wave + else + datpk=maxval(abs(wave)) + fac=32766.9/datpk + wave=fac*wave + endif + if(any(abs(wave).gt.32767.0)) print*,"Warning - data will be clipped." + iwave=nint(wave) + h=default_header(12000,NMAX) + write(fname,1102) ifile +1102 format('000000_',i6.6,'.wav') + open(10,file=fname,status='unknown',access='stream') + write(10) h,iwave !Save to *.wav file + close(10) + write(*,1110) ifile,xdt,f0,snrdb,fname +1110 format(i4,f7.2,f8.2,f7.1,2x,a17) + enddo +999 end program ft2sim diff --git a/lib/fsk4hf/ft4d.f90 b/lib/fsk4hf/ft4d.f90 new file mode 100644 index 000000000..d1bf262c1 --- /dev/null +++ b/lib/fsk4hf/ft4d.f90 @@ -0,0 +1,329 @@ +program ft4d + + use crc + use packjt77 + include 'ft4_params.f90' + character arg*8,message*37,c77*77,infile*80,fname*16,datetime*11 + character*37 decodes(100) + character*120 data_dir + character*90 dmsg + complex cd2(0:NMAX/16-1) !Complex waveform + complex cb(0:NMAX/16-1) + complex cd(0:76*20-1) !Complex waveform + complex csum,cterm + complex ctwk(80),ctwk2(80) + complex csymb(20) + complex cs(0:3,NN) + real s4(0:3,NN) + + real*8 fMHz + real ps(0:8191),psbest(0:8191) + real bmeta(152),bmetb(152),bmetc(152) + real s(NH1,NHSYM) + real a(5) + real llr(128),llr2(128),llra(128),llrb(128),llrc(128) + real s2(0:255) + real candidate(3,100) + real savg(NH1),sbase(NH1) + integer ihdr(11) + integer icos4(0:3) + integer*2 iwave(NMAX) !Generated full-length waveform + integer*1 message77(77),apmask(128),cw(128) + integer*1 hbits(152),hbits1(152),hbits3(152) + integer*1 s12(12) + integer graymap(0:3) + integer ip(1) + logical unpk77_success + logical one(0:511,0:7) ! 256 4-symbol sequences, 8 bits + data s12/1,1,1,2,2,2,2,2,2,1,1,1/ + data icos4/0,1,3,2/ + data graymap/0,1,3,2/ + save one + + fs=12000.0/NDOWN !Sample rate + dt=1/fs !Sample interval after downsample (s) + tt=NSPS*dt !Duration of "itone" symbols (s) + baud=1.0/tt !Keying rate for "itone" symbols (baud) + txt=NZ*dt !Transmission length (s) + twopi=8.0*atan(1.0) + h=1.0 !h=0.8 seems to be optimum for AWGN sensitivity (not for fading) + + one=.false. + do i=0,255 + do j=0,7 + if(iand(i,2**j).ne.0) one(i,j)=.true. + enddo + enddo + + nargs=iargc() + if(nargs.lt.1) then + print*,'Usage: ft4d [-a ] [-f fMHz] file1 [file2 ...]' + go to 999 + endif + iarg=1 + data_dir="." + call getarg(iarg,arg) + if(arg(1:2).eq.'-a') then + call getarg(iarg+1,data_dir) + iarg=iarg+2 + endif + call getarg(iarg,arg) + if(arg(1:2).eq.'-f') then + call getarg(iarg+1,arg) + read(arg,*) fMHz + iarg=iarg+2 + endif + ncoh=1 + + do ifile=iarg,nargs + call getarg(ifile,infile) + j2=index(infile,'.wav') + open(10,file=infile,status='old',access='stream') + read(10,end=999) ihdr,iwave + read(infile(j2-4:j2-1),*) nutc + datetime=infile(j2-11:j2-1) + close(10) + candidate=0.0 + ncand=0 + + nfqso=1500 + nfa=500 + nfb=2700 + syncmin=1.0 + maxcand=100 +! call syncft4(iwave,nfa,nfb,syncmin,nfqso,maxcand,s,candidate,ncand,sbase) + + call getcandidates4(iwave,375.0,3000.0,0.2,2200.0,100,savg,candidate,ncand,sbase) + ndecodes=0 + do icand=1,ncand + f0=candidate(1,icand)-1.5*37.5 + xsnr=1.0 + if( f0.le.375.0 .or. f0.ge.(5000.0-375.0) ) cycle + call ft4_downsample(iwave,f0,cd2) ! downsample from 320 Sa/Symbol to 20 Sa/Symbol + sum2=sum(cd2*conjg(cd2))/(20.0*76) + if(sum2.gt.0.0) cd2=cd2/sqrt(sum2) + +! 750 samples/second here + ibest=-1 + smax=-99. + dfbest=-1. + do idf=-90,+90,5 + df=idf + a=0. + a(1)=df + ctwk=1. + call twkfreq1(ctwk,80,fs,a,ctwk2) + do istart=0,315 + call sync4d(cd2,istart,ctwk2,1,sync) + if(sync.gt.smax) then + smax=sync + ibest=istart + dfbest=df + endif + enddo + enddo + + f0=f0+dfbest +!f0=1443.75 + call ft4_downsample(iwave,f0,cb) ! downsample from 320s/Symbol to 20s/Symbol + sum2=sum(abs(cb)**2)/(20.0*76) + if(sum2.gt.0.0) cb=cb/sqrt(sum2) +!ibest=208 + cd=cb(ibest:ibest+76*20-1) + do k=1,NN + i1=(k-1)*20 + csymb=cd(i1:i1+19) + call four2a(csymb,20,1,-1,1) + cs(0:3,k)=csymb(1:4)/1e2 + s4(0:3,k)=abs(csymb(1:4)) + enddo + +! sync quality check + is1=0 + is2=0 + is3=0 + do k=1,4 + ip=maxloc(s4(:,k)) + if(icos4(k-1).eq.(ip(1)-1)) is1=is1+1 + ip=maxloc(s4(:,k+36)) + if(icos4(k-1).eq.(ip(1)-1)) is2=is2+1 + ip=maxloc(s4(:,k+72)) + if(icos4(k-1).eq.(ip(1)-1)) is3=is3+1 + enddo +! hard sync sum - max is 12 + nsync=is1+is2+is3 + + do nseq=1,3 + if(nseq.eq.1) nsym=1 + if(nseq.eq.2) nsym=2 + if(nseq.eq.3) nsym=4 + nt=2**(2*nsym) + do ks=1,76,nsym + amax=-1.0 + do i=0,nt-1 + i1=i/64 + i2=iand(i,63)/16 + i3=iand(i,15)/4 + i4=iand(i,3) + if(nsym.eq.1) then + s2(i)=abs(cs(graymap(i4),ks)) + elseif(nsym.eq.2) then + s2(i)=abs(cs(graymap(i3),ks)+cs(graymap(i4),ks+1)) + elseif(nsym.eq.4) then + s2(i)=abs(cs(graymap(i1),ks ) + & + cs(graymap(i2),ks+1) + & + cs(graymap(i3),ks+2) + & + cs(graymap(i4),ks+3) & + ) + else + print*,"Error - nsym must be 1, 2, or 4." + endif + enddo + ipt=1+(ks-1)*2 + if(nsym.eq.1) ibmax=1 + if(nsym.eq.2) ibmax=3 + if(nsym.eq.4) ibmax=7 + do ib=0,ibmax + bm=maxval(s2(0:nt-1),one(0:nt-1,ibmax-ib)) - & + maxval(s2(0:nt-1),.not.one(0:nt-1,ibmax-ib)) + if(ipt+ib .gt.152) cycle + if(nsym.eq.1) then + bmeta(ipt+ib)=bm + elseif(nsym.eq.2) then + bmetb(ipt+ib)=bm + elseif(nsym.eq.4) then + bmetc(ipt+ib)=bm + endif + enddo + enddo + enddo + + call normalizebmet(bmeta,152) + call normalizebmet(bmetb,152) + call normalizebmet(bmetc,152) + + hbits=0 + where(bmeta.ge.0) hbits=1 + ns1=count(hbits( 1: 8).eq.(/0,0,0,1,1,0,1,1/)) + ns2=count(hbits( 73: 80).eq.(/0,0,0,1,1,0,1,1/)) + ns3=count(hbits(145:152).eq.(/0,0,0,1,1,0,1,1/)) + nsync_qual=ns1+ns2+ns3 + + sigma=0.7 + llra(1:64)=bmeta(9:72) + llra(65:128)=bmeta(81:144) + llra=2*llra/sigma**2 + llrb(1:64)=bmetb(9:72) + llrb(65:128)=bmetb(81:144) + llrb=2*llrb/sigma**2 + llrc(1:64)=bmetc(9:72) + llrc(65:128)=bmetc(81:144) + llrc=2*llrc/sigma**2 + + do isd=1,3 + if(isd.eq.1) llr=llra + if(isd.eq.2) llr=llrb + if(isd.eq.3) llr=llrc + apmask=0 + max_iterations=40 + do ibias=0,0 + llr2=llr + if(ibias.eq.1) llr2=llr+0.4 + if(ibias.eq.2) llr2=llr-0.4 + call bpdecode128_90(llr2,apmask,max_iterations,message77,cw,nharderror,niterations) + if(nharderror.ge.0) exit + enddo + if(sum(message77).eq.0) cycle + if( nharderror.ge.0 ) then + write(c77,'(77i1)') message77(1:77) + call unpack77(c77,1,message,unpk77_success) + idupe=0 + do i=1,ndecodes + if(decodes(i).eq.message) idupe=1 + enddo + if(idupe.eq.1) cycle + ndecodes=ndecodes+1 + decodes(ndecodes)=message + nsnr=nint(xsnr) + write(*,1212) datetime(8:11),nsnr,ibest/750.0,f0,message,'*',nharderror,nsync_qual,isd,niterations +1212 format(a4,i4,2x,f5.3,f11.1,2x,a22,a1,i5,i5,i5,i5) + endif + enddo ! sequence estimation + enddo !candidate list + enddo !files + + write(*,1120) +1120 format("") + +999 end program ft4d + +subroutine getbitmetric(ib,ps,ns,xmet) + real ps(0:ns-1) + xm1=0 + xm0=0 + do i=0,ns-1 + if( iand(i/ib,1) .eq. 1 .and. ps(i) .gt. xm1 ) xm1=ps(i) + if( iand(i/ib,1) .eq. 0 .and. ps(i) .gt. xm0 ) xm0=ps(i) + enddo + xmet=xm1-xm0 + return +end subroutine getbitmetric + +subroutine downsample4(ci,f0,co) + parameter(NI=144*160,NH=NI/2,NO=NI/16) ! downsample from 200 samples per symbol to 10 + complex ci(0:NI-1),ct(0:NI-1) + complex co(0:NO-1) + fs=12000.0 + df=fs/NI + ct=ci + call four2a(ct,NI,1,-1,1) !c2c FFT to freq domain + i0=nint(f0/df) + ct=cshift(ct,i0) + co=0.0 + co(0)=ct(0) + b=8.0 + do i=1,NO/2 + arg=(i*df/b)**2 + filt=exp(-arg) + co(i)=ct(i)*filt + co(NO-i)=ct(NI-i)*filt + enddo + co=co/NO + call four2a(co,NO,1,1,1) !c2c FFT back to time domain + return +end subroutine downsample4 + +subroutine ft4_downsample(iwave,f0,c) + +! Input: i*2 data in iwave() at sample rate 12000 Hz +! Output: Complex data in c(), sampled at 1200 Hz + + include 'ft4_params.f90' + parameter (NFFT2=NMAX/16) + integer*2 iwave(NMAX) + complex c(0:NMAX/16-1) + complex c1(0:NFFT2-1) + complex cx(0:NMAX/2) + real x(NMAX) + equivalence (x,cx) + + BW=6.0*75 + df=12000.0/NMAX + x=iwave + call four2a(x,NMAX,1,-1,0) !r2c FFT to freq domain + ibw=nint(BW/df) + i0=nint(f0/df) + c1=0. + c1(0)=cx(i0) + do i=1,NFFT2/2 + arg=(i-1)*df/bw + win=exp(-arg*arg) + c1(i)=cx(i0+i)*win + c1(NFFT2-i)=cx(i0-i)*win + enddo + c1=c1/NFFT2 + call four2a(c1,NFFT2,1,1,1) !c2c FFT back to time domain + c=c1(0:NMAX/16-1) + return +end subroutine ft4_downsample + diff --git a/lib/fsk4hf/genft2.f90 b/lib/fsk4hf/genft2.f90 new file mode 100644 index 000000000..2eb36bccc --- /dev/null +++ b/lib/fsk4hf/genft2.f90 @@ -0,0 +1,86 @@ +subroutine genft2(msg0,ichk,msgsent,i4tone,itype) +! s8 + 48bits + s8 + 80 bits = 144 bits (72ms message duration) +! +! Encode an MSK144 message +! Input: +! - msg0 requested message to be transmitted +! - ichk if ichk=1, return only msgsent +! if ichk.ge.10000, set imsg=ichk-10000 for short msg +! - msgsent message as it will be decoded +! - i4tone array of audio tone values, 0 or 1 +! - itype message type +! 1 = 77 bit message +! 7 = 16 bit message " Rpt" + + use iso_c_binding, only: c_loc,c_size_t + use packjt77 + character*37 msg0 + character*37 message !Message to be generated + character*37 msgsent !Message as it will be received + character*77 c77 + integer*4 i4tone(144) + integer*1 codeword(128) + integer*1 msgbits(77) + integer*1 bitseq(144) !Tone #s, data and sync (values 0-1) + integer*1 s16(16) + real*8 xi(864),xq(864),pi,twopi + data s16/0,0,0,0,1,1,1,1,1,1,1,1,0,0,0,0/ + equivalence (ihash,i1hash) + logical unpk77_success + + nsym=128 + pi=4.0*atan(1.0) + twopi=8.*atan(1.0) + + message(1:37)=' ' + itype=1 + if(msg0(1:1).eq.'@') then !Generate a fixed tone + read(msg0(2:5),*,end=1,err=1) nfreq !at specified frequency + go to 2 +1 nfreq=1000 +2 i4tone(1)=nfreq + else + message=msg0 + + do i=1, 37 + if(ichar(message(i:i)).eq.0) then + message(i:37)=' ' + exit + endif + enddo + do i=1,37 !Strip leading blanks + if(message(1:1).ne.' ') exit + message=message(i+1:) + enddo + + if(message(1:1).eq.'<') then + i2=index(message,'>') + i1=0 + if(i2.gt.0) i1=index(message(1:i2),' ') + if(i1.gt.0) then + call genmsk40(message,msgsent,ichk,i4tone,itype) + if(itype.lt.0) go to 999 + i4tone(41)=-40 + go to 999 + endif + endif + + i3=-1 + n3=-1 + call pack77(message,i3,n3,c77) + call unpack77(c77,0,msgsent,unpk77_success) !Unpack to get msgsent + + if(ichk.eq.1) go to 999 + read(c77,"(77i1)") msgbits + call encode_128_90(msgbits,codeword) + +!Create 144-bit channel vector: + bitseq=0 + bitseq(1:16)=s16 + bitseq(17:144)=codeword + + i4tone=bitseq + endif + +999 return +end subroutine genft2 diff --git a/lib/fsk4hf/getcandidates2.f90 b/lib/fsk4hf/getcandidates2.f90 new file mode 100644 index 000000000..3aa841c83 --- /dev/null +++ b/lib/fsk4hf/getcandidates2.f90 @@ -0,0 +1,63 @@ +subroutine getcandidates2(id,fa,fb,syncmin,nfqso,maxcand,savg,candidate, & + ncand,sbase) + +! For now, hardwired to find the largest peak in the average spectrum + + include 'ft2_params.f90' + real s(NH1,NHSYM) + real savg(NH1),savsm(NH1) + real sbase(NH1) + real x(NFFT1) + complex cx(0:NH1) + real candidate(3,maxcand) + integer*2 id(NMAX) + integer*1 s8(8) + integer indx(NH1) + data s8/0,1,1,1,0,0,1,0/ + equivalence (x,cx) + +! Compute symbol spectra, stepping by NSTEP steps. + savg=0. + tstep=NSTEP/12000.0 + df=12000.0/NFFT1 !3.125 Hz + fac=1.0/300.0 + do j=1,NHSYM + ia=(j-1)*NSTEP + 1 + ib=ia+NSPS-1 + x(1:NSPS)=fac*id(ia:ib) + x(NSPS+1:)=0. + call four2a(x,NFFT1,1,-1,0) !r2c FFT + do i=1,NH1 + s(i,j)=real(cx(i))**2 + aimag(cx(i))**2 + enddo + savg=savg + s(1:NH1,j) !Average spectrum + enddo + savsm=0. + do i=2,NH1-1 + savsm(i)=sum(savg(i-1:i+1))/3. + enddo + + nfa=fa/df + nfb=fb/df + np=nfb-nfa+1 + indx=0 + call indexx(savsm(nfa:nfb),np,indx) + xn=savsm(nfa+indx(nint(0.3*np))) + savsm=savsm/xn + imax=-1 + xmax=-99. + do i=2,NH1-1 + if(savsm(i).gt.savsm(i-1).and. & + savsm(i).gt.savsm(i+1).and. & + savsm(i).gt.xmax) then + xmax=savsm(i) + imax=i + endif + enddo + f0=imax*df + if(xmax.gt.1.2) then + ncand=ncand+1 + candidate(1,ncand)=f0 + endif +return +end subroutine getcandidates2 diff --git a/lib/fsk4hf/mskhfsim.f90 b/lib/fsk4hf/mskhfsim.f90 deleted file mode 100644 index 86140e1ce..000000000 --- a/lib/fsk4hf/mskhfsim.f90 +++ /dev/null @@ -1,194 +0,0 @@ -program msksim - -! Simulate characteristics of a potential "MSK10" mode using LDPC (168,84) -! code, OQPDK modulation, and 30 s T/R sequences. - -! Reception and Demodulation algorithm: -! 1. Compute coarse spectrum; find fc1 = approx carrier freq -! 2. Mix from fc1 to 0; LPF at +/- 0.75*R -! 3. Square, FFT; find peaks near -R/2 and +R/2 to get fc2 -! 4. Mix from fc2 to 0 -! 5. Fit cb13 (central part of csync) to c -> lag, phase -! 6. Fit complex ploynomial for channel equalization -! 7. Get soft bits from equalized data - - parameter (KK=84) !Information bits (72 + CRC12) - parameter (ND=168) !Data symbols: LDPC (168,84), r=1/2 - parameter (NS=65) !Sync symbols (2 x 26 + Barker 13) - parameter (NR=3) !Ramp up/down - parameter (NN=NR+NS+ND) !Total symbols (236) - parameter (NSPS=1152/72) !Samples per MSK symbol (16) - parameter (N2=2*NSPS) !Samples per OQPSK symbol (32) - parameter (N13=13*N2) !Samples in central sync vector (416) - parameter (NZ=NSPS*NN) !Samples in baseband waveform (3776) - parameter (NFFT1=4*NSPS,NH1=NFFT1/2) - - character*8 arg - complex cbb(0:NZ-1) !Complex baseband waveform - complex csync(0:NZ-1) !Sync symbols only, from cbb - complex cb13(0:N13-1) !Barker 13 waveform - complex c(0:NZ-1) !Complex waveform - complex c0(0:NZ-1) !Complex waveform - complex zz(NS+ND) !Complex symbol values (intermediate) - complex z - real xnoise(0:NZ-1) !Generated random noise - real ynoise(0:NZ-1) !Generated random noise - real rxdata(ND),llr(ND) !Soft symbols - real pp(2*NSPS) !Shaped pulse for OQPSK - real a(5) !For twkfreq1 - real aa(20),bb(20) !Fitted polyco's - integer id(NS+ND) !NRZ values (+/-1) for Sync and Data - integer ierror(NS+ND) - integer icw(NN) - integer*1 msgbits(KK),decoded(KK),apmask(ND),cw(ND) -! integer*1 codeword(ND) - data msgbits/0,0,1,0,0,1,1,1,1,0,0,1,0,0,0,0,0,0,0,0,1,0,0,0,1,1,0,0,0,1, & - 1,1,1,0,1,1,1,1,1,1,1,0,0,1,0,0,1,1,0,1,0,1,1,1,0,1,1,0,1,1, & - 1,1,0,1,0,1,1,0,0,0,0,0,1,0,0,0,0,0,1,0,1,0,1,0/ - - nargs=iargc() - if(nargs.ne.6) then - print*,'Usage: mskhfsim f0(Hz) delay(ms) fspread(Hz) maxn iters snr(dB)' - print*,'Example: mskhfsim 0 0 0 5 10 -20' - print*,'Set snr=0 to cycle through a range' - go to 999 - endif - call getarg(1,arg) - read(arg,*) f0 !Generated carrier frequency - call getarg(2,arg) - read(arg,*) delay !Delta_t (ms) for Watterson model - call getarg(3,arg) - read(arg,*) fspread !Fspread (Hz) for Watterson model - call getarg(4,arg) - read(arg,*) maxn !Max nterms for polyfit - call getarg(5,arg) - read(arg,*) iters !Iterations at each SNR - call getarg(6,arg) - read(arg,*) snrdb !Specified SNR_2500 - - twopi=8.0*atan(1.0) - fs=12000.0/72.0 !Sample rate = 166.6666667 Hz - dt=1.0/fs !Sample interval (s) - tt=NSPS*dt !Duration of "itone" symbols (s) - ts=2*NSPS*dt !Duration of OQPSK symbols (s) - baud=1.0/tt !Keying rate for "itone" symbols (baud) - txt=NZ*dt !Transmission length (s) - bandwidth_ratio=2500.0/(fs/2.0) - write(*,1000) f0,delay,fspread,maxn,iters,baud,3*baud,txt -1000 format('f0:',f5.1,' Delay:',f4.1,' fSpread:',f5.2,' maxn:',i3, & - ' Iters:',i6/'Baud:',f7.3,' BW:',f5.1,' TxT:',f5.1,f5.2/) - write(*,1004) -1004 format(/' SNR err ber fer fsigma'/37('-')) - - do i=1,N2 !Half-sine pulse shape - pp(i)=sin(0.5*(i-1)*twopi/(2*NSPS)) - enddo - - call genmskhf(msgbits,id,icw,cbb,csync)!Generate baseband waveform and csync - cb13=csync(1680:2095) !Copy the Barker 13 waveform - a=0. - a(1)=f0 - call twkfreq1(cbb,NZ,fs,a,cbb) !Mix to specified frequency - - isna=-10 - isnb=-30 - if(snrdb.ne.0.0) then - isna=nint(snrdb) - isnb=isna - endif - do isnr=isna,isnb,-1 !Loop over SNR range - snrdb=isnr - sig=sqrt(bandwidth_ratio) * 10.0**(0.05*snrdb) - if(snrdb.gt.90.0) sig=1.0 - nhard=0 - nhardsync=0 - nfe=0 - sqf=0. - do iter=1,iters !Loop over requested iterations - c=cbb - if(delay.ne.0.0 .or. fspread.ne.0.0) then - call watterson(c,NZ,fs,delay,fspread) - endif - c=sig*c !Scale to requested SNR - if(snrdb.lt.90) then - do i=0,NZ-1 !Generate gaussian noise - xnoise(i)=gran() - ynoise(i)=gran() - enddo - c=c + cmplx(xnoise,ynoise) !Add AWGN noise - endif - - call getfc1(c,fc1) !First approx for freq - call getfc2(c,csync,fc1,fc2,fc3) !Refined freq - sqf=sqf + (fc1+fc2-f0)**2 - -!NB: Measured performance is about equally good using fc2 or fc3 here: - a(1)=-(fc1+fc2) - a(2:5)=0. - call twkfreq1(c,NZ,fs,a,c) !Mix c down by fc1+fc2 - -! The following may not be necessary? -! z=sum(c(1680:2095)*cb13)/208.0 !Get phase from Barker 13 vector -! z0=z/abs(z) -! c=c*conjg(z0) - -!---------------------------------------------------------------- DT -! Not presently used: - amax=0. - jpk=0 - do j=-20*NSPS,20*NSPS !Get jpk - z=sum(c(1680+j:2095+j)*cb13)/208.0 - if(abs(z).gt.amax) then - amax=abs(z) - jpk=j - endif - enddo - xdt=jpk/fs - - nterms=maxn - c0=c - do itry=1,10 - idf=itry/2 - if(mod(itry,2).eq.0) idf=-idf - nhard0=0 - nhardsync0=0 - ifer=1 - a(1)=idf*0.01 - a(2:5)=0. - call twkfreq1(c0,NZ,fs,a,c) !Mix c0 into c - call cpolyfit(c,pp,id,maxn,aa,bb,zz,nhs) - call msksoftsym(zz,aa,bb,id,nterms,ierror,rxdata,nhard0,nhardsync0) - if(nhardsync0.gt.12) cycle - rxav=sum(rxdata)/ND - rx2av=sum(rxdata*rxdata)/ND - rxsig=sqrt(rx2av-rxav*rxav) - rxdata=rxdata/rxsig - ss=0.84 - llr=2.0*rxdata/(ss*ss) - apmask=0 - max_iterations=40 - ifer=0 - call bpdecode168(llr,apmask,max_iterations,decoded,niterations,cw) - nbadcrc=0 - if(niterations.ge.0) call chkcrc12(decoded,nbadcrc) - if(niterations.lt.0 .or. count(msgbits.ne.decoded).gt.0 .or. & - nbadcrc.ne.0) ifer=1 -! if(ifer.eq.0) write(67,1301) snrdb,itry,idf,niterations, & -! nhardsync0,nhard0 -!1301 format(f6.1,5i6) - if(ifer.eq.0) exit - enddo !Freq dither loop - nhard=nhard+nhard0 - nhardsync=nharsdync+nhardsync0 - nfe=nfe+ifer - enddo - - fsigma=sqrt(sqf/iters) - ber=float(nhard)/((NS+ND)*iters) - fer=float(nfe)/iters - write(*,1050) snrdb,nhard,ber,fer,fsigma -! write(60,1050) snrdb,nhard,ber,fer,fsigma -1050 format(f6.1,i7,f8.4,f7.3,f8.2) - enddo - -999 end program msksim diff --git a/lib/fsk4hf/spb.m b/lib/fsk4hf/spb.m index ed1761925..9bb164506 100644 --- a/lib/fsk4hf/spb.m +++ b/lib/fsk4hf/spb.m @@ -70,14 +70,14 @@ endfunction # M-ary PSK Block Coded Modulation," Igal Sason and Gil Weichman, # doi: 10.1109/EEEI.2006.321097 #------------------------------------------------------------------------------- -N=174 -K=75 +N=128 +K=90 R=K/N delta=0.01; [ths,fval,info,output]=fzero(@f1,[delta,pi/2-delta], optimset ("jacobian", "off")); -for ebnodb=-6:0.5:4 +for ebnodb=-3:0.5:4 ebno=10^(ebnodb/10.0); esno=ebno*R; A=sqrt(2*esno); diff --git a/lib/fsk4hf/spb_128_90.dat b/lib/fsk4hf/spb_128_90.dat new file mode 100644 index 000000000..9e32e28e9 --- /dev/null +++ b/lib/fsk4hf/spb_128_90.dat @@ -0,0 +1,19 @@ +N = 128 +K = 90 +R = 0.70312 +-3.000000 0.000341 +-2.500000 0.001513 +-2.000000 0.006049 +-1.500000 0.021280 +-1.000000 0.064283 +-0.500000 0.162755 +0.000000 0.338430 +0.500000 0.571867 +1.000000 0.791634 +1.500000 0.930284 +2.000000 0.985385 +2.500000 0.998258 +3.000000 0.999893 +3.500000 0.999997 +4.000000 1.000000 + diff --git a/lib/ft2/cdatetime.f90 b/lib/ft2/cdatetime.f90 new file mode 100644 index 000000000..33deaf30a --- /dev/null +++ b/lib/ft2/cdatetime.f90 @@ -0,0 +1,6 @@ +character*17 function cdatetime() + character cdate*8,ctime*10 + call date_and_time(cdate,ctime) + cdatetime=cdate(3:8)//'_'//ctime + return +end function cdatetime diff --git a/lib/ft2/ft2.f90 b/lib/ft2/ft2.f90 new file mode 100644 index 000000000..e5c389f90 --- /dev/null +++ b/lib/ft2/ft2.f90 @@ -0,0 +1,279 @@ +program ft2 + + use packjt77 + include 'gcom1.f90' + integer ft2audio,ptt + logical allok + character*20 pttport + character*8 arg + character*80 fname + integer*2 id2(30000) + + open(12,file='all_ft2.txt',status='unknown',position='append') + nargs=iargc() + if(nargs.eq.1) then + call getarg(1,fname) + open(10,file=fname,status='old',access='stream') + read(10) id2(1:22) !Read (and ignore) the header + read(10) id2 !Read the Rx data + close(10) + call ft2_decode(fname(1:17),nfqso,id2,ndecodes,mycall,hiscall,nrx) + go to 999 + endif + + allok=.true. +! Get home-station details + open(10,file='ft2.ini',status='old',err=1) + go to 2 +1 print*,'Cannot open ft2.ini' + allok=.false. +2 read(10,*,err=3) mycall,mygrid,ndevin,ndevout,pttport,exch + go to 4 +3 print*,'Error reading ft2.ini' + allok=.false. +4 if(index(pttport,'/').lt.1) read(pttport,*) nport + hiscall=' ' + hiscall_next=' ' + idevin=ndevin + idevout=ndevout + call padevsub(idevin,idevout) + if(idevin.ne.ndevin .or. idevout.ne.ndevout) allok=.false. + i1=0 + i1=ptt(nport,1,1,iptt) + i1=ptt(nport,1,0,iptt) + if(i1.lt.0 .and. nport.ne.0) allok=.false. + if(.not.allok) then + write(*,"('Please fix setup error(s) and restart.')") + go to 999 + endif + + nright=1 + iwrite=0 + iwave=0 + nwave=NTZ + nfsample=12000 + ngo=1 + npabuf=1152 + ntxok=0 + ntransmitting=0 + tx_once=.false. + snrdb=99.0 + txmsg='CQ K1JT FN20' + ltx=.false. + lrx=.false. + autoseq=.false. + QSO_in_progress=.false. + ntxed=0 + + if(nargs.eq.3) then + call getarg(1,txmsg) + call getarg(2,arg) + read(arg,*) f0 + call getarg(3,arg) + read(arg,*) snrdb + tx_once=.true. + ftx=1500.0 + call transmit(-1,ftx,iptt) + snrdb=99.0 + endif + +! Start the audio streams + ierr=ft2audio(idevin,idevout,npabuf,nright,y1,y2,NRING,iwrite,itx, & + iwave,nwave+3*1152,nfsample,nTxOK,nTransmitting,ngo) + if(ierr.ne.0) then + print*,'Error',ierr,' starting audio input and/or output.' + endif + +999 end program ft2 + +subroutine update(total_time,ic1,ic2) + + use wavhdr + type(hdr) h + real*8 total_time + integer*8 count0,count1,clkfreq + integer ptt + integer*2 id(30000) + logical transmitted,level,ok + character*70 line + character cdatetime*17,fname*17,mode*8,band*6 + include 'gcom1.f90' + data nt0/-1/,transmitted/.false./,snr/-99.0/ + data level/.false./ + save nt0,transmitted,level,snr,iptt + + if(ic1.ne.0 .or. ic2.ne.0) then + if(ic1.eq.27 .and. ic2.eq.0) ngo=0 !ESC + if(nTxOK.eq.0 .and. ntransmitting.eq.0) then + nfunc=0 + if(ic1.eq.0 .and. ic2.eq.59) nfunc=1 !F1 + if(ic1.eq.0 .and. ic2.eq.60) nfunc=2 !F2 + if(ic1.eq.0 .and. ic2.eq.61) nfunc=3 !F3 + if(ic1.eq.0 .and. ic2.eq.62) nfunc=4 !F4 + if(ic1.eq.0 .and. ic2.eq.63) nfunc=5 !F5 + if(nfunc.eq.1 .or. (nfunc.ge.2 .and. hiscall.ne.' ')) then + ftx=1500.0 + call transmit(nfunc,ftx,iptt) + endif + endif + if(ic1.eq.13 .and. ic2.eq.0) hiscall=hiscall_next + if((ic1.eq.97 .or. ic1.eq.65) .and. ic2.eq.0) autoseq=.not.autoseq + if((ic1.eq.108 .or. ic1.eq.76) .and. ic2.eq.0) level=.not.level + endif + + if(ntransmitting.eq.1) transmitted=.true. + if(transmitted .and. ntransmitting.eq.0) then + i1=0 + if(iptt.eq.1 .and. nport.gt.0) i1=ptt(nport,0,1,iptt) + if(tx_once .and. transmitted) stop + transmitted=.false. + endif + + nt=2*total_time + if(nt.gt.nt0 .or. ic1.ne.0 .or. ic2.ne.0) then + if(level) then +! Measure and display the average level of signal plus noise in past 0.5 s + k=iwrite-6000 + if(k.lt.1) k=k+NRING + sq=0. + do i=1,6000 + k=k+1 + if(k.gt.NRING) k=k-NRING + x=y1(k) + sq=sq + x*x + enddo + sigdb=0. + if(sq.gt.0.0) sigdb=db(sq/6000.0) + n=sigdb + if(n.lt.1) n=1 + if(n.gt.70) n=70 + line=' ' + line(n:n)='*' + write(*,1030) sigdb,ntxed,autoseq,QSO_in_progress,(line(i:i),i=1,n) +1030 format(f4.1,i3,2L2,1x,70a1) +! write(*,1020) nt,total_time,iwrite,itx,ntxok,ntransmitting,ndecodes, & +! snr,sigdb,line +!1020 format(i6,f9.3,i10,i6,3i3,f6.0,f6.1,1x,a30) + endif + k=iwrite-30000 + if(k.lt.1) k=k+NRING + do i=1,30000 + k=k+1 + if(k.gt.NRING) k=k-NRING + id(i)=y1(k) + enddo + nutc=0 + nfqso=1500 + ndecodes=0 + if(maxval(abs(id)).gt.0) then + call system_clock(count0,clkfreq) + nrx=-1 + call ft2_decode(cdatetime(),nfqso,id,ndecodes,mycall,hiscall,nrx) + call system_clock(count1,clkfreq) +! tdecode=float(count1-count0)/float(clkfreq) + + if(ndecodes.ge.1) then + fMHz=7.074 + mode='FT2' + nsubmode=1 + ntrperiod=0 + h=default_header(12000,30000) + k=0 + do i=1,250 + sq=0 + do n=1,120 + k=k+1 + x=id(k) + sq=sq + x*x + enddo + write(43,3043) i,0.01*i,1.e-4*sq +3043 format(i7,f12.6,f12.3) + enddo + call set_wsjtx_wav_params(fMHz,mode,nsubmode,ntrperiod,id) + band="" + mode="" + nsubmode=-1 + ntrperiod=-1 + call get_wsjtx_wav_params(id,band,mode,nsubmode,ntrperiod,ok) +! write(*,1010) band,ntrperiod,mode,char(ichar('A')-1+id(3)) +!1010 format('Band: ',a6,' T/R period:',i4,' Mode: ',a8,1x,a1) + + fname=cdatetime() + fname(14:17)='.wav' + open(13,file=fname,status='unknown',access='stream') + write(13) h,id + close(13) + endif + if(autoseq .and.nrx.eq.2) QSO_in_progress=.true. + if(autoseq .and. QSO_in_progress .and. nrx.ge.1 .and. nrx.le.4) then + lrx(nrx)=.true. + ftx=1500.0 + if(ntxed.eq.1) then + if(nrx.eq.2) then + call transmit(3,ftx,iptt) + else + call transmit(1,ftx,iptt) + endif + endif + if(ntxed.eq.2) then + if(nrx.eq.3) then + call transmit(4,ftx,iptt) + QSO_in_progress=.false. + write(*,1032) +1032 format('QSO complete: S+P side') + else + call transmit(2,ftx,iptt) + endif + endif + if(ntxed.eq.3) then + if(nrx.eq.4) then + QSO_in_progress=.false. + write(*,1034) +1034 format('QSO complete: CQ side') + else + call transmit(3,ftx,iptt) + endif + endif + endif + endif + nt0=nt + endif + + return +end subroutine update + +character*17 function cdatetime() + character cdate*8,ctime*10 + call date_and_time(cdate,ctime) + cdatetime=cdate(3:8)//'_'//ctime + return +end function cdatetime + +subroutine transmit(nfunc,ftx,iptt) + include 'gcom1.f90' + character*17 cdatetime + integer ptt + + if(nTxOK.eq.1) return + + if(nfunc.eq.1) txmsg='CQ '//trim(mycall)//' '//mygrid + if(nfunc.eq.2) txmsg=trim(hiscall)//' '//trim(mycall)// & + ' 559 '//trim(exch) + if(nfunc.eq.3) txmsg=trim(hiscall)//' '//trim(mycall)// & + ' R 559 '//trim(exch) + if(nfunc.eq.4) txmsg=trim(hiscall)//' '//trim(mycall)//' RR73' + if(nfunc.eq.5) txmsg='TNX 73 GL' + call ft2_iwave(txmsg,ftx,snrdb,iwave) + iwave(23041:)=0 + i1=ptt(nport,1,1,iptt) + ntxok=1 + n=len(trim(txmsg)) + write(*,1010) cdatetime(),0,0.0,nint(ftx),(txmsg(i:i),i=1,n) + write(12,1010) cdatetime(),0,0.0,nint(ftx),(txmsg(i:i),i=1,n) +1010 format(a17,i4,f6.2,i5,' Tx ',37a1) + if(nfunc.ge.1 .and. nfunc.le.4) ntxed=nfunc + if(nfunc.ge.1 .and. nfunc.le.5) ltx(nfunc)=.true. + if(nfunc.eq.2 .or. nfunc.eq.3) QSO_in_progress=.true. + + return +end subroutine transmit diff --git a/lib/ft2/ft2.ini b/lib/ft2/ft2.ini new file mode 100644 index 000000000..d66770d17 --- /dev/null +++ b/lib/ft2/ft2.ini @@ -0,0 +1,2 @@ +K1JT FN20 1 5 0 NJ +MyCall MyGrid AudioIn AudioOut PTTport Exch diff --git a/lib/ft2/ft2_decode.f90 b/lib/ft2/ft2_decode.f90 new file mode 100644 index 000000000..1d1bb6a53 --- /dev/null +++ b/lib/ft2/ft2_decode.f90 @@ -0,0 +1,298 @@ +subroutine ft2_decode(cdatetime0,nfqso,iwave,ndecodes,mycall,hiscall,nrx,line) + + use crc + use packjt77 + include 'ft2_params.f90' + character message*37,c77*77 + character*61 line + character*37 decodes(100) + character*120 data_dir + character*17 cdatetime0,cdatetime + character*6 mycall,hiscall,hhmmss + complex c2(0:NMAX/16-1) !Complex waveform + complex cb(0:NMAX/16-1) + complex cd(0:144*10-1) !Complex waveform + complex c1(0:9),c0(0:9) + complex ccor(0:1,144) + complex csum,cterm,cc0,cc1,csync1 + real*8 fMHz + + real a(5) + real rxdata(128),llr(128) !Soft symbols + real llr2(128) + real sbits(144),sbits1(144),sbits3(144) + real ps(0:8191),psbest(0:8191) + real candidate(3,100) + real savg(NH1) + integer*2 iwave(NMAX) !Generated full-length waveform + integer*1 message77(77),apmask(128),cw(128) + integer*1 hbits(144),hbits1(144),hbits3(144) + integer*1 s16(16) + logical unpk77_success + data s16/0,0,0,0,1,1,1,1,1,1,1,1,0,0,0,0/ + + hhmmss=cdatetime0(8:13) + fs=12000.0/NDOWN !Sample rate + dt=1/fs !Sample interval after downsample (s) + tt=NSPS*dt !Duration of "itone" symbols (s) + baud=1.0/tt !Keying rate for "itone" symbols (baud) + txt=NZ*dt !Transmission length (s) + twopi=8.0*atan(1.0) + h=0.8 !h=0.8 seems to be optimum for AWGN sensitivity (not for fading) + + dphi=twopi/2*baud*h*dt*16 ! dt*16 is samp interval after downsample + dphi0=-1*dphi + dphi1=+1*dphi + phi0=0.0 + phi1=0.0 + do i=0,9 + c1(i)=cmplx(cos(phi1),sin(phi1)) + c0(i)=cmplx(cos(phi0),sin(phi0)) + phi1=mod(phi1+dphi1,twopi) + phi0=mod(phi0+dphi0,twopi) + enddo + the=twopi*h/2.0 + cc1=cmplx(cos(the),-sin(the)) + cc0=cmplx(cos(the),sin(the)) + + data_dir="." + fMHz=7.074 + ncoh=1 + candidate=0.0 + ncand=0 + fa=375.0 + fb=3000.0 + syncmin=0.2 + maxcand=100 + nfqso=-1 + call getcandidates2a(iwave,fa,fb,maxcand,savg,candidate,ncand) + ndecodes=0 + do icand=1,ncand + f0=candidate(1,icand) + if( f0.le.375.0 .or. f0.ge.(5000.0-375.0) ) cycle + call ft2_downsample(iwave,f0,c2) ! downsample from 160s/Symbol to 10s/Symbol +! 750 samples/second here + ibest=-1 + sybest=-99. + dfbest=-1. +!### do if=-15,+15 + do if=-30,30 + df=if + a=0. + a(1)=-df + call twkfreq1(c2,NMAX/16,fs,a,cb) + do is=0,374 !DT search range is 0 - 0.5 s + csync1=0. + cterm=1 + do ib=1,16 + i1=(ib-1)*10+is + i2=i1+136*10 + if(s16(ib).eq.1) then + csync1=csync1+sum(cb(i1:i1+9)*conjg(c1(0:9)))*cterm + cterm=cterm*cc1 + else + csync1=csync1+sum(cb(i1:i1+9)*conjg(c0(0:9)))*cterm + cterm=cterm*cc0 + endif + enddo + if(abs(csync1).gt.sybest) then + ibest=is + sybest=abs(csync1) + dfbest=df + endif + enddo + enddo + + a=0. + a(1)=-dfbest + call twkfreq1(c2,NMAX/16,fs,a,cb) + ib=ibest + cd=cb(ib:ib+144*10-1) + s2=sum(real(cd*conjg(cd)))/(10*144) + cd=cd/sqrt(s2) + do nseq=1,5 + if( nseq.eq.1 ) then ! noncoherent single-symbol detection + sbits1=0.0 + do ibit=1,144 + ib=(ibit-1)*10 + ccor(1,ibit)=sum(cd(ib:ib+9)*conjg(c1(0:9))) + ccor(0,ibit)=sum(cd(ib:ib+9)*conjg(c0(0:9))) + sbits1(ibit)=abs(ccor(1,ibit))-abs(ccor(0,ibit)) + hbits1(ibit)=0 + if(sbits1(ibit).gt.0) hbits1(ibit)=1 + enddo + sbits=sbits1 + hbits=hbits1 + sbits3=sbits1 + hbits3=hbits1 + elseif( nseq.ge.2 ) then + nbit=2*nseq-1 + numseq=2**(nbit) + ps=0 + do ibit=nbit/2+1,144-nbit/2 + ps=0.0 + pmax=0.0 + do iseq=0,numseq-1 + csum=0.0 + cterm=1.0 + k=1 + do i=nbit-1,0,-1 + ibb=iand(iseq/(2**i),1) + csum=csum+ccor(ibb,ibit-(nbit/2+1)+k)*cterm + if(ibb.eq.0) cterm=cterm*cc0 + if(ibb.eq.1) cterm=cterm*cc1 + k=k+1 + enddo + ps(iseq)=abs(csum) + if( ps(iseq) .gt. pmax ) then + pmax=ps(iseq) + ibflag=1 + endif + enddo + if( ibflag .eq. 1 ) then + psbest=ps + ibflag=0 + endif + call getbitmetric(2**(nbit/2),psbest,numseq,sbits3(ibit)) + hbits3(ibit)=0 + if(sbits3(ibit).gt.0) hbits3(ibit)=1 + enddo + sbits=sbits3 + hbits=hbits3 + endif + nsync_qual=count(hbits(1:16).eq.s16) + if(nsync_qual.lt.10) exit + rxdata=sbits(17:144) + rxav=sum(rxdata(1:128))/128.0 + rx2av=sum(rxdata(1:128)*rxdata(1:128))/128.0 + rxsig=sqrt(rx2av-rxav*rxav) + rxdata=rxdata/rxsig + sigma=0.80 + llr(1:128)=2*rxdata/(sigma*sigma) + apmask=0 + max_iterations=40 + do ibias=0,0 + llr2=llr + if(ibias.eq.1) llr2=llr+0.4 + if(ibias.eq.2) llr2=llr-0.4 + call bpdecode128_90(llr2,apmask,max_iterations,message77,cw,nharderror,niterations) + if(nharderror.ge.0) exit + enddo + nhardmin=-1 + if(sum(message77).eq.0) cycle + if( nharderror.ge.0 ) then + write(c77,'(77i1)') message77(1:77) + call unpack77(c77,nrx,message,unpk77_success) + idupe=0 + do i=1,ndecodes + if(decodes(i).eq.message) idupe=1 + enddo + if(idupe.eq.1) exit + ndecodes=ndecodes+1 + decodes(ndecodes)=message + xsnr=db(sybest*sybest) - 115.0 !### Rough estimate of S/N ### + nsnr=nint(xsnr) + freq=f0+dfbest + write(line,1000) hhmmss,nsnr,ibest/750.0,nint(freq),message +1000 format(a6,i4,f5.2,i5,' + ',1x,a37) + open(24,file='all_ft2.txt',status='unknown',position='append') + write(24,1002) cdatetime0,nsnr,ibest/750.0,nint(freq),message, & + nseq,nharderror,nhardmin + if(hhmmss.eq.' ') write(*,1002) cdatetime0,nsnr, & + ibest/750.0,nint(freq),message,nseq,nharderror,nhardmin +1002 format(a17,i4,f6.2,i5,' Rx ',a37,3i5) + close(24) + +!### Temporary: assume most recent decoded message conveys "hiscall". + i0=index(message,' ') + if(i0.ge.3 .and. i0.le.7) then + hiscall=message(i0+1:i0+6) + i1=index(hiscall,' ') + if(i1.gt.0) hiscall=hiscall(1:i1) + endif + nrx=-1 + if(index(message,'CQ ').eq.1) nrx=1 + if((index(message,trim(mycall)//' ').eq.1) .and. & + (index(message,' '//trim(hiscall)//' ').ge.4)) then + if(index(message,' 559 ').gt.8) nrx=2 + if(index(message,' R 559 ').gt.8) nrx=3 + if(index(message,' RR73 ').gt.8) nrx=4 + endif +!### + exit + endif + enddo ! nseq + enddo !candidate list + + return +end subroutine ft2_decode + +subroutine getbitmetric(ib,ps,ns,xmet) + real ps(0:ns-1) + xm1=0 + xm0=0 + do i=0,ns-1 + if( iand(i/ib,1) .eq. 1 .and. ps(i) .gt. xm1 ) xm1=ps(i) + if( iand(i/ib,1) .eq. 0 .and. ps(i) .gt. xm0 ) xm0=ps(i) + enddo + xmet=xm1-xm0 + return +end subroutine getbitmetric + +subroutine downsample2(ci,f0,co) + parameter(NI=144*160,NH=NI/2,NO=NI/16) ! downsample from 200 samples per symbol to 10 + complex ci(0:NI-1),ct(0:NI-1) + complex co(0:NO-1) + fs=12000.0 + df=fs/NI + ct=ci + call four2a(ct,NI,1,-1,1) !c2c FFT to freq domain + i0=nint(f0/df) + ct=cshift(ct,i0) + co=0.0 + co(0)=ct(0) + b=8.0 + do i=1,NO/2 + arg=(i*df/b)**2 + filt=exp(-arg) + co(i)=ct(i)*filt + co(NO-i)=ct(NI-i)*filt + enddo + co=co/NO + call four2a(co,NO,1,1,1) !c2c FFT back to time domain + return +end subroutine downsample2 + +subroutine ft2_downsample(iwave,f0,c) + +! Input: i*2 data in iwave() at sample rate 12000 Hz +! Output: Complex data in c(), sampled at 1200 Hz + + include 'ft2_params.f90' + parameter (NFFT2=NMAX/16) + integer*2 iwave(NMAX) + complex c(0:NMAX/16-1) + complex c1(0:NFFT2-1) + complex cx(0:NMAX/2) + real x(NMAX) + equivalence (x,cx) + + BW=4.0*75 + df=12000.0/NMAX + x=iwave + call four2a(x,NMAX,1,-1,0) !r2c FFT to freq domain + ibw=nint(BW/df) + i0=nint(f0/df) + c1=0. + c1(0)=cx(i0) + do i=1,NFFT2/2 + arg=(i-1)*df/bw + win=exp(-arg*arg) + c1(i)=cx(i0+i)*win + c1(NFFT2-i)=cx(i0-i)*win + enddo + c1=c1/NFFT2 + call four2a(c1,NFFT2,1,1,1) !c2c FFT back to time domain + c=c1(0:NMAX/16-1) + return +end subroutine ft2_downsample diff --git a/lib/ft2/ft2_gfsk_iwave.f90 b/lib/ft2/ft2_gfsk_iwave.f90 new file mode 100644 index 000000000..2611eca09 --- /dev/null +++ b/lib/ft2/ft2_gfsk_iwave.f90 @@ -0,0 +1,88 @@ +subroutine ft2_gfsk_iwave(msg37,f0,snrdb,iwave) + +! Generate waveform for experimental "FT2" mode + + use packjt77 + include 'ft2_params.f90' !Set various constants + parameter (NWAVE=(NN+2)*NSPS) + character msg37*37,msgsent37*37 + real wave(NWAVE),xnoise(NWAVE) + real dphi(NWAVE) + real pulse(480) + + integer itone(NN) + integer*2 iwave(NWAVE) !Generated full-length waveform + logical first + data first/.true./ + save pulse + + twopi=8.0*atan(1.0) + fs=12000.0 !Sample rate (Hz) + dt=1.0/fs !Sample interval (s) + hmod=0.8 !Modulation index (MSK=0.5, FSK=1.0) + tt=NSPS*dt !Duration of symbols (s) + baud=1.0/tt !Keying rate (baud) + bw=1.5*baud !Occupied bandwidth (Hz) + txt=NZ*dt !Transmission length (s) + bandwidth_ratio=2500.0/(fs/2.0) +! sig=sqrt(2*bandwidth_ratio) * 10.0**(0.05*snrdb) +! if(snrdb.gt.90.0) sig=1.0 + txt=NN*NSPS/12000.0 + + if(first) then +! The filtered frequency pulse + do i=1,480 + tt=(i-240.5)/160.0 + pulse(i)=gfsk_pulse(1.0,tt) + enddo + dphi_peak=twopi*(hmod/2.0)/real(NSPS) + first=.false. + endif + +! Source-encode, then get itone(): + itype=1 + call genft2(msg37,0,msgsent37,itone,itype) + +! Create the instantaneous frequency waveform + dphi=0.0 + do j=1,NN + ib=(j-1)*160+1 + ie=ib+480-1 + dphi(ib:ie)=dphi(ib:ie)+dphi_peak*pulse*(2*itone(j)-1) + enddo + + phi=0.0 + wave=0.0 + sqrt2=sqrt(2.) + dphi=dphi+twopi*f0*dt + do j=1,NWAVE + wave(j)=sqrt2*sin(phi) + sqsig=sqsig + wave(j)**2 + phi=mod(phi+dphi(j),twopi) + enddo + wave(1:160)=wave(1:160)*(1.0-cos(twopi*(/(i,i=0,159)/)/320.0) )/2.0 + wave(145*160+1:146*160)=wave(145*160+1:146*160)*(1.0+cos(twopi*(/(i,i=0,159)/)/320.0 ))/2.0 + wave(146*160+1:)=0. + + if(snrdb.gt.90.0) then + iwave=nint((32767.0/sqrt(2.0))*wave) + return + endif + + sqnoise=1.e-30 + if(snrdb.lt.90) then + do i=1,NWAVE !Add gaussian noise at specified SNR + xnoise(i)=gran() !Noise has rms = 1.0 + enddo + endif + xnoise=xnoise*sqrt(0.5*fs/2500.0) + fac=30.0 + snr_amplitude=10.0**(0.05*snrdb) + wave=fac*(snr_amplitude*wave + xnoise) + datpk=maxval(abs(wave)) + print*,'A',snr_amplitude,datpk + + iwave=nint((30000.0/datpk)*wave) + + return +end subroutine ft2_gfsk_iwave diff --git a/lib/ft2/ft2_iwave.f90 b/lib/ft2/ft2_iwave.f90 new file mode 100644 index 000000000..10b9908de --- /dev/null +++ b/lib/ft2/ft2_iwave.f90 @@ -0,0 +1,64 @@ +subroutine ft2_iwave(msg37,f0,snrdb,iwave) + +! Generate waveform for experimental "FT2" mode + + use packjt77 + include 'ft2_params.f90' !Set various constants + parameter (NWAVE=NN*NSPS) + character msg37*37,msgsent37*37 + real wave(NWAVE),xnoise(NWAVE) + integer itone(NN) + integer*2 iwave(NWAVE) !Generated full-length waveform + + twopi=8.0*atan(1.0) + fs=12000.0 !Sample rate (Hz) + dt=1.0/fs !Sample interval (s) + hmod=0.8 !Modulation index (MSK=0.5, FSK=1.0) + tt=NSPS*dt !Duration of symbols (s) + baud=1.0/tt !Keying rate (baud) + bw=1.5*baud !Occupied bandwidth (Hz) + txt=NZ*dt !Transmission length (s) + bandwidth_ratio=2500.0/(fs/2.0) +! sig=sqrt(2*bandwidth_ratio) * 10.0**(0.05*snrdb) +! if(snrdb.gt.90.0) sig=1.0 + txt=NN*NSPS/12000.0 + +! Source-encode, then get itone(): + itype=1 + call genft2(msg37,0,msgsent37,itone,itype) + + k=0 + phi=0.0 + sqsig=0. + do j=1,NN !Generate real waveform + dphi=twopi*(f0*dt+(hmod/2.0)*(2*itone(j)-1)/real(NSPS)) + do i=1,NSPS + k=k+1 + wave(k)=sqrt(2.0)*sin(phi) !Signal has rms = 1.0 + sqsig=sqsig + wave(k)**2 + phi=mod(phi+dphi,twopi) + enddo + enddo + + if(snrdb.gt.90.0) then + iwave=nint((32767.0/sqrt(2.0))*wave) + return + endif + + sqnoise=1.e-30 + if(snrdb.lt.90) then + do i=1,NWAVE !Add gaussian noise at specified SNR + xnoise(i)=gran() !Noise has rms = 1.0 + enddo + endif + xnoise=xnoise*sqrt(0.5*fs/2500.0) + fac=30.0 + snr_amplitude=10.0**(0.05*snrdb) + wave=fac*(snr_amplitude*wave + xnoise) + datpk=maxval(abs(wave)) + print*,'A',snr_amplitude,datpk + + iwave=nint((30000.0/datpk)*wave) + + return +end subroutine ft2_iwave diff --git a/lib/ft2/ft2_params.f90 b/lib/ft2/ft2_params.f90 new file mode 100644 index 000000000..4751e47e4 --- /dev/null +++ b/lib/ft2/ft2_params.f90 @@ -0,0 +1,12 @@ +! LDPC (128,90) code +parameter (KK=90) !Information bits (77 + CRC13) +parameter (ND=128) !Data symbols +parameter (NS=16) !Sync symbols (2x8) +parameter (NN=NS+ND) !Total channel symbols (144) +parameter (NSPS=160) !Samples per symbol at 12000 S/s +parameter (NZ=NSPS*NN) !Samples in full 1.92 s waveform (23040) +parameter (NMAX=30000) !Samples in iwave (2.5*12000) +parameter (NFFT1=400, NH1=NFFT1/2) !Length of FFTs for symbol spectra +parameter (NSTEP=NSPS/4) !Rough time-sync step size +parameter (NHSYM=NMAX/NSTEP-3) !Number of symbol spectra (1/4-sym steps) +parameter (NDOWN=16) !Downsample factor diff --git a/lib/ft2/ft2audio.c b/lib/ft2/ft2audio.c new file mode 100644 index 000000000..b5f19fa40 --- /dev/null +++ b/lib/ft2/ft2audio.c @@ -0,0 +1,347 @@ +#include +#include "portaudio.h" +#include +#include + +int iaa; +int icc; +double total_time=0.0; + +// Definition of structure pointing to the audio data +typedef struct +{ + int *iwrite; + int *itx; + int *TxOK; + int *Transmitting; + int *nwave; + int *nright; + int nring; + int nfs; + short *y1; + short *y2; + short *iwave; +} paTestData; + +// Input callback routine: +static int +SoundIn( void *inputBuffer, void *outputBuffer, + unsigned long framesPerBuffer, + const PaStreamCallbackTimeInfo* timeInfo, + PaStreamCallbackFlags statusFlags, + void *userData ) +{ + paTestData *data = (paTestData*)userData; + short *in = (short*)inputBuffer; + unsigned int i; + static int ia=0; + + if(*data->Transmitting) return 0; + + if(statusFlags!=0) printf("Status flags %d\n",(int)statusFlags); + + if((statusFlags&1) == 0) { + //increment buffer pointers only if data available + ia=*data->iwrite; + if(*data->nright==0) { //Use left channel for input + for(i=0; iy1[ia] = (*in++); + data->y2[ia] = (*in++); + ia++; + } + } else { //Use right channel + for(i=0; iy2[ia] = (*in++); + data->y1[ia] = (*in++); + ia++; + } + } + } + + if(ia >= data->nring) ia=0; //Wrap buffer pointer if necessary + *data->iwrite = ia; //Save buffer pointer + iaa=ia; + total_time += (double)framesPerBuffer/12000.0; + // printf("iwrite: %d\n",*data->iwrite); + return 0; +} + +// Output callback routine: +static int +SoundOut( void *inputBuffer, void *outputBuffer, + unsigned long framesPerBuffer, + const PaStreamCallbackTimeInfo* timeInfo, + PaStreamCallbackFlags statusFlags, + void *userData ) +{ + paTestData *data = (paTestData*)userData; + short *wptr = (short*)outputBuffer; + unsigned int i,n; + static short int n2; + static int ic=0; + static int TxOKz=0; + static clock_t tstart=-1; + static clock_t tend=-1; + static int nsent=0; + + // printf("txOK: %d %d\n",TxOKz,*data->TxOK); + + if(*data->TxOK && (!TxOKz)) ic=0; //Reset buffer pointer to start Tx + *data->Transmitting=*data->TxOK; //Set the "transmitting" flag + + if(*data->TxOK) { + if(!TxOKz) { + // Start of a transmission + tstart=clock(); + nsent=0; + // printf("Start Tx\n"); + } + TxOKz=*data->TxOK; + for(i=0 ; i < framesPerBuffer; i++ ) { + n2=data->iwave[ic]; + *wptr++ = n2; //left + *wptr++ = n2; //right + ic++; + + if(ic > *data->nwave) { + *data->TxOK = 0; + *data->Transmitting = 0; + *data->iwrite = 0; //Reset Rx buffer pointer to 0 + ic=0; + tend=clock(); + double TxT=((double)(tend-tstart))/CLOCKS_PER_SEC; + // printf("End Tx, TxT = %f nSent = %d\n",TxT,nsent); + break; + } + } + nsent += framesPerBuffer; + } else { + memset((void*)outputBuffer, 0, 2*sizeof(short)*framesPerBuffer); + } + *data->itx = icc; //Save buffer pointer + icc=ic; + return 0; +} + +/*******************************************************************/ +int ft2audio_(int *ndevin, int *ndevout, int *npabuf, int *nright, + short y1[], short y2[], int *nring, int *iwrite, + int *itx, short iwave[], int *nwave, int *nfsample, + int *TxOK, int *Transmitting, int *ngo) + +{ + paTestData data; + PaStream *instream, *outstream; + PaStreamParameters inputParameters, outputParameters; + // PaStreamInfo *streamInfo; + + int nfpb = *npabuf; + int nSampleRate = *nfsample; + int ndevice_in = *ndevin; + int ndevice_out = *ndevout; + double dSampleRate = (double) *nfsample; + PaError err_init, err_open_in, err_open_out, err_start_in, err_start_out; + PaError err = 0; + + data.iwrite = iwrite; + data.itx = itx; + data.TxOK = TxOK; + data.Transmitting = Transmitting; + data.y1 = y1; + data.y2 = y2; + data.nring = *nring; + data.nright = nright; + data.nwave = nwave; + data.iwave = iwave; + data.nfs = nSampleRate; + + err_init = Pa_Initialize(); // Initialize PortAudio + + if(err_init) { + printf("Error initializing PortAudio.\n"); + printf("\tErrortext: %s\n\tNumber: %d\n",Pa_GetErrorText(err_init), + err_init); + Pa_Terminate(); // I don't think we need this but... + return(-1); + } + + // printf("Opening device %d for input, %d for output...\n", + // ndevice_in,ndevice_out); + + inputParameters.device = ndevice_in; + inputParameters.channelCount = 2; + inputParameters.sampleFormat = paInt16; + inputParameters.suggestedLatency = 0.2; + inputParameters.hostApiSpecificStreamInfo = NULL; + +// Test if this configuration actually works, so we do not run into an +// ugly assertion + err_open_in = Pa_IsFormatSupported(&inputParameters, NULL, dSampleRate); + + if (err_open_in == 0) { + err_open_in = Pa_OpenStream( + &instream, //address of stream + &inputParameters, + NULL, + dSampleRate, //Sample rate + nfpb, //Frames per buffer + paNoFlag, + (PaStreamCallback *)SoundIn, //Callback routine + (void *)&data); //address of data structure + + if(err_open_in) { // We should have no error here usually + printf("Error opening input audio stream:\n"); + printf("\tErrortext: %s\n\tNumber: %d\n",Pa_GetErrorText(err_open_in), + err_open_in); + err = 1; + } else { + // printf("Successfully opened audio input.\n"); + } + } else { + printf("Error opening input audio stream.\n"); + printf("\tErrortext: %s\n\tNumber: %d\n",Pa_GetErrorText(err_open_in), + err_open_in); + err = 1; + } + + outputParameters.device = ndevice_out; + outputParameters.channelCount = 2; + outputParameters.sampleFormat = paInt16; + outputParameters.suggestedLatency = 0.2; + outputParameters.hostApiSpecificStreamInfo = NULL; + +// Test if this configuration actually works, so we do not run into an +// ugly assertion. + err_open_out = Pa_IsFormatSupported(NULL, &outputParameters, dSampleRate); + + if (err_open_out == 0) { + err_open_out = Pa_OpenStream( + &outstream, //address of stream + NULL, + &outputParameters, + dSampleRate, //Sample rate + nfpb, //Frames per buffer + paNoFlag, + (PaStreamCallback *)SoundOut, //Callback routine + (void *)&data); //address of data structure + + if(err_open_out) { // We should have no error here usually + printf("Error opening output audio stream!\n"); + printf("\tErrortext: %s\n\tNumber: %d\n",Pa_GetErrorText(err_open_out), + err_open_out); + err += 2; + } else { + // printf("Successfully opened audio output.\n"); + } + } else { + printf("Error opening output audio stream.\n"); + printf("\tErrortext: %s\n\tNumber: %d\n",Pa_GetErrorText(err_open_out), + err_open_out); + err += 2; + } + + // if there was no error in opening both streams start them + if (err == 0) { + err_start_in = Pa_StartStream(instream); //Start input stream + if(err_start_in) { + printf("Error starting input audio stream!\n"); + printf("\tErrortext: %s\n\tNumber: %d\n",Pa_GetErrorText(err_start_in), + err_start_in); + err += 4; + } + + err_start_out = Pa_StartStream(outstream); //Start output stream + if(err_start_out) { + printf("Error starting output audio stream!\n"); + printf("\tErrortext: %s\n\tNumber: %d\n",Pa_GetErrorText(err_start_out), + err_start_out); + err += 8; + } + } + + if (err == 0) printf("Audio streams running normally.\n******************************************************************\n"); + + while( Pa_IsStreamActive(instream) && (*ngo != 0) && (err == 0) ) { + int ic1=0; + int ic2=0; + if(_kbhit()) ic1 = _getch(); + if(_kbhit()) ic2 = _getch(); + // if(ic1!=0 || ic2!=0) printf("%d %d %d\n",iaa,ic1,ic2); + update_(&total_time,&ic1,&ic2); + Pa_Sleep(100); + } + + Pa_AbortStream(instream); // Abort stream + Pa_CloseStream(instream); // Close stream, we're done. + Pa_AbortStream(outstream); // Abort stream + Pa_CloseStream(outstream); // Close stream, we're done. + + Pa_Terminate(); + + return(err); +} + + +int padevsub_(int *idevin, int *idevout) +{ + int numdev,ndefin,ndefout; + int nchin[101], nchout[101]; + int i, devIdx; + int numDevices; + const PaDeviceInfo *pdi; + PaError err; + + Pa_Initialize(); + numDevices = Pa_GetDeviceCount(); + numdev = numDevices; + + if( numDevices < 0 ) { + err = numDevices; + Pa_Terminate(); + return err; + } + + if ((devIdx = Pa_GetDefaultInputDevice()) > 0) { + ndefin = devIdx; + } else { + ndefin = 0; + } + + if ((devIdx = Pa_GetDefaultOutputDevice()) > 0) { + ndefout = devIdx; + } else { + ndefout = 0; + } + + printf("\nAudio Input Output Device Name\n"); + printf("Device Channels Channels\n"); + printf("------------------------------------------------------------------\n"); + + for( i=0; i < numDevices; i++ ) { + pdi = Pa_GetDeviceInfo(i); +// if(i == Pa_GetDefaultInputDevice()) ndefin = i; +// if(i == Pa_GetDefaultOutputDevice()) ndefout = i; + nchin[i]=pdi->maxInputChannels; + nchout[i]=pdi->maxOutputChannels; + printf(" %2d %2d %2d %s\n",i,nchin[i],nchout[i], + pdi->name); + } + + printf("\nUser requested devices: Input = %2d Output = %2d\n", + *idevin,*idevout); + printf("Default devices: Input = %2d Output = %2d\n", + ndefin,ndefout); + if((*idevin<0) || (*idevin>=numdev)) *idevin=ndefin; + if((*idevout<0) || (*idevout>=numdev)) *idevout=ndefout; + if((*idevin==0) && (*idevout==0)) { + *idevin=ndefin; + *idevout=ndefout; + } + printf("Will open devices: Input = %2d Output = %2d\n", + *idevin,*idevout); + + Pa_Terminate(); + + return 0; +} + diff --git a/lib/ft2/g4.cmd b/lib/ft2/g4.cmd new file mode 100644 index 000000000..0894dbebe --- /dev/null +++ b/lib/ft2/g4.cmd @@ -0,0 +1,7 @@ +gcc -c ft2audio.c +gcc -c ptt.c +gfortran -c ../77bit/packjt77.f90 +gfortran -c ../wavhdr.f90 +gfortran -c ../crc.f90 +gfortran -o ft2 -fbounds-check -fno-second-underscore -ffpe-trap=invalid,zero -Wall -Wno-conversion -Wno-character-truncation ft2.f90 ft2_iwave.f90 ft2_decode.f90 getcandidates2.f90 ft2audio.o ptt.o /JTSDK/wsjtx-output/qt55/2.1.0/Release/build/libwsjt_fort.a /JTSDK/wsjtx-output/qt55/2.1.0/Release/build/libwsjt_cxx.a libportaudio.a ../libfftw3f_win.a -lwinmm +rm *.o *.mod diff --git a/lib/ft2/gcom1.f90 b/lib/ft2/gcom1.f90 new file mode 100644 index 000000000..9402f9d9e --- /dev/null +++ b/lib/ft2/gcom1.f90 @@ -0,0 +1,34 @@ +! Variable Purpose +!--------------------------------------------------------------------------- +integer NRING !Length of Rx ring buffer +integer NTZ !Length of Tx waveform in samples +parameter(NRING=230400) !Ring buffer at 12000 samples/sec +parameter(NTZ=23040) !144*160 +parameter(NMAX=30000) !2.5*12000 +real snrdb +integer ndevin !Device# for audio input +integer ndevout !Device# for audio output +integer iwrite !Pointer to Rx ring buffer +integer itx !Pointer to Tx buffer +integer ngo !Set to 0 to terminate audio streams +integer nTransmitting !Actually transmitting? +integer nTxOK !OK to transmit? +integer nport !COM port for PTT +logical tx_once !Transmit one message, then exit +logical ltx !True if msg i has been transmitted +logical lrx !True if msg i has been received +logical autoseq +logical QSO_in_progress +integer*2 y1 !Ring buffer for audio channel 0 +integer*2 y2 !Ring buffer for audio channel 1 +integer*2 iwave !Data for Tx audio +character*6 mycall +character*6 hiscall +character*6 hiscall_next +character*4 mygrid +character*3 exch +character*37 txmsg + +common/gcom1/snrdb,ndevin,ndevout,iwrite,itx,ngo,nTransmitting,nTxOK,nport, & + ntxed,tx_once,y1(NRING),y2(NRING),iwave(NTZ+3*1152),ltx(5),lrx(5), & + autoseq,QSO_in_progress,mycall,hiscall,hiscall_next,mygrid,exch,txmsg diff --git a/lib/ft2/genft2.f90 b/lib/ft2/genft2.f90 new file mode 100644 index 000000000..2eb36bccc --- /dev/null +++ b/lib/ft2/genft2.f90 @@ -0,0 +1,86 @@ +subroutine genft2(msg0,ichk,msgsent,i4tone,itype) +! s8 + 48bits + s8 + 80 bits = 144 bits (72ms message duration) +! +! Encode an MSK144 message +! Input: +! - msg0 requested message to be transmitted +! - ichk if ichk=1, return only msgsent +! if ichk.ge.10000, set imsg=ichk-10000 for short msg +! - msgsent message as it will be decoded +! - i4tone array of audio tone values, 0 or 1 +! - itype message type +! 1 = 77 bit message +! 7 = 16 bit message " Rpt" + + use iso_c_binding, only: c_loc,c_size_t + use packjt77 + character*37 msg0 + character*37 message !Message to be generated + character*37 msgsent !Message as it will be received + character*77 c77 + integer*4 i4tone(144) + integer*1 codeword(128) + integer*1 msgbits(77) + integer*1 bitseq(144) !Tone #s, data and sync (values 0-1) + integer*1 s16(16) + real*8 xi(864),xq(864),pi,twopi + data s16/0,0,0,0,1,1,1,1,1,1,1,1,0,0,0,0/ + equivalence (ihash,i1hash) + logical unpk77_success + + nsym=128 + pi=4.0*atan(1.0) + twopi=8.*atan(1.0) + + message(1:37)=' ' + itype=1 + if(msg0(1:1).eq.'@') then !Generate a fixed tone + read(msg0(2:5),*,end=1,err=1) nfreq !at specified frequency + go to 2 +1 nfreq=1000 +2 i4tone(1)=nfreq + else + message=msg0 + + do i=1, 37 + if(ichar(message(i:i)).eq.0) then + message(i:37)=' ' + exit + endif + enddo + do i=1,37 !Strip leading blanks + if(message(1:1).ne.' ') exit + message=message(i+1:) + enddo + + if(message(1:1).eq.'<') then + i2=index(message,'>') + i1=0 + if(i2.gt.0) i1=index(message(1:i2),' ') + if(i1.gt.0) then + call genmsk40(message,msgsent,ichk,i4tone,itype) + if(itype.lt.0) go to 999 + i4tone(41)=-40 + go to 999 + endif + endif + + i3=-1 + n3=-1 + call pack77(message,i3,n3,c77) + call unpack77(c77,0,msgsent,unpk77_success) !Unpack to get msgsent + + if(ichk.eq.1) go to 999 + read(c77,"(77i1)") msgbits + call encode_128_90(msgbits,codeword) + +!Create 144-bit channel vector: + bitseq=0 + bitseq(1:16)=s16 + bitseq(17:144)=codeword + + i4tone=bitseq + endif + +999 return +end subroutine genft2 diff --git a/lib/ft2/getcandidates2a.f90 b/lib/ft2/getcandidates2a.f90 new file mode 100644 index 000000000..8e7209c9c --- /dev/null +++ b/lib/ft2/getcandidates2a.f90 @@ -0,0 +1,64 @@ +subroutine getcandidates2a(id,fa,fb,maxcand,savg,candidate,ncand) + +! For now, hardwired to find the largest peak in the average spectrum + + include 'ft2_params.f90' + real s(NH1,NHSYM) + real savg(NH1),savsm(NH1) + real x(NFFT1) + complex cx(0:NH1) + real candidate(3,100) + integer*2 id(NMAX) + integer*1 s8(8) + integer indx(NH1) + data s8/0,1,1,1,0,0,1,0/ + equivalence (x,cx) + +! Compute symbol spectra, stepping by NSTEP steps. + savg=0. + tstep=NSTEP/12000.0 + df=12000.0/NFFT1 !3.125 Hz + fac=1.0/300.0 + do j=1,NHSYM + ia=(j-1)*NSTEP + 1 + ib=ia+NSPS-1 + x(1:NSPS)=fac*id(ia:ib) + x(NSPS+1:)=0. + call four2a(x,NFFT1,1,-1,0) !r2c FFT + do i=1,NH1 + s(i,j)=real(cx(i))**2 + aimag(cx(i))**2 + enddo + savg=savg + s(1:NH1,j) !Average spectrum + enddo + savsm=0. + do i=2,NH1-1 + savsm(i)=sum(savg(i-1:i+1))/3. + enddo + savsm(1)=savg(1) + savsm(NH1)=savg(NH1) + + nfa=nint(fa/df) + nfb=nint(fb/df) + np=nfb-nfa+1 + indx=0 + call indexx(savsm(nfa:nfb),np,indx) + xn=savsm(nfa+indx(nint(0.3*np))) + if(xn.ne.0) savsm=savsm/xn + imax=-1 + xmax=-99. + do i=2,NH1-1 + if(savsm(i).gt.savsm(i-1).and. & + savsm(i).gt.savsm(i+1).and. & + savsm(i).gt.xmax) then + xmax=savsm(i) + imax=i + endif + enddo + f0=imax*df + if(xmax.gt.1.2) then + if(ncand.lt.maxcand) ncand=ncand+1 + candidate(1,ncand)=f0 + endif + + return +end subroutine getcandidates2a diff --git a/lib/ft2/gfsk_pulse.f90 b/lib/ft2/gfsk_pulse.f90 new file mode 100644 index 000000000..99ab78e35 --- /dev/null +++ b/lib/ft2/gfsk_pulse.f90 @@ -0,0 +1,6 @@ +real function gfsk_pulse(b,t) + pi=4.*atan(1.0) + c=pi*sqrt(2.0/log(2.0)) + gfsk_pulse=0.5*(erf(c*b*(t+0.5))-erf(c*b*(t-0.5))) + return +end function gfsk_pulse diff --git a/lib/ft2/libportaudio.a b/lib/ft2/libportaudio.a new file mode 100644 index 000000000..f20f02c16 Binary files /dev/null and b/lib/ft2/libportaudio.a differ diff --git a/lib/ft2/libwsjt_cxx.a b/lib/ft2/libwsjt_cxx.a new file mode 100644 index 000000000..d10d509cf Binary files /dev/null and b/lib/ft2/libwsjt_cxx.a differ diff --git a/lib/ft2/libwsjt_fort.a b/lib/ft2/libwsjt_fort.a new file mode 100644 index 000000000..7884c85bb Binary files /dev/null and b/lib/ft2/libwsjt_fort.a differ diff --git a/lib/ft2/portaudio.h b/lib/ft2/portaudio.h new file mode 100644 index 000000000..250fba021 --- /dev/null +++ b/lib/ft2/portaudio.h @@ -0,0 +1,1123 @@ + +#ifndef PORTAUDIO_H +#define PORTAUDIO_H +/* + * $Id: portaudio.h,v 1.1 2005/11/29 21:27:24 joe Exp $ + * PortAudio Portable Real-Time Audio Library + * PortAudio API Header File + * Latest version available at: http://www.portaudio.com/ + * + * Copyright (c) 1999-2002 Ross Bencina and Phil Burk + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files + * (the "Software"), to deal in the Software without restriction, + * including without limitation the rights to use, copy, modify, merge, + * publish, distribute, sublicense, and/or sell copies of the Software, + * and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * Any person wishing to distribute modifications to the Software is + * requested to send the modifications to the original developer so that + * they can be incorporated into the canonical version. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR + * ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF + * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +/** @file + @brief The PortAudio API. +*/ + + +#ifdef __cplusplus +extern "C" +{ +#endif /* __cplusplus */ + + +/** Retrieve the release number of the currently running PortAudio build, + eg 1900. +*/ +int Pa_GetVersion( void ); + + +/** Retrieve a textual description of the current PortAudio build, + eg "PortAudio V19-devel 13 October 2002". +*/ +const char* Pa_GetVersionText( void ); + + +/** Error codes returned by PortAudio functions. + Note that with the exception of paNoError, all PaErrorCodes are negative. +*/ + +typedef int PaError; +typedef enum PaErrorCode +{ + paNoError = 0, + + paNotInitialized = -10000, + paUnanticipatedHostError, + paInvalidChannelCount, + paInvalidSampleRate, + paInvalidDevice, + paInvalidFlag, + paSampleFormatNotSupported, + paBadIODeviceCombination, + paInsufficientMemory, + paBufferTooBig, + paBufferTooSmall, + paNullCallback, + paBadStreamPtr, + paTimedOut, + paInternalError, + paDeviceUnavailable, + paIncompatibleHostApiSpecificStreamInfo, + paStreamIsStopped, + paStreamIsNotStopped, + paInputOverflowed, + paOutputUnderflowed, + paHostApiNotFound, + paInvalidHostApi, + paCanNotReadFromACallbackStream, /**< @todo review error code name */ + paCanNotWriteToACallbackStream, /**< @todo review error code name */ + paCanNotReadFromAnOutputOnlyStream, /**< @todo review error code name */ + paCanNotWriteToAnInputOnlyStream, /**< @todo review error code name */ + paIncompatibleStreamHostApi +} PaErrorCode; + + +/** Translate the supplied PortAudio error code into a human readable + message. +*/ +const char *Pa_GetErrorText( PaError errorCode ); + + +/** Library initialization function - call this before using PortAudio. + This function initialises internal data structures and prepares underlying + host APIs for use. This function MUST be called before using any other + PortAudio API functions. + + If Pa_Initialize() is called multiple times, each successful + call must be matched with a corresponding call to Pa_Terminate(). + Pairs of calls to Pa_Initialize()/Pa_Terminate() may overlap, and are not + required to be fully nested. + + Note that if Pa_Initialize() returns an error code, Pa_Terminate() should + NOT be called. + + @return paNoError if successful, otherwise an error code indicating the cause + of failure. + + @see Pa_Terminate +*/ +PaError Pa_Initialize( void ); + + +/** Library termination function - call this when finished using PortAudio. + This function deallocates all resources allocated by PortAudio since it was + initializied by a call to Pa_Initialize(). In cases where Pa_Initialise() has + been called multiple times, each call must be matched with a corresponding call + to Pa_Terminate(). The final matching call to Pa_Terminate() will automatically + close any PortAudio streams that are still open. + + Pa_Terminate() MUST be called before exiting a program which uses PortAudio. + Failure to do so may result in serious resource leaks, such as audio devices + not being available until the next reboot. + + @return paNoError if successful, otherwise an error code indicating the cause + of failure. + + @see Pa_Initialize +*/ +PaError Pa_Terminate( void ); + + + +/** The type used to refer to audio devices. Values of this type usually + range from 0 to (Pa_DeviceCount-1), and may also take on the PaNoDevice + and paUseHostApiSpecificDeviceSpecification values. + + @see Pa_DeviceCount, paNoDevice, paUseHostApiSpecificDeviceSpecification +*/ +typedef int PaDeviceIndex; + + +/** A special PaDeviceIndex value indicating that no device is available, + or should be used. + + @see PaDeviceIndex +*/ +#define paNoDevice ((PaDeviceIndex)-1) + + +/** A special PaDeviceIndex value indicating that the device(s) to be used + are specified in the host api specific stream info structure. + + @see PaDeviceIndex +*/ +#define paUseHostApiSpecificDeviceSpecification ((PaDeviceIndex)-2) + + +/* Host API enumeration mechanism */ + +/** The type used to enumerate to host APIs at runtime. Values of this type + range from 0 to (Pa_GetHostApiCount()-1). + + @see Pa_GetHostApiCount +*/ +typedef int PaHostApiIndex; + + +/** Retrieve the number of available host APIs. Even if a host API is + available it may have no devices available. + + @return A non-negative value indicating the number of available host APIs + or, a PaErrorCode (which are always negative) if PortAudio is not initialized + or an error is encountered. + + @see PaHostApiIndex +*/ +PaHostApiIndex Pa_GetHostApiCount( void ); + + +/** Retrieve the index of the default host API. The default host API will be + the lowest common denominator host API on the current platform and is + unlikely to provide the best performance. + + @return A non-negative value ranging from 0 to (Pa_GetHostApiCount()-1) + indicating the default host API index or, a PaErrorCode (which are always + negative) if PortAudio is not initialized or an error is encountered. +*/ +PaHostApiIndex Pa_GetDefaultHostApi( void ); + + +/** Unchanging unique identifiers for each supported host API. This type + is used in the PaHostApiInfo structure. The values are guaranteed to be + unique and to never change, thus allowing code to be written that + conditionally uses host API specific extensions. + + New type ids will be allocated when support for a host API reaches + "public alpha" status, prior to that developers should use the + paInDevelopment type id. + + @see PaHostApiInfo +*/ +typedef enum PaHostApiTypeId +{ + paInDevelopment=0, /* use while developing support for a new host API */ + paDirectSound=1, + paMME=2, + paASIO=3, + paSoundManager=4, + paCoreAudio=5, + paOSS=7, + paALSA=8, + paAL=9, + paBeOS=10, + paWDMKS=11, + paJACK=12 +} PaHostApiTypeId; + + +/** A structure containing information about a particular host API. */ + +typedef struct PaHostApiInfo +{ + /** this is struct version 1 */ + int structVersion; + /** The well known unique identifier of this host API @see PaHostApiTypeId */ + PaHostApiTypeId type; + /** A textual description of the host API for display on user interfaces. */ + const char *name; + + /** The number of devices belonging to this host API. This field may be + used in conjunction with Pa_HostApiDeviceIndexToDeviceIndex() to enumerate + all devices for this host API. + @see Pa_HostApiDeviceIndexToDeviceIndex + */ + int deviceCount; + + /** The the default input device for this host API. The value will be a + device index ranging from 0 to (Pa_GetDeviceCount()-1), or paNoDevice + if no default input device is available. + */ + PaDeviceIndex defaultInputDevice; + + /** The the default output device for this host API. The value will be a + device index ranging from 0 to (Pa_GetDeviceCount()-1), or paNoDevice + if no default output device is available. + */ + PaDeviceIndex defaultOutputDevice; + +} PaHostApiInfo; + + +/** Retrieve a pointer to a structure containing information about a specific + host Api. + + @param hostApi A valid host API index ranging from 0 to (Pa_GetHostApiCount()-1) + + @return A pointer to an immutable PaHostApiInfo structure describing + a specific host API. If the hostApi parameter is out of range or an error + is encountered, the function returns NULL. + + The returned structure is owned by the PortAudio implementation and must not + be manipulated or freed. The pointer is only guaranteed to be valid between + calls to Pa_Initialize() and Pa_Terminate(). +*/ +const PaHostApiInfo * Pa_GetHostApiInfo( PaHostApiIndex hostApi ); + + +/** Convert a static host API unique identifier, into a runtime + host API index. + + @param type A unique host API identifier belonging to the PaHostApiTypeId + enumeration. + + @return A valid PaHostApiIndex ranging from 0 to (Pa_GetHostApiCount()-1) or, + a PaErrorCode (which are always negative) if PortAudio is not initialized + or an error is encountered. + + The paHostApiNotFound error code indicates that the host API specified by the + type parameter is not available. + + @see PaHostApiTypeId +*/ +PaHostApiIndex Pa_HostApiTypeIdToHostApiIndex( PaHostApiTypeId type ); + + +/** Convert a host-API-specific device index to standard PortAudio device index. + This function may be used in conjunction with the deviceCount field of + PaHostApiInfo to enumerate all devices for the specified host API. + + @param hostApi A valid host API index ranging from 0 to (Pa_GetHostApiCount()-1) + + @param hostApiDeviceIndex A valid per-host device index in the range + 0 to (Pa_GetHostApiInfo(hostApi)->deviceCount-1) + + @return A non-negative PaDeviceIndex ranging from 0 to (Pa_GetDeviceCount()-1) + or, a PaErrorCode (which are always negative) if PortAudio is not initialized + or an error is encountered. + + A paInvalidHostApi error code indicates that the host API index specified by + the hostApi parameter is out of range. + + A paInvalidDevice error code indicates that the hostApiDeviceIndex parameter + is out of range. + + @see PaHostApiInfo +*/ +PaDeviceIndex Pa_HostApiDeviceIndexToDeviceIndex( PaHostApiIndex hostApi, + int hostApiDeviceIndex ); + + + +/** Structure used to return information about a host error condition. +*/ +typedef struct PaHostErrorInfo{ + PaHostApiTypeId hostApiType; /**< the host API which returned the error code */ + long errorCode; /**< the error code returned */ + const char *errorText; /**< a textual description of the error if available, otherwise a zero-length string */ +}PaHostErrorInfo; + + +/** Return information about the last host error encountered. The error + information returned by Pa_GetLastHostErrorInfo() will never be modified + asyncronously by errors occurring in other PortAudio owned threads + (such as the thread that manages the stream callback.) + + This function is provided as a last resort, primarily to enhance debugging + by providing clients with access to all available error information. + + @return A pointer to an immutable structure constaining information about + the host error. The values in this structure will only be valid if a + PortAudio function has previously returned the paUnanticipatedHostError + error code. +*/ +const PaHostErrorInfo* Pa_GetLastHostErrorInfo( void ); + + + +/* Device enumeration and capabilities */ + +/** Retrieve the number of available devices. The number of available devices + may be zero. + + @return A non-negative value indicating the number of available devices or, + a PaErrorCode (which are always negative) if PortAudio is not initialized + or an error is encountered. +*/ +PaDeviceIndex Pa_GetDeviceCount( void ); + + +/** Retrieve the index of the default input device. The result can be + used in the inputDevice parameter to Pa_OpenStream(). + + @return The default input device index for the default host API, or paNoDevice + if no default input device is available or an error was encountered. +*/ +PaDeviceIndex Pa_GetDefaultInputDevice( void ); + + +/** Retrieve the index of the default output device. The result can be + used in the outputDevice parameter to Pa_OpenStream(). + + @return The default output device index for the defualt host API, or paNoDevice + if no default output device is available or an error was encountered. + + @note + On the PC, the user can specify a default device by + setting an environment variable. For example, to use device #1. +
+ set PA_RECOMMENDED_OUTPUT_DEVICE=1
+
+ The user should first determine the available device ids by using + the supplied application "pa_devs". +*/ +PaDeviceIndex Pa_GetDefaultOutputDevice( void ); + + +/** The type used to represent monotonic time in seconds that can be used + for syncronisation. The type is used for the outTime argument to the + PaStreamCallback and as the result of Pa_GetStreamTime(). + + @see PaStreamCallback, Pa_GetStreamTime +*/ +typedef double PaTime; + + +/** A type used to specify one or more sample formats. Each value indicates + a possible format for sound data passed to and from the stream callback, + Pa_ReadStream and Pa_WriteStream. + + The standard formats paFloat32, paInt16, paInt32, paInt24, paInt8 + and aUInt8 are usually implemented by all implementations. + + The floating point representation (paFloat32) uses +1.0 and -1.0 as the + maximum and minimum respectively. + + paUInt8 is an unsigned 8 bit format where 128 is considered "ground" + + The paNonInterleaved flag indicates that a multichannel buffer is passed + as a set of non-interleaved pointers. + + @see Pa_OpenStream, Pa_OpenDefaultStream, PaDeviceInfo + @see paFloat32, paInt16, paInt32, paInt24, paInt8 + @see paUInt8, paCustomFormat, paNonInterleaved +*/ +typedef unsigned long PaSampleFormat; + + +#define paFloat32 ((PaSampleFormat) 0x00000001) /**< @see PaSampleFormat */ +#define paInt32 ((PaSampleFormat) 0x00000002) /**< @see PaSampleFormat */ +#define paInt24 ((PaSampleFormat) 0x00000004) /**< Packed 24 bit format. @see PaSampleFormat */ +#define paInt16 ((PaSampleFormat) 0x00000008) /**< @see PaSampleFormat */ +#define paInt8 ((PaSampleFormat) 0x00000010) /**< @see PaSampleFormat */ +#define paUInt8 ((PaSampleFormat) 0x00000020) /**< @see PaSampleFormat */ +#define paCustomFormat ((PaSampleFormat) 0x00010000)/**< @see PaSampleFormat */ + +#define paNonInterleaved ((PaSampleFormat) 0x80000000) + +/** A structure providing information and capabilities of PortAudio devices. + Devices may support input, output or both input and output. +*/ +typedef struct PaDeviceInfo +{ + int structVersion; /* this is struct version 2 */ + const char *name; + PaHostApiIndex hostApi; /* note this is a host API index, not a type id*/ + + int maxInputChannels; + int maxOutputChannels; + + /* Default latency values for interactive performance. */ + PaTime defaultLowInputLatency; + PaTime defaultLowOutputLatency; + /* Default latency values for robust non-interactive applications (eg. playing sound files). */ + PaTime defaultHighInputLatency; + PaTime defaultHighOutputLatency; + + double defaultSampleRate; +} PaDeviceInfo; + + +/** Retrieve a pointer to a PaDeviceInfo structure containing information + about the specified device. + @return A pointer to an immutable PaDeviceInfo structure. If the device + parameter is out of range the function returns NULL. + + @param device A valid device index in the range 0 to (Pa_GetDeviceCount()-1) + + @note PortAudio manages the memory referenced by the returned pointer, + the client must not manipulate or free the memory. The pointer is only + guaranteed to be valid between calls to Pa_Initialize() and Pa_Terminate(). + + @see PaDeviceInfo, PaDeviceIndex +*/ +const PaDeviceInfo* Pa_GetDeviceInfo( PaDeviceIndex device ); + + +/** Parameters for one direction (input or output) of a stream. +*/ +typedef struct PaStreamParameters +{ + /** A valid device index in the range 0 to (Pa_GetDeviceCount()-1) + specifying the device to be used or the special constant + paUseHostApiSpecificDeviceSpecification which indicates that the actual + device(s) to use are specified in hostApiSpecificStreamInfo. + This field must not be set to paNoDevice. + */ + PaDeviceIndex device; + + /** The number of channels of sound to be delivered to the + stream callback or accessed by Pa_ReadStream() or Pa_WriteStream(). + It can range from 1 to the value of maxInputChannels in the + PaDeviceInfo record for the device specified by the device parameter. + */ + int channelCount; + + /** The sample format of the buffer provided to the stream callback, + a_ReadStream() or Pa_WriteStream(). It may be any of the formats described + by the PaSampleFormat enumeration. + */ + PaSampleFormat sampleFormat; + + /** The desired latency in seconds. Where practical, implementations should + configure their latency based on these parameters, otherwise they may + choose the closest viable latency instead. Unless the suggested latency + is greater than the absolute upper limit for the device implementations + shouldround the suggestedLatency up to the next practial value - ie to + provide an equal or higher latency than suggestedLatency whereever possibe. + Actual latency values for an open stream may be retrieved using the + inputLatency and outputLatency fields of the PaStreamInfo structure + returned by Pa_GetStreamInfo(). + @see default*Latency in PaDeviceInfo, *Latency in PaStreamInfo + */ + PaTime suggestedLatency; + + /** An optional pointer to a host api specific data structure + containing additional information for device setup and/or stream processing. + hostApiSpecificStreamInfo is never required for correct operation, + if not used it should be set to NULL. + */ + void *hostApiSpecificStreamInfo; + +} PaStreamParameters; + + +/** Return code for Pa_IsFormatSupported indicating success. */ +#define paFormatIsSupported (0) + +/** Determine whether it would be possible to open a stream with the specified + parameters. + + @param inputParameters A structure that describes the input parameters used to + open a stream. The suggestedLatency field is ignored. See PaStreamParameters + for a description of these parameters. inputParameters must be NULL for + output-only streams. + + @param outputParameters A structure that describes the output parameters used + to open a stream. The suggestedLatency field is ignored. See PaStreamParameters + for a description of these parameters. outputParameters must be NULL for + input-only streams. + + @param sampleRate The required sampleRate. For full-duplex streams it is the + sample rate for both input and output + + @return Returns 0 if the format is supported, and an error code indicating why + the format is not supported otherwise. The constant paFormatIsSupported is + provided to compare with the return value for success. + + @see paFormatIsSupported, PaStreamParameters +*/ +PaError Pa_IsFormatSupported( const PaStreamParameters *inputParameters, + const PaStreamParameters *outputParameters, + double sampleRate ); + + + +/* Streaming types and functions */ + + +/** + A single PaStream can provide multiple channels of real-time + streaming audio input and output to a client application. A stream + provides access to audio hardware represented by one or more + PaDevices. Depending on the underlying Host API, it may be possible + to open multiple streams using the same device, however this behavior + is implementation defined. Portable applications should assume that + a PaDevice may be simultaneously used by at most one PaStream. + + Pointers to PaStream objects are passed between PortAudio functions that + operate on streams. + + @see Pa_OpenStream, Pa_OpenDefaultStream, Pa_OpenDefaultStream, Pa_CloseStream, + Pa_StartStream, Pa_StopStream, Pa_AbortStream, Pa_IsStreamActive, + Pa_GetStreamTime, Pa_GetStreamCpuLoad + +*/ +typedef void PaStream; + + +/** Can be passed as the framesPerBuffer parameter to Pa_OpenStream() + or Pa_OpenDefaultStream() to indicate that the stream callback will + accept buffers of any size. +*/ +#define paFramesPerBufferUnspecified (0) + + +/** Flags used to control the behavior of a stream. They are passed as + parameters to Pa_OpenStream or Pa_OpenDefaultStream. Multiple flags may be + ORed together. + + @see Pa_OpenStream, Pa_OpenDefaultStream + @see paNoFlag, paClipOff, paDitherOff, paNeverDropInput, + paPrimeOutputBuffersUsingStreamCallback, paPlatformSpecificFlags +*/ +typedef unsigned long PaStreamFlags; + +/** @see PaStreamFlags */ +#define paNoFlag ((PaStreamFlags) 0) + +/** Disable default clipping of out of range samples. + @see PaStreamFlags +*/ +#define paClipOff ((PaStreamFlags) 0x00000001) + +/** Disable default dithering. + @see PaStreamFlags +*/ +#define paDitherOff ((PaStreamFlags) 0x00000002) + +/** Flag requests that where possible a full duplex stream will not discard + overflowed input samples without calling the stream callback. This flag is + only valid for full duplex callback streams and only when used in combination + with the paFramesPerBufferUnspecified (0) framesPerBuffer parameter. Using + this flag incorrectly results in a paInvalidFlag error being returned from + Pa_OpenStream and Pa_OpenDefaultStream. + + @see PaStreamFlags, paFramesPerBufferUnspecified +*/ +#define paNeverDropInput ((PaStreamFlags) 0x00000004) + +/** Call the stream callback to fill initial output buffers, rather than the + default behavior of priming the buffers with zeros (silence). This flag has + no effect for input-only and blocking read/write streams. + + @see PaStreamFlags +*/ +#define paPrimeOutputBuffersUsingStreamCallback ((PaStreamFlags) 0x00000008) + +/** A mask specifying the platform specific bits. + @see PaStreamFlags +*/ +#define paPlatformSpecificFlags ((PaStreamFlags)0xFFFF0000) + +/** + Timing information for the buffers passed to the stream callback. +*/ +typedef struct PaStreamCallbackTimeInfo{ + PaTime inputBufferAdcTime; + PaTime currentTime; + PaTime outputBufferDacTime; +} PaStreamCallbackTimeInfo; + + +/** + Flag bit constants for the statusFlags to PaStreamCallback. + + @see paInputUnderflow, paInputOverflow, paOutputUnderflow, paOutputOverflow, + paPrimingOutput +*/ +typedef unsigned long PaStreamCallbackFlags; + +/** In a stream opened with paFramesPerBufferUnspecified, indicates that + input data is all silence (zeros) because no real data is available. In a + stream opened without paFramesPerBufferUnspecified, it indicates that one or + more zero samples have been inserted into the input buffer to compensate + for an input underflow. + @see PaStreamCallbackFlags +*/ +#define paInputUnderflow ((PaStreamCallbackFlags) 0x00000001) + +/** In a stream opened with paFramesPerBufferUnspecified, indicates that data + prior to the first sample of the input buffer was discarded due to an + overflow, possibly because the stream callback is using too much CPU time. + Otherwise indicates that data prior to one or more samples in the + input buffer was discarded. + @see PaStreamCallbackFlags +*/ +#define paInputOverflow ((PaStreamCallbackFlags) 0x00000002) + +/** Indicates that output data (or a gap) was inserted, possibly because the + stream callback is using too much CPU time. + @see PaStreamCallbackFlags +*/ +#define paOutputUnderflow ((PaStreamCallbackFlags) 0x00000004) + +/** Indicates that output data will be discarded because no room is available. + @see PaStreamCallbackFlags +*/ +#define paOutputOverflow ((PaStreamCallbackFlags) 0x00000008) + +/** Some of all of the output data will be used to prime the stream, input + data may be zero. + @see PaStreamCallbackFlags +*/ +#define paPrimingOutput ((PaStreamCallbackFlags) 0x00000010) + +/** + Allowable return values for the PaStreamCallback. + @see PaStreamCallback +*/ +typedef enum PaStreamCallbackResult +{ + paContinue=0, + paComplete=1, + paAbort=2 +} PaStreamCallbackResult; + + +/** + Functions of type PaStreamCallback are implemented by PortAudio clients. + They consume, process or generate audio in response to requests from an + active PortAudio stream. + + @param input and @param output are arrays of interleaved samples, + the format, packing and number of channels used by the buffers are + determined by parameters to Pa_OpenStream(). + + @param frameCount The number of sample frames to be processed by + the stream callback. + + @param timeInfo The time in seconds when the first sample of the input + buffer was received at the audio input, the time in seconds when the first + sample of the output buffer will begin being played at the audio output, and + the time in seconds when the stream callback was called. + See also Pa_GetStreamTime() + + @param statusFlags Flags indicating whether input and/or output buffers + have been inserted or will be dropped to overcome underflow or overflow + conditions. + + @param userData The value of a user supplied pointer passed to + Pa_OpenStream() intended for storing synthesis data etc. + + @return + The stream callback should return one of the values in the + PaStreamCallbackResult enumeration. To ensure that the callback continues + to be called, it should return paContinue (0). Either paComplete or paAbort + can be returned to finish stream processing, after either of these values is + returned the callback will not be called again. If paAbort is returned the + stream will finish as soon as possible. If paComplete is returned, the stream + will continue until all buffers generated by the callback have been played. + This may be useful in applications such as soundfile players where a specific + duration of output is required. However, it is not necessary to utilise this + mechanism as Pa_StopStream(), Pa_AbortStream() or Pa_CloseStream() can also + be used to stop the stream. The callback must always fill the entire output + buffer irrespective of its return value. + + @see Pa_OpenStream, Pa_OpenDefaultStream + + @note With the exception of Pa_GetStreamCpuLoad() it is not permissable to call + PortAudio API functions from within the stream callback. +*/ +typedef int PaStreamCallback( + const void *input, void *output, + unsigned long frameCount, + const PaStreamCallbackTimeInfo* timeInfo, + PaStreamCallbackFlags statusFlags, + void *userData ); + + +/** Opens a stream for either input, output or both. + + @param stream The address of a PaStream pointer which will receive + a pointer to the newly opened stream. + + @param inputParameters A structure that describes the input parameters used by + the opened stream. See PaStreamParameters for a description of these parameters. + inputParameters must be NULL for output-only streams. + + @param outputParameters A structure that describes the output parameters used by + the opened stream. See PaStreamParameters for a description of these parameters. + outputParameters must be NULL for input-only streams. + + @param sampleRate The desired sampleRate. For full-duplex streams it is the + sample rate for both input and output + + @param framesPerBuffer The number of frames passed to the stream callback + function, or the preferred block granularity for a blocking read/write stream. + The special value paFramesPerBufferUnspecified (0) may be used to request that + the stream callback will recieve an optimal (and possibly varying) number of + frames based on host requirements and the requested latency settings. + Note: With some host APIs, the use of non-zero framesPerBuffer for a callback + stream may introduce an additional layer of buffering which could introduce + additional latency. PortAudio guarantees that the additional latency + will be kept to the theoretical minimum however, it is strongly recommended + that a non-zero framesPerBuffer value only be used when your algorithm + requires a fixed number of frames per stream callback. + + @param streamFlags Flags which modify the behaviour of the streaming process. + This parameter may contain a combination of flags ORed together. Some flags may + only be relevant to certain buffer formats. + + @param streamCallback A pointer to a client supplied function that is responsible + for processing and filling input and output buffers. If this parameter is NULL + the stream will be opened in 'blocking read/write' mode. In blocking mode, + the client can receive sample data using Pa_ReadStream and write sample data + using Pa_WriteStream, the number of samples that may be read or written + without blocking is returned by Pa_GetStreamReadAvailable and + Pa_GetStreamWriteAvailable respectively. + + @param userData A client supplied pointer which is passed to the stream callback + function. It could for example, contain a pointer to instance data necessary + for processing the audio buffers. This parameter is ignored if streamCallback + is NULL. + + @return + Upon success Pa_OpenStream() returns paNoError and places a pointer to a + valid PaStream in the stream argument. The stream is inactive (stopped). + If a call to Pa_OpenStream() fails, a non-zero error code is returned (see + PaError for possible error codes) and the value of stream is invalid. + + @see PaStreamParameters, PaStreamCallback, Pa_ReadStream, Pa_WriteStream, + Pa_GetStreamReadAvailable, Pa_GetStreamWriteAvailable +*/ +PaError Pa_OpenStream( PaStream** stream, + const PaStreamParameters *inputParameters, + const PaStreamParameters *outputParameters, + double sampleRate, + unsigned long framesPerBuffer, + PaStreamFlags streamFlags, + PaStreamCallback *streamCallback, + void *userData ); + + +/** A simplified version of Pa_OpenStream() that opens the default input + and/or output devices. + + @param stream The address of a PaStream pointer which will receive + a pointer to the newly opened stream. + + @param numInputChannels The number of channels of sound that will be supplied + to the stream callback or returned by Pa_ReadStream. It can range from 1 to + the value of maxInputChannels in the PaDeviceInfo record for the default input + device. If 0 the stream is opened as an output-only stream. + + @param numOutputChannels The number of channels of sound to be delivered to the + stream callback or passed to Pa_WriteStream. It can range from 1 to the value + of maxOutputChannels in the PaDeviceInfo record for the default output dvice. + If 0 the stream is opened as an output-only stream. + + @param sampleFormat The sample format of both the input and output buffers + provided to the callback or passed to and from Pa_ReadStream and Pa_WriteStream. + sampleFormat may be any of the formats described by the PaSampleFormat + enumeration. + + @param sampleRate Same as Pa_OpenStream parameter of the same name. + @param framesPerBuffer Same as Pa_OpenStream parameter of the same name. + @param streamCallback Same as Pa_OpenStream parameter of the same name. + @param userData Same as Pa_OpenStream parameter of the same name. + + @return As for Pa_OpenStream + + @see Pa_OpenStream, PaStreamCallback +*/ +PaError Pa_OpenDefaultStream( PaStream** stream, + int numInputChannels, + int numOutputChannels, + PaSampleFormat sampleFormat, + double sampleRate, + unsigned long framesPerBuffer, + PaStreamCallback *streamCallback, + void *userData ); + + +/** Closes an audio stream. If the audio stream is active it + discards any pending buffers as if Pa_AbortStream() had been called. +*/ +PaError Pa_CloseStream( PaStream *stream ); + + +/** Functions of type PaStreamFinishedCallback are implemented by PortAudio + clients. They can be registered with a stream using the Pa_SetStreamFinishedCallback + function. Once registered they are called when the stream becomes inactive + (ie once a call to Pa_StopStream() will not block). + A stream will become inactive after the stream callback returns non-zero, + or when Pa_StopStream or Pa_AbortStream is called. For a stream providing audio + output, if the stream callback returns paComplete, or Pa_StopStream is called, + the stream finished callback will not be called until all generated sample data + has been played. + + @param userData The userData parameter supplied to Pa_OpenStream() + + @see Pa_SetStreamFinishedCallback +*/ +typedef void PaStreamFinishedCallback( void *userData ); + + +/** Register a stream finished callback function which will be called when the + stream becomes inactive. See the description of PaStreamFinishedCallback for + further details about when the callback will be called. + + @param stream a pointer to a PaStream that is in the stopped state - if the + stream is not stopped, the stream's finished callback will remain unchanged + and an error code will be returned. + + @param streamFinishedCallback a pointer to a function with the same signature + as PaStreamFinishedCallback, that will be called when the stream becomes + inactive. Passing NULL for this parameter will un-register a previously + registered stream finished callback function. + + @return on success returns paNoError, otherwise an error code indicating the cause + of the error. + + @see PaStreamFinishedCallback +*/ +PaError Pa_SetStreamFinishedCallback( PaStream *stream, PaStreamFinishedCallback* streamFinishedCallback ); + + +/** Commences audio processing. +*/ +PaError Pa_StartStream( PaStream *stream ); + + +/** Terminates audio processing. It waits until all pending + audio buffers have been played before it returns. +*/ +PaError Pa_StopStream( PaStream *stream ); + + +/** Terminates audio processing immediately without waiting for pending + buffers to complete. +*/ +PaError Pa_AbortStream( PaStream *stream ); + + +/** Determine whether the stream is stopped. + A stream is considered to be stopped prior to a successful call to + Pa_StartStream and after a successful call to Pa_StopStream or Pa_AbortStream. + If a stream callback returns a value other than paContinue the stream is NOT + considered to be stopped. + + @return Returns one (1) when the stream is stopped, zero (0) when + the stream is running or, a PaErrorCode (which are always negative) if + PortAudio is not initialized or an error is encountered. + + @see Pa_StopStream, Pa_AbortStream, Pa_IsStreamActive +*/ +PaError Pa_IsStreamStopped( PaStream *stream ); + + +/** Determine whether the stream is active. + A stream is active after a successful call to Pa_StartStream(), until it + becomes inactive either as a result of a call to Pa_StopStream() or + Pa_AbortStream(), or as a result of a return value other than paContinue from + the stream callback. In the latter case, the stream is considered inactive + after the last buffer has finished playing. + + @return Returns one (1) when the stream is active (ie playing or recording + audio), zero (0) when not playing or, a PaErrorCode (which are always negative) + if PortAudio is not initialized or an error is encountered. + + @see Pa_StopStream, Pa_AbortStream, Pa_IsStreamStopped +*/ +PaError Pa_IsStreamActive( PaStream *stream ); + + + +/** A structure containing unchanging information about an open stream. + @see Pa_GetStreamInfo +*/ + +typedef struct PaStreamInfo +{ + /** this is struct version 1 */ + int structVersion; + + /** The input latency of the stream in seconds. This value provides the most + accurate estimate of input latency available to the implementation. It may + differ significantly from the suggestedLatency value passed to Pa_OpenStream(). + The value of this field will be zero (0.) for output-only streams. + @see PaTime + */ + PaTime inputLatency; + + /** The output latency of the stream in seconds. This value provides the most + accurate estimate of output latency available to the implementation. It may + differ significantly from the suggestedLatency value passed to Pa_OpenStream(). + The value of this field will be zero (0.) for input-only streams. + @see PaTime + */ + PaTime outputLatency; + + /** The sample rate of the stream in Hertz (samples per second). In cases + where the hardware sample rate is inaccurate and PortAudio is aware of it, + the value of this field may be different from the sampleRate parameter + passed to Pa_OpenStream(). If information about the actual hardware sample + rate is not available, this field will have the same value as the sampleRate + parameter passed to Pa_OpenStream(). + */ + double sampleRate; + +} PaStreamInfo; + + +/** Retrieve a pointer to a PaStreamInfo structure containing information + about the specified stream. + @return A pointer to an immutable PaStreamInfo structure. If the stream + parameter invalid, or an error is encountered, the function returns NULL. + + @param stream A pointer to an open stream previously created with Pa_OpenStream. + + @note PortAudio manages the memory referenced by the returned pointer, + the client must not manipulate or free the memory. The pointer is only + guaranteed to be valid until the specified stream is closed. + + @see PaStreamInfo +*/ +const PaStreamInfo* Pa_GetStreamInfo( PaStream *stream ); + + +/** Determine the current time for the stream according to the same clock used + to generate buffer timestamps. This time may be used for syncronising other + events to the audio stream, for example synchronizing audio to MIDI. + + @return The stream's current time in seconds, or 0 if an error occurred. + + @see PaTime, PaStreamCallback +*/ +PaTime Pa_GetStreamTime( PaStream *stream ); + + +/** Retrieve CPU usage information for the specified stream. + The "CPU Load" is a fraction of total CPU time consumed by a callback stream's + audio processing routines including, but not limited to the client supplied + stream callback. This function does not work with blocking read/write streams. + + This function may be called from the stream callback function or the + application. + + @return + A floating point value, typically between 0.0 and 1.0, where 1.0 indicates + that the stream callback is consuming the maximum number of CPU cycles possible + to maintain real-time operation. A value of 0.5 would imply that PortAudio and + the stream callback was consuming roughly 50% of the available CPU time. The + return value may exceed 1.0. A value of 0.0 will always be returned for a + blocking read/write stream, or if an error occurrs. +*/ +double Pa_GetStreamCpuLoad( PaStream* stream ); + + +/** Read samples from an input stream. The function doesn't return until + the entire buffer has been filled - this may involve waiting for the operating + system to supply the data. + + @param stream A pointer to an open stream previously created with Pa_OpenStream. + + @param buffer A pointer to a buffer of sample frames. The buffer contains + samples in the format specified by the inputParameters->sampleFormat field + used to open the stream, and the number of channels specified by + inputParameters->numChannels. If non-interleaved samples were requested, + buffer is a pointer to the first element of an array of non-interleaved + buffer pointers, one for each channel. + + @param frames The number of frames to be read into buffer. This parameter + is not constrained to a specific range, however high performance applications + will want to match this parameter to the framesPerBuffer parameter used + when opening the stream. + + @return On success PaNoError will be returned, or PaInputOverflowed if input + data was discarded by PortAudio after the previous call and before this call. +*/ +PaError Pa_ReadStream( PaStream* stream, + void *buffer, + unsigned long frames ); + + +/** Write samples to an output stream. This function doesn't return until the + entire buffer has been consumed - this may involve waiting for the operating + system to consume the data. + + @param stream A pointer to an open stream previously created with Pa_OpenStream. + + @param buffer A pointer to a buffer of sample frames. The buffer contains + samples in the format specified by the outputParameters->sampleFormat field + used to open the stream, and the number of channels specified by + outputParameters->numChannels. If non-interleaved samples were requested, + buffer is a pointer to the first element of an array of non-interleaved + buffer pointers, one for each channel. + + @param frames The number of frames to be written from buffer. This parameter + is not constrained to a specific range, however high performance applications + will want to match this parameter to the framesPerBuffer parameter used + when opening the stream. + + @return On success PaNoError will be returned, or paOutputUnderflowed if + additional output data was inserted after the previous call and before this + call. +*/ +PaError Pa_WriteStream( PaStream* stream, + const void *buffer, + unsigned long frames ); + + +/** Retrieve the number of frames that can be read from the stream without + waiting. + + @return Returns a non-negative value representing the maximum number of frames + that can be read from the stream without blocking or busy waiting or, a + PaErrorCode (which are always negative) if PortAudio is not initialized or an + error is encountered. +*/ +signed long Pa_GetStreamReadAvailable( PaStream* stream ); + + +/** Retrieve the number of frames that can be written to the stream without + waiting. + + @return Returns a non-negative value representing the maximum number of frames + that can be written to the stream without blocking or busy waiting or, a + PaErrorCode (which are always negative) if PortAudio is not initialized or an + error is encountered. +*/ +signed long Pa_GetStreamWriteAvailable( PaStream* stream ); + + +/* Miscellaneous utilities */ + + +/** Retrieve the size of a given sample format in bytes. + + @return The size in bytes of a single sample in the specified format, + or paSampleFormatNotSupported if the format is not supported. +*/ +PaError Pa_GetSampleSize( PaSampleFormat format ); + + +/** Put the caller to sleep for at least 'msec' milliseconds. This function is + provided only as a convenience for authors of portable code (such as the tests + and examples in the PortAudio distribution.) + + The function may sleep longer than requested so don't rely on this for accurate + musical timing. +*/ +void Pa_Sleep( long msec ); + + + +#ifdef __cplusplus +} +#endif /* __cplusplus */ +#endif /* PORTAUDIO_H */ diff --git a/lib/ft2/ptt.c b/lib/ft2/ptt.c new file mode 100644 index 000000000..fdda4ff97 --- /dev/null +++ b/lib/ft2/ptt.c @@ -0,0 +1,58 @@ +#include +#include + +int ptt_(int *nport, int *ntx, int *ndtr, int *iptt) +{ + static HANDLE hFile; + static int open=0, nhold=0; + char s[10]; + int i3,i4,i5,i6,i9,i00; + + if(*nport==0) { + *iptt=*ntx; + return(0); + } + + nhold=0; + if(*nport>100) nhold=1; + + if(*ntx && (!open)) { + sprintf(s,"\\\\.\\COM%d",*nport%100); + hFile=CreateFile( + TEXT(s), + GENERIC_WRITE, + 0, + NULL, + OPEN_EXISTING, + FILE_ATTRIBUTE_NORMAL, + NULL + ); + if(hFile==INVALID_HANDLE_VALUE) { + printf("PTT: Cannot open COM port %d.\n",*nport%100); + return(-1); + } + open=1; + } + + if(*ntx && open) { + if(*ndtr) + EscapeCommFunction(hFile,5); //set DTR + else + EscapeCommFunction(hFile,3); //set RTS + *iptt=1; + } + + else { + if(*ndtr) + EscapeCommFunction(hFile,6); //clear DTR + else + EscapeCommFunction(hFile,4); //clear RTS + EscapeCommFunction(hFile,9); //clear BREAK + if(nhold==0) { + i00=CloseHandle(hFile); + open=0; + } + *iptt=0; + } + return(0); +} diff --git a/lib/ft2/ptt_unix.c b/lib/ft2/ptt_unix.c new file mode 100644 index 000000000..b16380608 --- /dev/null +++ b/lib/ft2/ptt_unix.c @@ -0,0 +1,341 @@ +#include +#include +#include +#include +#include +#include +//#include +//#include +//#include +//#include +//#include + +int lp_reset (int fd); +int lp_ptt (int fd, int onoff); + +#ifdef HAVE_SYS_STAT_H +# include +#endif +#if (defined(__unix__) || defined(unix)) && !defined(USG) +# include +#endif + +#include +/* parport functions */ + +int dev_is_parport(int fd); +int ptt_parallel(int fd, int *ntx, int *iptt); +int ptt_serial(int fd, int *ntx, int *iptt); + +int fd=-1; /* Used for both serial and parallel */ + +/* + * ptt_ + * + * generic unix PTT routine called from Fortran + * + * Inputs + * unused Unused, to satisfy old windows calling convention + * ptt_port device name serial or parallel + * ntx pointer to fortran command on or off + * iptt pointer to fortran command status on or off + * Returns - non 0 if error +*/ + +/* Tiny state machine */ +#define STATE_PORT_CLOSED 0 +#define STATE_PORT_OPEN_PARALLEL 1 +#define STATE_PORT_OPEN_SERIAL 2 + +int +ptt_(int *unused, char *ptt_port, int *ntx, int *ndtr, int *iptt) +{ + static int state=0; + char *p; + + /* In the very unlikely event of a NULL pointer, just return. + * Yes, I realise this should not be possible in WSJT. + */ + if (ptt_port == NULL) { + *iptt = *ntx; + return (0); + } + + switch (state) { + case STATE_PORT_CLOSED: + + /* Remove trailing ' ' */ + if ((p = strchr(ptt_port, ' ')) != NULL) + *p = '\0'; + + /* If all that is left is a '\0' then also just return */ + if (*ptt_port == '\0') { + *iptt = *ntx; + return(0); + } + + if ((fd = open(ptt_port, O_RDWR|O_NONBLOCK)) < 0) { + fprintf(stderr, "Can't open %s.\n", ptt_port); + return (1); + } + + if (dev_is_parport(fd)) { + state = STATE_PORT_OPEN_PARALLEL; + lp_reset(fd); + ptt_parallel(fd, ntx, iptt); + } else { + state = STATE_PORT_OPEN_SERIAL; + ptt_serial(fd, ntx, iptt); + } + break; + + case STATE_PORT_OPEN_PARALLEL: + ptt_parallel(fd, ntx, iptt); + break; + + case STATE_PORT_OPEN_SERIAL: + ptt_serial(fd, ntx, iptt); + break; + + default: + close(fd); + fd = -1; + state = STATE_PORT_CLOSED; + break; + } + return(0); +} + +/* + * ptt_serial + * + * generic serial unix PTT routine called indirectly from Fortran + * + * fd - already opened file descriptor + * ntx - pointer to fortran command on or off + * iptt - pointer to fortran command status on or off + */ + +int +ptt_serial(int fd, int *ntx, int *iptt) +{ + int control = TIOCM_RTS | TIOCM_DTR; + + if(*ntx) { + ioctl(fd, TIOCMBIS, &control); /* Set DTR and RTS */ + *iptt = 1; + } else { + ioctl(fd, TIOCMBIC, &control); + *iptt = 0; + } + return(0); +} + + +/* parport functions */ + +/* + * dev_is_parport(fd): + * + * inputs - Already open fd + * output - 1 if parallel port, 0 if not + * side effects - Unfortunately, this is platform specific. + */ + +#if defined(HAVE_LINUX_PPDEV_H) /* Linux (ppdev) */ + +int +dev_is_parport(int fd) +{ + struct stat st; + int m; + + if ((fstat(fd, &st) == -1) || + ((st.st_mode & S_IFMT) != S_IFCHR) || + (ioctl(fd, PPGETMODE, &m) == -1)) + return(0); + + return(1); +} + +#elif defined(HAVE_DEV_PPBUS_PPI_H) /* FreeBSD (ppbus/ppi) */ + +int +dev_is_parport(int fd) +{ + struct stat st; + unsigned char c; + + if ((fstat(fd, &st) == -1) || + ((st.st_mode & S_IFMT) != S_IFCHR) || + (ioctl(fd, PPISSTATUS, &c) == -1)) + return(0); + + return(1); +} + +#else /* Fallback (nothing) */ + +int +dev_is_parport(int fd) +{ + return(0); +} + +#endif +/* Linux wrapper around PPFCONTROL */ +#ifdef HAVE_LINUX_PPDEV_H +static void +parport_control (int fd, unsigned char controlbits, int values) +{ + struct ppdev_frob_struct frob; + frob.mask = controlbits; + frob.val = values; + + if (ioctl (fd, PPFCONTROL, &frob) == -1) + { + fprintf(stderr, "Parallel port PPFCONTROL"); + exit (1); + } +} +#endif + +/* FreeBSD wrapper around PPISCTRL */ +#ifdef HAVE_DEV_PPBUS_PPI_H +static void +parport_control (int fd, unsigned char controlbits, int values) +{ + unsigned char val; + + if (ioctl (fd, PPIGCTRL, &val) == -1) + { + fprintf(stderr, "Parallel port PPIGCTRL"); + exit (1); + } + + val &= ~controlbits; + val |= values; + + if (ioctl (fd, PPISCTRL, &val) == -1) + { + fprintf(stderr, "Parallel port PPISCTRL"); + exit (1); + } +} +#endif + +/* Initialise a parallel port, given open fd */ +int +lp_init (int fd) +{ +#ifdef HAVE_LINUX_PPDEV_H + int mode; +#endif + +#ifdef HAVE_LINUX_PPDEV_H + mode = PARPORT_MODE_PCSPP; + + if (ioctl (fd, PPSETMODE, &mode) == -1) + { + fprintf(stderr, "Setting parallel port mode"); + close (fd); + return(-1); + } + + if (ioctl (fd, PPEXCL, NULL) == -1) + { + fprintf(stderr, "Parallel port is already in use.\n"); + close (fd); + return(-1); + } + if (ioctl (fd, PPCLAIM, NULL) == -1) + { + fprintf(stderr, "Claiming parallel port.\n"); + fprintf(stderr, "HINT: did you unload the lp kernel module?"); + close (fd); + return(-1); + } + + /* Enable CW & PTT - /STROBE bit (pin 1) */ + parport_control (fd, PARPORT_CONTROL_STROBE, PARPORT_CONTROL_STROBE); +#endif +#ifdef HAVE_DEV_PPBUS_PPI_H + parport_control (fd, STROBE, STROBE); +#endif + lp_reset (fd); + return(0); +} + +/* release ppdev and close port */ +int +lp_free (int fd) +{ +#ifdef HAVE_LINUX_PPDEV_H + lp_reset (fd); + + /* Disable CW & PTT - /STROBE bit (pin 1) */ + parport_control (fd, PARPORT_CONTROL_STROBE, 0); + + ioctl (fd, PPRELEASE); +#endif +#ifdef HAVE_DEV_PPBUS_PPI_H + /* Disable CW & PTT - /STROBE bit (pin 1) */ + parport_control (fd, STROBE, 0); +#endif + close (fd); + return(0); +} + +/* set to a known state */ +int +lp_reset (int fd) +{ +#if defined (HAVE_LINUX_PPDEV_H) || defined (HAVE_DEV_PPBUS_PPI_H) + lp_ptt (fd, 0); +#endif + return(0); +} + +/* SSB PTT keying - /INIT bit (pin 16) (inverted) */ +int +lp_ptt (int fd, int onoff) +{ +#ifdef HAVE_LINUX_PPDEV_H + if (onoff == 1) + parport_control (fd, PARPORT_CONTROL_INIT, + PARPORT_CONTROL_INIT); + else + parport_control (fd, PARPORT_CONTROL_INIT, 0); +#endif +#ifdef HAVE_DEV_PPBUS_PPI_H + if (onoff == 1) + parport_control (fd, nINIT, + nINIT); + else + parport_control (fd, nINIT, 0); +#endif + return(0); +} + +/* + * ptt_parallel + * + * generic parallel unix PTT routine called indirectly from Fortran + * + * fd - already opened file descriptor + * ntx - pointer to fortran command on or off + * iptt - pointer to fortran command status on or off + */ + +int +ptt_parallel(int fd, int *ntx, int *iptt) +{ + if(*ntx) { + lp_ptt(fd, 1); + *iptt=1; + } else { + lp_ptt(fd, 0); + *iptt=0; + } + return(0); +} diff --git a/lib/ft4/clockit.f90 b/lib/ft4/clockit.f90 new file mode 100644 index 000000000..eb3e7d8b1 --- /dev/null +++ b/lib/ft4/clockit.f90 @@ -0,0 +1,111 @@ +subroutine clockit(dname,k) + +! Times procedure number n between a call with k=0 (tstart) and with +! k=1 (tstop). Accumulates sums of these times in array ut (user time). +! Also traces all calls (for debugging purposes) if limtrace.gt.0 + + character*8 dname,name(50),space,ename + character*16 sname + character*512 data_dir,fname + logical first,on(50) + real ut(50),ut0(50),dut(50),tt(2) + integer ncall(50),nlevel(50),nparent(50) + integer onlevel(0:10) + data first/.true./,eps/0.000001/,ntrace/0/ + data level/0/,nmax/0/,space/' '/ + data limtrace/0/,lu/29/,ntimer/1/ +! data limtrace/1000000/,lu/29/,ntimer/1/ + save + + if(ntimer.eq.0) return + if(lu.lt.1) lu=6 + if(k.gt.1) go to 40 !Check for "all done" (k>1) + onlevel(0)=0 + + do n=1,nmax !Check for existing name + if(name(n).eq.dname) go to 20 + enddo + + nmax=nmax+1 !This is a new one + n=nmax + ncall(n)=0 + on(n)=.false. + ut(n)=eps + name(n)=dname + +20 if(k.eq.0) then !Get start times (k=0) + if(on(n)) print*,'Error in timer: ',dname,' already on.' + level=level+1 !Increment the level + on(n)=.true. + ut0(n)=etime(tt) + ncall(n)=ncall(n)+1 + if(ncall(n).gt.1.and.nlevel(n).ne.level) then + nlevel(n)=-1 + else + nlevel(n)=level + endif + nparent(n)=onlevel(level-1) + onlevel(level)=n + + else if(k.eq.1) then !Get stop times and accumulate sums. (k=1) + if(on(n)) then + on(n)=.false. + ut1=etime(tt) + ut(n)=ut(n)+ut1-ut0(n) + endif + level=level-1 + endif + + ntrace=ntrace+1 + if(ntrace.lt.limtrace) write(28,1020) ntrace,dname,k,level,nparent(n) +1020 format(i8,': ',a8,3i5) + return + +! Write out the timer statistics + +40 open(lu,file=trim(fname),status='unknown') + write(lu,1040) +1040 format(/' name time frac dtime', & + ' dfrac calls level parent'/73('-')) + + if(k.gt.100) then + ndiv=k-100 + do i=1,nmax + ncall(i)=ncall(i)/ndiv + ut(i)=ut(i)/ndiv + enddo + endif + + total=ut(1) + sum=0. + sumf=0. + do i=1,nmax + dut(i)=ut(i) + do j=i,nmax + if(nparent(j).eq.i) dut(i)=dut(i)-ut(j) + enddo + utf=ut(i)/total + dutf=dut(i)/total + sum=sum+dut(i) + sumf=sumf+dutf + kk=nlevel(i) + sname=space(1:kk)//name(i)//space(1:8-kk) + ename=space + if(i.ge.2) ename=name(nparent(i)) + write(lu,1060) float(i),sname,ut(i),utf,dut(i),dutf, & + ncall(i),nlevel(i),ename +1060 format(f4.0,a16,2(f10.2,f6.2),i7,i5,2x,a8) + enddo + + write(lu,1070) sum,sumf +1070 format(/36x,f10.2,f6.2) + close(lu) + return + + entry clockit2(data_dir) + l1=index(data_dir,char(0))-1 + if(l1.ge.1) data_dir(l1+1:l1+1)='/' + fname=data_dir(1:l1+1)//'clockit.out' + return + +end subroutine clockit diff --git a/lib/ft4/ft4_decode.f90 b/lib/ft4/ft4_decode.f90 new file mode 100644 index 000000000..63cc6753e --- /dev/null +++ b/lib/ft4/ft4_decode.f90 @@ -0,0 +1,486 @@ +subroutine ft4_decode(cdatetime0,tbuf,nfa,nfb,nQSOProgress,ncontest,nfqso, & + iwave,ndecodes,mycall,hiscall,cqstr,line,data_dir) + + use packjt77 + include 'ft4_params.f90' + parameter (NSS=NSPS/NDOWN) + + character message*37,msgsent*37,msg0*37 + character c77*77 + character*61 line,linex(100) + character*37 decodes(100) + character*512 data_dir,fname + character*17 cdatetime0 + character*12 mycall,hiscall + character*12 mycall0,hiscall0 + character*6 hhmmss + character*4 cqstr,cqstr0 + + complex cd2(0:NMAX/NDOWN-1) !Complex waveform + complex cb(0:NMAX/NDOWN-1) + complex cd(0:NN*NSS-1) !Complex waveform + complex ctwk(4*NSS),ctwk2(4*NSS) + complex csymb(NSS) + complex cs(0:3,NN) + real s4(0:3,NN) + + real bmeta(2*NN),bmetb(2*NN),bmetc(2*NN) + real a(5) + real llr(2*ND),llra(2*ND),llrb(2*ND),llrc(2*ND),llrd(2*ND) + real s2(0:255) + real candidate(3,100) + real savg(NH1),sbase(NH1) + + integer apbits(2*ND) + integer apmy_ru(28),aphis_fd(28) + integer icos4a(0:3),icos4b(0:3),icos4c(0:3),icos4d(0:3) + integer*2 iwave(NMAX) !Raw received data + integer*1 message77(77),rvec(77),apmask(2*ND),cw(2*ND) + integer*1 hbits(2*NN) + integer graymap(0:3) + integer ip(1) + integer nappasses(0:5) ! # of decoding passes for QSO States 0-5 + integer naptypes(0:5,4) ! nQSOProgress, decoding pass + integer mcq(29) + integer mrrr(19),m73(19),mrr73(19) + + logical nohiscall,unpk77_success + logical one(0:255,0:7) ! 256 4-symbol sequences, 8 bits + logical first + + data icos4a/0,1,3,2/ + data icos4b/1,0,2,3/ + data icos4c/2,3,1,0/ + data icos4d/3,2,0,1/ + data graymap/0,1,3,2/ + data msg0/' '/ + data first/.true./ + data mcq/0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0/ + data mrrr/0,1,1,1,1,1,1,0,1,0,0,1,0,0,1,0,0,0,1/ + data m73/0,1,1,1,1,1,1,0,1,0,0,1,0,1,0,0,0,0,1/ + data mrr73/0,1,1,1,1,1,1,0,0,1,1,1,0,1,0,1,0,0,1/ + data rvec/0,1,0,0,1,0,1,0,0,1,0,1,1,1,1,0,1,0,0,0,1,0,0,1,1,0,1,1,0, & + 1,0,0,1,0,1,1,0,0,0,0,1,0,0,0,1,0,1,0,0,1,1,1,1,0,0,1,0,1, & + 0,1,0,1,0,1,1,0,1,1,1,1,1,0,0,0,1,0,1/ + save fs,dt,tt,txt,twopi,h,one,first,linex,apbits,nappasses,naptypes, & + mycall0,hiscall0,msg0,cqstr0 + + call clockit('ft4_deco',0) + hhmmss=cdatetime0(8:13) + + if(first) then + fs=12000.0/NDOWN !Sample rate after downsampling + dt=1/fs !Sample interval after downsample (s) + tt=NSPS*dt !Duration of "itone" symbols (s) + txt=NZ*dt !Transmission length (s) without ramp up/down + twopi=8.0*atan(1.0) + h=1.0 + one=.false. + do i=0,255 + do j=0,7 + if(iand(i,2**j).ne.0) one(i,j)=.true. + enddo + enddo + + mrrr=2*mod(mrrr+rvec(59:77),2)-1 + m73=2*mod(m73+rvec(59:77),2)-1 + mrr73=2*mod(mrr73+rvec(59:77),2)-1 + nappasses(0)=2 + nappasses(1)=2 + nappasses(2)=2 + nappasses(3)=2 + nappasses(4)=2 + nappasses(5)=3 + +! iaptype +!------------------------ +! 1 CQ ??? ??? (29 ap bits) +! 2 MyCall ??? ??? (29 ap bits) +! 3 MyCall DxCall ??? (58 ap bits) +! 4 MyCall DxCall RRR (77 ap bits) +! 5 MyCall DxCall 73 (77 ap bits) +! 6 MyCall DxCall RR73 (77 ap bits) +!******** + naptypes(0,1:4)=(/1,2,0,0/) ! Tx6 selected (CQ) + naptypes(1,1:4)=(/2,3,0,0/) ! Tx1 + naptypes(2,1:4)=(/2,3,0,0/) ! Tx2 + naptypes(3,1:4)=(/3,6,0,0/) ! Tx3 + naptypes(4,1:4)=(/3,6,0,0/) ! Tx4 + naptypes(5,1:4)=(/3,1,2,0/) ! Tx5 + + mycall0='' + hiscall0='' + cqstr0='' + first=.false. + endif + + if(cqstr.ne.cqstr0) then + i0=index(cqstr,' ') + if(i0.le.1) then + message='CQ A1AA AA01' + else + message='CQ '//cqstr(1:i0-1)//' A1AA AA01' + endif + i3=-1 + n3=-1 + call pack77(message,i3,n3,c77) + call unpack77(c77,1,msgsent,unpk77_success) + read(c77,'(29i1)') mcq + mcq=2*mod(mcq+rvec(1:29),2)-1 + cqstr0=cqstr + endif + + l1=index(mycall,char(0)) + if(l1.ne.0) mycall(l1:)=" " + l1=index(hiscall,char(0)) + if(l1.ne.0) hiscall(l1:)=" " + if(mycall.ne.mycall0 .or. hiscall.ne.hiscall0) then + apbits=0 + apbits(1)=99 + apbits(30)=99 + apmy_ru=0 + aphis_fd=0 + + if(len(trim(mycall)) .lt. 3) go to 10 + + nohiscall=.false. + hiscall0=hiscall + if(len(trim(hiscall0)).lt.3) then + hiscall0=mycall ! use mycall for dummy hiscall - mycall won't be hashed. + nohiscall=.true. + endif + message=trim(mycall)//' '//trim(hiscall0)//' RR73' + i3=-1 + n3=-1 + call pack77(message,i3,n3,c77) + call unpack77(c77,1,msgsent,unpk77_success) + if(i3.ne.1 .or. (message.ne.msgsent) .or. .not.unpk77_success) go to 10 + read(c77,'(77i1)') message77 + apmy_ru=2*mod(message77(1:28)+rvec(2:29),2)-1 + aphis_fd=2*mod(message77(30:57)+rvec(29:56),2)-1 + message77=mod(message77+rvec,2) + call encode174_91(message77,cw) + apbits=2*cw-1 + if(nohiscall) apbits(30)=99 + +10 continue + mycall0=mycall + hiscall0=hiscall + endif + candidate=0.0 + ncand=0 + syncmin=1.2 + maxcand=100 + + fa=nfa + fb=nfb + call clockit('getcand4',0) + call getcandidates4(iwave,fa,fb,syncmin,nfqso,maxcand,savg,candidate, & + ncand,sbase) + call clockit('getcand4',1) + + ndecodes=0 + do icand=1,ncand + f0=candidate(1,icand) + snr=candidate(3,icand)-1.0 + if( f0.le.10.0 .or. f0.ge.4990.0 ) cycle + call clockit('ft4_down',0) + call ft4_downsample(iwave,f0,cd2) !Downsample from 512 to 32 Sa/Symbol + call clockit('ft4_down',1) + + sum2=sum(cd2*conjg(cd2))/(real(NMAX)/real(NDOWN)) + if(sum2.gt.0.0) cd2=cd2/sqrt(sum2) +! Sample rate is now 12000/16 = 750 samples/second + do isync=1,2 + if(isync.eq.1) then + idfmin=-12 + idfmax=12 + idfstp=3 + ibmin=0 + ibmax=216 !Max DT = 216/750 = 0.288 s + ibstp=4 + else + idfmin=idfbest-4 + idfmax=idfbest+4 + idfstp=1 + ibmin=max(0,ibest-5) + ibmax=min(ibest+5,NMAX/NDOWN-1) + ibstp=1 + endif + ibest=-1 + smax=-99. + idfbest=0 + do idf=idfmin,idfmax,idfstp + a=0. + a(1)=real(idf) + ctwk=1. + call clockit('twkfreq1',0) + call twkfreq1(ctwk,4*NSS,fs,a,ctwk2) + call clockit('twkfreq1',1) + + call clockit('sync4d ',0) + do istart=ibmin,ibmax,ibstp + call sync4d(cd2,istart,ctwk2,1,sync,sync2) !Find sync power + if(sync.gt.smax) then + smax=sync + ibest=istart + idfbest=idf + endif + enddo + call clockit('sync4d ',1) + + enddo + enddo + f0=f0+real(idfbest) + if( f0.le.10.0 .or. f0.ge.4990.0 ) cycle + + call clockit('ft4down ',0) + call ft4_downsample(iwave,f0,cb) !Final downsample with corrected f0 + call clockit('ft4down ',1) + sum2=sum(abs(cb)**2)/(real(NSS)*NN) + if(sum2.gt.0.0) cb=cb/sqrt(sum2) + cd=cb(ibest:ibest+NN*NSS-1) + call clockit('four2a ',0) + do k=1,NN + i1=(k-1)*NSS + csymb=cd(i1:i1+NSS-1) + call four2a(csymb,NSS,1,-1,1) + cs(0:3,k)=csymb(1:4) + s4(0:3,k)=abs(csymb(1:4)) + enddo + call clockit('four2a ',1) + +! Sync quality check + is1=0 + is2=0 + is3=0 + is4=0 + do k=1,4 + ip=maxloc(s4(:,k)) + if(icos4a(k-1).eq.(ip(1)-1)) is1=is1+1 + ip=maxloc(s4(:,k+33)) + if(icos4b(k-1).eq.(ip(1)-1)) is2=is2+1 + ip=maxloc(s4(:,k+66)) + if(icos4c(k-1).eq.(ip(1)-1)) is3=is3+1 + ip=maxloc(s4(:,k+99)) + if(icos4d(k-1).eq.(ip(1)-1)) is4=is4+1 + enddo + nsync=is1+is2+is3+is4 !Number of hard sync errors, 0-16 + if(smax .lt. 0.7 .or. nsync .lt. 8) cycle + + do nseq=1,3 !Try coherent sequences of 1, 2, and 4 symbols + if(nseq.eq.1) nsym=1 + if(nseq.eq.2) nsym=2 + if(nseq.eq.3) nsym=4 + nt=2**(2*nsym) + do ks=1,NN-nsym+1,nsym !87+16=103 symbols. + amax=-1.0 + do i=0,nt-1 + i1=i/64 + i2=iand(i,63)/16 + i3=iand(i,15)/4 + i4=iand(i,3) + if(nsym.eq.1) then + s2(i)=abs(cs(graymap(i4),ks)) + elseif(nsym.eq.2) then + s2(i)=abs(cs(graymap(i3),ks)+cs(graymap(i4),ks+1)) + elseif(nsym.eq.4) then + s2(i)=abs(cs(graymap(i1),ks ) + & + cs(graymap(i2),ks+1) + & + cs(graymap(i3),ks+2) + & + cs(graymap(i4),ks+3) & + ) + else + print*,"Error - nsym must be 1, 2, or 4." + endif + enddo + ipt=1+(ks-1)*2 + if(nsym.eq.1) ibmax=1 + if(nsym.eq.2) ibmax=3 + if(nsym.eq.4) ibmax=7 + do ib=0,ibmax + bm=maxval(s2(0:nt-1),one(0:nt-1,ibmax-ib)) - & + maxval(s2(0:nt-1),.not.one(0:nt-1,ibmax-ib)) + if(ipt+ib.gt.2*NN) cycle + if(nsym.eq.1) then + bmeta(ipt+ib)=bm + elseif(nsym.eq.2) then + bmetb(ipt+ib)=bm + elseif(nsym.eq.4) then + bmetc(ipt+ib)=bm + endif + enddo + enddo + enddo + + bmetb(205:206)=bmeta(205:206) + bmetc(201:204)=bmetb(201:204) + bmetc(205:206)=bmeta(205:206) + + call clockit('normaliz',0) + call normalizebmet(bmeta,2*NN) + call normalizebmet(bmetb,2*NN) + call normalizebmet(bmetc,2*NN) + call clockit('normaliz',1) + + hbits=0 + where(bmeta.ge.0) hbits=1 + ns1=count(hbits( 1: 8).eq.(/0,0,0,1,1,0,1,1/)) + ns2=count(hbits( 67: 74).eq.(/0,1,0,0,1,1,1,0/)) + ns3=count(hbits(133:140).eq.(/1,1,1,0,0,1,0,0/)) + ns4=count(hbits(199:206).eq.(/1,0,1,1,0,0,0,1/)) + nsync_qual=ns1+ns2+ns3+ns4 + if(nsync_qual.lt. 20) cycle + + scalefac=2.83 + llra( 1: 58)=bmeta( 9: 66) + llra( 59:116)=bmeta( 75:132) + llra(117:174)=bmeta(141:198) + llra=scalefac*llra + llrb( 1: 58)=bmetb( 9: 66) + llrb( 59:116)=bmetb( 75:132) + llrb(117:174)=bmetb(141:198) + llrb=scalefac*llrb + llrc( 1: 58)=bmetc( 9: 66) + llrc( 59:116)=bmetc( 75:132) + llrc(117:174)=bmetc(141:198) + llrc=scalefac*llrc + + apmag=maxval(abs(llra))*1.1 + npasses=3+nappasses(nQSOProgress) + if(ncontest.ge.5) npasses=3 ! Don't support Fox and Hound + do ipass=1,npasses + if(ipass.eq.1) llr=llra + if(ipass.eq.2) llr=llrb + if(ipass.eq.3) llr=llrc + if(ipass.le.3) then + apmask=0 + iaptype=0 + endif + + if(ipass .gt. 3) then + llrd=llrc + iaptype=naptypes(nQSOProgress,ipass-3) + +! ncontest=0 : NONE +! 1 : NA_VHF +! 2 : EU_VHF +! 3 : FIELD DAY +! 4 : RTTY +! 5 : FOX +! 6 : HOUND +! +! Conditions that cause us to bail out of AP decoding + napwid=50 + if(ncontest.le.4 .and. iaptype.ge.3 .and. (abs(f0-nfqso).gt.napwid) ) cycle + if(iaptype.ge.2 .and. apbits(1).gt.1) cycle ! No, or nonstandard, mycall + if(iaptype.ge.3 .and. apbits(30).gt.1) cycle ! No, or nonstandard, dxcall + + if(iaptype.eq.1) then ! CQ or CQ TEST or CQ FD or CQ RU or CQ SCC + apmask=0 + apmask(1:29)=1 + llrd(1:29)=apmag*mcq(1:29) + endif + + if(iaptype.eq.2) then ! MyCall,???,??? + apmask=0 + if(ncontest.eq.0.or.ncontest.eq.1) then + apmask(1:29)=1 + llrd(1:29)=apmag*apbits(1:29) + else if(ncontest.eq.2) then + apmask(1:28)=1 + llrd(1:28)=apmag*apbits(1:28) + else if(ncontest.eq.3) then + apmask(1:28)=1 + llrd(1:28)=apmag*apbits(1:28) + else if(ncontest.eq.4) then + apmask(2:29)=1 + llrd(2:29)=apmag*apmy_ru(1:28) + endif + endif + + if(iaptype.eq.3) then ! MyCall,DxCall,??? + apmask=0 + if(ncontest.eq.0.or.ncontest.eq.1.or.ncontest.eq.2) then + apmask(1:58)=1 + llrd(1:58)=apmag*apbits(1:58) + else if(ncontest.eq.3) then ! Field Day + apmask(1:56)=1 + llrd(1:28)=apmag*apbits(1:28) + llrd(29:56)=apmag*aphis_fd(1:28) + else if(ncontest.eq.4) then ! RTTY RU + apmask(2:57)=1 + llrd(2:29)=apmag*apmy_ru(1:28) + llrd(30:57)=apmag*apbits(30:57) + endif + endif + + if(iaptype.eq.4 .or. iaptype.eq.5 .or. iaptype.eq.6) then + apmask=0 + if(ncontest.le.4) then + apmask(1:91)=1 ! mycall, hiscall, RRR|73|RR73 + if(iaptype.eq.6) llrd(1:91)=apmag*apbits(1:91) + endif + endif + + llr=llrd + endif + max_iterations=40 + message77=0 + call clockit('bpdecode',0) + call bpdecode174_91(llr,apmask,max_iterations,message77, & + cw,nharderror,niterations) + call clockit('bpdecode',1) + if(sum(message77).eq.0) cycle + if( nharderror.ge.0 ) then + message77=mod(message77+rvec,2) ! remove rvec scrambling + write(c77,'(77i1)') message77(1:77) + call unpack77(c77,1,message,unpk77_success) + idupe=0 + do i=1,ndecodes + if(decodes(i).eq.message) idupe=1 + enddo + if(ibest.le.10 .and. message.eq.msg0) idupe=1 !Already decoded + if(idupe.eq.1) exit + ndecodes=ndecodes+1 + decodes(ndecodes)=message + if(snr.gt.0.0) then + xsnr=10*log10(snr)-14.0 + else + xsnr=-20.0 + endif + nsnr=nint(max(-20.0,xsnr)) + freq=f0 + tsig=mod(tbuf + ibest/750.0,100.0) + + write(line,1000) hhmmss,nsnr,tsig,nint(freq),message +1000 format(a6,i4,f5.1,i5,' + ',1x,a37) + l1=index(data_dir,char(0))-1 + if(l1.ge.1) data_dir(l1+1:l1+1)="/" + fname=data_dir(1:l1+1)//'all_ft4.txt' + open(24,file=trim(fname),status='unknown',position='append') + write(24,1002) cdatetime0,nsnr,tsig,nint(freq),message, & + nharderror,nsync_qual,ipass,niterations,iaptype + if(hhmmss.eq.' ') write(*,1002) cdatetime0,nsnr, & + tsig,nint(freq),message,nharderror,nsync_qual,ipass, & + niterations,iaptype +1002 format(a17,i4,f5.1,i5,' Rx ',a37,5i5) + close(24) + linex(ndecodes)=line + if(ibest.ge.ibmax-15) msg0=message !Possible dupe candidate + + exit + + endif + enddo !Sequence estimation + enddo !Candidate list + call clockit('ft4_deco',1) + call clockit2(data_dir) + call clockit('ft4_deco',101) + return + + entry get_ft4msg(idecode,line) + line=linex(idecode) + return + +end subroutine ft4_decode diff --git a/lib/ft4/ft4_downsample.f90 b/lib/ft4/ft4_downsample.f90 new file mode 100644 index 000000000..ca5991346 --- /dev/null +++ b/lib/ft4/ft4_downsample.f90 @@ -0,0 +1,49 @@ +subroutine ft4_downsample(iwave,f0,c) + +! Input: i*2 data in iwave() at sample rate 12000 Hz +! Output: Complex data in c(), sampled at 1200 Hz + + include 'ft4_params.f90' + parameter (NFFT2=NMAX/16) + integer*2 iwave(NMAX) + complex c(0:NMAX/NDOWN-1) + complex c1(0:NFFT2-1) + complex cx(0:NMAX/2) + real x(NMAX), window(0:NFFT2-1) + equivalence (x,cx) + logical first + data first/.true./ + save first,window + + df=12000.0/NMAX + baud=12000.0/NSPS + if(first) then + bw_transition = 0.5*baud + bw_flat = 4*baud + iwt = bw_transition / df + iwf = bw_flat / df + pi=4.0*atan(1.0) + window(0:iwt-1) = 0.5*(1+cos(pi*(/(i,i=iwt-1,0,-1)/)/iwt)) + window(iwt:iwt+iwf-1)=1.0 + window(iwt+iwf:2*iwt+iwf-1) = 0.5*(1+cos(pi*(/(i,i=0,iwt-1)/)/iwt)) + window(2*iwt+iwf:)=0.0 + iws = baud / df + window=cshift(window,iws) + first=.false. + endif + + x=iwave + call four2a(x,NMAX,1,-1,0) !r2c FFT to freq domain + i0=nint(f0/df) + c1=0. + c1(0)=cx(i0) + do i=1,NFFT2/2 + if(i0+i.le.NMAX/2) c1(i)=cx(i0+i) + if(i0-i.ge.0) c1(NFFT2-i)=cx(i0-i) + enddo + c1=c1*window/NFFT2 + call four2a(c1,NFFT2,1,1,1) !c2c FFT back to time domain + c=c1(0:NMAX/NDOWN-1) + + return +end subroutine ft4_downsample diff --git a/lib/ft4/ft4_params.f90 b/lib/ft4/ft4_params.f90 new file mode 100644 index 000000000..02d9de655 --- /dev/null +++ b/lib/ft4/ft4_params.f90 @@ -0,0 +1,16 @@ +! FT4 +! LDPC(174,91) code, four 4x4 Costas arrays for Sync + +parameter (KK=91) !Information bits (77 + CRC14) +parameter (ND=87) !Data symbols +parameter (NS=16) !Sync symbols +parameter (NN=NS+ND) !Sync and data symbols (103) +parameter (NN2=NS+ND+2) !Total channel symbols (105) +parameter (NSPS=512) !Samples per symbol at 12000 S/s +parameter (NZ=NSPS*NN) !Sync and Data samples (52736) +parameter (NZ2=NSPS*NN2) !Total samples in shaped waveform (53760) +parameter (NMAX=5*12000) !Samples in iwave (60,000) +parameter (NFFT1=2048, NH1=NFFT1/2) !Length of FFTs for symbol spectra +parameter (NSTEP=NSPS) !Coarse time-sync step size +parameter (NHSYM=(NMAX-NFFT1)/NSTEP) !Number of symbol spectra (1/4-sym steps) +parameter (NDOWN=16) !Downsample factor diff --git a/lib/ft4/ft4d.f90 b/lib/ft4/ft4d.f90 new file mode 100644 index 000000000..f8e6dab71 --- /dev/null +++ b/lib/ft4/ft4d.f90 @@ -0,0 +1,83 @@ +program ft4d + + include 'ft4_params.f90' + character*8 arg + character*17 cdatetime + character*512 data_dir + character*12 mycall + character*12 hiscall + character*80 infile + character*61 line + character*4 cqstr + real*8 fMHz + integer ihdr(11) + integer*2 iwave(180000) !15*12000 + + fs=12000.0/NDOWN !Sample rate + dt=1/fs !Sample interval after downsample (s) + tt=NSPS*dt !Duration of "itone" symbols (s) + baud=1.0/tt !Keying rate for "itone" symbols (baud) + txt=NZ*dt !Transmission length (s) + + nargs=iargc() + if(nargs.lt.1) then + print*,'Usage: ft4d [-a ] [-f fMHz] [-n nQSOProgress] file1 [file2 ...]' + go to 999 + endif + iarg=1 + data_dir="." + call getarg(iarg,arg) + if(arg(1:2).eq.'-a') then + call getarg(iarg+1,data_dir) + iarg=iarg+2 + endif + call getarg(iarg,arg) + if(arg(1:2).eq.'-f') then + call getarg(iarg+1,arg) + read(arg,*) fMHz + iarg=iarg+2 + endif + nQSOProgress=0 + if(arg(1:2).eq.'-n') then + call getarg(iarg+1,arg) + read(arg,*) nQSOProgress + iarg=iarg+2 + endif + nfa=10 + nfb=4990 + ndecodes=0 + nfqso=1500 + mycall="K9AN" + hiscall="K1JT" + ncontest=4 + cqstr="RU " + + do ifile=iarg,nargs + call getarg(ifile,infile) + j2=index(infile,'.wav') + open(10,file=infile,status='old',access='stream') + read(10) ihdr + npts=ihdr(11)/2 + read(10) iwave(1:npts) + close(10) + cdatetime=infile(1:13)//'.000' + + istep=3456 + nsteps=(npts-52800)/istep + 1 + do n=1,nsteps + i0=(n-1)*istep + 1 + tbuf=(i0-1)/12000.0 + call ft4_decode(cdatetime,tbuf,nfa,nfb,nQSOProgress,ncontest, & + nfqso,iwave(i0),ndecodes,mycall,hiscall,cqstr,line,data_dir) + do idecode=1,ndecodes + call get_ft4msg(idecode,line) + write(*,'(a61)') line + enddo + enddo !steps + enddo !files + + call four2a(xx,-1,1,-1,1) !Destroy FFTW plans to free their memory + +999 end program ft4d + + diff --git a/lib/ft4/ft4sim.f90 b/lib/ft4/ft4sim.f90 new file mode 100644 index 000000000..0a9a1dcbc --- /dev/null +++ b/lib/ft4/ft4sim.f90 @@ -0,0 +1,155 @@ +program ft4sim + +! Generate simulated signals for experimental "FT4" mode + + use wavhdr + use packjt77 + include 'ft4_params.f90' !Set various constants + parameter (NWAVE=NN*NSPS) + type(hdr) h !Header for .wav file + character arg*12,fname*17 + character msg37*37,msgsent37*37 + character c77*77 + complex c0(0:NMAX-1) + complex c(0:NMAX-1) + real wave(NMAX) + real dphi(0:NMAX-1) + real pulse(3*NSPS) + integer itone(NN) + integer*1 msgbits(77) + integer*2 iwave(NMAX) !Generated full-length waveform + integer icos4(4) + data icos4/0,1,3,2/ + +! Get command-line argument(s) + nargs=iargc() + if(nargs.ne.7) then + print*,'Usage: ft4sim "message" f0 DT fdop del nfiles snr' + print*,'Examples: ft4sim "K1ABC W9XYZ EN37" 1500.0 0.0 0.1 1.0 10 -15' + print*,' ft4sim "WA9XYZ/R KA1ABC/R FN42" 1500.0 0.0 0.1 1.0 10 -15' + print*,' ft4sim "K1ABC RR73; W9XYZ -11" 300 0 0 0 1 -10' + go to 999 + endif + call getarg(1,msg37) !Message to be transmitted + call getarg(2,arg) + read(arg,*) f0 !Frequency (only used for single-signal) + call getarg(3,arg) + read(arg,*) xdt !Time offset from nominal (s) + call getarg(4,arg) + read(arg,*) fspread !Watterson frequency spread (Hz) + call getarg(5,arg) + read(arg,*) delay !Watterson delay (ms) + call getarg(6,arg) + read(arg,*) nfiles !Number of files + call getarg(7,arg) + read(arg,*) snrdb !SNR_2500 + + nfiles=abs(nfiles) + twopi=8.0*atan(1.0) + fs=12000.0 !Sample rate (Hz) + dt=1.0/fs !Sample interval (s) + hmod=1.0 !Modulation index (0.5 is MSK, 1.0 is FSK) + tt=NSPS*dt !Duration of symbols (s) + baud=1.0/tt !Keying rate (baud) + txt=NZ*dt !Transmission length (s) + + bandwidth_ratio=2500.0/(fs/2.0) + sig=sqrt(2*bandwidth_ratio) * 10.0**(0.05*snrdb) + if(snrdb.gt.90.0) sig=1.0 + txt=NN*NSPS/12000.0 + + ! Source-encode, then get itone() + i3=-1 + n3=-1 + call pack77(msg37,i3,n3,c77) + read(c77,'(77i1)') msgbits + call genft4(msg37,0,msgsent37,itone) + write(*,*) + write(*,'(a9,a37,3x,a7,i1,a1,i1)') 'Message: ',msgsent37,'i3.n3: ',i3,'.',n3 + write(*,1000) f0,xdt,txt,snrdb +1000 format('f0:',f9.3,' DT:',f6.2,' TxT:',f6.1,' SNR:',f6.1) + write(*,*) + if(i3.eq.1) then + write(*,*) ' mycall hiscall hisgrid' + write(*,'(28i1,1x,i1,1x,28i1,1x,i1,1x,i1,1x,15i1,1x,3i1)') msgbits(1:77) + else + write(*,'(a14)') 'Message bits: ' + write(*,'(77i1)') msgbits + endif + write(*,*) + write(*,'(a17)') 'Channel symbols: ' + write(*,'(76i1)') itone + write(*,*) + + call sgran() + +! The filtered frequency pulse + do i=1,3*NSPS + tt=(i-1.5*NSPS)/real(NSPS) + pulse(i)=gfsk_pulse(1.0,tt) + enddo + +! Define the instantaneous frequency waveform + dphi_peak=twopi*hmod/real(NSPS) + dphi=0.0 + do j=1,NN + ib=(j-1)*NSPS + ie=ib+3*NSPS-1 + dphi(ib:ie)=dphi(ib:ie)+dphi_peak*pulse*itone(j) + enddo + + phi=0.0 + c0=0.0 + dphi=dphi+twopi*f0*dt + do j=0,NMAX-1 + c0(j)=cmplx(cos(phi),sin(phi)) + phi=mod(phi+dphi(j),twopi) + enddo + + c0(0:NSPS-1)=c0(0:NSPS-1)*(1.0-cos(twopi*(/(i,i=0,NSPS-1)/)/(2.0*NSPS)) )/2.0 + c0((NN+1)*NSPS:(NN+2)*NSPS-1)=c0((NN+1)*NSPS:(NN+2)*NSPS-1)*(1.0+cos(twopi*(/(i,i=0,NSPS-1)/)/(2.0*NSPS) ))/2.0 + c0((NN+2)*NSPS:)=0. + + k=nint((xdt+0.14)/dt) + c0=cshift(c0,-k) + ia=k + + do ifile=1,nfiles + c=c0 + if(fspread.ne.0.0 .or. delay.ne.0.0) call watterson(c,NMAX,NWAVE,fs,delay,fspread) + c=sig*c + + ib=k + wave=real(c) + peak=maxval(abs(wave(ia:ib))) + nslots=1 + + if(snrdb.lt.90) then + do i=1,NMAX !Add gaussian noise at specified SNR + xnoise=gran() + wave(i)=wave(i) + xnoise + enddo + endif + + gain=100.0 + if(snrdb.lt.90.0) then + wave=gain*wave + else + datpk=maxval(abs(wave)) + fac=32766.9/datpk + wave=fac*wave + endif + if(any(abs(wave).gt.32767.0)) print*,"Warning - data will be clipped." + iwave=nint(wave) + h=default_header(12000,NMAX+2208) + write(fname,1102) ifile +1102 format('000000_',i6.6,'.wav') + open(10,file=fname,status='unknown',access='stream') + write(10) h,iwave !Save to *.wav file + iwave(1:2208)=0 + write(10) iwave(1:2208) !Add 0.5 s of zeroes + close(10) + write(*,1110) ifile,xdt,f0,snrdb,fname +1110 format(i4,f7.2,f8.2,f7.1,2x,a17) + enddo +999 end program ft4sim diff --git a/lib/ft4/ft4sim_mult.f90 b/lib/ft4/ft4sim_mult.f90 new file mode 100644 index 000000000..bc696e814 --- /dev/null +++ b/lib/ft4/ft4sim_mult.f90 @@ -0,0 +1,103 @@ +program ft4sim_mult + +! Generate simulated signals for experimental "FT4" mode + + use wavhdr + use packjt77 + include 'ft4_params.f90' !FT4 protocol constants + parameter (NWAVE=NN*NSPS) + parameter (NZZ=15*12000) !Length of .wav file, 180,000 i*2 samples + type(hdr) h !Header for .wav file + character arg*12,fname*17,cjunk*4 + character msg37*37,msgsent37*37,c77*77 + real wave0((NN+2)*NSPS) + real wave(NZZ) + real tmp(NZZ) + integer itone(NN) + integer*2 iwave(NZZ) !Generated full-length waveform + integer icos4(4) + data icos4/0,1,3,2/ + +! Get command-line argument(s) + nargs=iargc() + if(nargs.ne.2) then + print*,'Usage: ft4sim_mult nsigs nfiles' + print*,'Example: ft4sim_mult 20 8 ' + go to 999 + endif + call getarg(1,arg) + read(arg,*) nsigs !Number of signals + call getarg(2,arg) + read(arg,*) nfiles !Number of files + + twopi=8.0*atan(1.0) + fs=12000.0 !Sample rate (Hz) + dt=1.0/fs !Sample interval (s) + hmod=1.0 !Modulation index (0.5 is MSK, 1.0 is FSK) + tt=NSPS*dt !Duration of unsmoothed symbols (s) + baud=1.0/tt !Keying rate (baud) + txt=NZ*dt !Transmission length (s) without ramp up/down + bandwidth_ratio=2500.0/(fs/2.0) + txt=NN*NSPS/12000.0 + xdtmax=10.0 - 0.086 + open(10,file='messages.txt',status='old',err=998) + + do ifile=1,nfiles +1 read(10,1001,end=999) cjunk,n +1001 format(a4,i2) + if(cjunk.ne.'File' .or. n.ne.ifile) go to 1 + wave=0. + write(fname,1002) ifile +1002 format('000000_',i6.6,'.wav') + + do isig=1,nsigs + read(10,1003,end=100) cjunk,isnr,xdt0,ifreq,msg37 +1003 format(a4,30x,i3,f5.1,i5,1x,a37) + if(cjunk.eq.'File') go to 100 + if(isnr.lt.-16) isnr=-16 + f0=ifreq*93.75/50.0 + call random_number(r) + xdt=r*xdtmax +! Source-encode, then get itone() + i3=-1 + n3=-1 + call pack77(msg37,i3,n3,c77) + call genft4(msg37,0,msgsent37,itone) + nwave0=(NN+2)*NSPS + call gen_ft4wave(itone,NN,NSPS,12000.0,f0,wave0,nwave0) + + k0=nint(xdt/dt) + if(k0.lt.1) k0=1 + tmp(:k0-1)=0.0 + tmp(k0:k0+nwave0-1)=wave0 + tmp(k0+nwave0:)=0.0 + + ! Insert this signal into wave() array + sig=sqrt(2*bandwidth_ratio) * 10.0**(0.05*isnr) + wave=wave + sig*tmp + write(*,1100) fname(1:13),isig,isnr,xdt,nint(f0),msg37 +1100 format(a13,i4,i5,f5.1,i6,2x,a37) + enddo ! isig + +100 backspace 10 + + do i=1,NZZ !Add gaussian noise at specified SNR + xnoise=gran() + wave(i)=wave(i) + xnoise + enddo + + gain=30.0 + wave=gain*wave + if(any(abs(wave).gt.32767.0)) print*,"Warning - data will be clipped." + iwave=nint(wave) + h=default_header(12000,NZZ) + open(12,file=fname,status='unknown',access='stream') + write(12) h,iwave !Save to *.wav file + close(12) + print*,' ' + enddo ! ifile + go to 999 + +998 print*,'Cannot open file "messages.txt"' + +999 end program ft4sim_mult diff --git a/lib/ft4/gen_ft4wave.f90 b/lib/ft4/gen_ft4wave.f90 new file mode 100644 index 000000000..305310e68 --- /dev/null +++ b/lib/ft4/gen_ft4wave.f90 @@ -0,0 +1,52 @@ +subroutine gen_ft4wave(itone,nsym,nsps,fsample,f0,wave,nwave) + + real wave(nwave) + real pulse(6144) !512*4*3 + real dphi(0:240000-1) + integer itone(nsym) + logical first + data first/.true./ + save pulse,first,twopi,dt,hmod + + if(first) then + twopi=8.0*atan(1.0) + dt=1.0/fsample + hmod=1.0 +! Compute the frequency-smoothing pulse + do i=1,3*nsps + tt=(i-1.5*nsps)/real(nsps) + pulse(i)=gfsk_pulse(1.0,tt) + enddo + first=.false. + endif + +! Compute the smoothed frequency waveform. +! Length = (nsym+2)*nsps samples, zero-padded + dphi_peak=twopi*hmod/real(nsps) + dphi=0.0 + do j=1,nsym + ib=(j-1)*nsps + ie=ib+3*nsps-1 + dphi(ib:ie) = dphi(ib:ie) + dphi_peak*pulse(1:3*nsps)*itone(j) + enddo + +! Calculate and insert the audio waveform + phi=0.0 + dphi = dphi + twopi*f0*dt !Shift frequency up by f0 + wave=0. + k=0 + do j=0,nwave-1 + k=k+1 + wave(k)=sin(phi) + phi=mod(phi+dphi(j),twopi) + enddo + +! Compute the ramp-up and ramp-down symbols + wave(1:nsps)=wave(1:nsps) * & + (1.0-cos(twopi*(/(i,i=0,nsps-1)/)/(2.0*nsps)))/2.0 + k1=(nsym+1)*nsps+1 + wave(k1:k1+nsps-1)=wave(k1:k1+nsps-1) * & + (1.0+cos(twopi*(/(i,i=0,nsps-1)/)/(2.0*nsps)))/2.0 + + return +end subroutine gen_ft4wave diff --git a/lib/ft4/genft4.f90 b/lib/ft4/genft4.f90 new file mode 100644 index 000000000..3c6b8ead1 --- /dev/null +++ b/lib/ft4/genft4.f90 @@ -0,0 +1,82 @@ +subroutine genft4(msg0,ichk,msgsent,i4tone) + +! Encode an FT4 message +! Input: +! - msg0 requested message to be transmitted +! - ichk if ichk=1, return only msgsent +! - msgsent message as it will be decoded +! - i4tone array of audio tone values, {0,1,2,3} + +! Frame structure: +! s16 + 87symbols + 2 ramp up/down = 105 total channel symbols +! r1 + s4 + d29 + s4 + d29 + s4 + d29 + s4 + r1 + +! Message duration: TxT = 105*512/12000 = 4.48 s + +! use iso_c_binding, only: c_loc,c_size_t + + use packjt77 + include 'ft4_params.f90' + character*37 msg0 + character*37 message !Message to be generated + character*37 msgsent !Message as it will be received + character*77 c77 + integer*4 i4tone(NN),itmp(ND) + integer*1 codeword(2*ND) + integer*1 msgbits(77),rvec(77) + integer icos4a(4),icos4b(4),icos4c(4),icos4d(4) + logical unpk77_success + data icos4a/0,1,3,2/ + data icos4b/1,0,2,3/ + data icos4c/2,3,1,0/ + data icos4d/3,2,0,1/ + data rvec/0,1,0,0,1,0,1,0,0,1,0,1,1,1,1,0,1,0,0,0,1,0,0,1,1,0,1,1,0, & + 1,0,0,1,0,1,1,0,0,0,0,1,0,0,0,1,0,1,0,0,1,1,1,1,0,0,1,0,1, & + 0,1,0,1,0,1,1,0,1,1,1,1,1,0,0,0,1,0,1/ + message=msg0 + + do i=1, 37 + if(ichar(message(i:i)).eq.0) then + message(i:37)=' ' + exit + endif + enddo + do i=1,37 !Strip leading blanks + if(message(1:1).ne.' ') exit + message=message(i+1:) + enddo + + i3=-1 + n3=-1 + call pack77(message,i3,n3,c77) + call unpack77(c77,0,msgsent,unpk77_success) !Unpack to get msgsent + + if(ichk.eq.1) go to 999 + read(c77,"(77i1)") msgbits + msgbits=mod(msgbits+rvec,2) + call encode174_91(msgbits,codeword) + +! Grayscale mapping: +! bits tone +! 00 0 +! 01 1 +! 11 2 +! 10 3 + + do i=1,ND + is=codeword(2*i)+2*codeword(2*i-1) + if(is.le.1) itmp(i)=is + if(is.eq.2) itmp(i)=3 + if(is.eq.3) itmp(i)=2 + enddo + + i4tone(1:4)=icos4a + i4tone(5:33)=itmp(1:29) + i4tone(34:37)=icos4b + i4tone(38:66)=itmp(30:58) + i4tone(67:70)=icos4c + i4tone(71:99)=itmp(59:87) + i4tone(100:103)=icos4d + +999 return +end subroutine genft4 diff --git a/lib/ft4/getcandidates4.f90 b/lib/ft4/getcandidates4.f90 new file mode 100644 index 000000000..9b5200845 --- /dev/null +++ b/lib/ft4/getcandidates4.f90 @@ -0,0 +1,73 @@ +subroutine getcandidates4(id,fa,fb,syncmin,nfqso,maxcand,savg,candidate, & + ncand,sbase) + + include 'ft4_params.f90' + real s(NH1,NHSYM) + real savg(NH1),savsm(NH1) + real sbase(NH1) + real x(NFFT1) + real window(NFFT1) + complex cx(0:NH1) + real candidate(3,maxcand) + integer*2 id(NMAX) + integer indx(NH1) + integer ipk(1) + equivalence (x,cx) + logical first + data first/.true./ + save first,window + + if(first) then + first=.false. + pi=4.0*atan(1.) + window=0. + call nuttal_window(window,NFFT1) + endif + +! Compute symbol spectra, stepping by NSTEP steps. + savg=0. + tstep=NSTEP/12000.0 + df=12000.0/NFFT1 !5.86 Hz + fac=1.0/300.0 + do j=1,NHSYM + ia=(j-1)*NSTEP + 1 + ib=ia+NFFT1-1 + if(ib.gt.NMAX) exit + x=fac*id(ia:ib)*window + call four2a(x,NFFT1,1,-1,0) !r2c FFT + do i=1,NH1 + s(i,j)=real(cx(i))**2 + aimag(cx(i))**2 + enddo + savg=savg + s(1:NH1,j) !Average spectrum + enddo + savsm=0. + do i=8,NH1-7 + savsm(i)=sum(savg(i-7:i+7))/15. + enddo + nfa=fa/df + if(nfa.lt.1) nfa=1 + nfb=fb/df + if(nfb.gt.nint(5000.0/df)) nfb=nint(5000.0/df) + np=nfb-nfa+1 + indx=0 + call indexx(savsm(nfa:nfb),np,indx) + xn=savsm(nfa+indx(nint(0.3*np))) + savsm=savsm/xn + + ncand=0 + f_offset = -1.5*12000/512 + do i=nfa+1,nfb-1 + if(savsm(i).ge.savsm(i-1) .and. savsm(i).ge.savsm(i+1) .and. savsm(i).ge.syncmin) then + del=0.5*(savsm(i-1)-savsm(i+1))/(savsm(i-1)-2*savsm(i)+savsm(i+1)) + fpeak=(i+del)*df+f_offset + speak=savsm(i) - 0.25*(savsm(i-1)-savsm(i+1))*del + ncand=ncand+1 + if(ncand.gt.maxcand) exit + candidate(1,ncand)=fpeak + candidate(2,ncand)=-99.99 + candidate(3,ncand)=speak + endif + enddo + +return +end subroutine getcandidates4 diff --git a/lib/ft4/messages.txt b/lib/ft4/messages.txt new file mode 100644 index 000000000..16329fceb --- /dev/null +++ b/lib/ft4/messages.txt @@ -0,0 +1,162 @@ +File 1 +190106_000015 7.080 Rx FT8 -15 0.2 178 N1TRK N4FKH 569 VA +190106_000015 7.080 Rx FT8 -13 -0.1 253 N1TRK KB7RUQ 539 UT +190106_000015 7.080 Rx FT8 10 0.2 389 W0ZF N3LFC RR73 +190106_000015 7.080 Rx FT8 -10 0.1 450 PY4AZ KF7YED 559 MT +190106_000015 7.080 Rx FT8 -3 0.2 507 N1TRK WA4DYD 559 GA +190106_000015 7.080 Rx FT8 14 0.2 689 KB0VHA KA1YQC R 539 MA +190106_000015 7.080 Rx FT8 -5 0.1 984 W9JA N9OY RR73 +190106_000015 7.080 Rx FT8 -12 0.1 1123 VA2CZ K8GNG RR73 +190106_000015 7.080 Rx FT8 2 0.1 1240 CQ RU K7RL CN88 +190106_000015 7.080 Rx FT8 -11 0.5 1293 K4ZMW K7VZ RR73 +190106_000015 7.080 Rx FT8 2 0.1 1387 WD9IGY KX1X 73 +190106_000015 7.080 Rx FT8 -10 0.1 1536 CQ RU W0FRC DM79 +190106_000015 7.080 Rx FT8 -2 0.1 1635 K4SQC VE3RX 549 ON +190106_000030 7.080 Rx FT8 -11 0.2 1745 CQ RU K1LOG FN56 +190106_000030 7.080 Rx FT8 3 0.8 1798 WD5DAX WS4WW 73 +190106_000030 7.080 Rx FT8 12 0.1 1895 DJ6GI KG4W 73 +190106_000030 7.080 Rx FT8 -14 0.2 2119 KF7YED KF5ZNQ 549 TX +190106_000030 7.080 Rx FT8 -5 0.1 336 CQ RU AB5XS EM12 +190106_000030 7.080 Rx FT8 0 0.0 1415 W3KIT AA8SW RR73 +190106_000030 7.080 Rx FT8 -13 0.3 1540 NI6G W7DRW 579 AZ +190106_000030 7.080 Rx FT8 -10 0.1 312 SHIFT W9JA +190106_000030 7.080 Rx FT8 -16 0.1 1447 W7BOB KJ7G 549 WA +File 2 +190106_000045 7.080 Rx FT8 -9 0.1 178 N1TRK N4FKH 569 VA +190106_000045 7.080 Rx FT8 -9 -0.1 253 N1TRK KB7RUQ RR73 +190106_000045 7.080 Rx FT8 -8 0.1 336 CQ RU AB5XS EM12 +190106_000045 7.080 Rx FT8 16 0.3 689 KB0VHA KA1YQC R 539 MA +190106_000045 7.080 Rx FT8 -3 0.1 984 CQ RU N9OY EN43 +190106_000045 7.080 Rx FT8 -10 0.1 1146 K1JT WB4HXE 559 GA +190106_000045 7.080 Rx FT8 6 0.1 1240 VE3LON K7RL R 549 WA +190106_000045 7.080 Rx FT8 -1 0.1 1386 WD9IGY KX1X 73 +190106_000045 7.080 Rx FT8 -12 0.1 1536 CQ RU W0FRC DM79 +190106_000045 7.080 Rx FT8 3 0.1 1635 K4SQC VE3RX RR73 +190106_000045 7.080 Rx FT8 -5 0.1 1688 CQ RU W1QA FN32 +190106_000045 7.080 Rx FT8 1 0.8 1797 CQ RU WS4WW FM17 +190106_000045 7.080 Rx FT8 14 0.1 1895 HB9BUN KG4W R 549 VA +190106_000100 7.080 Rx FT8 -7 0.1 2002 W9TO KN3ILZ 529 PA +190106_000100 7.080 Rx FT8 -9 0.1 312 W9JA PY2APK RRR +190106_000100 7.080 Rx FT8 -12 0.1 436 NZ7P WA7JAY 589 CA +190106_000100 7.080 Rx FT8 -13 -0.1 1380 AC6BW KR9A R 559 WI +190106_000100 7.080 Rx FT8 -17 0.1 1448 W7BOB KJ7G RR73 +190106_000100 7.080 Rx FT8 -7 0.3 1540 NI6G W7DRW 569 AZ +File 3 +190106_000115 7.080 Rx FT8 -13 0.2 178 N1TRK N4FKH 569 VA +190106_000115 7.080 Rx FT8 -14 -0.1 253 N1TRK KB7RUQ RR73 +190106_000115 7.080 Rx FT8 -5 0.1 336 CQ RU AB5XS EM12 +190106_000115 7.080 Rx FT8 -7 0.1 449 NI6G KF7YED 569 MT +190106_000115 7.080 Rx FT8 14 0.2 689 KB0VHA KA1YQC 73 +190106_000115 7.080 Rx FT8 11 0.4 852 W9TO N4QWF 569 VA +190106_000115 7.080 Rx FT8 -3 0.1 984 W4EMB N9OY R 529 WI +190106_000115 7.080 Rx FT8 -14 0.1 1117 WB4FAY K7PDW 549 UT +190106_000115 7.080 Rx FT8 3 0.2 1240 VE3LON K7RL R 559 WA +190106_000115 7.080 Rx FT8 -18 0.1 1380 AC6BW KR9A R 559 WI +190106_000115 7.080 Rx FT8 4 0.1 1475 K4KCL KB4S 559 VA +190106_000130 7.080 Rx FT8 -5 0.1 1612 K1JT WB4HXE RR73 +190106_000115 7.080 Rx FT8 -1 0.1 1688 KW4RTR W1QA R 539 MA +190106_000115 7.080 Rx FT8 -15 0.2 1745 CQ RU K1LOG FN56 +190106_000115 7.080 Rx FT8 3 0.8 1798 WA1T WS4WW R 579 VA +190106_000115 7.080 Rx FT8 14 0.1 1895 HB9BUN KG4W R 549 VA +190106_000115 7.080 Rx FT8 -9 0.2 2125 CQ RU NF3R FN20 +190106_000130 7.080 Rx FT8 -8 0.4 976 AG1T K7VZ 569 AZ +190106_000130 7.080 Rx FT8 -7 0.1 1434 Z36W N4KMC 529 FL +File 4 +190106_000145 7.080 Rx FT8 -8 0.1 335 CQ RU AB5XS EM12 +190106_000145 7.080 Rx FT8 -11 0.2 388 W0ZF WW4LL 559 ME +190106_000145 7.080 Rx FT8 -10 0.1 449 NI6G KF7YED 569 MT +190106_000145 7.080 Rx FT8 10 0.3 689 CQ RU KA1YQC FN42 +190106_000145 7.080 Rx FT8 12 0.4 852 W9TO N4QWF RR73 +190106_000145 7.080 Rx FT8 -2 0.1 984 W4EMB N9OY R 529 WI +190106_000145 7.080 Rx FT8 -12 0.1 1117 WB4FAY K7PDW 549 UT +190106_000145 7.080 Rx FT8 4 0.2 1240 VE3LON K7RL 73 +190106_000145 7.080 Rx FT8 -13 0.1 1348 WW5M KO9V 559 NC +190106_000145 7.080 Rx FT8 -14 0.1 1380 AC6BW KR9A R 559 WI +190106_000145 7.080 Rx FT8 2 0.1 1475 K4KCL KB4S RR73 +190106_000145 7.080 Rx FT8 -9 0.1 1536 KC1HTT W0FRC 73 +190106_000145 7.080 Rx FT8 1 0.1 1688 KW4RTR W1QA R 539 MA +190106_000200 7.080 Rx FT8 1 0.8 1797 WA1T WS4WW R 569 VA +190106_000200 7.080 Rx FT8 15 0.1 1895 CQ RU KG4W FM17 +190106_000200 7.080 Rx FT8 -6 0.2 2125 CQ RU NF3R FN20 +190106_000200 7.080 Rx FT8 -7 0.1 311 PD8DX PY2APK 539 0081 +190106_000200 7.080 Rx FT8 -3 0.5 976 AG1T K7VZ RR73 +190106_000200 7.080 Rx FT8 -5 0.1 1432 Z36W N4KMC 529 FL +190106_000200 7.080 Rx FT8 -7 0.3 1540 N7BT W7DRW 559 AZ +190106_000200 7.080 Rx FT8 -16 0.3 2124 N3KCR ON9COP 529 0053 +190106_000200 7.080 Rx FT8 -12 0.1 1448 K2DH KJ7G 549 WA +File 5 +190106_000215 7.080 Rx FT8 11 0.1 435 K1JT WV8WA 569 WV +190106_000215 7.080 Rx FT8 -4 0.1 335 CQ RU AB5XS EM12 +190106_000215 7.080 Rx FT8 10 0.5 689 CQ RU KA1YQC FN42 +190106_000215 7.080 Rx FT8 -10 0.1 843 CQ RU AE5JH EL07 +190106_000215 7.080 Rx FT8 -3 0.1 984 W4EMB N9OY R 529 WI +190106_000215 7.080 Rx FT8 -10 0.1 1118 WB4FAY W5MO 539 TX +190106_000215 7.080 Rx FT8 -16 0.1 1170 K1JT K8GNG 569 MI +190106_000215 7.080 Rx FT8 6 0.1 1240 CQ RU K7RL CN88 +190106_000215 7.080 Rx FT8 -8 0.1 1348 WW5M KO9V RR73 +190106_000215 7.080 Rx FT8 -12 0.1 1432 Z36W N4KMC 529 FL +190106_000215 7.080 Rx FT8 2 0.1 1490 KA6BIM KB4S 529 VA +190106_000230 7.080 Rx FT8 -1 0.1 1688 CQ RU W1QA FN32 +190106_000230 7.080 Rx FT8 15 0.1 1895 DD5ZZ KG4W R 539 VA +190106_000230 7.080 Rx FT8 -8 0.2 2125 CQ RU NF3R FN20 +190106_000230 7.080 Rx FT8 -13 0.1 1380 AC6BW KR9A R 559 WI +190106_000230 7.080 Rx FT8 -4 0.1 1536 CQ RU W0FRC DM79 +190106_000230 7.080 Rx FT8 -8 0.3 1540 N7BT W7DRW 559 AZ +File 6 +190106_000245 7.080 Rx FT8 6 0.1 635 K1JT WV8WA RR73 +190106_000245 7.080 Rx FT8 -12 0.1 335 VE3RX AB5XS R 589 TX +190106_000245 7.080 Rx FT8 -16 0.1 423 WA1T PY2APK 529 0081 +190106_000245 7.080 Rx FT8 9 0.4 507 N1TRK N4QWF RR73 +190106_000245 7.080 Rx FT8 -10 0.1 869 W7BOB W3KIT 539 MD +190106_000245 7.080 Rx FT8 -7 0.1 984 W4EMB N9OY R 529 WI +190106_000245 7.080 Rx FT8 -13 0.1 1170 K1JT K8GNG 569 MI +190106_000245 7.080 Rx FT8 5 0.1 1240 W6GMT K7RL R 569 WA +190106_000245 7.080 Rx FT8 2 0.1 1490 KA6BIM KB4S 529 VA +190106_000245 7.080 Rx FT8 -1 0.1 1688 CQ RU W1QA FN32 +190106_000245 7.080 Rx FT8 1 0.7 1798 N3KCR WS4WW R 599 VA +190106_000245 7.080 Rx FT8 17 0.1 1895 DD5ZZ KG4W 73 +190106_000300 7.080 Rx FT8 -10 0.2 2125 CQ RU NF3R FN20 +190106_000300 7.080 Rx FT8 8 0.1 546 W7BOB W4DHE 539 KY +190106_000300 7.080 Rx FT8 -9 0.2 844 CQ RU AE5JH EL07 +190106_000300 7.080 Rx FT8 -5 0.1 1536 W9WLX W0FRC R 549 CO +File 7 +190106_000315 7.080 Rx FT8 -5 0.1 335 VE3RX AB5XS 73 +190106_000315 7.080 Rx FT8 -13 0.1 424 WA1T PY2APK 529 0081 +190106_000315 7.080 Rx FT8 6 0.1 545 W7BOB W4DHE 539 KY +190106_000315 7.080 Rx FT8 -7 0.4 600 K2DH K7VZ 549 AZ +190106_000315 7.080 Rx FT8 9 0.1 689 VE3LON KA1YQC 73 +190106_000315 7.080 Rx FT8 -8 0.1 869 W7BOB W3KIT 539 MD +190106_000315 7.080 Rx FT8 -1 0.1 984 W4EMB N9OY R 529 WI +190106_000315 7.080 Rx FT8 -15 -0.1 1117 WB4FAY KB7RUQ 529 UT +190106_000315 7.080 Rx FT8 7 -0.2 1240 W6GMT K7RL 73 +190106_000315 7.080 Rx FT8 -5 0.2 1581 VA3WW WA4DYD 559 GA +190106_000315 7.080 Rx FT8 -8 0.1 1688 DD5ZZ W1QA R 569 MA +190106_000315 7.080 Rx FT8 4 0.7 1798 N3KCR WS4WW 73 +190106_000315 7.080 Rx FT8 16 0.1 1895 CQ RU KG4W FM17 +190106_000315 7.080 Rx FT8 -11 0.2 2125 CQ RU NF3R FN20 +190106_000330 7.080 Rx FT8 -8 0.1 1170 K1JT K8GNG RR73 +190106_000330 7.080 Rx FT8 -13 -0.1 454 NZ7P KM7S R 549 CA +190106_000330 7.080 Rx FT8 1 0.1 941 KW4RTR KO9V 559 NC +190106_000330 7.080 Rx FT8 -16 0.3 1034 W9JA W9OY 539 FL +190106_000330 7.080 Rx FT8 -2 0.1 1240 K2DH KD2GXL 559 NY +190106_000330 7.080 Rx FT8 -5 0.1 1536 W9WLX W0FRC 73 +190106_000330 7.080 Rx FT8 -2 -0.1 918 KF6JXM W5BT 529 TX +File 8 +190106_000345 7.080 Rx FT8 -5 0.1 335 CQ RU AB5XS EM12 +190106_000345 7.080 Rx FT8 -13 0.1 423 WA1T PY2APK 529 0081 +190106_000345 7.080 Rx FT8 6 0.2 689 CQ RU KA1YQC FN42 +190106_000345 7.080 Rx FT8 -16 0.1 852 W9TO KM4LFT RR73 +190106_000345 7.080 Rx FT8 -4 -0.1 918 KF6JXM W5BT 529 TX +190106_000345 7.080 Rx FT8 2 0.1 984 W4EMB N9OY R 529 WI +190106_000345 7.080 Rx FT8 -15 0.3 1034 W9JA W9OY RR73 +190106_000345 7.080 Rx FT8 -7 0.1 1175 K2DH KD2GXL 559 NY +190106_000345 7.080 Rx FT8 6 -0.1 1240 CQ RU K7RL CN88 +190106_000345 7.080 Rx FT8 -10 0.1 1536 CQ RU W0FRC DM79 +190106_000345 7.080 Rx FT8 -7 0.2 1581 VA3WW WA4DYD RR73 +190106_000345 7.080 Rx FT8 -9 0.1 1688 DD5ZZ W1QA 73 +190106_000345 7.080 Rx FT8 17 0.1 1895 CQ RU KG4W FM17 +190106_000345 7.080 Rx FT8 -9 0.1 2002 K2DH KN3ILZ 549 PA +190106_000400 7.080 Rx FT8 -5 0.7 2052 CQ RU WS4WW FM17 +190106_000400 7.080 Rx FT8 -9 0.2 2125 NV4G NF3R R 549 PA +190106_000400 7.080 Rx FT8 -5 0.0 941 KW4RTR KO9V RR73 +190106_000400 7.080 Rx FT8 -7 0.2 1744 KC1HTT K1LOG R 569 ME diff --git a/lib/ft4/sync4d.f90 b/lib/ft4/sync4d.f90 new file mode 100644 index 000000000..4d8ba2df2 --- /dev/null +++ b/lib/ft4/sync4d.f90 @@ -0,0 +1,95 @@ +subroutine sync4d(cd0,i0,ctwk,itwk,sync,sync2) + +! Compute sync power for a complex, downsampled FT4 signal. + + include 'ft4_params.f90' + parameter(NP=NMAX/NDOWN,NSS=NSPS/NDOWN) + complex cd0(0:NP-1) + complex csynca(4*NSS),csyncb(4*NSS),csyncc(4*NSS),csyncd(4*NSS) + complex csync2(4*NSS) + complex ctwk(4*NSS) + complex z1,z2,z3,z4 + complex zz1,zz2,zz3,zz4 + logical first + integer icos4a(0:3),icos4b(0:3),icos4c(0:3),icos4d(0:3) + data icos4a/0,1,3,2/ + data icos4b/1,0,2,3/ + data icos4c/2,3,1,0/ + data icos4d/3,2,0,1/ + data first/.true./ + save first,twopi,csynca,csyncb,csyncc,csyncd,fac + + p(z1)=real(z1*fac)**2 + aimag(z1*fac)**2 !Statement function for power + + if( first ) then + twopi=8.0*atan(1.0) + k=1 + phia=0.0 + phib=0.0 + phic=0.0 + phid=0.0 + do i=0,3 + dphia=twopi*icos4a(i)/real(NSS) + dphib=twopi*icos4b(i)/real(NSS) + dphic=twopi*icos4c(i)/real(NSS) + dphid=twopi*icos4d(i)/real(NSS) + do j=1,NSS + csynca(k)=cmplx(cos(phia),sin(phia)) + csyncb(k)=cmplx(cos(phib),sin(phib)) + csyncc(k)=cmplx(cos(phic),sin(phic)) + csyncd(k)=cmplx(cos(phid),sin(phid)) + phia=mod(phia+dphia,twopi) + phib=mod(phib+dphib,twopi) + phic=mod(phic+dphic,twopi) + phid=mod(phid+dphid,twopi) + k=k+1 + enddo + enddo + first=.false. + fac=1.0/(4.0*NSS) + endif + + i1=i0 !four Costas arrays + i2=i0+33*NSS + i3=i0+66*NSS + i4=i0+99*NSS + + z1=0. + z2=0. + z3=0. + z4=0. + + if(itwk.eq.1) csync2=ctwk*csynca !Tweak the frequency + if(i1.ge.0 .and. i1+4*NSS-1.le.NP-1) z1=sum(cd0(i1:i1+4*NSS-1)*conjg(csync2)) + + if(itwk.eq.1) csync2=ctwk*csyncb !Tweak the frequency + if(i2.ge.0 .and. i2+4*NSS-1.le.NP-1) z2=sum(cd0(i2:i2+4*NSS-1)*conjg(csync2)) + + if(itwk.eq.1) csync2=ctwk*csyncc !Tweak the frequency + if(i3.ge.0 .and. i3+4*NSS-1.le.NP-1) z3=sum(cd0(i3:i3+4*NSS-1)*conjg(csync2)) + + if(itwk.eq.1) csync2=ctwk*csyncd !Tweak the frequency + if(i4.ge.0 .and. i4+4*NSS-1.le.NP-1) z4=sum(cd0(i4:i4+4*NSS-1)*conjg(csync2)) + + sync = p(z1) + p(z2) + p(z3) + p(z4) + +sync2=0.0 +!do i=1,4 +! i1=i0+(i-1)*33*NSS +! if(i.eq.1) csync2=ctwk*csynca +! if(i.eq.2) csync2=ctwk*csyncb +! if(i.eq.3) csync2=ctwk*csyncc +! if(i.eq.4) csync2=ctwk*csyncd +! z1=sum(cd0(i1 :i1+ NSS-1)*conjg(csync2( 1: NSS))) +! z2=sum(cd0(i1+ NSS:i1+2*NSS-1)*conjg(csync2( NSS+1:2*NSS))) +! z3=sum(cd0(i1+2*NSS:i1+3*NSS-1)*conjg(csync2(2*NSS+1:3*NSS))) +! z4=sum(cd0(i1+3*NSS:i1+4*NSS-1)*conjg(csync2(3*NSS+1:4*NSS))) +! sync2=sync2 + abs(z1)**2+abs(z2)**2+abs(z3)**2+abs(z4)**2+& +! 2*abs(z1*conjg(z2)+z2*conjg(z3)+z3*conjg(z4)) + & +! 2*abs(z1*conjg(z3)+z2*conjg(z4)) + & +! 2*abs(z1*conjg(z4)) +!enddo +!sync2=sync2*(fac**2) + + return +end subroutine sync4d diff --git a/lib/ft4/syncft4.f90 b/lib/ft4/syncft4.f90 new file mode 100644 index 000000000..4841c560e --- /dev/null +++ b/lib/ft4/syncft4.f90 @@ -0,0 +1,145 @@ +subroutine syncft4(iwave,nfa,nfb,syncmin,nfqso,maxcand,s,candidate, & + ncand,sbase) + + include 'ft4_params.f90' +! Search over +/- 2.5s relative to 0.5s TX start time. + parameter (JZ=20) + complex cx(0:NH1) + real s(NH1,NHSYM) + real savg(NH1) + real sbase(NH1) + real x(NFFT1) + real sync2d(NH1,-JZ:JZ) + real red(NH1) + real candidate0(3,maxcand) + real candidate(3,maxcand) + real dd(NMAX) + integer jpeak(NH1) + integer indx(NH1) + integer ii(1) + integer*2 iwave(NMAX) + integer icos4(0:3) + data icos4/0,1,3,2/ !Costas 4x4 tone pattern + equivalence (x,cx) + + dd=iwave/1e3 +! Compute symbol spectra, stepping by NSTEP steps. + savg=0. + tstep=NSTEP/12000.0 + df=12000.0/NFFT1 + fac=1.0/300.0 + do j=1,NHSYM + ia=(j-1)*NSTEP + 1 + ib=ia+NSPS-1 + x(1:NSPS)=fac*dd(ia:ib) + x(NSPS+1:)=0. + call four2a(x,NFFT1,1,-1,0) !r2c FFT + do i=1,NH1 + s(i,j)=real(cx(i))**2 + aimag(cx(i))**2 + enddo + savg=savg + s(1:NH1,j) !Average spectrum + enddo + + call baseline(savg,nfa,nfb,sbase) + + ia=max(1,nint(nfa/df)) + ib=nint(nfb/df) + nssy=NSPS/NSTEP ! # steps per symbol + nfos=NFFT1/NSPS ! # frequency bin oversampling factor + jstrt=0.25/tstep + candidate0=0. + k=0 + + do i=ia,ib + do j=-JZ,+JZ + ta=0. + tb=0. + tc=0. + t0a=0. + t0b=0. + t0c=0. + do n=0,3 + m=j+jstrt+nssy*n + if(m.ge.1.and.m.le.NHSYM) then + ta=ta + s(i+nfos*icos4(n),m) + t0a=t0a + sum(s(i:i+nfos*3:nfos,m)) + endif + tb=tb + s(i+nfos*icos4(n),m+nssy*36) + t0b=t0b + sum(s(i:i+nfos*3:nfos,m+nssy*36)) + if(m+nssy*72.le.NHSYM) then + tc=tc + s(i+nfos*icos4(n),m+nssy*72) + t0c=t0c + sum(s(i:i+nfos*3:nfos,m+nssy*72)) + endif + enddo + t=ta+tb+tc + t0=t0a+t0b+t0c + t0=(t0-t)/3.0 + sync_abc=t/t0 + t=tb+tc + t0=t0b+t0c + t0=(t0-t)/3.0 + sync_bc=t/t0 + sync2d(i,j)=max(sync_abc,sync_bc) + enddo + enddo + + red=0. + do i=ia,ib + ii=maxloc(sync2d(i,-JZ:JZ)) - 1 - JZ + j0=ii(1) + jpeak(i)=j0 + red(i)=sync2d(i,j0) + enddo + iz=ib-ia+1 + call indexx(red(ia:ib),iz,indx) + ibase=indx(nint(0.40*iz)) - 1 + ia + if(ibase.lt.1) ibase=1 + if(ibase.gt.nh1) ibase=nh1 + base=red(ibase) + red=red/base + do i=1,min(maxcand,iz) + n=ia + indx(iz+1-i) - 1 + if(red(n).lt.syncmin.or.isnan(red(n)).or.k.eq.maxcand) exit + k=k+1 +! candidate0(1,k)=n*df+37.5*1.5 + candidate0(1,k)=n*df + candidate0(2,k)=(jpeak(n)-1)*tstep + candidate0(3,k)=red(n) + enddo + ncand=k + +! Put nfqso at top of list, and save only the best of near-dupe freqs. + do i=1,ncand + if(abs(candidate0(1,i)-nfqso).lt.10.0) candidate0(1,i)=-candidate0(1,i) + if(i.ge.2) then + do j=1,i-1 + fdiff=abs(candidate0(1,i))-abs(candidate0(1,j)) + if(abs(fdiff).lt.4.0) then + if(candidate0(3,i).ge.candidate0(3,j)) candidate0(3,j)=0. + if(candidate0(3,i).lt.candidate0(3,j)) candidate0(3,i)=0. + endif + enddo + endif + enddo + + fac=20.0/maxval(s) + s=fac*s + +! Sort by sync +! call indexx(candidate0(3,1:ncand),ncand,indx) +! Sort by frequency + call indexx(candidate0(1,1:ncand),ncand,indx) + k=1 +! do i=ncand,1,-1 + do i=1,ncand + j=indx(i) +! if( candidate0(3,j) .ge. syncmin .and. candidate0(2,j).ge.-1.5 ) then + if( candidate0(3,j) .ge. syncmin ) then + candidate(2:3,k)=candidate0(2:3,j) + candidate(1,k)=abs(candidate0(1,j)) + k=k+1 + endif + enddo + ncand=k-1 + return +end subroutine syncft4 diff --git a/lib/ft8/ft8sim_gfsk.f90 b/lib/ft8/ft8sim_gfsk.f90 new file mode 100644 index 000000000..aba046cef --- /dev/null +++ b/lib/ft8/ft8sim_gfsk.f90 @@ -0,0 +1,130 @@ +program ft8sim_gfsk + +! Generate simulated "type 2" ft8 files +! Output is saved to a *.wav file. + + use wavhdr + use packjt77 + include 'ft8_params.f90' !Set various constants + parameter (NWAVE=NN*NSPS) + type(hdr) h !Header for .wav file + character arg*12,fname*17 + character msg37*37,msgsent37*37 + character c77*77 + complex c0(0:NMAX-1) + complex c(0:NMAX-1) + complex cwave(0:NWAVE-1) + real wave(NMAX) + real xjunk(NWAVE) + integer itone(NN) + integer*1 msgbits(77) + integer*2 iwave(NMAX) !Generated full-length waveform + +! Get command-line argument(s) + nargs=iargc() + if(nargs.ne.7) then + print*,'Usage: ft8sim "message" f0 DT fdop del nfiles snr' + print*,'Examples: ft8sim "K1ABC W9XYZ EN37" 1500.0 0.0 0.1 1.0 10 -18' + print*,' ft8sim "WA9XYZ/R KA1ABC/R FN42" 1500.0 0.0 0.1 1.0 10 -18' + print*,' ft8sim "K1ABC RR73; W9XYZ -11" 300 0 0 0 25 1 -10' + go to 999 + endif + call getarg(1,msg37) !Message to be transmitted + call getarg(2,arg) + read(arg,*) f0 !Frequency (only used for single-signal) + call getarg(3,arg) + read(arg,*) xdt !Time offset from nominal (s) + call getarg(4,arg) + read(arg,*) fspread !Watterson frequency spread (Hz) + call getarg(5,arg) + read(arg,*) delay !Watterson delay (ms) + call getarg(6,arg) + read(arg,*) nfiles !Number of files + call getarg(7,arg) + read(arg,*) snrdb !SNR_2500 + + nsig=1 + if(f0.lt.100.0) then + nsig=f0 + f0=1500 + endif + + nfiles=abs(nfiles) + twopi=8.0*atan(1.0) + fs=12000.0 !Sample rate (Hz) + dt=1.0/fs !Sample interval (s) + tt=NSPS*dt !Duration of symbols (s) + baud=1.0/tt !Keying rate (baud) + bw=8*baud !Occupied bandwidth (Hz) + txt=NZ*dt !Transmission length (s) + bandwidth_ratio=2500.0/(fs/2.0) + sig=sqrt(2*bandwidth_ratio) * 10.0**(0.05*snrdb) + if(snrdb.gt.90.0) sig=1.0 + txt=NN*NSPS/12000.0 + + ! Source-encode, then get itone() + i3=-1 + n3=-1 + call pack77(msg37,i3,n3,c77) + call genft8(msg37,i3,n3,msgsent37,msgbits,itone) + call gen_ft8wave(itone,NN,NSPS,fs,f0,cwave,xjunk,1,NWAVE) !Generate complex cwave + + write(*,*) + write(*,'(a23,a37,3x,a7,i1,a1,i1)') 'New Style FT8 Message: ',msgsent37,'i3.n3: ',i3,'.',n3 + write(*,1000) f0,xdt,txt,snrdb,bw +1000 format('f0:',f9.3,' DT:',f6.2,' TxT:',f6.1,' SNR:',f6.1, & + ' BW:',f4.1) + write(*,*) + if(i3.eq.1) then + write(*,*) ' mycall hiscall hisgrid' + write(*,'(28i1,1x,i1,1x,28i1,1x,i1,1x,i1,1x,15i1,1x,3i1)') msgbits(1:77) + else + write(*,'(a14)') 'Message bits: ' + write(*,'(77i1)') msgbits + endif + write(*,*) + write(*,'(a17)') 'Channel symbols: ' + write(*,'(79i1)') itone + write(*,*) + + call sgran() + + msg0=msg + do ifile=1,nfiles + c0=0. + c0(0:NWAVE-1)=cwave + c0=cshift(c0,-nint((xdt+0.5)/dt)) + if(fspread.ne.0.0 .or. delay.ne.0.0) call watterson(c0,NMAX,NWAVE,fs,delay,fspread) + c=sig*c0 + + wave=imag(c) + peak=maxval(abs(wave)) + nslots=1 + + if(snrdb.lt.90) then + do i=1,NMAX !Add gaussian noise at specified SNR + xnoise=gran() + wave(i)=wave(i) + xnoise + enddo + endif + + gain=100.0 + if(snrdb.lt.90.0) then + wave=gain*wave + else + datpk=maxval(abs(wave)) + fac=32766.9/datpk + wave=fac*wave + endif + if(any(abs(wave).gt.32767.0)) print*,"Warning - data will be clipped." + iwave=nint(wave) + h=default_header(12000,NMAX) + write(fname,1102) ifile +1102 format('000000_',i6.6,'.wav') + open(10,file=fname,status='unknown',access='stream') + write(10) h,iwave !Save to *.wav file + close(10) + write(*,1110) ifile,xdt,f0,snrdb,fname +1110 format(i4,f7.2,f8.2,f7.1,2x,a17) + enddo +999 end program ft8sim_gfsk diff --git a/lib/ft8/gen_ft8wave.f90 b/lib/ft8/gen_ft8wave.f90 new file mode 100644 index 000000000..d4b58a426 --- /dev/null +++ b/lib/ft8/gen_ft8wave.f90 @@ -0,0 +1,74 @@ +subroutine gen_ft8wave(itone,nsym,nsps,fsample,f0,cwave,wave,icmplx,nwave) +! +! generate ft8 waveform using Gaussian-filtered frequency pulses. +! + + parameter(MAX_SECONDS=20) + real wave(nwave) + complex cwave(nwave) + real pulse(23040) + real dphi(0:(nsym+2)*nsps-1) + integer itone(nsym) + logical first + data first/.true./ + save pulse,first,twopi,dt,hmod + + if(first) then + twopi=8.0*atan(1.0) + dt=1.0/fsample + hmod=1.0 + bt=2.0 +! Compute the frequency-smoothing pulse + do i=1,3*nsps + tt=(i-1.5*nsps)/real(nsps) + pulse(i)=gfsk_pulse(bt,tt) + enddo + first=.false. + endif + +! Compute the smoothed frequency waveform. +! Length = (nsym+2)*nsps samples, first and last symbols extended + dphi_peak=twopi*hmod/real(nsps) + dphi=0.0 + do j=1,nsym + ib=(j-1)*nsps + ie=ib+3*nsps-1 + dphi(ib:ie) = dphi(ib:ie) + dphi_peak*pulse(1:3*nsps)*itone(j) + enddo +! Add dummy symbols at beginning and end with tone values equal to 1st and last symbol, respectively + dphi(0:2*nsps-1)=dphi(0:2*nsps-1)+dphi_peak*itone(1)*pulse(nsps+1:3*nsps) + dphi(nsym*nsps:(nsym+2)*nsps-1)=dphi(nsym*nsps:(nsym+2)*nsps-1)+dphi_peak*itone(nsym)*pulse(1:2*nsps) + +! Calculate and insert the audio waveform + phi=0.0 + dphi = dphi + twopi*f0*dt !Shift frequency up by f0 + wave=0. + k=0 + do j=nsps,nsps+nwave-1 !Don't include dummy symbols + k=k+1 + if(icmplx.eq.0) then + wave(k)=sin(phi) + else + cwave(k)=cmplx(cos(phi),sin(phi)) + endif + phi=mod(phi+dphi(j),twopi) + enddo + +! Apply envelope shaping to the first and last symbols + nramp=nint(nsps/8.0) + if(icmplx.eq.0) then + wave(1:nramp)=wave(1:nramp) * & + (1.0-cos(twopi*(/(i,i=0,nramp-1)/)/(2.0*nramp)))/2.0 + k1=nsym*nsps-nramp+1 + wave(k1:k1+nramp-1)=wave(k1:k1+nramp-1) * & + (1.0+cos(twopi*(/(i,i=0,nramp-1)/)/(2.0*nramp)))/2.0 + else + cwave(1:nramp)=cwave(1:nramp) * & + (1.0-cos(twopi*(/(i,i=0,nramp-1)/)/(2.0*nramp)))/2.0 + k1=nsym*nsps-nramp+1 + cwave(k1:k1+nramp-1)=cwave(k1:k1+nramp-1) * & + (1.0+cos(twopi*(/(i,i=0,nramp-1)/)/(2.0*nramp)))/2.0 + endif + + return +end subroutine gen_ft8wave diff --git a/lib/ft8/watterson.f90 b/lib/ft8/watterson.f90 index a1131d7b1..7c3aa35d3 100644 --- a/lib/ft8/watterson.f90 +++ b/lib/ft8/watterson.f90 @@ -46,7 +46,11 @@ subroutine watterson(c,npts,nsig,fs,delay,fspread) endif nshift=nint(0.001*delay*fs) - c2(0:npts-1)=cshift(c(0:npts-1),nshift) + if(delay.gt.0.0) then + c2(0:npts-1)=cshift(c(0:npts-1),-nshift) !negative shifts are right shifts + else + c2(0:npts-1)=0.0 + endif sq=0. do i=0,npts-1 if(nonzero.gt.1) then diff --git a/lib/nuttal_window.f90 b/lib/nuttal_window.f90 new file mode 100644 index 000000000..aa813b15d --- /dev/null +++ b/lib/nuttal_window.f90 @@ -0,0 +1,15 @@ +subroutine nuttal_window(win,n) + real win(n) + + pi=4.0*atan(1.0) + a0=0.3635819 + a1=-0.4891775; + a2=0.1365995; + a3=-0.0106411; + do i=1,n + win(i)=a0+a1*cos(2*pi*(i-1)/(n))+ & + a2*cos(4*pi*(i-1)/(n))+ & + a3*cos(6*pi*(i-1)/(n)) + enddo + return +end subroutine nuttal_window diff --git a/lib/symspec.f90 b/lib/symspec.f90 index 28035d2c8..b9f5e2088 100644 --- a/lib/symspec.f90 +++ b/lib/symspec.f90 @@ -1,13 +1,14 @@ -subroutine symspec(shared_data,k,ntrperiod,nsps,ingain,nminw,pxdb,s, & - df3,ihsym,npts8,pxdbmax) +subroutine symspec(shared_data,k,ntrperiod,nsps,ingain,bLowSidelobes, & + nminw,pxdb,s,df3,ihsym,npts8,pxdbmax) ! Input: -! k pointer to the most recent new data -! ntrperiod T/R sequence length, minutes -! nsps samples per symbol, at 12000 Hz -! ndiskdat 0/1 to indicate if data from disk -! nb 0/1 status of noise blanker (off/on) -! nbslider NB setting, 0-100 +! k pointer to the most recent new data +! ntrperiod T/R sequence length, minutes +! nsps samples per symbol, at 12000 Hz +! bLowSidelobes true to use windowed FFTs +! ndiskdat 0/1 to indicate if data from disk +! nb 0/1 status of noise blanker (off/on) +! nbslider NB setting, 0-100 ! Output: ! pxdb raw power (0-90 dB) @@ -29,6 +30,7 @@ subroutine symspec(shared_data,k,ntrperiod,nsps,ingain,nminw,pxdb,s, & real*4 tmp(NSMAX) complex cx(0:MAXFFT3/2) integer nch(7) + logical*1 bLowSidelobes common/spectra/syellow(NSMAX),ref(0:3456),filter(0:3456) data k0/99999999/,nfft3z/0/ @@ -48,11 +50,8 @@ subroutine symspec(shared_data,k,ntrperiod,nsps,ingain,nminw,pxdb,s, & if(nfft3.ne.nfft3z) then ! Compute new window pi=4.0*atan(1.0) - width=0.25*nsps - do i=1,nfft3 - z=(i-nfft3/2)/width - w3(i)=exp(-z*z) - enddo + w3=0 + call nuttal_window(w3,nfft3) nfft3z=nfft3 endif @@ -86,10 +85,10 @@ subroutine symspec(shared_data,k,ntrperiod,nsps,ingain,nminw,pxdb,s, & enddo ihsym=ihsym+1 -! xc(0:nfft3-1)=w3(1:nfft3)*xc(0:nfft3-1) !Apply window w3 - call four2a(xc,nfft3,1,-1,0) !Real-to-complex FFT + if(bLowSidelobes) xc(0:nfft3-1)=w3(1:nfft3)*xc(0:nfft3-1) !Apply window + call four2a(xc,nfft3,1,-1,0) !Real-to-complex FFT - df3=12000.0/nfft3 !JT9-1: 0.732 Hz = 0.42 * tone spacing + df3=12000.0/nfft3 !JT9: 0.732 Hz = 0.42 * tone spacing iz=min(NSMAX,nint(5000.0/df3)) fac=(1.0/nfft3)**2 do i=1,iz diff --git a/lib/wavhdr.f90 b/lib/wavhdr.f90 index b54fba787..6568c1fa6 100644 --- a/lib/wavhdr.f90 +++ b/lib/wavhdr.f90 @@ -37,17 +37,17 @@ module wavhdr subroutine set_wsjtx_wav_params(fMHz,mode,nsubmode,ntrperiod,id2) - parameter (NBANDS=23,NMODES=11) + parameter (NBANDS=23,NMODES=13) character*8 mode,modes(NMODES) integer*2 id2(4) - integer iperiod(7) + integer iperiod(8) real fband(NBANDS) data fband/0.137,0.474,1.8,3.5,5.1,7.0,10.14,14.0,18.1,21.0,24.9, & 28.0,50.0,144.0,222.0,432.0,902.0,1296.0,2304.0,3400.0, & 5760.0,10368.0,24048.0/ data modes/'Echo','FSK441','ISCAT','JT4','JT65','JT6M','JT9', & - 'JT9+JT65','JTMS','JTMSK','WSPR'/ - data iperiod/5,10,15,30,60,120,900/ + 'JT9+JT65','JTMS','JTMSK','WSPR','FT8','FT2'/ + data iperiod/5,10,15,30,60,120,900,0/ dmin=1.e30 iband=0 @@ -64,7 +64,7 @@ module wavhdr enddo ip=0 - do i=1,7 + do i=1,8 if(ntrperiod.eq.iperiod(i)) ip=i enddo @@ -78,15 +78,15 @@ module wavhdr subroutine get_wsjtx_wav_params(id2,band,mode,nsubmode,ntrperiod,ok) - parameter (NBANDS=23,NMODES=11) + parameter (NBANDS=23,NMODES=13) character*8 mode,modes(NMODES) character*6 band,bands(NBANDS) integer*2 id2(4) - integer iperiod(7) + integer iperiod(8) logical ok data modes/'Echo','FSK441','ISCAT','JT4','JT65','JT6M','JT9', & - 'JT9+JT65','JTMS','JTMSK','WSPR'/ - data iperiod/5,10,15,30,60,120,900/ + 'JT9+JT65','JTMS','JTMSK','WSPR','FT8','FT2'/ + data iperiod/5,10,15,30,60,120,900,0/ data bands/'2190m','630m','160m','80m','60m','40m','30m','20m', & '17m','15m','12m','10m','6m','2m','1.25m','70cm','33cm', & '23cm','13cm','9cm','6cm','3cm','1.25cm'/ @@ -95,7 +95,7 @@ module wavhdr if(id2(1).lt.1 .or. id2(1).gt.NBANDS) ok=.false. if(id2(2).lt.1 .or. id2(2).gt.NMODES) ok=.false. if(id2(3).lt.1 .or. id2(3).gt.8) ok=.false. - if(id2(4).lt.1 .or. id2(4).gt.7) ok=.false. + if(id2(4).lt.1 .or. id2(4).gt.8) ok=.false. if(ok) then band=bands(id2(1)) diff --git a/lib/wsprd/Makefile b/lib/wsprd/Makefile index 920407349..c8e15723f 100644 --- a/lib/wsprd/Makefile +++ b/lib/wsprd/Makefile @@ -1,11 +1,10 @@ CC = gcc -#CC = clang-3.5 FC = gfortran -CFLAGS= -I/usr/include -Wall -Wno-missing-braces -O3 -ffast-math +CFLAGS= -I/usr/include -Wall -Wno-missing-braces -Wno-unused-result -O3 -ffast-math LDFLAGS = -L/usr/lib -FFLAGS = -O2 -Wall -Wno-conversion -LIBS = -lfftw3f -lm +FFLAGS = -O2 -Wall -Wno-conversion +LIBS = -lfftw3f -lm -lgfortran # Default rules %.o: %.c $(DEPS) @@ -14,21 +13,27 @@ LIBS = -lfftw3f -lm ${FC} ${FFLAGS} -c $< %.o: %.F ${FC} ${FFLAGS} -c $< -%.o: %.f90 +%.o: %.f90 ${FC} ${FFLAGS} -c $< %.o: %.F90 ${FC} ${FFLAGS} -c $< -all: wsprd wsprsim wsprd_exp +all: wsprd wsprsim DEPS = wsprsim_utils.h wsprd_utils.h fano.h jelinek.h nhash.h -OBJS1 = wsprd.o wsprsim_utils.o wsprd_utils.o tab.o fano.o jelinek.o nhash.o + +indexx.o: ../indexx.f90 + ${FC} -o indexx.o ${FFLAGS} -c ../indexx.f90 + +OBJS1 = wsprd.o wsprsim_utils.o wsprd_utils.o tab.o fano.o jelinek.o nhash.o indexx.o osdwspr.o + wsprd: $(OBJS1) $(CC) -o $@ $^ $(CFLAGS) $(LDFLAGS) $(LIBS) OBJS2 = wsprsim.o wsprsim_utils.o wsprd_utils.o tab.o fano.o nhash.o + wsprsim: $(OBJS2) $(CC) -o $@ $^ $(CFLAGS) $(LDFLAGS) $(LIBS) clean: - rm *.o wsprd wsprsim + $(RM) *.o wsprd wsprsim diff --git a/main.cpp b/main.cpp index fd510bffb..1ea0c6a49 100644 --- a/main.cpp +++ b/main.cpp @@ -91,8 +91,8 @@ namespace int main(int argc, char *argv[]) { - // Add timestamps to all debug messages - qSetMessagePattern ("[%{time yyyyMMdd HH:mm:ss.zzz t} %{if-debug}D%{endif}%{if-info}I%{endif}%{if-warning}W%{endif}%{if-critical}C%{endif}%{if-fatal}F%{endif}] %{message}"); + // ### Add timestamps to all debug messages +// qSetMessagePattern ("[%{time yyyyMMdd HH:mm:ss.zzz t} %{if-debug}D%{endif}%{if-info}I%{endif}%{if-warning}W%{endif}%{if-critical}C%{endif}%{if-fatal}F%{endif}] %{message}"); init_random_seed (); diff --git a/models/FrequencyList.cpp b/models/FrequencyList.cpp index e57977d1d..d080f76cf 100644 --- a/models/FrequencyList.cpp +++ b/models/FrequencyList.cpp @@ -68,6 +68,7 @@ namespace {7038600, Modes::WSPR, IARURegions::ALL}, {7074000, Modes::FT8, IARURegions::ALL}, {7076000, Modes::JT65, IARURegions::ALL}, + {7078000, Modes::FT4, IARURegions::ALL}, {7078000, Modes::JT9, IARURegions::ALL}, {10136000, Modes::FT8, IARURegions::ALL}, @@ -78,6 +79,7 @@ namespace {14095600, Modes::WSPR, IARURegions::ALL}, {14074000, Modes::FT8, IARURegions::ALL}, {14076000, Modes::JT65, IARURegions::ALL}, + {14078000, Modes::FT4, IARURegions::ALL}, {14078000, Modes::JT9, IARURegions::ALL}, {18100000, Modes::FT8, IARURegions::ALL}, diff --git a/models/Modes.cpp b/models/Modes.cpp index 9d21459c7..838585b2a 100644 --- a/models/Modes.cpp +++ b/models/Modes.cpp @@ -23,7 +23,8 @@ namespace "MSK144", "QRA64", "FreqCal", - "FT8" + "FT8", + "FT4" }; std::size_t constexpr mode_names_size = sizeof (mode_names) / sizeof (mode_names[0]); } diff --git a/models/Modes.hpp b/models/Modes.hpp index cdf299749..fea55e4d4 100644 --- a/models/Modes.hpp +++ b/models/Modes.hpp @@ -49,6 +49,7 @@ public: QRA64, FreqCal, FT8, + FT4, MODES_END_SENTINAL_AND_COUNT // this must be last }; Q_ENUM (Mode) diff --git a/samples/CMakeLists.txt b/samples/CMakeLists.txt index 78bdcf07e..cab1609cb 100644 --- a/samples/CMakeLists.txt +++ b/samples/CMakeLists.txt @@ -1,4 +1,5 @@ set (SAMPLE_FILES + FT4/190106_000115.wav FT8/181201_180245.wav ISCAT/ISCAT-A/VK7MO_110401_235515.wav ISCAT/ISCAT-B/K0AWU_100714_115000.wav diff --git a/samples/FT4/190106_000115.wav b/samples/FT4/190106_000115.wav new file mode 100644 index 000000000..cf23159f8 Binary files /dev/null and b/samples/FT4/190106_000115.wav differ diff --git a/soundout.cpp b/soundout.cpp index a4f8800df..b21675b3a 100644 --- a/soundout.cpp +++ b/soundout.cpp @@ -101,7 +101,7 @@ void SoundOutput::restart (QIODevice * source) m_stream->setBufferSize (m_stream->format().bytesForDuration((m_msBuffered ? m_msBuffered : MS_BUFFERED) * 1000)); // qDebug() << "B" << m_stream->bufferSize() << // m_stream->periodSize() << m_stream->notifyInterval(); - + m_stream->setCategory ("production"); m_stream->start (source); } diff --git a/widgets/displaytext.cpp b/widgets/displaytext.cpp index ca3372f48..eca4dd4f5 100644 --- a/widgets/displaytext.cpp +++ b/widgets/displaytext.cpp @@ -454,6 +454,7 @@ void DisplayText::displayDecodedText(DecodedText const& decodedText, QString con void DisplayText::displayTransmittedText(QString text, QString modeTx, qint32 txFreq,bool bFastMode) { QString t1=" @ "; + if(modeTx=="FT4") t1=" + "; if(modeTx=="FT8") t1=" ~ "; if(modeTx=="JT4") t1=" $ "; if(modeTx=="JT65") t1=" # "; @@ -461,7 +462,7 @@ void DisplayText::displayTransmittedText(QString text, QString modeTx, qint32 tx QString t2; t2.sprintf("%4d",txFreq); QString t; - if(bFastMode or modeTx=="FT8") { + if(bFastMode or modeTx=="FT8" or modeTx=="FT4") { t = QDateTime::currentDateTimeUtc().toString("hhmmss") + \ " Tx " + t2 + t1 + text; } else if(modeTx.mid(0,6)=="FT8fox") { diff --git a/widgets/mainwindow.cpp b/widgets/mainwindow.cpp index 1fefdbe4b..b318d2528 100644 --- a/widgets/mainwindow.cpp +++ b/widgets/mainwindow.cpp @@ -86,8 +86,8 @@ extern "C" { //----------------------------------------------------- C and Fortran routines void symspec_(struct dec_data *, int* k, int* ntrperiod, int* nsps, int* ingain, - int* minw, float* px, float s[], float* df3, int* nhsym, int* npts8, - float *m_pxmax); + bool* bLowSidelobes, int* minw, float* px, float s[], float* df3, + int* nhsym, int* npts8, float *m_pxmax); void hspec_(short int d2[], int* k, int* nutc0, int* ntrperiod, int* nrxfreq, int* ntol, int* nContest, bool* bmsk144, bool* btrain, double const pcoeffs[], int* ingain, @@ -99,6 +99,15 @@ extern "C" { void genft8_(char* msg, int* i3, int* n3, char* msgsent, char ft8msgbits[], int itone[], fortran_charlen_t, fortran_charlen_t); + void genft4_(char* msg, int* ichk, char* msgsent, int itone[], + fortran_charlen_t, fortran_charlen_t); + + void gen_ft8wave_(int itone[], int* nsym, int* nsps, float* fsample, float* f0, + float xjunk[], float wave[], int* icmplx, int* nwave); + + void gen_ft4wave_(int itone[], int* nsym, int* nsps, float* fsample, float* f0, + float wave[], int* nwave); + void gen4_(char* msg, int* ichk, char* msgsent, int itone[], int* itext, fortran_charlen_t, fortran_charlen_t); @@ -159,6 +168,14 @@ extern "C" { void plotsave_(float swide[], int* m_w , int* m_h1, int* irow); void chkcall_(char* w, char* basc_call, bool cok, int len1, int len2); + + void ft4_decode_(char* cdatetime, float* tbuf, int* nfa, int* nfb, int* nQSOProgress, + int* nContest, int* nfqso, short int id[], int* ndecodes, char* mycall, + char* hiscall, char* cqstr, char* line, char* ddir, int len1, + int len2, int len3, int len4, int len5, int len6); + + void get_ft4msg_(int* idecode, char* line, int len); + } int volatile itone[NUM_ISCAT_SYMBOLS]; //Audio tones for all Tx symbols @@ -555,6 +572,7 @@ MainWindow::MainWindow(QDir const& temp_directory, bool multiple, on_EraseButton_clicked (); QActionGroup* modeGroup = new QActionGroup(this); + ui->actionFT4->setActionGroup(modeGroup); ui->actionFT8->setActionGroup(modeGroup); ui->actionJT9->setActionGroup(modeGroup); ui->actionJT65->setActionGroup(modeGroup); @@ -730,6 +748,13 @@ MainWindow::MainWindow(QDir const& temp_directory, bool multiple, connect(&m_guiTimer, &QTimer::timeout, this, &MainWindow::guiUpdate); m_guiTimer.start(100); //### Don't change the 100 ms! ### + + FT4_TxTimer.setSingleShot(true); + connect(&FT4_TxTimer, &QTimer::timeout, this, &MainWindow::stopTx); + + FT4_WriteTxTimer.setSingleShot(true); + connect(&FT4_WriteTxTimer, &QTimer::timeout, this, &MainWindow::FT4_writeTx); + ptt0Timer.setSingleShot(true); connect(&ptt0Timer, &QTimer::timeout, this, &MainWindow::stopTx2); @@ -783,6 +808,9 @@ MainWindow::MainWindow(QDir const& temp_directory, bool multiple, ui->TxPowerComboBox->addItem(t); } + m_dateTimeRcvdRR73=QDateTime::currentDateTimeUtc(); + m_dateTimeSentTx3=QDateTime::currentDateTimeUtc(); + ui->labAz->setStyleSheet("border: 0px;"); ui->labAz->setText(""); auto t = "UTC dB DT Freq Message"; @@ -885,6 +913,7 @@ MainWindow::MainWindow(QDir const& temp_directory, bool multiple, if(m_bFast9) m_bFastMode=true; ui->cbFast9->setChecked(m_bFast9 or m_bFastMode); + if(m_mode=="FT4") on_actionFT4_triggered(); if(m_mode=="FT8") on_actionFT8_triggered(); if(m_mode=="JT4") on_actionJT4_triggered(); if(m_mode=="JT9") on_actionJT9_triggered(); @@ -959,7 +988,7 @@ MainWindow::MainWindow(QDir const& temp_directory, bool multiple, if(QCoreApplication::applicationVersion().contains("-devel") or QCoreApplication::applicationVersion().contains("-rc")) { - // QTimer::singleShot (0, this, SLOT (not_GA_warning_message ())); + QTimer::singleShot (0, this, SLOT (not_GA_warning_message ())); } if(!ui->cbMenus->isChecked()) { @@ -974,18 +1003,12 @@ void MainWindow::not_GA_warning_message () { MessageBox::critical_message (this, "

" - "IMPORTANT: New protocols for the FT8 and MSK144 modes " - "became the world‑wide standards on December 10, 2018." - , "

" - "WSJT‑X 2.0 cannot communicate in these modes with other " - "stations using WSJT‑X v1.9.1 or earlier." - "

" - "Please help by urging everyone to upgrade to WSJT‑X 2.0 " - "no later than January 1, 2019."); - -// QDateTime now=QDateTime::currentDateTime(); -// QDateTime timeout=QDateTime(QDate(2018,12,31)); -// if(now.daysTo(timeout) < 0) Q_EMIT finished(); + "This is a pre-release version of WSJT-X 2.1.0 made " + "available for testing purposes. It will become nonfunctional " + "after May 1, 2019."); + QDateTime now=QDateTime::currentDateTime(); + QDateTime timeout=QDateTime(QDate(2019,5,1)); + if(now.daysTo(timeout) < 0) Q_EMIT finished(); } void MainWindow::initialize_fonts () @@ -1349,14 +1372,17 @@ void MainWindow::dataSink(qint64 frames) int nsps=m_nsps; if(m_bFastMode) nsps=6912; int nsmo=m_wideGraph->smoothYellow()-1; - symspec_(&dec_data,&k,&trmin,&nsps,&m_inGain,&nsmo,&m_px,s,&m_df3,&m_ihsym,&m_npts8,&m_pxmax); + bool bLowSidelobes=m_config.lowSidelobes(); + symspec_(&dec_data,&k,&trmin,&nsps,&m_inGain,&bLowSidelobes,&nsmo,&m_px,s, + &m_df3,&m_ihsym,&m_npts8,&m_pxmax); if(m_mode=="WSPR") wspr_downsample_(dec_data.d2,&k); if(m_ihsym <=0) return; if(ui) ui->signal_meter_widget->setValue(m_px,m_pxmax); // Update thermometer if(m_monitoring || m_diskData) { m_wideGraph->dataSink2(s,m_df3,m_ihsym,m_diskData); } - if(m_mode=="MSK144") return; + if(m_mode=="FT4") ft4_rx(k); + if(m_mode=="MSK144" or m_mode=="FT4") return; fixStop(); if (m_mode == "FreqCal" @@ -1450,7 +1476,7 @@ void MainWindow::dataSink(qint64 frames) // the following is potential a threading hazard - not a good // idea to pass pointer to be processed in another thread m_saveWAVWatcher.setFuture (QtConcurrent::run (std::bind (&MainWindow::save_wave_file, - this, m_fnameWE, &dec_data.d2[0], m_TRperiod, m_config.my_callsign(), + this, m_fnameWE, &dec_data.d2[0], m_TRperiod*12000, m_config.my_callsign(), m_config.my_grid(), m_mode, m_nSubMode, m_freqNominal, m_hisCall, m_hisGrid))); if (m_mode=="WSPR") { QString c2name_string {m_fnameWE + ".c2"}; @@ -1509,7 +1535,7 @@ void MainWindow::startP1() p1.start(m_cmndP1); } -QString MainWindow::save_wave_file (QString const& name, short const * data, int seconds, +QString MainWindow::save_wave_file (QString const& name, short const * data, int samples, QString const& my_callsign, QString const& my_grid, QString const& mode, qint32 sub_mode, Frequency frequency, QString const& his_call, QString const& his_grid) const { @@ -1545,7 +1571,7 @@ QString MainWindow::save_wave_file (QString const& name, short const * data, int BWFFile wav {format, file_name, list_info}; if (!wav.open (BWFFile::WriteOnly) || 0 > wav.write (reinterpret_cast (data) - , sizeof (short) * seconds * format.sampleRate ())) + , sizeof (short) * samples)) { return file_name + ": " + wav.errorString (); } @@ -1557,7 +1583,6 @@ void MainWindow::fastSink(qint64 frames) { int k (frames); bool decodeNow=false; - if(k < m_k0) { //New sequence ? memcpy(fast_green2,fast_green,4*703); //Copy fast_green[] to fast_green2[] memcpy(fast_s2,fast_s,4*703*64); //Copy fast_s[] into fast_s2[] @@ -1661,7 +1686,7 @@ void MainWindow::fastSink(qint64 frames) // the following is potential a threading hazard - not a good // idea to pass pointer to be processed in another thread m_saveWAVWatcher.setFuture (QtConcurrent::run (std::bind (&MainWindow::save_wave_file, - this, m_fnameWE, &dec_data.d2[0], m_TRperiod, m_config.my_callsign(), + this, m_fnameWE, &dec_data.d2[0], m_TRperiod*12000, m_config.my_callsign(), m_config.my_grid(), m_mode, m_nSubMode, m_freqNominal, m_hisCall, m_hisGrid))); } if(m_mode!="MSK144") { @@ -1764,6 +1789,7 @@ void MainWindow::on_actionSettings_triggered() //Setup Dialog ui->actionEnable_AP_JT65->setVisible(false); } if(m_config.special_op_id()!=nContest0) ui->tx1->setEnabled(true); + chkFT4(); } } @@ -1888,6 +1914,14 @@ void MainWindow::keyPressEvent (QKeyEvent * e) break; case Qt::Key_F1: if(bAltF1F5) { + if(m_mode=="FT4") { + if(e->modifiers() & Qt::ControlModifier) { + ft4_tx(1); + } else { + ft4_tx(6); + } + return; + } auto_tx_mode(true); on_txb6_clicked(); return; @@ -1897,6 +1931,10 @@ void MainWindow::keyPressEvent (QKeyEvent * e) } case Qt::Key_F2: if(bAltF1F5) { + if(m_mode=="FT4") { + ft4_tx(2); + return; + } auto_tx_mode(true); on_txb2_clicked(); return; @@ -1906,6 +1944,10 @@ void MainWindow::keyPressEvent (QKeyEvent * e) } case Qt::Key_F3: if(bAltF1F5) { + if(m_mode=="FT4") { + ft4_tx(3); + return; + } auto_tx_mode(true); on_txb3_clicked(); return; @@ -1915,6 +1957,10 @@ void MainWindow::keyPressEvent (QKeyEvent * e) } case Qt::Key_F4: if(bAltF1F5) { + if(m_mode=="FT4") { + ft4_tx(4); + return; + } auto_tx_mode(true); on_txb4_clicked(); return; @@ -1925,6 +1971,10 @@ void MainWindow::keyPressEvent (QKeyEvent * e) } case Qt::Key_F5: if(bAltF1F5) { + if(m_mode=="FT4") { + ft4_tx(5); + return; + } auto_tx_mode(true); on_txb5_clicked(); return; @@ -1947,7 +1997,9 @@ void MainWindow::keyPressEvent (QKeyEvent * e) n=11; if(e->modifiers() & Qt::ControlModifier) n+=100; if(e->modifiers() & Qt::ShiftModifier) { - ui->TxFreqSpinBox->setValue(ui->TxFreqSpinBox->value()-60); + int offset=60; + if(m_mode=="FT4") offset=120; + ui->TxFreqSpinBox->setValue(ui->TxFreqSpinBox->value()-offset); } else{ bumpFqso(n); } @@ -1961,7 +2013,9 @@ void MainWindow::keyPressEvent (QKeyEvent * e) n=12; if(e->modifiers() & Qt::ControlModifier) n+=100; if(e->modifiers() & Qt::ShiftModifier) { - ui->TxFreqSpinBox->setValue(ui->TxFreqSpinBox->value()+60); + int offset=60; + if(m_mode=="FT4") offset=120; + ui->TxFreqSpinBox->setValue(ui->TxFreqSpinBox->value()+offset); } else { bumpFqso(n); } @@ -1976,7 +2030,7 @@ void MainWindow::keyPressEvent (QKeyEvent * e) return; case Qt::Key_X: if(e->modifiers() & Qt::AltModifier) { - foxTest(); +// foxTest(); return; } case Qt::Key_E: @@ -3083,7 +3137,6 @@ void MainWindow::readFromStdout() //readFromStdout //Right (Rx Frequency) window bool bDisplayRight=bAvgMsg; int audioFreq=decodedtext.frequencyOffset(); - if(m_mode=="FT8") { auto const& parts = decodedtext.string().remove("<").remove(">") .split (' ', QString::SkipEmptyParts); @@ -3377,7 +3430,7 @@ void MainWindow::guiUpdate() double tx1=0.0; double tx2=txDuration; - if(m_mode=="FT8") icw[0]=0; //No CW ID in FT8 mode + if(m_mode=="FT8" or m_mode=="FT4") icw[0]=0; //No CW ID in FT4 or FT8 mode if((icw[0]>0) and (!m_bFast9)) tx2 += icw[0]*2560.0/48000.0; //Full length including CW ID if(tx2>m_TRperiod) tx2=m_TRperiod; @@ -3535,7 +3588,7 @@ void MainWindow::guiUpdate() Q_EMIT m_config.transceiver_ptt (true); //Assert the PTT m_tx_when_ready = true; } - if(!m_bTxTime and !m_tune) m_btxok=false; //Time to stop transmitting + if(!m_bTxTime and !m_tune and m_mode!="FT4") m_btxok=false; //Time to stop transmitting } if(m_mode.startsWith ("WSPR") and @@ -3677,10 +3730,10 @@ void MainWindow::guiUpdate() } } - m_currentMessage = QString::fromLatin1(msgsent); + if(m_mode!="FT4") m_currentMessage = QString::fromLatin1(msgsent); m_bCallingCQ = CALLING == m_QSOProgress || m_currentMessage.contains (QRegularExpression {"^(CQ|QRZ) "}); - if(m_mode=="FT8") { + if(m_mode=="FT8" or m_mode=="FT4") { if(m_bCallingCQ && ui->cbFirst->isVisible () && ui->cbFirst->isChecked ()) { ui->cbFirst->setStyleSheet("QCheckBox{color:red}"); } else { @@ -3787,37 +3840,38 @@ void MainWindow::guiUpdate() } } - if (g_iptt == 1 && m_iptt0 == 0) - { - auto const& current_message = QString::fromLatin1 (msgsent); - if(m_config.watchdog () && !m_mode.startsWith ("WSPR") - && current_message != m_msgSent0) { - tx_watchdog (false); // in case we are auto sequencing - m_msgSent0 = current_message; - } + if (g_iptt == 1 && m_iptt0 == 0) { + auto const& current_message = QString::fromLatin1 (msgsent); + if(m_config.watchdog () && !m_mode.startsWith ("WSPR") + && current_message != m_msgSent0) { + tx_watchdog (false); // in case we are auto sequencing + m_msgSent0 = current_message; + } + if(m_mode!="FT4") { if(!m_tune) write_all("Tx",m_currentMessage); if (m_config.TX_messages () && !m_tune && SpecOp::FOX!=m_config.special_op_id()) { ui->decodedTextBrowser2->displayTransmittedText(current_message, m_modeTx, - ui->TxFreqSpinBox->value(),m_bFastMode); + ui->TxFreqSpinBox->value(),m_bFastMode); } - - switch (m_ntx) - { - case 1: m_QSOProgress = REPLYING; break; - case 2: m_QSOProgress = REPORT; break; - case 3: m_QSOProgress = ROGER_REPORT; break; - case 4: m_QSOProgress = ROGERS; break; - case 5: m_QSOProgress = SIGNOFF; break; - case 6: m_QSOProgress = CALLING; break; - default: break; // determined elsewhere - } - m_transmitting = true; - transmitDisplay (true); - statusUpdate (); } + switch (m_ntx) + { + case 1: m_QSOProgress = REPLYING; break; + case 2: m_QSOProgress = REPORT; break; + case 3: m_QSOProgress = ROGER_REPORT; break; + case 4: m_QSOProgress = ROGERS; break; + case 5: m_QSOProgress = SIGNOFF; break; + case 6: m_QSOProgress = CALLING; break; + default: break; // determined elsewhere + } + m_transmitting = true; + transmitDisplay (true); + statusUpdate (); + } + if(!m_btxok && m_btxok0 && g_iptt==1) stopTx(); if(m_startAnother) { @@ -3831,7 +3885,7 @@ void MainWindow::guiUpdate() } } - if(m_mode=="FT8" or m_mode=="MSK144") { + if(m_mode=="FT8" or m_mode=="MSK144" or m_mode=="FT4") { if(ui->txrb1->isEnabled() and (SpecOp::NA_VHF==m_config.special_op_id() or SpecOp::FIELD_DAY==m_config.special_op_id() or @@ -3847,6 +3901,7 @@ void MainWindow::guiUpdate() //Once per second: if(nsec != m_sec0) { +// qDebug() << "cc onesec" << (SpecOp::RTTY == m_config.special_op_id()); // if((!m_msgAvgWidget or (m_msgAvgWidget and !m_msgAvgWidget->isVisible())) // and (SpecOp::NONE < m_config.special_op_id()) and (SpecOp::HOUND > m_config.special_op_id())) on_actionFox_Log_triggered(); if(m_freqNominal!=0 and m_freqNominal<50000000 and m_config.enable_VHF_features()) { @@ -3861,12 +3916,13 @@ void MainWindow::guiUpdate() //To keep calling Fox, Hound must reactivate Enable Tx at least once every 2 minutes if(tHound >= 120 and m_ntx==1) auto_tx_mode(false); } + + progressBar.setVisible(!(m_mode=="FT4")); if(m_auto and m_mode=="Echo" and m_bEchoTxOK) { progressBar.setMaximum(6); progressBar.setValue(int(m_s6)); } - - if(m_mode!="Echo") { + if(m_mode!="Echo" and m_mode!="FT4") { if(m_monitoring or m_transmitting) { progressBar.setMaximum(m_TRperiod); int isec=int(fmod(tsec,m_TRperiod)); @@ -3905,6 +3961,7 @@ void MainWindow::guiUpdate() if(SpecOp::FOX==m_config.special_op_id() and ui->tabWidget->currentIndex()==2 and foxcom_.nslots==1) { t=m_fm1.trimmed(); } + if(m_mode=="FT4") t="Tx: "+ m_currentMessage; tx_status_label.setText(t.trimmed()); } } @@ -3980,7 +4037,6 @@ void MainWindow::startTx2() ui->decodedTextBrowser->appendText(t); } write_all("Tx",m_currentMessage); -// write_transmit_entry ("ALL_WSPR.TXT"); } } } @@ -4009,13 +4065,13 @@ void MainWindow::stopTx2() on_stopTxButton_clicked (); m_nTx73 = 0; } - if(m_mode.startsWith ("WSPR") and m_ntr==-1 and !m_tuneup) { + if(((m_mode.startsWith("WSPR") and m_ntr==-1) or m_mode=="FT4") and + !m_tuneup) { m_wideGraph->setWSPRtransmitted(); WSPR_scheduling (); m_ntr=0; } last_tx_label.setText("Last Tx: " + m_currentMessage.trimmed()); -//### if(m_mode=="FT8" and (SpecOp::HOUND == m_config.special_op_id())) auto_tx_mode(false); ### } void MainWindow::ba2msg(QByteArray ba, char message[]) //ba2msg() @@ -4120,6 +4176,7 @@ void MainWindow::on_txrb4_doubleClicked () auto const& my_callsign = m_config.my_callsign (); auto is_compound = my_callsign != m_baseCall; m_send_RR73 = !((is_compound && !shortList (my_callsign)) || m_send_RR73); + if(m_mode=="FT4") m_send_RR73=true; genStdMsgs (m_rpt); } @@ -4195,6 +4252,7 @@ void MainWindow::on_txb4_doubleClicked() auto const& my_callsign = m_config.my_callsign (); auto is_compound = my_callsign != m_baseCall; m_send_RR73 = !((is_compound && !shortList (my_callsign)) || m_send_RR73); + if(m_mode=="FT4") m_send_RR73=true; genStdMsgs (m_rpt); } @@ -4222,6 +4280,7 @@ void MainWindow::on_txb6_clicked() void MainWindow::doubleClickOnCall2(Qt::KeyboardModifiers modifiers) { + if(m_mode=="FT4" and m_inQSOwith!="") return; set_dateTimeQSO(-1); // reset our QSO start time m_decodedText2=true; doubleClickOnCall(modifiers); @@ -4230,10 +4289,7 @@ void MainWindow::doubleClickOnCall2(Qt::KeyboardModifiers modifiers) void MainWindow::doubleClickOnCall(Qt::KeyboardModifiers modifiers) { -// if(!(modifiers & Qt::AltModifier) and m_transmitting) { -// qDebug() << "aa" << "Double-click on decode is ignored while transmitting"; -// return; -// } + if(m_mode=="FT4" and m_inQSOwith!="") return; QTextCursor cursor; if(m_mode=="ISCAT") { MessageBox::information_message (this, @@ -4280,7 +4336,7 @@ void MainWindow::processMessage (DecodedText const& message, Qt::KeyboardModifie || ("JT9" == m_mode && mode != "@") || ("MSK144" == m_mode && !("&" == mode || "^" == mode)) || ("QRA64" == m_mode && mode.left (1) != ":")) { - return; + return; //Currently we do auto-sequencing only in FT4, FT8, and MSK144 } //Skip the rest if no decoded text extracted @@ -4337,6 +4393,7 @@ void MainWindow::processMessage (DecodedText const& message, Qt::KeyboardModifie return; } + // only allow automatic mode changes between JT9 and JT65, and when not transmitting if (!m_transmitting and m_mode == "JT9+JT65") { if (message.isJT9()) @@ -4369,7 +4426,7 @@ void MainWindow::processMessage (DecodedText const& message, Qt::KeyboardModifie ui->TxFreqSpinBox->setValue(frequency); } if(m_mode != "JT4" && m_mode != "JT65" && !m_mode.startsWith ("JT9") && - m_mode != "QRA64" && m_mode!="FT8") { + m_mode != "QRA64" && m_mode!="FT8" && m_mode!="FT4") { return; } } @@ -4488,6 +4545,7 @@ void MainWindow::processMessage (DecodedText const& message, Qt::KeyboardModifie } else { m_bTUmsg=false; m_nextCall=""; //### Temporary: disable use of "TU;" message + if(SpecOp::RTTY == m_config.special_op_id() and m_nextCall!="") { // We're in RTTY contest and have "nextCall" queued up: send a "TU; ..." message logQSOTimer.start(0); @@ -4510,7 +4568,8 @@ void MainWindow::processMessage (DecodedText const& message, Qt::KeyboardModifie } m_QSOProgress = SIGNOFF; } else if((m_QSOProgress >= REPORT - || (m_QSOProgress >= REPLYING && (m_mode=="MSK144" or m_mode=="FT8"))) + || (m_QSOProgress >= REPLYING && + (m_mode=="MSK144" or m_mode=="FT8" or m_mode=="FT4"))) && r.mid(0,1)=="R") { m_ntx=4; m_QSOProgress = ROGERS; @@ -4693,6 +4752,7 @@ void MainWindow::processMessage (DecodedText const& message, Qt::KeyboardModifie // i.e. compound version of same base call ui->dxCallEntry->setText (hiscall); } + if (hisgrid.contains (grid_regexp)) { if(ui->dxGridEntry->text().mid(0,4) != hisgrid) ui->dxGridEntry->setText(hisgrid); } @@ -4734,7 +4794,20 @@ void MainWindow::processMessage (DecodedText const& message, Qt::KeyboardModifie } if(m_transmitting) m_restart=true; - if (ui->cbAutoSeq->isVisible () && ui->cbAutoSeq->isChecked () && !m_bDoubleClicked) return; + if (ui->cbAutoSeq->isVisible () && ui->cbAutoSeq->isChecked () + && !m_bDoubleClicked && m_mode!="FT4") { + return; + } + if(m_mode=="FT4" and ui->cbAutoSeq->isChecked()) { + if((m_ntx==4 or m_ntx==5) and !m_diskData) { + save_FT4(); + logQSOTimer.start(0); // Log the QSO + } + if((m_ntx==3 and ui->cbFirst->isChecked()) or m_ntx==4 or m_bDoubleClicked) { + QThread::msleep(600); //Wait a bit. ### Is this a good idea??? ### + ft4_tx(m_ntx); + } + } if(m_config.quick_call()) auto_tx_mode(true); m_bDoubleClicked=false; } @@ -4747,6 +4820,7 @@ int MainWindow::setTxMsg(int n) if(n==3) ui->txrb3->setChecked(true); if(n==4) ui->txrb4->setChecked(true); if(n==5) ui->txrb5->setChecked(true); + if(n==6) ui->txrb6->setChecked(true); if(ui->tabWidget->currentIndex()==1) { m_ntx=7; //### FIX THIS ### m_gen_message_is_cq = false; @@ -4788,12 +4862,17 @@ void MainWindow::genCQMsg () } QString t=ui->tx6->text(); - if((m_mode=="FT8" or m_mode=="MSK144") and SpecOp::NONE != m_config.special_op_id() and - t.split(" ").at(1)==m_config.my_callsign() and stdCall(m_config.my_callsign())) { + if((m_mode=="FT4" or m_mode=="FT8" or m_mode=="MSK144") and + SpecOp::NONE != m_config.special_op_id() and + t.split(" ").at(1)==m_config.my_callsign() and + stdCall(m_config.my_callsign())) { if(SpecOp::NA_VHF == m_config.special_op_id()) t="CQ TEST" + t.mid(2,-1); if(SpecOp::EU_VHF == m_config.special_op_id()) t="CQ TEST" + t.mid(2,-1); if(SpecOp::FIELD_DAY == m_config.special_op_id()) t="CQ FD" + t.mid(2,-1); - if(SpecOp::RTTY == m_config.special_op_id()) t="CQ RU" + t.mid(2,-1); + if(SpecOp::RTTY == m_config.special_op_id()) { + if(m_config.RTTY_Exchange()!="SCC") t="CQ RU" + t.mid(2,-1); + if(m_config.RTTY_Exchange()=="SCC") t="CQ SCC" + t.mid(2,-1); + } ui->tx6->setText(t); } } else { @@ -4824,11 +4903,6 @@ bool MainWindow::stdCall(QString const& w) void MainWindow::genStdMsgs(QString rpt, bool unconditional) { -// Prevent abortQSO from working when a TU; message is already queued -// if(ui->tx3->text().left(4)=="TU; ") { -// return; -// } - genCQMsg (); auto const& hisCall=ui->dxCallEntry->text(); if(!hisCall.size ()) { @@ -4875,7 +4949,7 @@ void MainWindow::genStdMsgs(QString rpt, bool unconditional) int n=rpt.toInt(); rpt.sprintf("%+2.2d",n); - if(m_mode=="MSK144" or m_mode=="FT8") { + if(m_mode=="MSK144" or m_mode=="FT8" or m_mode=="FT4") { QString t2,t3; QString sent=rpt; QString rs,rst; @@ -4912,6 +4986,15 @@ void MainWindow::genStdMsgs(QString rpt, bool unconditional) msgtype(t + sent, ui->tx2); if(sent==rpt) msgtype(t + "R" + sent, ui->tx3); if(sent!=rpt) msgtype(t + "R " + sent, ui->tx3); + if(m_mode=="FT4" and SpecOp::RTTY==m_config.special_op_id()) { + QDateTime now=QDateTime::currentDateTimeUtc(); + int sinceTx3 = m_dateTimeSentTx3.secsTo(now); + int sinceRR73 = m_dateTimeRcvdRR73.secsTo(now); + if(m_bDoubleClicked and (qAbs(sinceTx3-12) <= 3) and (sinceRR73 < 5)) { + t="TU; " + ui->tx3->text(); + ui->tx3->setText(t); + } + } } if(m_mode=="MSK144" and m_bShMsgs) { @@ -4929,7 +5012,7 @@ void MainWindow::genStdMsgs(QString rpt, bool unconditional) } } - if((m_mode!="MSK144" and m_mode!="FT8")) { + if((m_mode!="MSK144" and m_mode!="FT8" and m_mode!="FT4")) { t=t00 + rpt; msgtype(t, ui->tx2); t=t0 + "R" + rpt; @@ -4945,7 +5028,7 @@ void MainWindow::genStdMsgs(QString rpt, bool unconditional) } t=t0 + (m_send_RR73 ? "RR73" : "RRR"); - if((m_mode=="MSK144" and !m_bShMsgs) or m_mode=="FT8") { + if((m_mode=="MSK144" and !m_bShMsgs) or m_mode=="FT8" or m_mode=="FT4") { if(!bHisCall and bMyCall) t=hisCall + " <" + my_callsign + "> " + (m_send_RR73 ? "RR73" : "RRR"); if(bHisCall and !bMyCall) t="<" + hisCall + "> " + my_callsign + " " + (m_send_RR73 ? "RR73" : "RRR"); } @@ -5392,6 +5475,7 @@ void MainWindow::on_logQSOButton_clicked() //Log QSO button m_dateTimeQSOOn, dateTimeQSOOff, m_freqNominal + ui->TxFreqSpinBox->value(), m_noSuffix, m_xSent, m_xRcvd, m_cabrilloLog.data ()); + m_inQSOwith=""; } void MainWindow::acceptQSO (QDateTime const& QSO_date_off, QString const& call, QString const& grid @@ -5431,10 +5515,10 @@ void MainWindow::acceptQSO (QDateTime const& QSO_date_off, QString const& call, if (m_config.clear_DX () and SpecOp::HOUND != m_config.special_op_id()) clearDX (); m_dateTimeQSOOn = QDateTime {}; auto special_op = m_config.special_op_id (); - if (SpecOp::NONE < special_op && special_op < SpecOp::FOX) - { - ui->sbSerialNumber->setValue (ui->sbSerialNumber->value () + 1); - } + if (SpecOp::NONE < special_op && special_op < SpecOp::FOX && + m_config.RTTY_Exchange()!="SCC") { + ui->sbSerialNumber->setValue(ui->sbSerialNumber->value() + 1); + } m_xSent.clear (); m_xRcvd.clear (); @@ -5503,12 +5587,57 @@ void MainWindow::displayWidgets(qint64 n) j=j>>1; } b=SpecOp::EU_VHF==m_config.special_op_id() or (SpecOp::RTTY==m_config.special_op_id() and - (m_config.RTTY_Exchange()=="#" or m_config.RTTY_Exchange()=="DX")); + (m_config.RTTY_Exchange()=="DX" or m_config.RTTY_Exchange()=="SCC")); ui->sbSerialNumber->setVisible(b); m_lastCallsign.clear (); // ensures Tx5 is updated for new modes genStdMsgs (m_rpt, true); } +void MainWindow::on_actionFT4_triggered() +{ + m_mode="FT4"; + m_modeTx="FT4"; + m_TRperiod=2147483647; + bool bVHF=m_config.enable_VHF_features(); + m_bFast9=false; + m_bFastMode=false; + WSPR_config(false); + switch_mode (Modes::FT4); + m_nsps=6912; + m_FFTSize = m_nsps/2; + Q_EMIT FFTSize (m_FFTSize); + m_hsymStop=50; + setup_status_bar (bVHF); + m_toneSpacing=12000.0/512.0; + ui->actionFT4->setChecked(true); + m_wideGraph->setMode(m_mode); + m_wideGraph->setModeTx(m_modeTx); + m_send_RR73=true; + VHF_features_enabled(bVHF); +// ui->cbAutoSeq->setChecked(false); + m_fastGraph->hide(); + m_wideGraph->show(); + ui->decodedTextLabel2->setText(" UTC dB DT Freq Message"); + m_wideGraph->setPeriod(m_TRperiod,m_nsps); + m_modulator->setTRPeriod(m_TRperiod); // TODO - not thread safe + m_detector->setTRPeriod(m_TRperiod); // TODO - not thread safe + ui->label_7->setText("Rx Frequency"); + ui->label_6->setText("Band Activity"); + ui->decodedTextLabel->setText( " UTC dB DT Freq Message"); + displayWidgets(nWidgets("011010000100111000010000100110001")); + ui->txrb2->setEnabled(true); + ui->txrb4->setEnabled(true); + ui->txrb5->setEnabled(true); + ui->txrb6->setEnabled(true); + ui->txb2->setEnabled(true); + ui->txb4->setEnabled(true); + ui->txb5->setEnabled(true); + ui->txb6->setEnabled(true); + ui->txFirstCheckBox->setEnabled(true); + chkFT4(); + statusChanged(); +} + void MainWindow::on_actionFT8_triggered() { m_mode="FT8"; @@ -5611,8 +5740,6 @@ void MainWindow::on_actionFT8_triggered() statusChanged(); } - - void MainWindow::on_actionJT4_triggered() { m_mode="JT4"; @@ -6180,7 +6307,7 @@ void MainWindow::on_reset_cabrillo_log_action_triggered () "They will be kept in the ADIF log file but will not be available " "for export in your Cabrillo log."))) { - ui->sbSerialNumber->setValue (1); + if(m_config.RTTY_Exchange()!="SCC") ui->sbSerialNumber->setValue(1); if (!m_cabrilloLog) m_cabrilloLog.reset (new CabrilloLog {&m_config}); m_cabrilloLog->reset (); } @@ -6766,7 +6893,17 @@ void MainWindow::transmit (double snr) } if (m_modeTx == "FT8") { - toneSpacing=12000.0/1920.0; +// toneSpacing=12000.0/1920.0; + toneSpacing=-3; + int nsym=79; + int nsps=4*1920; + float fsample=48000.0; + float f0=ui->TxFreqSpinBox->value() - m_XIT; + int icmplx=0; + int nwave=nsym*nsps; + gen_ft8wave_(const_cast(itone),&nsym,&nsps,&fsample,&f0,foxcom_.wave, + foxcom_.wave,&icmplx,&nwave); + if(m_config.x2ToneSpacing()) toneSpacing=2*12000.0/1920.0; if(m_config.x4ToneSpacing()) toneSpacing=4*12000.0/1920.0; if(SpecOp::FOX==m_config.special_op_id() and !m_tune) toneSpacing=-1; @@ -6776,6 +6913,15 @@ void MainWindow::transmit (double snr) true, false, snr, m_TRperiod); } + if (m_modeTx == "FT4") { +// toneSpacing=12000.0/512.0; //Generate Tx waveform from itone[] array + toneSpacing=-2.0; //Transmit a pre-computed, filtered waveform. + Q_EMIT sendMessage (NUM_FT4_SYMBOLS, + 512.0, ui->TxFreqSpinBox->value() - m_XIT, + toneSpacing, m_soundOutput, m_config.audio_output_channel (), + true, false, snr, 2); + } + if (m_modeTx == "QRA64") { if(m_nSubMode==0) toneSpacing=12000.0/6912.0; if(m_nSubMode==1) toneSpacing=2*12000.0/6912.0; @@ -7145,7 +7291,8 @@ void MainWindow::replyToCQ (QTime time, qint32 snr, float delta_time, quint32 de } QString format_string {"%1 %2 %3 %4 %5 %6"}; - auto const& time_string = time.toString ("~" == mode || "&" == mode ? "hhmmss" : "hhmm"); + auto const& time_string = time.toString ("~" == mode || "&" == mode + || "+" == mode ? "hhmmss" : "hhmm"); auto message_line = format_string .arg (time_string) .arg (snr, 3) @@ -7827,7 +7974,7 @@ void MainWindow::on_cbFirst_toggled(bool b) void MainWindow::on_cbAutoSeq_toggled(bool b) { if(!b) ui->cbFirst->setChecked(false); - ui->cbFirst->setVisible((m_mode=="FT8") and b); + ui->cbFirst->setVisible((m_mode=="FT8" or m_mode=="FT4") and b); } void MainWindow::on_measure_check_box_stateChanged (int state) @@ -8489,7 +8636,7 @@ void MainWindow::write_all(QString txRx, QString message) t.sprintf("%5d",ui->TxFreqSpinBox->value()); if(txRx=="Tx") msg=" 0 0.0" + t + " " + message; auto time = QDateTime::currentDateTimeUtc (); - time = time.addSecs (-(time.time ().second () % m_TRperiod)); + if(m_mode!="FT4") time = time.addSecs(-(time.time().second() % m_TRperiod)); t.sprintf("%10.3f ",m_freqNominal/1.e6); if(m_diskData) { line=m_fileDateTime + t + txRx + " " + m_mode.leftJustified(6,' ') + msg; @@ -8511,3 +8658,251 @@ void MainWindow::write_all(QString txRx, QString message) MessageBox::warning_message(this, tr ("Log File Error"), message2); }); } } + +void MainWindow::ft4_rx(int k) +{ + static int nhsec0=-1; + static bool wrapped=false; + short id[60000]; + const int istep=3456; + const int k_enough=55296; //4.608 s + + if(knhsec) nhsec0=-1; + if(nhsec==nhsec0) return; + if(k=NRING) { + j=j-NRING; + wrapped=true; + } + } + if(j>60000) wrapped=false; + if(m_saveAll and ((k-m_kin0)/12000.0 > 15.0) and !m_diskData) save_FT4(); + + if(k>=NRING) { + if(m_saveAll and !m_diskData) save_FT4(); + //Wrap the ring buffer pointer + k=k-NRING; + dec_data.params.kin=k; + } + + QByteArray ba; + if(m_diskData) { + ba=(m_fileDateTime + ".000").toLatin1(); + } else { + auto time = QDateTime::currentDateTimeUtc (); + ba=time.toString("yyMMdd_hhmmss.sss").toLatin1(); + } + char* cdatetime=ba.data(); + + strncpy(dec_data.params.mycall, (m_config.my_callsign()+" ").toLatin1(),12); + char mycall[13]; + strncpy(mycall,m_config.my_callsign().toLatin1(),12); + char hiscall[13]; + strncpy(hiscall,m_hisCall.toLatin1(),12); + + char line[61]; + int nfqso=1500; + int ndecodes=0; + int nfa=m_wideGraph->nStartFreq(); + int nfb=m_wideGraph->Fmax(); + int nQSOProgress = static_cast ( m_QSOProgress ); + int nContest = static_cast (m_config.special_op_id()); + QString dataDir; + dataDir = m_config.writeable_data_dir ().absolutePath (); + char ddir[512]; + strncpy(ddir,dataDir.toLatin1(), sizeof (ddir) - 1); + char cqstr[4]; + strncpy(cqstr," ",4); + if(SpecOp::NA_VHF == m_config.special_op_id()) strncpy(cqstr,"TEST",4); + if(SpecOp::EU_VHF == m_config.special_op_id()) strncpy(cqstr,"TEST",4); + if(SpecOp::FIELD_DAY == m_config.special_op_id()) strncpy(cqstr,"FD",2); + if(SpecOp::RTTY == m_config.special_op_id()) { + if(m_config.RTTY_Exchange()!="SCC") strncpy(cqstr,"RU",2); + if(m_config.RTTY_Exchange()=="SCC") strncpy(cqstr,"SCC",3); + } + ft4_decode_(cdatetime,&tbuf,&nfa,&nfb,&nQSOProgress,&nContest,&nfqso,id,&ndecodes,&mycall[0],&hiscall[0], + &cqstr[0],&line[0],&ddir[0],17,12,12,4,61,512); + line[60]=0; + for (int idecode=1; idecode<=ndecodes; idecode++) { + get_ft4msg_(&idecode,&line[0],61); + line[60]=0; + QString sline{QString::fromLatin1(line)}; + DecodedText decodedtext {sline.replace(QChar::LineFeed,"")}; + ui->decodedTextBrowser->displayDecodedText (decodedtext,m_baseCall,m_mode, + m_config.DXCC(),m_logBook,m_currentBand,m_config.ppfx()); + +//Right (Rx Frequency) window +// int audioFreq=decodedtext.frequencyOffset(); + auto const& parts = decodedtext.string().remove("<").remove(">") + .split (' ', QString::SkipEmptyParts); + if(parts.size() > 6) { + int iFirstCall=5; + if(parts[5]=="TU;") iFirstCall=6; + auto for_us = parts[iFirstCall].contains(m_baseCall); + if(m_baseCall==m_config.my_callsign() and m_baseCall!=parts[iFirstCall]) for_us=false; + if(m_bCallingCQ && !m_bAutoReply && for_us && ui->cbFirst->isChecked()) { + m_bDoubleClicked=true; + m_bAutoReply = true; + ui->cbFirst->setStyleSheet(""); + } + if(for_us) { + ui->decodedTextBrowser2->displayDecodedText(decodedtext,m_baseCall, + m_mode,m_config.DXCC(),m_logBook,m_currentBand,m_config.ppfx()); + if(decodedtext.string().trimmed().contains(m_inQSOwith)) processMessage(decodedtext); + m_QSOText = decodedtext.string().trimmed (); + } + if(for_us and parts[iFirstCall+2]=="RR73") m_dateTimeRcvdRR73=QDateTime::currentDateTimeUtc(); + write_all("Rx",decodedtext.string().trimmed()); + } + } + nhsec0=nhsec; + if(m_diskData and (k > (dec_data.params.kin-istep))) m_startAnother=m_loopall; + if(m_bNoMoreFiles) { + MessageBox::information_message(this, tr("Just one more file to open.")); + m_bNoMoreFiles=false; + } +} + +void MainWindow::ft4_tx(int ntx) +{ + if(g_iptt!=0) return; //Already transmitting? + static char message[38]; + static char msgsent[38]; + QByteArray ba; + m_ntx=ntx; + setTxMsg(m_ntx); + if(m_ntx == 1) ba=ui->tx1->text().toLocal8Bit(); + if(m_ntx == 2) ba=ui->tx2->text().toLocal8Bit(); + if(m_ntx == 3) ba=ui->tx3->text().toLocal8Bit(); + if(m_ntx == 4) ba=ui->tx4->text().toLocal8Bit(); + if(m_ntx == 5) ba=ui->tx5->currentText().toLocal8Bit(); + if(m_ntx == 6) ba=ui->tx6->text().toLocal8Bit(); + QString msg = QString::fromLatin1(ba.data()); + if(m_ntx==2 or m_ntx==3) m_inQSOwith=m_hisCall; + if(msg.trimmed().length()==0) return; //Don't transmit a blank message, or ... + if(m_diskData) return; //... in response to a decode from disk + ba2msg(ba,message); + int ichk=0; + genft4_(message, &ichk, msgsent, const_cast(itone), 37, 37); + msgsent[37]=0; + m_currentMessage = QString::fromLatin1(msgsent).trimmed(); + tx_status_label.setStyleSheet("QLabel{background-color: #ffff33}"); + tx_status_label.setText("TX: " + m_currentMessage); + if(m_ntx==2 or m_ntx==3) { + QStringList t=ui->tx2->text().split(' ', QString::SkipEmptyParts); + int n=t.size(); + m_xSent=t.at(n-2) + " " + t.at(n-1); + } + auto_tx_mode(true); //Enable Tx + icw[0]=0; + g_iptt = 1; + setRig (); + setXIT (ui->TxFreqSpinBox->value ()); + + int nsym=103; + int nsps=4*512; + float fsample=48000.0; + float f0=ui->TxFreqSpinBox->value() - m_XIT; + int nwave=(nsym+2)*nsps; + gen_ft4wave_(const_cast(itone),&nsym,&nsps,&fsample,&f0,foxcom_.wave,&nwave); + if(m_ntx==3) m_dateTimeSentTx3=QDateTime::currentDateTimeUtc(); + Q_EMIT m_config.transceiver_ptt (true); //Assert the PTT + m_tx_when_ready = true; + qint64 ms=QDateTime::currentMSecsSinceEpoch(); + m_modulator->set_ms0(ms); + FT4_TxTimer.start(4600); //Slightly more than FT4 transmission length + + if (g_iptt == 1 && m_iptt0 == 0) { + auto const& current_message = QString::fromLatin1 (msgsent); + FT4_WriteTxTimer.start(100); //Why is a delay necessary to ensure Tx after Rx in all.txt? + if (m_config.TX_messages () && !m_tune && SpecOp::FOX!=m_config.special_op_id()) { + ui->decodedTextBrowser2->displayTransmittedText(current_message, m_modeTx, + ui->TxFreqSpinBox->value(),m_bFastMode); + } + + switch (m_ntx) + { + case 1: m_QSOProgress = REPLYING; break; + case 2: m_QSOProgress = REPORT; break; + case 3: m_QSOProgress = ROGER_REPORT; break; + case 4: m_QSOProgress = ROGERS; break; + case 5: m_QSOProgress = SIGNOFF; break; + case 6: m_QSOProgress = CALLING; break; + default: break; // determined elsewhere + } + m_transmitting = true; + transmitDisplay (true); + statusUpdate (); + } + m_dateTimeQSOOn=QDateTime::currentDateTimeUtc(); + if(!m_btxok && m_btxok0 && g_iptt==1) stopTx(); + if(m_saveAll and !m_diskData) save_FT4(); +} + +void MainWindow::FT4_writeTx() +{ + write_all("Tx",m_currentMessage); +} + +void MainWindow::save_FT4() +{ + double tsec=(dec_data.params.kin - m_kin0)/12000.0; + if(tsec<4.4) return; //Saved data must be at least 4.4 seconds long. + auto time = QDateTime::currentDateTimeUtc (); + QString t=time.toString("yyMMdd_hhmmss"); + m_fnameWE=m_config.save_directory().absoluteFilePath(t); + +// The following is potential a threading hazard - not a good +// idea to pass pointer to be processed in another thread + int nsamples=dec_data.params.kin - m_kin0 + 1; + m_saveWAVWatcher.setFuture (QtConcurrent::run (std::bind (&MainWindow::save_wave_file, + this, m_fnameWE, &dec_data.d2[m_kin0], nsamples, m_config.my_callsign(), + m_config.my_grid(), m_mode, m_nSubMode, m_freqNominal, m_hisCall, + m_hisGrid))); + + m_kin0=dec_data.params.kin; +} + +void MainWindow::chkFT4() +{ + if(m_mode!="FT4") return; + if(m_config.special_op_id()==SpecOp::RTTY or m_config.special_op_id()==SpecOp::NA_VHF) { + ui->cbAutoSeq->setEnabled(true); + ui->cbFirst->setEnabled(true); + ui->labDXped->setVisible(true); + } else { + ui->cbAutoSeq->setChecked(false); + ui->cbAutoSeq->setEnabled(false); + ui->cbFirst->setChecked(false); + ui->cbFirst->setEnabled(false); + ui->cbFirst->setVisible(false); + ui->labDXped->setVisible(false); + } + ui->cbFirst->setVisible(ui->cbAutoSeq->isChecked()); + + if (SpecOp::NONE < m_config.special_op_id () && SpecOp::FOX > m_config.special_op_id ()) { + QString t0=""; + if(SpecOp::NA_VHF==m_config.special_op_id()) t0+="NA VHF"; + if(SpecOp::EU_VHF==m_config.special_op_id()) t0+="EU VHF"; + if(SpecOp::FIELD_DAY==m_config.special_op_id()) t0+="Field Day"; + if(SpecOp::RTTY==m_config.special_op_id()) t0+="RTTY"; + if(t0=="") { + ui->labDXped->setVisible(false); + } else { + ui->labDXped->setVisible(true); + ui->labDXped->setText(t0); + } + on_contest_log_action_triggered(); + } +} diff --git a/widgets/mainwindow.h b/widgets/mainwindow.h index acabffe7f..8be317596 100644 --- a/widgets/mainwindow.h +++ b/widgets/mainwindow.h @@ -46,9 +46,11 @@ #define NUM_MSK144_SYMBOLS 144 //s8 + d48 + s8 + d80 #define NUM_QRA64_SYMBOLS 84 //63 data + 21 sync #define NUM_FT8_SYMBOLS 79 +#define NUM_FT4_SYMBOLS 103 #define NUM_CW_SYMBOLS 250 #define TX_SAMPLE_RATE 48000 #define N_WIDGETS 33 +#define NRING 3456000 extern int volatile itone[NUM_ISCAT_SYMBOLS]; //Audio tones for all Tx symbols extern int volatile icw[NUM_CW_SYMBOLS]; //Dits for CW ID @@ -200,6 +202,7 @@ private slots: void on_actionJT65_triggered(); void on_actionJT9_JT65_triggered(); void on_actionJT4_triggered(); + void on_actionFT4_triggered(); void on_actionFT8_triggered(); void on_TxFreqSpinBox_valueChanged(int arg1); void on_actionSave_decoded_triggered(); @@ -309,6 +312,8 @@ private slots: void on_comboBoxHoundSort_activated (int index); void not_GA_warning_message (); void checkMSK144ContestType(); + void ft4_rx(int k); + void ft4_tx(int ntx); int setTxMsg(int n); bool stdCall(QString const& w); @@ -343,6 +348,7 @@ private: void hideMenus(bool b); void foxTest(); void setColorHighlighting(); + void chkFT4(); NetworkAccessManager m_network_manager; bool m_valid; @@ -461,6 +467,7 @@ private: qint32 m_tFoxTxSinceCQ=999; //Fox Tx cycles since most recent CQ qint32 m_nFoxFreq; //Audio freq at which Hound received a call from Fox qint32 m_nSentFoxRrpt=0; //Serial number for next R+rpt Hound will send to Fox + qint32 m_kin0=0; bool m_btxok; //True if OK to transmit bool m_diskData; @@ -576,6 +583,8 @@ private: QTimer minuteTimer; QTimer splashTimer; QTimer p1Timer; + QTimer FT4_TxTimer; + QTimer FT4_WriteTxTimer; QString m_path; QString m_baseCall; @@ -609,6 +618,7 @@ private: QString m_nextCall; QString m_nextGrid; QString m_fileDateTime; + QString m_inQSOwith; QSet m_pfx; QSet m_sfx; @@ -627,12 +637,23 @@ private: QMap m_foxQSO; //Key = HoundCall, value = parameters for QSO in progress QMap m_loggedByFox; //Key = HoundCall, value = logged band + struct FixupQSO //Info for fixing Fox's log from file "FoxQSO.txt" + { + QString grid; //Hound's declared locator + QString sent; //Report sent to Hound + QString rcvd; //Report received from Hound + QDateTime QSO_time; + }; + QMap m_fixupQSO; //Key = HoundCall, value = info for QSO in progress + QQueue m_houndQueue; //Selected Hounds available for starting a QSO QQueue m_foxQSOinProgress; //QSOs in progress: Fox has sent a report QQueue m_foxRateQueue; QDateTime m_dateTimeQSOOn; QDateTime m_dateTimeLastTX; + QDateTime m_dateTimeSentTx3; + QDateTime m_dateTimeRcvdRR73; QSharedMemory *mem_jt9; QString m_QSOText; @@ -709,7 +730,7 @@ private: QString save_wave_file (QString const& name , short const * data - , int seconds + , int samples , QString const& my_callsign , QString const& my_grid , QString const& mode @@ -743,6 +764,8 @@ private: void foxTxSequencer(); void foxGenWaveform(int i,QString fm); void writeFoxQSO (QString const& msg); + void FT4_writeTx(); + void save_FT4(); }; extern int killbyname(const char* progName); diff --git a/widgets/mainwindow.ui b/widgets/mainwindow.ui index 4e39e6d6c..a779cb413 100644 --- a/widgets/mainwindow.ui +++ b/widgets/mainwindow.ui @@ -2705,6 +2705,7 @@ list. The list can be maintained in Settings (F2). Mode + @@ -2773,9 +2774,6 @@ list. The list can be maintained in Settings (F2). About WSJT-X - - Ctrl+F1 - @@ -3338,6 +3336,14 @@ list. The list can be maintained in Settings (F2). Erase WSPR hashtable + + + true + + + FT4 + + diff --git a/widgets/plotter.cpp b/widgets/plotter.cpp index 761dae08d..60f0892c3 100644 --- a/widgets/plotter.cpp +++ b/widgets/plotter.cpp @@ -166,7 +166,7 @@ void CPlotter::draw(float swide[], bool bScroll, bool bRed) ymin=1.e30; if(swide[0]>1.e29 and swide[0]< 1.5e30) painter1.setPen(Qt::green); - if(swide[0]>1.4e30) painter1.setPen(Qt::yellow); + if(swide[0]>1.4e30) painter1.setPen(Qt::red); if(!m_bReplot) { m_j=0; int irow=-1; @@ -235,13 +235,15 @@ void CPlotter::draw(float swide[], bool bScroll, bool bRed) if(m_bReplot) return; if(swide[0]>1.0e29) m_line=0; + if(m_mode=="FT4" and m_line==34) m_line=0; if(m_line == painter1.fontMetrics ().height ()) { painter1.setPen(Qt::white); QString t; qint64 ms = QDateTime::currentMSecsSinceEpoch() % 86400000; int n=(ms/1000) % m_TRperiod; + if(m_mode=="FT4") n=0; QDateTime t1=QDateTime::currentDateTimeUtc().addSecs(-n); - if(m_TRperiod < 60) { + if(m_TRperiod < 60 or m_mode=="FT4") { t=t1.toString("hh:mm:ss") + " " + m_rxBand; } else { t=t1.toString("hh:mm") + " " + m_rxBand; @@ -409,7 +411,7 @@ void CPlotter::DrawOverlay() //DrawOverlay() } float bw=9.0*12000.0/m_nsps; //JT9 - + if(m_mode=="FT4") bw=3*12000.0/512.0; //FT4 ### (3x, or 4x???) ### if(m_mode=="FT8") bw=7*12000.0/1920.0; //FT8 if(m_mode=="JT4") { //JT4 @@ -484,7 +486,8 @@ void CPlotter::DrawOverlay() //DrawOverlay() painter0.drawLine(x1,24,x1,30); } - if(m_mode=="JT9" or m_mode=="JT65" or m_mode=="JT9+JT65" or m_mode=="QRA64" or m_mode=="FT8") { + if(m_mode=="JT9" or m_mode=="JT65" or m_mode=="JT9+JT65" + or m_mode=="QRA64" or m_mode=="FT8" or m_mode=="FT4") { if(m_mode=="QRA64" or (m_mode=="JT65" and m_bVHF)) { painter0.setPen(penGreen); @@ -518,7 +521,8 @@ void CPlotter::DrawOverlay() //DrawOverlay() } if(m_mode=="JT9" or m_mode=="JT65" or m_mode=="JT9+JT65" or - m_mode.mid(0,4)=="WSPR" or m_mode=="QRA64" or m_mode=="FT8") { + m_mode.mid(0,4)=="WSPR" or m_mode=="QRA64" or m_mode=="FT8" + or m_mode=="FT4") { painter0.setPen(penRed); x1=XfromFreq(m_txFreq); x2=XfromFreq(m_txFreq+bw); diff --git a/widgets/widegraph.cpp b/widgets/widegraph.cpp index b4601ba4c..1f3110cee 100644 --- a/widgets/widegraph.cpp +++ b/widgets/widegraph.cpp @@ -187,7 +187,8 @@ void WideGraph::dataSink2(float s[], float df3, int ihsym, int ndiskdata) //dat // Time according to this computer qint64 ms = QDateTime::currentMSecsSinceEpoch() % 86400000; int ntr = (ms/1000) % m_TRperiod; - if((ndiskdata && ihsym <= m_waterfallAvg) || (!ndiskdata && ntr