diff --git a/Detector.cpp b/Detector.cpp index 8c55d68..9f7f2b6 100644 --- a/Detector.cpp +++ b/Detector.cpp @@ -43,18 +43,9 @@ bool Detector::reset () void Detector::clear () { - QMutexLocker mutex(&m_lock); - #if JS8_RING_BUFFER - // set index to roughly where we are in time (1ms resolution) - qint64 now (DriftingDateTime::currentMSecsSinceEpoch ()); - unsigned msInPeriod ((now % 86400000LL) % (m_period * 1000)); - int prevKin = dec_data.params.kin; - dec_data.params.kin = qMin ((msInPeriod * m_frameRate) / 1000, static_cast (sizeof (dec_data.d2) / sizeof (dec_data.d2[0]))); - m_bufferPos = 0; - m_ns=secondInPeriod(); - memset(dec_data.d2, 0, sizeof(dec_data.d2)); - qDebug() << "advancing detector buffer from" << prevKin << "to" << dec_data.params.kin << "delta" << dec_data.params.kin - prevKin; + resetBufferPosition(); + resetBufferContent(); #else dec_data.params.kin = 0; m_bufferPos = 0; @@ -64,6 +55,63 @@ void Detector::clear () // qFill (dec_data.d2, dec_data.d2 + sizeof (dec_data.d2) / sizeof (dec_data.d2[0]), 0); } +/** + * shift the elements of an array left by n items using a temporary array for efficient moving + * + * this function moves the leading n elements to the end of the array + * + * the temporary array needs to be allocated to accept at least n items + */ +template void rotate_array_left(T array[], qint64 size, T temp[], qint64 n) { + memcpy(temp, array, n * sizeof(T)); // temporarily save leading n elements + memmove(array, array + n, (size - n) * sizeof(T)); // shift array to the left + memmove(array + size - n, temp, n * sizeof(T)); // append saved +} + +/** + * shift the elements of an array right by n items using a temporary array for efficient moving + * + * this function moves the trailing n elements to the start of the array + * + * the temporary array needs to be allocated to accept at least n items + */ +template void rotate_array_right(T array[], qint64 size, T temp[], qint64 n) { + memcpy(temp, array + size - n, n * sizeof(T)); // temporarily save trailing n elements + memmove(array + n, array, (size - n) * sizeof(T)); // shift array to the right + memcpy(array, temp, n * sizeof(T)); // prepend saved +} + +void Detector::resetBufferPosition(){ + // temporary buffer for efficient copies + static short int d0[NTMAX*RX_SAMPLE_RATE]; + + QMutexLocker mutex(&m_lock); + + // set index to roughly where we are in time (1ms resolution) + qint64 now (DriftingDateTime::currentMSecsSinceEpoch ()); + unsigned msInPeriod ((now % 86400000LL) % (m_period * 1000)); + int prevKin = dec_data.params.kin; + dec_data.params.kin = qMin ((msInPeriod * m_frameRate) / 1000, static_cast (sizeof (dec_data.d2) / sizeof (dec_data.d2[0]))); + m_bufferPos = 0; + m_ns=secondInPeriod(); + int delta = dec_data.params.kin - prevKin; + qDebug() << "advancing detector buffer from" << prevKin << "to" << dec_data.params.kin << "delta" << delta; + + // rotate buffer moving the contents that were at prevKin to the new kin position + if(delta < 0){ + rotate_array_left(dec_data.d2, NTMAX*RX_SAMPLE_RATE, d0, -delta); + } else { + rotate_array_right(dec_data.d2, NTMAX*RX_SAMPLE_RATE, d0, delta); + } +} + +void Detector::resetBufferContent(){ + QMutexLocker mutex(&m_lock); + + memset(dec_data.d2, 0, sizeof(dec_data.d2)); + qDebug() << "clearing detector buffer content"; +} + qint64 Detector::writeData (char const * data, qint64 maxSize) { QMutexLocker mutex(&m_lock); @@ -138,7 +186,6 @@ unsigned Detector::secondInPeriod () const // we take the time of the data as the following assuming no latency // delivering it to us (not true but close enough for us) qint64 now (DriftingDateTime::currentMSecsSinceEpoch ()); - unsigned secondInToday ((now % 86400000LL) / 1000); return secondInToday % m_period; } diff --git a/Detector.hpp b/Detector.hpp index f4f6b93..8f7bce9 100644 --- a/Detector.hpp +++ b/Detector.hpp @@ -35,6 +35,9 @@ public: Q_SLOT void setBlockSize (unsigned); void clear (); // discard buffer contents + void resetBufferPosition(); + void resetBufferContent(); + unsigned secondInPeriod () const; protected: diff --git a/commons.h b/commons.h index 687a1ef..a1a2d03 100644 --- a/commons.h +++ b/commons.h @@ -13,6 +13,7 @@ #define JS8_DECODE_THREAD 1 // use a separate thread for decode process handling #define JS8_ALLOW_EXTENDED 1 // allow extended latin-1 capital charset #define JS8_SAVE_AUDIO 0 // enable the save menu +#define JS8_AUTO_SYNC 1 // enable the experimental auto sync feature #ifdef QT_DEBUG #define JS8_DEBUG_DECODE 0 // emit debug statements for the decode pipeline @@ -97,6 +98,7 @@ extern struct dec_data { int nfSplit; // JT65 | JT9 split frequency int nfb; // High decode limit (Hz) (filter max) int ntol; // +/- decoding range around fQSO (Hz) + bool syncStats; // only compute sync candidates int kin; // number of frames written to d2 int kposA; // starting position of decode for submode A int kposB; // starting position of decode for submode B diff --git a/decodedtext.cpp b/decodedtext.cpp index e37e974..f2e8e0a 100644 --- a/decodedtext.cpp +++ b/decodedtext.cpp @@ -26,6 +26,7 @@ DecodedText::DecodedText (QString const& the_string, bool contest_mode, QString , isAlt_(false) , bits_{0} , submode_{ string_.mid(column_mode + padding_, 3).trimmed().at(0).cell() - 'A' } + , frame_ { string_.mid (column_qsoText + padding_, 12).trimmed () } { if(message_.length() >= 1) { message_ = message_.left (21).remove (QRegularExpression {"[<>]"}); @@ -71,7 +72,8 @@ DecodedText::DecodedText (QString const& js8callmessage, int bits, int submode): isHeartbeat_(false), isAlt_(false), bits_(bits), - submode_(submode) + submode_(submode), + frame_(js8callmessage) { is_standard_ = QRegularExpression("^(CQ|DE|QRZ)\\s").match(message_).hasMatch(); diff --git a/decodedtext.h b/decodedtext.h index 1eb51af..868e16f 100644 --- a/decodedtext.h +++ b/decodedtext.h @@ -41,6 +41,7 @@ public: bool tryUnpackFastData(); quint8 frameType() const { return frameType_; } + QString frame() const { return frame_; } QString extra() const { return extra_; } QString compoundCall() const { return compound_; } @@ -113,6 +114,7 @@ private: bool is_standard_; int bits_; int submode_; + QString frame_; }; #endif // DECODEDTEXT_H diff --git a/lib/decoder.f90 b/lib/decoder.f90 index a41c14e..020e7f9 100644 --- a/lib/decoder.f90 +++ b/lib/decoder.f90 @@ -88,13 +88,28 @@ subroutine multimode_decoder(ss,id2,params,nfsample) pos = max(0,params%kposI) sz = max(0,params%kszI) id0=0 - id0(1:sz+1)=id2(pos+1:pos+sz+1) + imax=int(NTMAX*12000) + + if(params%syncStats) then + write(*,*) ' sync start', pos, sz + endif + + if((imax-pos).lt.sz) then + ! this means that the first part of the id0 is at the end of the buffer + ! and the second half is at the beginning of the buffer + firstsize=int(imax-pos)-1 + secondsize=int(sz-firstsize)+1 + id0(1:firstsize+1)=id2(pos+1:pos+firstsize+1) + id0(firstsize+1:firstsize+secondsize+1)=id2(1:secondsize+1) + else + id0(1:sz+1)=id2(pos+1:pos+sz+1) + endif call my_js8i%decode(js8i_decoded,id0,params%nQSOProgress,params%nfqso, & params%nftx,newdat,params%nutc,params%nfa,params%nfb, & params%nexp_decode,params%ndepth,logical(params%nagain), & logical(params%lft8apon),logical(params%lapcqonly),params%napwid, & - mycall,mygrid,hiscall,hisgrid) + mycall,mygrid,hiscall,hisgrid,logical(params%syncStats)) write(*,*) ' mode I decode finished' @@ -111,13 +126,28 @@ subroutine multimode_decoder(ss,id2,params,nfsample) pos = max(0,params%kposE) sz = max(0,params%kszE) id0=0 - id0(1:sz+1)=id2(pos+1:pos+sz+1) + imax=int(NTMAX*12000) + + if(params%syncStats) then + write(*,*) ' sync start', pos, sz + endif + + if((imax-pos).lt.sz) then + ! this means that the first part of the id0 is at the end of the buffer + ! and the second half is at the beginning of the buffer + firstsize=int(imax-pos)-1 + secondsize=int(sz-firstsize)+1 + id0(1:firstsize+1)=id2(pos+1:pos+firstsize+1) + id0(firstsize+1:firstsize+secondsize+1)=id2(1:secondsize+1) + else + id0(1:sz+1)=id2(pos+1:pos+sz+1) + endif call my_js8e%decode(js8e_decoded,id0,params%nQSOProgress,params%nfqso, & params%nftx,newdat,params%nutc,params%nfa,params%nfb, & params%nexp_decode,params%ndepth,logical(params%nagain), & logical(params%lft8apon),logical(params%lapcqonly),params%napwid, & - mycall,mygrid,hiscall,hisgrid) + mycall,mygrid,hiscall,hisgrid,logical(params%syncStats)) write(*,*) ' mode E decode finished' @@ -134,13 +164,28 @@ subroutine multimode_decoder(ss,id2,params,nfsample) pos = max(0,params%kposC) sz = max(0,params%kszC) id0=0 - id0(1:sz+1)=id2(pos+1:pos+sz+1) + imax=int(NTMAX*12000) + + if(params%syncStats) then + write(*,*) ' sync start', pos, sz + endif + + if((imax-pos).lt.sz) then + ! this means that the first part of the id0 is at the end of the buffer + ! and the second half is at the beginning of the buffer + firstsize=int(imax-pos)-1 + secondsize=int(sz-firstsize)+1 + id0(1:firstsize+1)=id2(pos+1:pos+firstsize+1) + id0(firstsize+1:firstsize+secondsize+1)=id2(1:secondsize+1) + else + id0(1:sz+1)=id2(pos+1:pos+sz+1) + endif call my_js8c%decode(js8c_decoded,id0,params%nQSOProgress,params%nfqso, & params%nftx,newdat,params%nutc,params%nfa,params%nfb, & params%nexp_decode,params%ndepth,logical(params%nagain), & logical(params%lft8apon),logical(params%lapcqonly),params%napwid, & - mycall,mygrid,hiscall,hisgrid) + mycall,mygrid,hiscall,hisgrid,logical(params%syncStats)) write(*,*) ' mode C decode finished' @@ -157,13 +202,28 @@ subroutine multimode_decoder(ss,id2,params,nfsample) pos = max(0,params%kposB) sz = max(0,params%kszB) id0=0 - id0(1:sz+1)=id2(pos+1:pos+sz+1) + imax=int(NTMAX*12000) + + if(params%syncStats) then + write(*,*) ' sync start', pos, sz + endif + + if((imax-pos).lt.sz) then + ! this means that the first part of the id0 is at the end of the buffer + ! and the second half is at the beginning of the buffer + firstsize=int(imax-pos)-1 + secondsize=int(sz-firstsize)+1 + id0(1:firstsize+1)=id2(pos+1:pos+firstsize+1) + id0(firstsize+1:firstsize+secondsize+1)=id2(1:secondsize+1) + else + id0(1:sz+1)=id2(pos+1:pos+sz+1) + endif call my_js8b%decode(js8b_decoded,id0,params%nQSOProgress,params%nfqso, & params%nftx,newdat,params%nutc,params%nfa,params%nfb, & params%nexp_decode,params%ndepth,logical(params%nagain), & logical(params%lft8apon),logical(params%lapcqonly),params%napwid, & - mycall,mygrid,hiscall,hisgrid) + mycall,mygrid,hiscall,hisgrid,logical(params%syncStats)) write(*,*) ' mode B decode finished' @@ -177,16 +237,31 @@ subroutine multimode_decoder(ss,id2,params,nfsample) write(*,*) ' mode A decode started' ! copy the relevant frames for decoding - pos = max(0,params%kposA) - sz = max(0,params%kszA) + pos = int(max(0,params%kposA)) + sz = int(max(0,params%kszA)) id0=0 - id0(1:sz+1)=id2(pos+1:pos+sz+1) + imax=int(NTMAX*12000) + + if(params%syncStats) then + write(*,*) ' sync start', pos, sz + endif + + if((imax-pos).lt.sz) then + ! this means that the first part of the id0 is at the end of the buffer + ! and the second half is at the beginning of the buffer + firstsize=int(imax-pos)-1 + secondsize=int(sz-firstsize)+1 + id0(1:firstsize+1)=id2(pos+1:pos+firstsize+1) + id0(firstsize+1:firstsize+secondsize+1)=id2(1:secondsize+1) + else + id0(1:sz+1)=id2(pos+1:pos+sz+1) + endif call my_js8a%decode(js8a_decoded,id0,params%nQSOProgress,params%nfqso, & params%nftx,newdat,params%nutc,params%nfa,params%nfb, & params%nexp_decode,params%ndepth,logical(params%nagain), & logical(params%lft8apon),logical(params%lapcqonly),params%napwid, & - mycall,mygrid,hiscall,hisgrid) + mycall,mygrid,hiscall,hisgrid,logical(params%syncStats)) write(*,*) ' mode A decode finished' @@ -196,7 +271,7 @@ subroutine multimode_decoder(ss,id2,params,nfsample) write(*,*) ' finished' call flush(6) - ndecoded = my_js8a%decoded + my_js8b%decoded + my_js8c%decoded + my_js8e%decoded + ndecoded = my_js8a%decoded + my_js8b%decoded + my_js8c%decoded + my_js8e%decoded + my_js8i%decoded !call sleep_msec(3000) write(*,1010) ndecoded 1010 format('',i4) diff --git a/lib/js8/js8a_params.f90 b/lib/js8/js8a_params.f90 index 806754e..788c6c2 100644 --- a/lib/js8/js8a_params.f90 +++ b/lib/js8/js8a_params.f90 @@ -1,5 +1,6 @@ ! When modifying this file, please ensure the modifications are made in ft8_params.f90 too. +parameter (NSUBMODE=0) parameter (NCOSTAS=1) !Which JS8 Costas Arrays to use (1=original, 2=three symmetrical costas) parameter (NSPS=1920, NTXDUR=15, NDOWNSPS=32, NDD=100, JZ=62) ! 50 Hz 6.250 baud 16 wpm -25.0dB (1.0Eb/N0) 12.64s diff --git a/lib/js8/js8b_params.f90 b/lib/js8/js8b_params.f90 index 65aba11..8f98a3e 100644 --- a/lib/js8/js8b_params.f90 +++ b/lib/js8/js8b_params.f90 @@ -1,3 +1,4 @@ +parameter (NSUBMODE=1) parameter (NCOSTAS=2) !Which JS8 Costas Arrays to use (1=original, 2=three symmetrical costas) parameter (NSPS=1200, NTXDUR=10, NDOWNSPS=20, NDD=100, JZ=144) ! 80 Hz 10 baud 24 wpm -23.0dB (1.0Eb/N0) 7.90s diff --git a/lib/js8/js8c_params.f90 b/lib/js8/js8c_params.f90 index 7be1230..2cbb088 100644 --- a/lib/js8/js8c_params.f90 +++ b/lib/js8/js8c_params.f90 @@ -1,3 +1,4 @@ +parameter (NSUBMODE=2) parameter (NCOSTAS=2) !Which JS8 Costas Arrays to use (1=original, 2=three symmetrical costas) parameter (NSPS=600, NTXDUR=6, NDOWNSPS=12, NDD=120, JZ=172) ! 160 Hz 20 baud 40 wpm -20.0dB (1.0Eb/N0) 3.95s diff --git a/lib/js8/js8dec.f90 b/lib/js8/js8dec.f90 index d98bd70..7db0d2d 100644 --- a/lib/js8/js8dec.f90 +++ b/lib/js8/js8dec.f90 @@ -1,4 +1,4 @@ -subroutine js8dec(dd0,icos,newdat,nQSOProgress,nfqso,nftx,ndepth,lapon,lapcqonly, & +subroutine js8dec(dd0,icos,newdat,syncStats,nQSOProgress,nfqso,nftx,ndepth,lapon,lapcqonly, & napwid,lsubtract,nagain,iaptype,mycall12,mygrid6,hiscall12,bcontest, & sync0,f1,xdt,xbase,apsym,nharderrors,dmin,nbadcrc,ipass,iera,msg37,xsnr) @@ -14,7 +14,7 @@ subroutine js8dec(dd0,icos,newdat,nQSOProgress,nfqso,nftx,ndepth,lapon,lapcqonly character*12 mycall12,hiscall12 character*6 mycall6,mygrid6,hiscall6,c1,c2 character*87 cbits - logical bcontest + logical bcontest,syncStats real a(5) real s1(0:7,ND),s2(0:7,NN),s1sort(8*ND) real ps(0:7),psl(0:7) @@ -224,6 +224,11 @@ subroutine js8dec(dd0,icos,newdat,nQSOProgress,nfqso,nftx,ndepth,lapon,lapcqonly return endif + if(syncStats) then + write(*,*) ' candidate ', NSUBMODE, 'f1', f1, 'sync', nsync, 'xdt', xdt + flush(6) + endif + j=0 do k=1,NN if(k.le.7) cycle @@ -417,6 +422,11 @@ subroutine js8dec(dd0,icos,newdat,nQSOProgress,nfqso,nftx,ndepth,lapon,lapcqonly i3bit=4*decoded(73) + 2*decoded(74) + decoded(75) if(nbadcrc.eq.0) then + if(syncStats) then + write(*,*) ' decode ', NSUBMODE, 'f1', f1, 'sync', (sync*10), 'xdt', xdt2 + flush(6) + endif + decoded0=decoded call extractmessage174(decoded,origmsg,ncrcflag) decoded=decoded0 diff --git a/lib/js8/js8e_params.f90 b/lib/js8/js8e_params.f90 index 9c9a6bd..9f444ff 100644 --- a/lib/js8/js8e_params.f90 +++ b/lib/js8/js8e_params.f90 @@ -1,3 +1,4 @@ +parameter (NSUBMODE=4) parameter (NCOSTAS=2) !Which JS8 Costas Arrays to use (1=original, 2=three symmetrical costas) parameter (NSPS=3840, NTXDUR=28, NDOWNSPS=32, NDD=90, JZ=32) ! 25 Hz 3.125 baud 8 wpm -28.0dB (1.0Eb/N0) 25.28s diff --git a/lib/js8/js8i_params.f90 b/lib/js8/js8i_params.f90 index 688a991..4aeaaf1 100644 --- a/lib/js8/js8i_params.f90 +++ b/lib/js8/js8i_params.f90 @@ -1,3 +1,4 @@ +parameter (NSUBMODE=8) parameter (NCOSTAS=2) !Which JS8 Costas Arrays to use (1=original, 2=three symmetrical costas) parameter (NSPS=384, NTXDUR=4, NDOWNSPS=12, NDD=125, JZ=250) ! 250 Hz 31.25 baud 60 wpm -18.0dB (1.0Eb/N0) 2.52s diff --git a/lib/js8a_decode.f90 b/lib/js8a_decode.f90 index 5c67bcb..22ddc7b 100644 --- a/lib/js8a_decode.f90 +++ b/lib/js8a_decode.f90 @@ -25,7 +25,7 @@ contains subroutine decode(this,callback,iwave,nQSOProgress,nfqso,nftx,newdat, & nutc,nfa,nfb,nexp_decode,ndepth,nagain,lft8apon,lapcqonly,napwid, & - mycall12,mygrid6,hiscall12,hisgrid6) + mycall12,mygrid6,hiscall12,hisgrid6,syncStats) ! use wavhdr use timer_module, only: timer ! type(hdr) h @@ -38,7 +38,7 @@ contains real candidate(3,NMAXCAND) real dd(NMAX) logical, intent(in) :: lft8apon,lapcqonly,nagain - logical newdat,lsubtract,ldupe,bcontest + logical newdat,lsubtract,ldupe,bcontest,syncStats character*12 mycall12, hiscall12 character*6 mygrid6,hisgrid6 integer*2 iwave(NMAX) @@ -93,25 +93,15 @@ contains lsubtract=.false. endif + if(NWRITELOG.eq.1) then + write(*,*) ' pass', ipass, 'of', npass, 'subtract', lsubtract + flush(6) + endif + call timer('syncjs8 ',0) call syncjs8(dd,icos,ifa,ifb,syncmin,nfqso,s,candidate,ncand,sbase) call timer('syncjs8 ',1) - if(NWRITELOG.eq.1) then - write(*,*) '', ncand, "candidates" - flush(6) - - do icand=1,ncand - sync=candidate(3,icand) - f1=candidate(1,icand) - xdt=candidate(2,icand) - xbase=10.0**(0.1*(sbase(nint(f1/(12000.0/NFFT1)))-40.0)) ! 3.125Hz - - write(*,*) ' candidate', icand, 'f1', f1, 'sync', sync, 'xdt', xdt, 'xbase', xbase - flush(6) - enddo - endif - do icand=1,ncand sync=candidate(3,icand) f1=candidate(1,icand) @@ -124,7 +114,7 @@ contains endif call timer('js8dec ',0) - call js8dec(dd,icos,newdat,nQSOProgress,nfqso,nftx,ndepth,lft8apon, & + call js8dec(dd,icos,newdat,syncStats,nQSOProgress,nfqso,nftx,ndepth,lft8apon, & lapcqonly,napwid,lsubtract,nagain,iaptype,mycall12,mygrid6, & hiscall12,bcontest,sync,f1,xdt,xbase,apsym,nharderrors,dmin, & nbadcrc,iappass,iera,msg37,xsnr) @@ -137,7 +127,7 @@ contains write(*,*) ' candidate', icand, 'hard', hd, 'nbadcrc', nbadcrc flush(6) endif - + call timer('js8dec ',1) if(nbadcrc.eq.0) then ldupe=.false. diff --git a/lib/js8b_decode.f90 b/lib/js8b_decode.f90 index c3ea4ce..b58d28e 100644 --- a/lib/js8b_decode.f90 +++ b/lib/js8b_decode.f90 @@ -25,7 +25,7 @@ contains subroutine decode(this,callback,iwave,nQSOProgress,nfqso,nftx,newdat, & nutc,nfa,nfb,nexp_decode,ndepth,nagain,lft8apon,lapcqonly,napwid, & - mycall12,mygrid6,hiscall12,hisgrid6) + mycall12,mygrid6,hiscall12,hisgrid6,syncStats) ! use wavhdr use timer_module, only: timer ! type(hdr) h @@ -38,7 +38,7 @@ contains real candidate(3,NMAXCAND) real dd(NMAX) logical, intent(in) :: lft8apon,lapcqonly,nagain - logical newdat,lsubtract,ldupe,bcontest + logical newdat,lsubtract,ldupe,bcontest,syncStats character*12 mycall12, hiscall12 character*6 mygrid6,hisgrid6 integer*2 iwave(NMAX) @@ -93,6 +93,11 @@ contains lsubtract=.false. endif + if(NWRITELOG.eq.1) then + write(*,*) ' pass', ipass, 'of', npass, 'subtract', lsubtract + flush(6) + endif + call timer('syncjs8 ',0) call syncjs8(dd,icos,ifa,ifb,syncmin,nfqso,s,candidate,ncand,sbase) call timer('syncjs8 ',1) @@ -114,7 +119,7 @@ contains endif call timer('js8dec ',0) - call js8dec(dd,icos,newdat,nQSOProgress,nfqso,nftx,ndepth,lft8apon, & + call js8dec(dd,icos,newdat,syncStats,nQSOProgress,nfqso,nftx,ndepth,lft8apon, & lapcqonly,napwid,lsubtract,nagain,iaptype,mycall12,mygrid6, & hiscall12,bcontest,sync,f1,xdt,xbase,apsym,nharderrors,dmin, & nbadcrc,iappass,iera,msg37,xsnr) diff --git a/lib/js8c_decode.f90 b/lib/js8c_decode.f90 index af97a74..586460c 100644 --- a/lib/js8c_decode.f90 +++ b/lib/js8c_decode.f90 @@ -25,7 +25,7 @@ contains subroutine decode(this,callback,iwave,nQSOProgress,nfqso,nftx,newdat, & nutc,nfa,nfb,nexp_decode,ndepth,nagain,lft8apon,lapcqonly,napwid, & - mycall12,mygrid6,hiscall12,hisgrid6) + mycall12,mygrid6,hiscall12,hisgrid6,syncStats) ! use wavhdr use timer_module, only: timer ! type(hdr) h @@ -38,7 +38,7 @@ contains real candidate(3,NMAXCAND) real dd(NMAX) logical, intent(in) :: lft8apon,lapcqonly,nagain - logical newdat,lsubtract,ldupe,bcontest + logical newdat,lsubtract,ldupe,bcontest,syncStats character*12 mycall12, hiscall12 character*6 mygrid6,hisgrid6 integer*2 iwave(NMAX) @@ -93,6 +93,11 @@ contains lsubtract=.false. endif + if(NWRITELOG.eq.1) then + write(*,*) ' pass', ipass, 'of', npass, 'subtract', lsubtract + flush(6) + endif + call timer('syncjs8 ',0) call syncjs8(dd,icos,ifa,ifb,syncmin,nfqso,s,candidate,ncand,sbase) call timer('syncjs8 ',1) @@ -114,7 +119,7 @@ contains endif call timer('js8dec ',0) - call js8dec(dd,icos,newdat,nQSOProgress,nfqso,nftx,ndepth,lft8apon, & + call js8dec(dd,icos,newdat,syncStats,nQSOProgress,nfqso,nftx,ndepth,lft8apon, & lapcqonly,napwid,lsubtract,nagain,iaptype,mycall12,mygrid6, & hiscall12,bcontest,sync,f1,xdt,xbase,apsym,nharderrors,dmin, & nbadcrc,iappass,iera,msg37,xsnr) diff --git a/lib/js8e_decode.f90 b/lib/js8e_decode.f90 index 6a6674d..ffc7a6e 100644 --- a/lib/js8e_decode.f90 +++ b/lib/js8e_decode.f90 @@ -25,7 +25,7 @@ contains subroutine decode(this,callback,iwave,nQSOProgress,nfqso,nftx,newdat, & nutc,nfa,nfb,nexp_decode,ndepth,nagain,lft8apon,lapcqonly,napwid, & - mycall12,mygrid6,hiscall12,hisgrid6) + mycall12,mygrid6,hiscall12,hisgrid6,syncStats) ! use wavhdr use timer_module, only: timer ! type(hdr) h @@ -38,7 +38,7 @@ contains real candidate(3,NMAXCAND) real dd(NMAX) logical, intent(in) :: lft8apon,lapcqonly,nagain - logical newdat,lsubtract,ldupe,bcontest + logical newdat,lsubtract,ldupe,bcontest,syncStats character*12 mycall12, hiscall12 character*6 mygrid6,hisgrid6 integer*2 iwave(NMAX) @@ -93,6 +93,11 @@ contains lsubtract=.false. endif + if(NWRITELOG.eq.1) then + write(*,*) ' pass', ipass, 'of', npass, 'subtract', lsubtract + flush(6) + endif + call timer('syncjs8 ',0) call syncjs8(dd,icos,ifa,ifb,syncmin,nfqso,s,candidate,ncand,sbase) call timer('syncjs8 ',1) @@ -114,7 +119,7 @@ contains endif call timer('js8dec ',0) - call js8dec(dd,icos,newdat,nQSOProgress,nfqso,nftx,ndepth,lft8apon, & + call js8dec(dd,icos,newdat,syncStats,nQSOProgress,nfqso,nftx,ndepth,lft8apon, & lapcqonly,napwid,lsubtract,nagain,iaptype,mycall12,mygrid6, & hiscall12,bcontest,sync,f1,xdt,xbase,apsym,nharderrors,dmin, & nbadcrc,iappass,iera,msg37,xsnr) diff --git a/lib/js8i_decode.f90 b/lib/js8i_decode.f90 index ee161b7..4a9e085 100644 --- a/lib/js8i_decode.f90 +++ b/lib/js8i_decode.f90 @@ -25,7 +25,7 @@ contains subroutine decode(this,callback,iwave,nQSOProgress,nfqso,nftx,newdat, & nutc,nfa,nfb,nexp_decode,ndepth,nagain,lft8apon,lapcqonly,napwid, & - mycall12,mygrid6,hiscall12,hisgrid6) + mycall12,mygrid6,hiscall12,hisgrid6,syncStats) ! use wavhdr use timer_module, only: timer ! type(hdr) h @@ -38,7 +38,7 @@ contains real candidate(3,NMAXCAND) real dd(NMAX) logical, intent(in) :: lft8apon,lapcqonly,nagain - logical newdat,lsubtract,ldupe,bcontest + logical newdat,lsubtract,ldupe,bcontest,syncStats character*12 mycall12, hiscall12 character*6 mygrid6,hisgrid6 integer*2 iwave(NMAX) @@ -93,6 +93,11 @@ contains lsubtract=.false. endif + if(NWRITELOG.eq.1) then + write(*,*) ' pass', ipass, 'of', npass, 'subtract', lsubtract + flush(6) + endif + call timer('syncjs8 ',0) call syncjs8(dd,icos,ifa,ifb,syncmin,nfqso,s,candidate,ncand,sbase) call timer('syncjs8 ',1) @@ -114,7 +119,7 @@ contains endif call timer('js8dec ',0) - call js8dec(dd,icos,newdat,nQSOProgress,nfqso,nftx,ndepth,lft8apon, & + call js8dec(dd,icos,newdat,syncStats,nQSOProgress,nfqso,nftx,ndepth,lft8apon, & lapcqonly,napwid,lsubtract,nagain,iaptype,mycall12,mygrid6, & hiscall12,bcontest,sync,f1,xdt,xbase,apsym,nharderrors,dmin, & nbadcrc,iappass,iera,msg37,xsnr) diff --git a/lib/jt9.f90 b/lib/jt9.f90 index 279c126..7888afd 100644 --- a/lib/jt9.f90 +++ b/lib/jt9.f90 @@ -21,8 +21,8 @@ program jt9 !### ndepth was defined as 60001. Why??? integer :: arglen,stat,offset,remain,mode=0,flow=200,fsplit=2700, & fhigh=4000,nrxfreq=1500,ntrperiod=1,ndepth=1,nexp_decode=0 - logical :: read_files = .true., tx9 = .false., display_help = .false. - type (option) :: long_options(21) = [ & + logical :: read_files = .true., tx9 = .false., display_help = .false., syncStats = .false. + type (option) :: long_options(22) = [ & option ('help', .false., 'h', 'Display this help message', ''), & option ('shmem',.true.,'s','Use shared memory for sample data','KEY'), & option ('tr-period', .true., 'p', 'Tx/Rx period, default MINUTES=1', & @@ -50,6 +50,7 @@ program jt9 !option ('jt65', .false., '6', 'JT65 mode', ''), & !option ('jt9', .false., '9', 'JT9 mode', ''), & option ('js8', .false., '8', 'JS8 mode', ''), & + option ('syncStats', .false., 'y', 'Sync only', ''), & !option ('jt4', .false., '4', 'JT4 mode', ''), & !option ('qra64', .false., 'q', 'QRA64 mode', ''), & option ('sub-mode', .true., 'b', 'Sub mode, default SUBMODE=A', 'A'), & @@ -118,6 +119,8 @@ program jt9 ! if (mode.lt.9.or.mode.eq.65) mode = mode + 9 case ('8') mode = 8 + case ('y') + syncStats = .true. case ('T') tx9 = .true. case ('w') @@ -254,6 +257,7 @@ program jt9 shared_data%params%ljt65apon=.true. shared_data%params%napwid=75 shared_data%params%dttol=3. + shared_data%params%syncStats=syncStats ! shared_data%params%minsync=0 !### TEST ONLY ! shared_data%params%nfqso=1500 !### TEST ONLY @@ -290,10 +294,12 @@ program jt9 shared_data%params%kposB=0 shared_data%params%kposC=0 shared_data%params%kposE=0 + shared_data%params%kposI=0 shared_data%params%kszA=NMAX-1 shared_data%params%kszB=NMAX-1 shared_data%params%kszC=NMAX-1 shared_data%params%kszE=NMAX-1 + shared_data%params%kszI=NMAX-1 call multimode_decoder(shared_data%ss,shared_data%id2,shared_data%params,nfsample) enddo diff --git a/lib/jt9com.f90 b/lib/jt9com.f90 index bb30d57..0a69ef2 100644 --- a/lib/jt9com.f90 +++ b/lib/jt9com.f90 @@ -18,6 +18,7 @@ integer(c_int) :: nfsplit integer(c_int) :: nfb integer(c_int) :: ntol + logical(c_bool) :: syncStats integer(c_int) :: kin integer(c_int) :: kposA integer(c_int) :: kposB diff --git a/mainwindow.cpp b/mainwindow.cpp index 4310c4c..78d38a0 100644 --- a/mainwindow.cpp +++ b/mainwindow.cpp @@ -810,6 +810,8 @@ MainWindow::MainWindow(QDir const& temp_directory, bool multiple, connect(m_wideGraph.data(), &WideGraph::qsy, this, &MainWindow::qsy); + connect(m_wideGraph.data(), &WideGraph::drifted, this, &MainWindow::drifted); + decodeBusy(false); QString t1[28]={"1 uW","2 uW","5 uW","10 uW","20 uW","50 uW","100 uW","200 uW","500 uW", "1 mW","2 mW","5 mW","10 mW","20 mW","50 mW","100 mW","200 mW","500 mW", @@ -2636,36 +2638,73 @@ int MainWindow::computeStop(int submode, int period){ return stop; } -// int MainWindow::computeCurrentCycle(int period){ -// return m_detector->secondInPeriod() / period; -// } -// -// int MainWindow::computeCycleStartForDecode(int cycle, int period){ -// qint32 samplesPerCycle = period * RX_SAMPLE_RATE; -// return cycle * samplesPerCycle; -// } - +/** + * @brief MainWindow::computeCycleForDecode + * + * compute which cycle we are currently in based on a submode frames per cycle and our current k position + * + * @param submode + * @param k + * @return + */ int MainWindow::computeCycleForDecode(int submode, int k){ - qint32 maxFrames = m_detector->period() * RX_SAMPLE_RATE; + qint32 maxFrames = NTMAX * RX_SAMPLE_RATE; qint32 cycleFrames = computeFramesPerCycleForDecode(submode); qint32 currentCycle = (k / cycleFrames) % (maxFrames / cycleFrames); // we mod here so we loop back to zero correctly return currentCycle; } +/** + * @brief MainWindow::computeAltCycleForDecode + * + * compute an alternate cycle offset by a specific number of frames + * + * e.g., if we want the 0 cycle to start at second 5, we'd provide an offset of 5*RX_SAMPLE_RATE + * + * @param submode + * @param k + * @param offsetFrames + * @return + */ +int MainWindow::computeAltCycleForDecode(int submode, int k, int offsetFrames){ + int altK = k - offsetFrames; + if(altK < 0){ + altK += NTMAX * RX_SAMPLE_RATE; + } + return computeCycleForDecode(submode, altK); +} + int MainWindow::computeFramesPerCycleForDecode(int submode){ return computePeriodForSubmode(submode) * RX_SAMPLE_RATE; } -int MainWindow::computeFramesNeededForDecode(int submode){ - int symbolSamples = 0; - float threshold = 0.0; +int MainWindow::computePeriodStartDelayForDecode(int submode){ + int delay = 0; switch(submode){ - case Varicode::JS8CallNormal: symbolSamples = JS8A_SYMBOL_SAMPLES; threshold = JS8A_START_DELAY_MS/1000.0 + 0.5; break; - case Varicode::JS8CallFast: symbolSamples = JS8B_SYMBOL_SAMPLES; threshold = JS8B_START_DELAY_MS/1000.0 + 0.5; break; - case Varicode::JS8CallTurbo: symbolSamples = JS8C_SYMBOL_SAMPLES; threshold = JS8C_START_DELAY_MS/1000.0 + 0.5; break; - case Varicode::JS8CallSlow: symbolSamples = JS8E_SYMBOL_SAMPLES; threshold = JS8E_START_DELAY_MS/1000.0 + 0.5; break; - case Varicode::JS8CallUltra: symbolSamples = JS8I_SYMBOL_SAMPLES; threshold = JS8I_START_DELAY_MS/1000.0 + 0.5; break; + case Varicode::JS8CallNormal: delay = JS8A_START_DELAY_MS; break; + case Varicode::JS8CallFast: delay = JS8B_START_DELAY_MS; break; + case Varicode::JS8CallTurbo: delay = JS8C_START_DELAY_MS; break; + case Varicode::JS8CallSlow: delay = JS8E_START_DELAY_MS; break; + case Varicode::JS8CallUltra: delay = JS8I_START_DELAY_MS; break; } + return delay; +} + +int MainWindow::computeFramesPerSymbolForDecode(int submode){ + int symbolSamples = 0; + switch(submode){ + case Varicode::JS8CallNormal: symbolSamples = JS8A_SYMBOL_SAMPLES; break; + case Varicode::JS8CallFast: symbolSamples = JS8B_SYMBOL_SAMPLES; break; + case Varicode::JS8CallTurbo: symbolSamples = JS8C_SYMBOL_SAMPLES; break; + case Varicode::JS8CallSlow: symbolSamples = JS8E_SYMBOL_SAMPLES; break; + case Varicode::JS8CallUltra: symbolSamples = JS8I_SYMBOL_SAMPLES; break; + } + return symbolSamples; +} + +int MainWindow::computeFramesNeededForDecode(int submode){ + float threshold = 0.5 + computePeriodStartDelayForDecode(submode)/1000.0; + int symbolSamples = computeFramesPerSymbolForDecode(submode); return int(qFloor(float(symbolSamples*JS8_NUM_SYMBOLS + threshold*RX_SAMPLE_RATE))); } @@ -2686,6 +2725,8 @@ void MainWindow::dataSink(qint64 frames) k0 = k; } + //qDebug() << "k" << k << "k0" << k0 << "delta" << k-k0; + #if JS8_USE_REFSPEC QString fname {QDir::toNativeSeparators(m_config.writeable_data_dir ().absoluteFilePath ("refspec.dat"))}; QByteArray bafname = fname.toLatin1(); @@ -4214,10 +4255,19 @@ bool MainWindow::decode(qint32 k){ return false; } - bool ready = decodeEnqueueReady(k, kZero); + bool ready = false; + +#if JS8_USE_EXPERIMENTAL_DECODE_TIMING + ready = decodeEnqueueReady(k, kZero); if(ready || !m_decoderQueue.isEmpty()){ if(JS8_DEBUG_DECODE) qDebug() << "--> decoder is ready to be run with" << m_decoderQueue.count() << "decode periods"; } +#else + ready = decodeEnqueueReadyExperiment(k, kZero); + if(ready || !m_decoderQueue.isEmpty()){ + if(JS8_DEBUG_DECODE) qDebug() << "--> decoder is ready to be run with" << m_decoderQueue.count() << "decode periods"; + } +#endif // // TODO: what follows can likely be pulled out to an async process @@ -4232,6 +4282,11 @@ bool MainWindow::decode(qint32 k){ return false; } + if(m_decoderBusyStartTime.isValid() && m_decoderBusyStartTime.msecsTo(QDateTime::currentDateTimeUtc()) < 1000){ + if(JS8_DEBUG_DECODE) qDebug() << "--> decoder paused for 1000 ms after last decode start"; + return false; + } + int threshold = m_nSubMode == Varicode::JS8CallSlow ? 4000 : 2000; // two seconds if(isInDecodeDelayThreshold(threshold)){ if(JS8_DEBUG_DECODE) qDebug() << "--> decoder paused for" << threshold << "ms after transmit stop"; @@ -4265,14 +4320,41 @@ bool MainWindow::decodeEnqueueReady(qint32 k, qint32 k0){ // compute the next decode for each submode // enqueue those decodes that are "ready" // on an interval, issue a decode + int decodes = 0; - static qint32 currentDecodeStartA = -1; - static qint32 nextDecodeStartA = -1; + bool couldDecodeA = false; qint32 startA = -1; qint32 szA = -1; qint32 cycleA = -1; + + bool couldDecodeB = false; + qint32 startB = -1; + qint32 szB = -1; + qint32 cycleB = -1; + + bool couldDecodeC = false; + qint32 startC = -1; + qint32 szC = -1; + qint32 cycleC = -1; + +#if JS8_ENABLE_JS8E + bool couldDecodeE = false; + qint32 startE = -1; + qint32 szE = -1; + qint32 cycleE = -1; +#endif + +#if JS8_ENABLE_JS8I + bool couldDecodeI = false; + qint32 startI = -1; + qint32 szI = -1; + qint32 cycleI = -1; +#endif + + static qint32 currentDecodeStartA = -1; + static qint32 nextDecodeStartA = -1; if(JS8_DEBUG_DECODE) qDebug() << "? NORMAL " << currentDecodeStartA << nextDecodeStartA; - bool couldDecodeA = isDecodeReady(Varicode::JS8CallNormal, k, k0, ¤tDecodeStartA, &nextDecodeStartA, &startA, &szA, &cycleA); + couldDecodeA = isDecodeReady(Varicode::JS8CallNormal, k, k0, ¤tDecodeStartA, &nextDecodeStartA, &startA, &szA, &cycleA); if(m_diskData){ startA = 0; szA = NTMAX*RX_SAMPLE_RATE-1; @@ -4281,11 +4363,8 @@ bool MainWindow::decodeEnqueueReady(qint32 k, qint32 k0){ static qint32 currentDecodeStartB = -1; static qint32 nextDecodeStartB = -1; - qint32 startB = -1; - qint32 szB = -1; - qint32 cycleB = -1; if(JS8_DEBUG_DECODE) qDebug() << "? FAST " << currentDecodeStartB << nextDecodeStartB; - bool couldDecodeB = isDecodeReady(Varicode::JS8CallFast, k, k0, ¤tDecodeStartB, &nextDecodeStartB, &startB, &szB, &cycleB); + couldDecodeB = isDecodeReady(Varicode::JS8CallFast, k, k0, ¤tDecodeStartB, &nextDecodeStartB, &startB, &szB, &cycleB); if(m_diskData){ startB = 0; szB = NTMAX*RX_SAMPLE_RATE-1; @@ -4294,11 +4373,8 @@ bool MainWindow::decodeEnqueueReady(qint32 k, qint32 k0){ static qint32 currentDecodeStartC = -1; static qint32 nextDecodeStartC = -1; - qint32 startC = -1; - qint32 szC = -1; - qint32 cycleC = -1; if(JS8_DEBUG_DECODE) qDebug() << "? TURBO " << currentDecodeStartC << nextDecodeStartC; - bool couldDecodeC = isDecodeReady(Varicode::JS8CallTurbo, k, k0, ¤tDecodeStartC, &nextDecodeStartC, &startC, &szC, &cycleC); + couldDecodeC = isDecodeReady(Varicode::JS8CallTurbo, k, k0, ¤tDecodeStartC, &nextDecodeStartC, &startC, &szC, &cycleC); if(m_diskData){ startC = 0; szC = NTMAX*RX_SAMPLE_RATE-1; @@ -4308,11 +4384,8 @@ bool MainWindow::decodeEnqueueReady(qint32 k, qint32 k0){ #if JS8_ENABLE_JS8E static qint32 currentDecodeStartE = -1; static qint32 nextDecodeStartE = -1; - qint32 startE = -1; - qint32 szE = -1; - qint32 cycleE = -1; if(JS8_DEBUG_DECODE) qDebug() << "? SLOW " << currentDecodeStartE << nextDecodeStartE; - bool couldDecodeE = isDecodeReady(Varicode::JS8CallSlow, k, k0, ¤tDecodeStartE, &nextDecodeStartE, &startE, &szE, &cycleE); + couldDecodeE = isDecodeReady(Varicode::JS8CallSlow, k, k0, ¤tDecodeStartE, &nextDecodeStartE, &startE, &szE, &cycleE); if(m_diskData){ startE = 0; szE = NTMAX*RX_SAMPLE_RATE-1; @@ -4323,11 +4396,8 @@ bool MainWindow::decodeEnqueueReady(qint32 k, qint32 k0){ #if JS8_ENABLE_JS8I static qint32 currentDecodeStartI = -1; static qint32 nextDecodeStartI = -1; - qint32 startI = -1; - qint32 szI = -1; - qint32 cycleI = -1; if(JS8_DEBUG_DECODE) qDebug() << "? ULTRA " << currentDecodeStartI << nextDecodeStartI; - bool couldDecodeI = isDecodeReady(Varicode::JS8CallUltra, k, k0, ¤tDecodeStartI, &nextDecodeStartI, &startI, &szI, &cycleI); + couldDecodeI = isDecodeReady(Varicode::JS8CallUltra, k, k0, ¤tDecodeStartI, &nextDecodeStartI, &startI, &szI, &cycleI); if(m_diskData){ startI = 0; szI = NTMAX*RX_SAMPLE_RATE-1; @@ -4335,12 +4405,9 @@ bool MainWindow::decodeEnqueueReady(qint32 k, qint32 k0){ } #endif - int decodes = 0; - if(couldDecodeA){ DecodeParams d; d.submode = Varicode::JS8CallNormal; - d.cycle = cycleA; d.start = startA; d.sz = szA; m_decoderQueue.append(d); @@ -4350,7 +4417,6 @@ bool MainWindow::decodeEnqueueReady(qint32 k, qint32 k0){ if(couldDecodeB){ DecodeParams d; d.submode = Varicode::JS8CallFast; - d.cycle = cycleB; d.start = startB; d.sz = szB; m_decoderQueue.append(d); @@ -4360,7 +4426,6 @@ bool MainWindow::decodeEnqueueReady(qint32 k, qint32 k0){ if(couldDecodeC){ DecodeParams d; d.submode = Varicode::JS8CallTurbo; - d.cycle = cycleC; d.start = startC; d.sz = szC; m_decoderQueue.append(d); @@ -4371,7 +4436,6 @@ bool MainWindow::decodeEnqueueReady(qint32 k, qint32 k0){ if(couldDecodeE){ DecodeParams d; d.submode = Varicode::JS8CallSlow; - d.cycle = cycleE; d.start = startE; d.sz = szE; m_decoderQueue.append(d); @@ -4383,7 +4447,6 @@ bool MainWindow::decodeEnqueueReady(qint32 k, qint32 k0){ if(couldDecodeI){ DecodeParams d; d.submode = Varicode::JS8CallUltra; - d.cycle = cycleI; d.start = startI; d.sz = szI; m_decoderQueue.append(d); @@ -4394,6 +4457,311 @@ bool MainWindow::decodeEnqueueReady(qint32 k, qint32 k0){ return decodes > 0; } +/** + * @brief MainWindow::decodeEnqueueReadyExperiment + * compute the available decoder ranges that can be processed and + * place them in the decode queue + * + * experiment with decoding on a much shorter interval than usual + * + * @param k - the current frame count + * @param k0 - the previous frame count + * @return true if decoder ranges were queued, false otherwise + */ +bool MainWindow::decodeEnqueueReadyExperiment(qint32 k, qint32 /*k0*/){ + // TODO: make this non-static field of MainWindow? + // map of last decode positions for each submode + // static QMap m_lastDecodeStartMap; + + // TODO: make this non-static field of MainWindow? + // map of submodes to decode + optional alternate decode positions + static QMap> submodes = { + {Varicode::JS8CallSlow, {0}}, + {Varicode::JS8CallNormal, {0}}, + {Varicode::JS8CallFast, {0, 5}}, + {Varicode::JS8CallTurbo, {0, 3}}, + }; + + static qint32 maxSamples = NTMAX*RX_SAMPLE_RATE; + static qint32 oneSecondSamples = RX_SAMPLE_RATE; + + int decodes = 0; + + // do we have a better way to check this? + bool multi = ui->actionModeMultiDecoder->isChecked(); + + // do we have a better way to check this? + bool everySecond = m_wideGraph->shouldAutoSync(); + + // do we need to process alternate positions? + bool skipAlt = false; + + foreach(auto submode, submodes.keys()){ + // skip if multi is disabled and this mode is not the current submode + if(!multi && submode != m_nSubMode){ + continue; + } + + // check all alternate decode positions + foreach(auto alt, submodes.value(submode)){ + // skip alts if we are decoding every second + if(everySecond && alt != 0){ + continue; + } + + // skip alt decode positions if needed + if(skipAlt && alt != 0){ + continue; + } + + qint32 cycle = computeAltCycleForDecode(submode, k, alt*oneSecondSamples); + qint32 cycleFrames = computeFramesPerCycleForDecode(submode); + qint32 cycleFramesNeeded = computeFramesPerSymbolForDecode(submode)*JS8_NUM_SYMBOLS; //computeFramesNeededForDecode(submode) - oneSecondSamples; + qint32 cycleFramesReady = k - (cycle * cycleFrames); + + if(!m_lastDecodeStartMap.contains(submode)){ + m_lastDecodeStartMap[submode] = cycle * cycleFrames; + } + + qint32 lastDecodeStart = m_lastDecodeStartMap[submode]; + qint32 incrementedBy = k - lastDecodeStart; + if(k < lastDecodeStart){ + incrementedBy = maxSamples - lastDecodeStart + k; + } + + if(JS8_DEBUG_DECODE) qDebug() << submodeName(submode) << "alt" << alt << "cycle" << cycle << "cycle frames" << cycleFrames << "cycle start" << cycle*cycleFrames << "cycle end" << (cycle+1)*cycleFrames << "k" << k << "frames ready" << cycleFramesReady << "incremeted by" << incrementedBy; + + if(everySecond && incrementedBy >= oneSecondSamples){ + DecodeParams d; + d.submode = submode; + d.sz = cycleFrames; + d.start = k - d.sz; + if(d.start < 0){ + d.start += maxSamples; + } + m_decoderQueue.append(d); + decodes++; + + // keep track of last decode position + m_lastDecodeStartMap[submode] = k; + } + else if( + (incrementedBy >= 2*oneSecondSamples && cycleFramesReady >= cycleFramesNeeded ) || + (incrementedBy >= oneSecondSamples && cycleFramesReady < 1.25*oneSecondSamples) + ){ + DecodeParams d; + d.submode = submode; + d.start = cycle*cycleFrames; + d.sz = cycleFramesReady; + m_decoderQueue.append(d); + decodes++; + + // keep track of last decode position + m_lastDecodeStartMap[submode] = k; + } + } + } + + return decodes > 0; +} + +#if 0 + static qint32 lastDecodeStartK = -1; + + //if(lastDecodeStartK == -1){ + // qint32 cycleStartK = computeCycleForDecode(Varicode::JS8CallNormal, k) * computeFramesPerCycleForDecode(Varicode::JS8CallNormal); + // qint32 secondStartK = ((k - cycleStartK) / RX_SAMPLE_RATE) * RX_SAMPLE_RATE; + // lastDecodeStartK = secondStartK; + //} + + static qint32 lastDecodeStartSec = -1; + + int decodes = 0; + qint32 startA = 0; + qint32 szA = 0; + qint32 maxSamples = NTMAX*RX_SAMPLE_RATE; + qint32 oneSecondSamples = RX_SAMPLE_RATE; + + // compute how much we've incremented since the last decode ready event + qint32 incrementedBy = k - lastDecodeStartK; + if(k < lastDecodeStartK){ + incrementedBy = maxSamples - lastDecodeStartK + k; + } + + // if we've advanced in time enough since the last decode + int thisSec = DriftingDateTime::currentDateTimeUtc().time().second(); + if(incrementedBy >= oneSecondSamples){ // && lastDecodeStartSec != thisSec){ + qDebug() << "ready to detect decode" << incrementedBy; + + QList submodes = { + //Varicode::JS8CallSlow, + Varicode::JS8CallNormal, + //Varicode::JS8CallFast, + //Varicode::JS8CallTurbo + }; + foreach(auto submode, submodes){ + + // start at now and subtract the frames in one cycle... + // for normal mode this allows us to look through the last 15 seconds of data + // + the amount that we've just incremented (say if we were caught in a decode) + // to search for decodable signals... and we do this _every_ second! + // szA = computeFramesPerCycleForDecode(submode) + incrementedBy; + szA = computeFramesNeededForDecode(submode); + startA = k - szA; + + // when the start position is negative, we need to start at the end of the + // buffer and wrap around. the decoder knows how to do the wrap around, so + // all we need to do is + if(startA < 0){ + startA += maxSamples; + } + + // create the decode params and queue it + DecodeParams d; + d.submode = submode; + d.start = startA; + d.sz = szA; + m_decoderQueue.append(d); + decodes++; + } + + // the decoder is going to look +/- multiple seconds... so this may partial decode + // up to a few seconds in the future...meaning if we're doing this every second + // we may actually decode this same signal 2-3 more times... but we have a + // message decode dedupe that should prevent any issues with dupes out of the + // decoder when this happens. + lastDecodeStartK = k; + lastDecodeStartSec = thisSec; + + // TODO: remove this after testing + m_wideGraph->drawHorizontalLine(QColor(Qt::yellow), 0, 25); + } + + return decodes > 0; + +#if 0 + // when no other mode is being decoded, do a sync stats decode for normal mode + bool experiment = true; + + if(experiment){ + static qint32 lastDecodeStartA = 0; + + qint32 maxSamples = m_detector->period()*RX_SAMPLE_RATE; + qint32 oneSecondSamples = RX_SAMPLE_RATE; + + // when we've incremented at least one second into the future + qint32 incrementedBy = k - lastDecodeStartA; + if(k < lastDecodeStartA){ + incrementedBy = maxSamples - lastDecodeStartA + k; + } + + if(incrementedBy >= oneSecondSamples){ + // we've incremented at least one second, so look backwards + + + //startA = lastDecodeStartA + oneSecondSamples; + //szA = computeFramesNeededForDecode(Varicode::JS8CallNormal) + oneSecondSamples; + //lastDecodeStartA += + // startA = k - incrementedBy - computeFramesNeededForDecode(Varicode::JS8CallNormal); + // if(startA < 0){ + // startA += maxSamples; + // } + // + // szA = incrementedBy + computeFramesNeededForDecode(Varicode::JS8CallNormal); + // + // qDebug() << "A: start:" << startA << "sz:" << szA << "stop:" << startA + szA; + // + // lastDecodeStartA = k; + // couldDecodeA = true; + } + + //qint32 oneSecondFramesA = computeFramesPerCycleForDecode(Varicode::JS8CallNormal)/computePeriodForSubmode(Varicode::JS8CallNormal); + //if(lastDecodeStartA == -1 || k < k0 || k - lastDecodeStartA > oneSecondFramesA){ + // startA = k-computeFramesNeededForDecode(Varicode::JS8CallNormal); + // + // if(startA < 0){ + // // decoder wraps around ranges + // startA += m_detector->period() * RX_SAMPLE_RATE; + // } + // + // szA = computeFramesNeededForDecode(Varicode::JS8CallNormal); + // lastDecodeStartA = k; + // couldDecodeA = true; + //} + + couldDecodeB = couldDecodeC = couldDecodeE = false; + +#if 0 + static qint32 lastDecodeStartB = -1; + qint32 oneSecondFramesB = computeFramesPerCycleForDecode(Varicode::JS8CallFast)/computePeriodForSubmode(Varicode::JS8CallFast); + if(lastDecodeStartB == -1 || k < k0 || k - lastDecodeStartB > oneSecondFramesB){ + startB = k-computeFramesNeededForDecode(Varicode::JS8CallFast); + + if(startB < 0){ + // decoder wraps around ranges + startB += m_detector->period() * RX_SAMPLE_RATE; + } + + szB = computeFramesNeededForDecode(Varicode::JS8CallFast); + lastDecodeStartB = k; + couldDecodeB = true; + } + + static qint32 lastDecodeStartC = -1; + qint32 oneSecondFramesC = computeFramesPerCycleForDecode(Varicode::JS8CallTurbo)/computePeriodForSubmode(Varicode::JS8CallTurbo); + if(lastDecodeStartC == -1 || k < k0 || k - lastDecodeStartC > oneSecondFramesC){ + startC = k-computeFramesNeededForDecode(Varicode::JS8CallTurbo); + + if(startC < 0){ + // decoder wraps around ranges + startC += m_detector->period() * RX_SAMPLE_RATE; + } + + szC = computeFramesNeededForDecode(Varicode::JS8CallTurbo); + lastDecodeStartC = k; + couldDecodeC = true; + } + +#if JS8_ENABLE_JS8E + static qint32 lastDecodeStartE = -1; + qint32 oneSecondFramesE = computeFramesPerCycleForDecode(Varicode::JS8CallSlow)/computePeriodForSubmode(Varicode::JS8CallSlow); + if(lastDecodeStartE == -1 || k < k0 || k - lastDecodeStartE > oneSecondFramesE){ + startE = k-computeFramesNeededForDecode(Varicode::JS8CallSlow); + + if(startE < 0){ + // decoder wraps around ranges + startE += m_detector->period() * RX_SAMPLE_RATE; + } + + szE = computeFramesNeededForDecode(Varicode::JS8CallSlow); + lastDecodeStartE = k; + couldDecodeE = true; + } +#endif + +#if JS8_ENABLE_JS8I + static qint32 lastDecodeStartI = -1; + qint32 oneSecondFramesI = computeFramesPerCycleForDecode(Varicode::JS8CallUltra)/computePeriodForSubmode(Varicode::JS8CallUltra); + if(lastDecodeStartI == -1 || k < k0 || k - lastDecodeStartI > oneSecondFramesI){ + startI = k-computeFramesNeededForDecode(Varicode::JS8CallUltra); + + if(startI < 0){ + // decoder wraps around ranges + startI += m_detector->period() * RX_SAMPLE_RATE; + } + + szI = computeFramesNeededForDecode(Varicode::JS8CallUltra); + lastDecodeStartI = k; + couldDecodeI = true; + } +#endif +#endif + } +#endif + +} +#endif + /** * @brief MainWindow::decodeProcessQueue * process the decode queue by merging available decode ranges @@ -4406,7 +4774,7 @@ bool MainWindow::decodeProcessQueue(qint32 *pSubmode){ QMutexLocker mutex(m_detector->getMutex()); if(m_decoderBusy){ - int seconds = m_decoderBusyStartTime.secsTo(DriftingDateTime::currentDateTimeUtc()); + int seconds = m_decoderBusyStartTime.secsTo(QDateTime::currentDateTimeUtc()); if(seconds > 60){ if(JS8_DEBUG_DECODE) qDebug() << "--> decoder should be killed!" << QString("(%1 seconds)").arg(seconds); } else if(seconds > 30){ @@ -4436,6 +4804,7 @@ bool MainWindow::decodeProcessQueue(qint32 *pSubmode){ if(JS8_DEBUG_DECODE) qDebug() << "--> decoder skipping at least 1 decode cycle" << "count" << count << "max" << maxDecodes; } + // default to no submodes being decoded, then bitwise OR the modes together to decode them all at once dec_data.params.nsubmodes = 0; while(!m_decoderQueue.isEmpty()){ @@ -4494,6 +4863,7 @@ bool MainWindow::decodeProcessQueue(qint32 *pSubmode){ int period = computePeriodForSubmode(submode); + dec_data.params.syncStats = (m_wideGraph->shouldDisplayDecodeAttempts() || m_wideGraph->shouldAutoSync()); dec_data.params.npts8=(m_ihsym*m_nsps)/16; dec_data.params.newdat=1; dec_data.params.nagain=0; @@ -4673,7 +5043,7 @@ void MainWindow::decodeBusy(bool b) //decodeBusy() m_decoderBusy=b; if(m_decoderBusy){ tx_status_label.setText("Decoding"); - m_decoderBusyStartTime = DriftingDateTime::currentDateTimeUtc(); + m_decoderBusyStartTime = QDateTime::currentDateTimeUtc(); //DriftingDateTime::currentDateTimeUtc(); m_decoderBusyFreq = dialFrequency(); m_decoderBusyBand = m_config.bands()->find (m_decoderBusyFreq); } @@ -4699,7 +5069,16 @@ void MainWindow::decodeDone () m_nclearave=0; m_RxLog=0; m_blankLine=true; - m_messageDupeCache.clear(); + + // cleanup old cached messages (messages > submode period old) + for (auto it = m_messageDupeCache.begin(); it != m_messageDupeCache.end();){ + auto cached = it.value(); + if (cached.date.secsTo(QDateTime::currentDateTimeUtc()) > computePeriodForSubmode(cached.submode)){ + it = m_messageDupeCache.erase(it); + } else { + ++it; + } + } decodeBusy(false); } @@ -4750,7 +5129,7 @@ void MainWindow::decodeCheckHangingDecoder(){ return; } - if(!m_decoderBusyStartTime.isValid() || m_decoderBusyStartTime.secsTo(DriftingDateTime::currentDateTimeUtc()) < 60){ + if(!m_decoderBusyStartTime.isValid() || m_decoderBusyStartTime.secsTo(QDateTime::currentDateTimeUtc()) < 60){ return; } @@ -4836,22 +5215,186 @@ void MainWindow::readFromStdout(QProcess * proc) //r } void MainWindow::processDecodedLine(QByteArray t){ - qDebug() << "JS8: " << QString(t); + if(JS8_DEBUG_DECODE) qDebug() << "JS8: " << QString(t); bool bAvgMsg=false; int navg=0; - if(t.indexOf("") >= 0) { - if(JS8_DEBUG_DECODE) qDebug() << "--> busy?" << m_decoderBusy << "lock exists?" << ( QFile{m_config.temp_dir ().absoluteFilePath (".lock")}.exists()); - return; + + static QList driftQueue; + + static qint32 syncStart = -1; + if(t.indexOf(" sync start") >= 0){ + auto segs = QString(t.trimmed()).split(QRegExp("[\\s\\t]+"), QString::SkipEmptyParts); + if(segs.isEmpty()){ + return; + } + + auto spos = segs.at(3); + syncStart = spos.toInt(); + return; + } + + if(t.indexOf("") >= 0) { + auto segs = QString(t.trimmed()).split(QRegExp("[\\s\\t]+"), QString::SkipEmptyParts); + if(segs.isEmpty()){ + return; + } + + // only continue if we should either display decode attempts or if we should try to auto sync + if(!m_wideGraph->shouldDisplayDecodeAttempts() && !m_wideGraph->shouldAutoSync()){ + return; + } + + auto m1 = QString(segs.at(2)); + auto m = int(m1.toInt()); + + auto f1 = QString(segs.at(4)); + auto f = int(f1.toFloat()); + + auto s1 = QString(segs.at(6)); + auto s = int(s1.toFloat()); + + auto xdt1 = QString(segs.at(8)); + auto xdt = xdt1.toFloat(); + auto xdtMs = int(xdt*1000); + + // draw candidates + if(abs(xdtMs) <= 2000){ + if(s < 10){ + m_wideGraph->drawDecodeLine(QColor(Qt::darkCyan), f, f + computeBandwidthForSubmode(m)); + } else if (s <= 15){ + m_wideGraph->drawDecodeLine(QColor(Qt::cyan), f, f + computeBandwidthForSubmode(m)); + } else if (s <= 21){ + m_wideGraph->drawDecodeLine(QColor(Qt::white), f, f + computeBandwidthForSubmode(m)); + } + } + + if(!t.contains("decode")){ + return; + } + + // draw decodes + m_wideGraph->drawDecodeLine(QColor(Qt::red), f, f + computeBandwidthForSubmode(m)); + + // compute time drift if needed + if(!m_wideGraph->shouldAutoSync()){ + return; + } + + // TODO: can we do this for FAST & TURBO + // if fast/turbo is observed and we're in a period post 15 seconds (i.e., second 18 turbo decode) + // then make the drift relative to the first cycle instead + if(m != Varicode::JS8CallNormal && m != Varicode::JS8CallSlow){ + return; + } + + // if we're here at this point, we _should_ be operating a decode every second + // + // so we need to figure out where: + // + // 1) this current decode started + // 2) when that cycle _should_ have started + // 3) compute the delta + // 4) apply the drift + + int periodMs = 1000 * computePeriodForSubmode(m); + + //writeNoticeTextToUI(now, QString("Decode at %1 (kin: %2, lastDecoded: %3)").arg(syncStart).arg(dec_data.params.kin).arg(m_lastDecodeStartMap.value(m))); + + float expectedStartDelay = computePeriodStartDelayForDecode(m)/1000.0; + + float decodedSignalTime = (float)syncStart/(float)RX_SAMPLE_RATE; + + //writeNoticeTextToUI(now, QString("--> started at %1 seconds into the start of my drifted minute").arg(decodedSignalTime)); + + //writeNoticeTextToUI(now, QString("--> we add a time delta of %1 seconds into the start of the cycle").arg(xdt)); + + // adjust for expected start delay + decodedSignalTime -= expectedStartDelay; + + // adjust for time delta + decodedSignalTime += xdt; + + // ensure that we are within a 60 second minute + if(decodedSignalTime < 0){ + decodedSignalTime += 60.0; + } else if(decodedSignalTime > 60){ + decodedSignalTime -= 60.0; + } + + //writeNoticeTextToUI(now, QString("--> so signal adjusted started at %1 seconds into the start of my drifted minute").arg(decodedSignalTime)); + + int decodedSignalTimeMs = 1000 * decodedSignalTime; + int cycleStartTimeMs = (decodedSignalTimeMs / periodMs) * periodMs; + int driftMs = cycleStartTimeMs - decodedSignalTimeMs; + + //writeNoticeTextToUI(now, QString("--> which is a drift adjustment of %1 milliseconds").arg(driftMs)); + + // if we have a large negative offset (say -14000), use the positive inverse of +1000 + if(driftMs + periodMs < qAbs(driftMs)){ + driftMs += periodMs; + } + // if we have a large positive offset (say 14000, use the negative inverse of -1000) + else if(qAbs(driftMs - periodMs) < driftMs){ + driftMs -= periodMs; + } + + //writeNoticeTextToUI(now, QString("--> which is a corrected drift adjustment of %1 milliseconds").arg(driftMs)); + + int newDrift = DriftingDateTime::drift() + driftMs; + if(newDrift < 0){ + newDrift %= -periodMs; + } else { + newDrift %= periodMs; + } + + //writeNoticeTextToUI(now, QString("--> which is rounded to a total drift of %1 milliseconds for this period").arg(newDrift)); + + driftQueue.append(newDrift); + + if(JS8_DEBUG_DECODE) qDebug() << "--> busy?" << m_decoderBusy << "lock exists?" << ( QFile{m_config.temp_dir ().absoluteFilePath (".lock")}.exists()); + + return; } if(t.indexOf("") >= 0) { - if(JS8_DEBUG_DECODE) qDebug() << "--> busy?" << m_decoderBusy << "lock exists?" << ( QFile{m_config.temp_dir ().absoluteFilePath (".lock")}.exists()); - return; + if(m_wideGraph->shouldDisplayDecodeAttempts()){ + m_wideGraph->drawHorizontalLine(QColor(Qt::yellow), 0, 5); + } + + if(JS8_DEBUG_DECODE) qDebug() << "--> busy?" << m_decoderBusy << "lock exists?" << ( QFile{m_config.temp_dir ().absoluteFilePath (".lock")}.exists()); + return; + } + + if(t.indexOf("") >= 0) { + return; } if(t.indexOf("") >= 0) { - if(m_mode=="QRA64") m_wideGraph->drawRed(0,0); + int msec = m_decoderBusyStartTime.msecsTo(QDateTime::currentDateTimeUtc()); + if(JS8_DEBUG_DECODE) qDebug() << "decode duration" << msec << "ms"; + + if(!driftQueue.isEmpty()){ + static int driftN = 1; + static int driftAvg = DriftingDateTime::drift(); + + // let the widegraph know for timing control + m_wideGraph->notifyDriftedSignalsDecoded(driftQueue.count()); + + while(!driftQueue.isEmpty()){ + int newDrift = driftQueue.first(); + driftQueue.removeFirst(); + + driftAvg = ((driftN-1)*driftAvg + newDrift)/driftN; + if(driftN < 60) driftN++; // cap it to 60 observations + } + + setDrift(driftAvg); + + + //writeNoticeTextToUI(QDateTime::currentDateTimeUtc(), QString("Automatic Drift: %1").arg(driftAvg)); + } + m_bDecoded = t.mid(16).trimmed().toInt() > 0; int mswait=3*1000*m_TRperiod/4; if(!m_diskData) killFileTimer.start(mswait); //Kill in 3/4 period @@ -4879,26 +5422,38 @@ void MainWindow::processDecodedLine(QByteArray t){ } auto rawText = QString::fromUtf8 (t.constData ()).remove (QRegularExpression {"\r|\n"}); + DecodedText decodedtext {rawText, "FT8" == m_mode && ui->cbVHFcontest->isChecked(), m_config.my_grid ()}; - bool bValidFrame = decodedtext.snr() >= rxSnrThreshold(decodedtext.submode()); - // dupe check - auto frame = decodedtext.message(); + // frames are also valid if they pass our dupe check (haven't seen the same frame in the past 1/2 decode period) auto frameOffset = decodedtext.frequencyOffset(); - if(m_messageDupeCache.contains(frame)){ - // check to see if the frequency is near our previous frame - auto cachedFreq = m_messageDupeCache.value(frame, 0); - if(qAbs(cachedFreq - frameOffset) <= rxThreshold(decodedtext.submode())){ - qDebug() << "duplicate frame from" << cachedFreq << "and" << frameOffset; - bValidFrame = false; + auto frameDedupeKey = QString("%1:%2").arg(decodedtext.submode()).arg(decodedtext.frame()); + if(m_messageDupeCache.contains(frameDedupeKey)){ + auto cached = m_messageDupeCache.value(frameDedupeKey); + + // check to see if the time since last seen is > 1/2 decode period + auto cachedDate = cached.date; + if(cachedDate.secsTo(QDateTime::currentDateTimeUtc()) < 0.5*computePeriodForSubmode(decodedtext.submode())){ + qDebug() << "duplicate frame at" << cachedDate << "using key" << frameDedupeKey; + return; } - } else { - // cache for this decode cycle - m_messageDupeCache[frame] = frameOffset; + + // check to see if the frequency is near our previous frame + auto cachedFreq = cached.freq; + if(qAbs(cachedFreq - frameOffset) <= rxThreshold(decodedtext.submode())){ + qDebug() << "duplicate frame from" << cachedFreq << "and" << frameOffset << "using key" << frameDedupeKey; + return; + } + + // huzzah! + // if we make it here, the cache is invalid and will be bumped when we cache the new frame below } + // frames are valid if they meet our minimum rx threshold for the submode + bool bValidFrame = decodedtext.snr() >= rxSnrThreshold(decodedtext.submode()); + qDebug() << "valid" << bValidFrame << submodeName(decodedtext.submode()) << "decoded text" << decodedtext.message(); // skip if invalid @@ -4906,6 +5461,9 @@ void MainWindow::processDecodedLine(QByteArray t){ return; } + // if the frame is valid, cache it! + m_messageDupeCache[frameDedupeKey] = {QDateTime::currentDateTimeUtc(), decodedtext.submode(), frameOffset}; + // log valid frames to ALL.txt (and correct their timestamp format) auto freq = dialFrequency(); @@ -4954,7 +5512,7 @@ void MainWindow::processDecodedLine(QByteArray t){ d.utcTimestamp = DriftingDateTime::currentDateTimeUtc(); d.snr = decodedtext.snr(); d.isBuffered = false; - d.tdrift = decodedtext.dt(); + d.tdrift = m_wideGraph->shouldAutoSync() ? DriftingDateTime::drift()/1000.0 : decodedtext.dt(); d.submode = decodedtext.submode(); // if we have any "first" frame, and a buffer is already established, clear it... @@ -4992,7 +5550,7 @@ void MainWindow::processDecodedLine(QByteArray t){ cd.offset = decodedtext.frequencyOffset(); cd.utcTimestamp = DriftingDateTime::currentDateTimeUtc(); cd.bits = decodedtext.bits(); - cd.tdrift = decodedtext.dt(); + cd.tdrift = m_wideGraph->shouldAutoSync() ? DriftingDateTime::drift()/1000.0 : decodedtext.dt(); cd.submode = decodedtext.submode(); // Only respond to HEARTBEATS...remember that CQ messages are "Alt" pings @@ -5071,7 +5629,7 @@ void MainWindow::processDecodedLine(QByteArray t){ cmd.utcTimestamp = DriftingDateTime::currentDateTimeUtc(); cmd.bits = decodedtext.bits(); cmd.extra = parts.length() > 2 ? parts.mid(3).join(" ") : ""; - cmd.tdrift = decodedtext.dt(); + cmd.tdrift = m_wideGraph->shouldAutoSync() ? DriftingDateTime::drift()/1000.0 : decodedtext.dt(); cmd.submode = decodedtext.submode(); // if the command is a buffered command and its not the last frame OR we have from or to in a separate message (compound call) @@ -5900,7 +6458,7 @@ void MainWindow::guiUpdate() auto drift = DriftingDateTime::drift(); QDateTime t = DriftingDateTime::currentDateTimeUtc(); QStringList parts; - parts << (t.time().toString() + (!drift ? " " : QString(" (%1%2ms)").arg(drift > 0 ? "+" : "").arg(drift))); + parts << (t.time().toString() + (!drift ? " " : QString(" (%1%2ms%3)").arg(drift > 0 ? "+" : "").arg(drift).arg(m_wideGraph->shouldAutoSync() ? " auto" : ""))); parts << t.date().toString("yyyy MMM dd"); ui->labUTC->setText(parts.join("\n")); @@ -7803,19 +8361,11 @@ void MainWindow::on_actionJS8_triggered() if(m_isWideGraphMDI) m_wideGraph->show(); ui->decodedTextLabel2->setText(" UTC dB DT Freq Message"); m_modulator->setTRPeriod(m_TRperiod); // TODO - not thread safe -#if JS8_RING_BUFFER + Q_ASSERT(NTMAX == 60); m_wideGraph->setPeriod(m_TRperiod, m_nsps); -#if JS8_ENABLE_JS8E && !JS8E_IS_ULTRA m_detector->setTRPeriod(NTMAX); // TODO - not thread safe -#else - m_detector->setTRPeriod(NTMAX / 2); // TODO - not thread safe -#endif -#else - m_wideGraph->setPeriod(m_TRperiod, m_nsps); - m_detector->setTRPeriod(m_TRperiod); // TODO - not thread safe -#endif ui->label_7->setText("Rx Frequency"); if(m_config.bFox()) { ui->label_6->setText("Stations calling DXpedition " + m_config.my_callsign()); @@ -9623,6 +10173,12 @@ void MainWindow::qsy(int hzDelta){ displayActivity(true); } +void MainWindow::drifted(int /*prev*/, int /*cur*/){ + // here we reset the buffer position without clearing the buffer + // this makes the detected emit the correct k when drifting time + m_detector->resetBufferPosition(); +} + void MainWindow::setFreqOffsetForRestore(int freq, bool shouldRestore){ setFreq4(freq, freq); if(shouldRestore){ @@ -10137,6 +10693,7 @@ void MainWindow::updateModeButtonText(){ modeText += QString("+HB"); } } + ui->modeButton->setText(modeText); } diff --git a/mainwindow.h b/mainwindow.h index b47aa82..1fdc867 100644 --- a/mainwindow.h +++ b/mainwindow.h @@ -132,6 +132,7 @@ public slots: void readFromStdout(QProcess * proc); void setXIT(int n, Frequency base = 0u); void qsy(int hzDelta); + void drifted(int prev, int cur); void setFreqOffsetForRestore(int freq, bool shouldRestore); bool tryRestoreFreqOffset(); void setFreq4(int rxFreq, int txFreq); @@ -244,6 +245,7 @@ private slots: bool decode(qint32 k); bool isDecodeReady(int submode, qint32 k, qint32 k0, qint32 *pCurrentDecodeStart, qint32 *pNextDecodeStart, qint32 *pStart, qint32 *pSz, qint32 *pCycle); bool decodeEnqueueReady(qint32 k, qint32 k0); + bool decodeEnqueueReadyExperiment(qint32 k, qint32 k0); bool decodeProcessQueue(qint32 *pSubmode); void decodeStart(); void decodePrepareSaveAudio(int submode); @@ -614,6 +616,7 @@ private: bool m_loopall; bool m_decoderBusy; QString m_decoderBusyBand; + QMap m_lastDecodeStartMap; // submode, decode k start position Radio::Frequency m_decoderBusyFreq; QDateTime m_decoderBusyStartTime; bool m_auto; @@ -853,13 +856,18 @@ private: struct DecodeParams { int submode; - int cycle; int start; int sz; }; + struct CachedFrame { + QDateTime date; + int submode; + int freq; + }; + QQueue m_decoderQueue; - QMap m_messageDupeCache; // message frame -> freq offset seen + QMap m_messageDupeCache; // message frame -> date seen, submode seen, freq offset seen QMap m_showColumnsCache; // table column:key -> show boolean QMap m_sortCache; // table key -> sort by QPriorityQueue m_txMessageQueue; // messages to be sent @@ -983,7 +991,10 @@ private: //int computeCurrentCycle(int period); //int computeCycleStartForDecode(int cycle, int period); int computeCycleForDecode(int submode, int k); + int computeAltCycleForDecode(int submode, int k, int offsetFrames); int computeFramesPerCycleForDecode(int submode); + int computePeriodStartDelayForDecode(int submode); + int computeFramesPerSymbolForDecode(int submode); int computeFramesNeededForDecode(int submode); bool shortList(QString callsign); void transmit (double snr = 99.); diff --git a/mainwindow.ui b/mainwindow.ui index b2daa8c..2107591 100644 --- a/mainwindow.ui +++ b/mainwindow.ui @@ -4753,6 +4753,7 @@ list. The list can be maintained in Settings (F2). + @@ -5762,6 +5763,14 @@ list. The list can be maintained in Settings (F2). Enable Tuning Tone (T&UNE) + + + true + + + Enable Automatic Timing Synchronization (S&YNC) + + diff --git a/plotter.cpp b/plotter.cpp index 231b450..a1fc029 100644 --- a/plotter.cpp +++ b/plotter.cpp @@ -277,42 +277,31 @@ void CPlotter::draw(float swide[], bool bScroll, bool bRed) painter2D.drawText(x1-4,y,"73"); } - if(bRed) { - std::ifstream f; - f.open(m_redFile.toLatin1()); - if(f) { - int x,y; - float freq,sync; - float slimit=6.0; - QPen pen0(Qt::red,1); - painter1.setPen(pen0); - for(int i=0; i<99999; i++) { - f >> freq >> sync; - if(f.eof()) break; - x=XfromFreq(freq); - y=(sync-slimit)*3.0; - if(y>0) { - if(y>15.0) y=15.0; - if(x>=0 and x<=m_w) { - painter1.setPen(pen0); - painter1.drawLine(x,0,x,y); - } - } - } - f.close(); - } -// m_bDecodeFinished=false; - } - update(); //trigger a new paintEvent m_bScaleOK=true; } -void CPlotter::drawRed(int ia, int ib, float swide[]) +void CPlotter::drawDecodeLine(const QColor &color, int ia, int ib) { - m_ia=ia; - m_ib=ib; - draw(swide,false,true); + int x1=XfromFreq(ia); + int x2=XfromFreq(ib); + + QPen pen0(color, 1); + + QPainter painter1(&m_WaterfallPixmap); + painter1.setPen(pen0); + painter1.drawLine(qMin(x1, x2),4,qMax(x1, x2),4); + painter1.drawLine(qMin(x1, x2),0,qMin(x1, x2),9); + painter1.drawLine(qMax(x1, x2),0,qMax(x1, x2),9); +} + +void CPlotter::drawHorizontalLine(const QColor &color, int x, int width) +{ + QPen pen0(color, 1); + + QPainter painter1(&m_WaterfallPixmap); + painter1.setPen(pen0); + painter1.drawLine(x,0,width <= 0 ? m_w : x+width,0); } void CPlotter::replot() @@ -890,6 +879,10 @@ void CPlotter::setTol(int n) //setTol() DrawOverlay(); } +QVector const& CPlotter::colors(){ + return g_ColorTbl; +} + void CPlotter::setColours(QVector const& cl) { g_ColorTbl = cl; diff --git a/plotter.h b/plotter.h index 54e6d74..60db84f 100644 --- a/plotter.h +++ b/plotter.h @@ -77,6 +77,7 @@ public: void setBreadth(qint32 w) {m_w = w;} qint32 breadth() const {return m_w;} float fSpan() const {return m_fSpan;} + QVector const& colors(); void setColours(QVector const& cl); void setFlatten(bool b1, bool b2); void setTol(int n); @@ -90,7 +91,8 @@ public: void setReference(bool b) {m_bReference = b;} bool Reference() const {return m_bReference;} #endif - void drawRed(int ia, int ib, float swide[]); + void drawDecodeLine(const QColor &color, int ia, int ib); + void drawHorizontalLine(const QColor &color, int x, int width); void setVHF(bool bVHF); void setRedFile(QString fRed); bool scaleOK () const {return m_bScaleOK;} diff --git a/widegraph.cpp b/widegraph.cpp index a897027..66aa649 100644 --- a/widegraph.cpp +++ b/widegraph.cpp @@ -167,6 +167,8 @@ WideGraph::WideGraph(QSettings * settings, QWidget *parent) : ui->controls_widget->setVisible(!m_settings->value("HideControls", false).toBool()); ui->cbControls->setChecked(!m_settings->value("HideControls", false).toBool()); ui->fpsSpinBox->setValue(m_settings->value ("WaterfallFPS", 4).toInt()); + ui->decodeAttemptCheckBox->setChecked(m_settings->value("DisplayDecodeAttempts", false).toBool()); + ui->autoDriftAutoStopCheckBox->setChecked(m_settings->value ("StopAutoSyncOnDecode", true).toBool()); auto splitState = m_settings->value("SplitState").toByteArray(); if(!splitState.isEmpty()){ @@ -242,11 +244,80 @@ void WideGraph::saveSettings() //saveS m_settings->setValue ("FilterOpacityPercent", ui->filterOpacitySpinBox->value()); m_settings->setValue ("SplitState", ui->splitter->saveState()); m_settings->setValue ("WaterfallFPS", ui->fpsSpinBox->value()); + m_settings->setValue ("DisplayDecodeAttempts", ui->decodeAttemptCheckBox->isChecked()); + m_settings->setValue ("StopAutoSyncOnDecode", ui->autoDriftAutoStopCheckBox->isChecked()); } -void WideGraph::drawRed(int ia, int ib) +bool WideGraph::shouldDisplayDecodeAttempts(){ + return ui->decodeAttemptCheckBox->isChecked(); +} + +bool WideGraph::shouldAutoSync(){ + return ui->autoDriftButton->isChecked(); +} + +void WideGraph::notifyDriftedSignalsDecoded(int /*signalsDecoded*/){ + if(ui->autoDriftAutoStopCheckBox->isChecked()){ + ui->autoDriftButton->setChecked(false); + } +} + +void WideGraph::on_autoDriftButton_toggled(bool checked){ + static bool connected = false; + if(!connected){ + connect(&m_autoSyncTimer, &QTimer::timeout, this, [this](){ + // if auto drift isn't checked, don't worry about this... + if(!ui->autoDriftButton->isChecked()){ + return; + } + + // uncheck after timeout + if(m_autoSyncTimeLeft == 0){ + ui->autoDriftButton->setChecked(false); + return; + } + + // set new text and decrement timeleft + auto text = ui->autoDriftButton->text(); + auto newText = QString("%1 (%2)").arg(text.left(text.indexOf("(")).trimmed()).arg(m_autoSyncTimeLeft--); + ui->autoDriftButton->setText(newText); + }); + connected = true; + } + + // if in the future we want to auto sync timeout after a time period + bool autoSyncTimeout = false; + + auto text = ui->autoDriftButton->text(); + + if(autoSyncTimeout){ + if(checked){ + m_autoSyncTimeLeft = 120; + m_autoSyncTimer.setInterval(1000); + m_autoSyncTimer.start(); + ui->autoDriftButton->setText(QString("%1 (%2)").arg(text.replace("Start", "Stop")).arg(m_autoSyncTimeLeft--)); + } else { + m_autoSyncTimeLeft = 0; + m_autoSyncTimer.stop(); + ui->autoDriftButton->setText(text.left(text.indexOf("(")).trimmed().replace("Stop", "Start")); + } + return; + } else { + if(checked){ + ui->autoDriftButton->setText(text.left(text.indexOf("(")).trimmed().replace("Start", "Stop")); + } else { + ui->autoDriftButton->setText(text.left(text.indexOf("(")).trimmed().replace("Stop", "Start")); + } + } +} + +void WideGraph::drawDecodeLine(const QColor &color, int ia, int ib) { - ui->widePlot->drawRed(ia,ib,swide); + ui->widePlot->drawDecodeLine(color, ia, ib); +} + +void WideGraph::drawHorizontalLine(const QColor &color, int x, int width){ + ui->widePlot->drawHorizontalLine(color, x, width); } void WideGraph::dataSink2(float s[], float df3, int ihsym, int ndiskdata) //dataSink2 @@ -356,6 +427,8 @@ void WideGraph::drawSwide(){ swideLocal[i] = flagValue; } ui->widePlot->draw(swideLocal,true,false); + } else if(lastSecondInPeriod != secondInPeriod) { + //ui->widePlot->drawHorizontalLine(Qt::white, 0, 5); } lastSecondInPeriod=secondInPeriod; @@ -678,6 +751,10 @@ void WideGraph::readPalette () //readPalette } } +QVector const& WideGraph::colors(){ + return ui->widePlot->colors(); +} + void WideGraph::on_paletteComboBox_activated (QString const& palette) //palette selector { m_waterfallPalette = palette; @@ -905,6 +982,8 @@ void WideGraph::on_driftSyncResetButton_clicked(){ } void WideGraph::setDrift(int n){ + int prev = drift(); + DriftingDateTime::setDrift(n); qDebug() << qSetRealNumberPrecision(12) << "Drift milliseconds:" << n; @@ -914,6 +993,12 @@ void WideGraph::setDrift(int n){ if(ui->driftSpinBox->value() != n){ ui->driftSpinBox->setValue(n); } + + emit drifted(prev, n); +} + +int WideGraph::drift(){ + return DriftingDateTime::drift(); } void WideGraph::setQSYEnabled(bool enabled){ diff --git a/widegraph.h b/widegraph.h index 1f2eed9..f5facc7 100644 --- a/widegraph.h +++ b/widegraph.h @@ -63,10 +63,14 @@ public: int smoothYellow(); void setRxBand (QString const& band); void setWSPRtransmitted(); - void drawRed(int ia, int ib); + void drawDecodeLine(const QColor &color, int ia, int ib); + void drawHorizontalLine(const QColor &color, int x, int width); void setVHF(bool bVHF); void setRedFile(QString fRed); void setTurbo(bool turbo); + bool shouldDisplayDecodeAttempts(); + bool shouldAutoSync(); + QVector const& colors(); signals: void freezeDecode2(int n); @@ -74,6 +78,7 @@ signals: void setXIT2(int n); void setFreq3(int rxFreq, int txFreq); void qsy(int hzDelta); + void drifted(int prev, int cur); public slots: void wideFreezeDecode(int n); @@ -82,8 +87,10 @@ public slots: void setControlsVisible(bool visible); bool controlsVisible(); void setDrift(int n); + int drift(); void setQSYEnabled(bool enabled); void setPaused(bool paused){ m_paused = paused; } + void notifyDriftedSignalsDecoded(int signalsDecoded); protected: void keyPressEvent (QKeyEvent *e) override; @@ -117,6 +124,7 @@ private slots: void on_filterCheckBox_toggled(bool b); void on_filterOpacitySpinBox_valueChanged(int n); + void on_autoDriftButton_toggled(bool checked); void on_driftSpinBox_valueChanged(int n); void on_driftSyncButton_clicked(); void on_driftSyncEndButton_clicked(); @@ -158,6 +166,9 @@ private: bool m_bRef; bool m_bHaveTransmitted; //Set true at end of a WSPR transmission + QTimer m_autoSyncTimer; + int m_autoSyncTimeLeft; + QTimer m_drawTimer; QMutex m_drawLock; diff --git a/widegraph.ui b/widegraph.ui index 0e0774e..ce7b81b 100644 --- a/widegraph.ui +++ b/widegraph.ui @@ -179,7 +179,7 @@ 0 0 - 270 + 323 372 @@ -427,9 +427,9 @@ 0 - -193 - 267 - 723 + 0 + 323 + 742 @@ -656,92 +656,106 @@ Waterfall - - - + + + + + 0 + + + + + Gain: + + + + + + + + 0 + 0 + + + + + 100 + 0 + + + + + 16777215 + 16777215 + + + + Waterfall gain + + + -50 + + + 50 + + + Qt::Horizontal + + + QSlider::TicksAbove + + + + + + + Zero: + + + + + + + + 0 + 0 + + + + + 100 + 0 + + + + + 16777215 + 16777215 + + + + Waterfall zero + + + -50 + + + 50 + + + Qt::Horizontal + + + QSlider::TicksAbove + + + + + + + - Gain: - - - - - - - - 0 - 0 - - - - - 100 - 0 - - - - - 16777215 - 16777215 - - - - Waterfall gain - - - -50 - - - 50 - - - Qt::Horizontal - - - QSlider::TicksAbove - - - - - - - - 0 - 0 - - - - - 100 - 0 - - - - - 16777215 - 16777215 - - - - Waterfall zero - - - -50 - - - 50 - - - Qt::Horizontal - - - QSlider::TicksAbove - - - - - - - Zero: + Display Decode Attempts @@ -961,8 +975,8 @@ 0 0 - 281 - 198 + 337 + 351 @@ -1007,67 +1021,114 @@ - - - - 0 - 30 - - - - <html><head/><body><p>Observe signals in the waterfall and click this to synchronize your time drift with the start of a minute.</p></body></html> - - - Sync Time Drift to Now (Minute Start) + + + Automatic + + + + + + 0 + 30 + + + + <html><head/><body><p>Automatically synchronize time drift every second to decodes of NORMAL and SLOW signals observed.</p><p>This process is CPU intensive and may cause abnormal decoder behavior if run for extended periods of time. Default operation should be paired with stopping automatic time drift after signals have been decoded. </p></body></html> + + + Start Automatic Time Drift + + + true + + + + + + + Stop Automatic Drift After First Decode + + + true + + + + - - - - 0 - 30 - - - - <html><head/><body><p>Observe signals in the waterfall and click this to synchronize your time drift with the start of a TX cycle in the current transmission speed.</p></body></html> - - - Sync Time Drift to Now (TX Start) - - - - - - - - 0 - 30 - - - - <html><head/><body><p>Observe signals in the waterfall and click this to synchronize your time drift with the end of a TX cycle in the current transmission speed.</p></body></html> - - - Sync Time Drift to Now (TX End) - - - - - - - - 0 - 30 - - - - Reset your time drift to zero. - - - Reset Time Drift + + + Manual + + + + + + 0 + 30 + + + + <html><head/><body><p>Observe signals in the waterfall and click this to synchronize your time drift with the start of a minute.</p></body></html> + + + Set Time Drift to Now (Minute Start) + + + + + + + + 0 + 30 + + + + <html><head/><body><p>Observe signals in the waterfall and click this to synchronize your time drift with the end of a TX cycle in the current transmission speed.</p></body></html> + + + Set Time Drift to Now (TX End) + + + + + + + + 0 + 30 + + + + <html><head/><body><p>Observe signals in the waterfall and click this to synchronize your time drift with the start of a TX cycle in the current transmission speed.</p></body></html> + + + Set Time Drift to Now (TX Start) + + + + + + + + 0 + 30 + + + + Reset your time drift to zero. + + + Reset Time Drift + + + +