Merge branch 'decoder-experiments' into ft8call-develop

This commit is contained in:
Jordan Sherer 2020-05-16 14:14:57 -04:00
commit bb548c3672
27 changed files with 1218 additions and 326 deletions

View File

@ -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<unsigned> (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<typename T> 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<typename T> 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<unsigned> (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<short int>(dec_data.d2, NTMAX*RX_SAMPLE_RATE, d0, -delta);
} else {
rotate_array_right<short int>(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;
}

View File

@ -35,6 +35,9 @@ public:
Q_SLOT void setBlockSize (unsigned);
void clear (); // discard buffer contents
void resetBufferPosition();
void resetBufferContent();
unsigned secondInPeriod () const;
protected:

View File

@ -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

View File

@ -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();

View File

@ -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

View File

@ -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(*,*) '<DecodeSyncMeta> 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(*,*) '<DecodeDebug> 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(*,*) '<DecodeSyncMeta> 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(*,*) '<DecodeDebug> 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(*,*) '<DecodeSyncMeta> 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(*,*) '<DecodeDebug> 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(*,*) '<DecodeSyncMeta> 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(*,*) '<DecodeDebug> mode B decode finished'
@ -177,16 +237,31 @@ subroutine multimode_decoder(ss,id2,params,nfsample)
write(*,*) '<DecodeDebug> 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(*,*) '<DecodeSyncMeta> 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(*,*) '<DecodeDebug> mode A decode finished'
@ -196,7 +271,7 @@ subroutine multimode_decoder(ss,id2,params,nfsample)
write(*,*) '<DecodeDebug> 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('<DecodeFinished>',i4)

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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(*,*) '<DecodeSyncStat> 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(*,*) '<DecodeSyncStat> decode ', NSUBMODE, 'f1', f1, 'sync', (sync*10), 'xdt', xdt2
flush(6)
endif
decoded0=decoded
call extractmessage174(decoded,origmsg,ncrcflag)
decoded=decoded0

View File

@ -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

View File

@ -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

View File

@ -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(*,*) '<DecodeDebug> 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(*,*) '<DecodeDebug>', 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(*,*) '<DecodeDebug> 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(*,*) '<DecodeDebug> candidate', icand, 'hard', hd, 'nbadcrc', nbadcrc
flush(6)
endif
call timer('js8dec ',1)
if(nbadcrc.eq.0) then
ldupe=.false.

View File

@ -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(*,*) '<DecodeDebug> 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)

View File

@ -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(*,*) '<DecodeDebug> 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)

View File

@ -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(*,*) '<DecodeDebug> 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)

View File

@ -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(*,*) '<DecodeDebug> 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)

View File

@ -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

View File

@ -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

View File

@ -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, &currentDecodeStartA, &nextDecodeStartA, &startA, &szA, &cycleA);
couldDecodeA = isDecodeReady(Varicode::JS8CallNormal, k, k0, &currentDecodeStartA, &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, &currentDecodeStartB, &nextDecodeStartB, &startB, &szB, &cycleB);
couldDecodeB = isDecodeReady(Varicode::JS8CallFast, k, k0, &currentDecodeStartB, &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, &currentDecodeStartC, &nextDecodeStartC, &startC, &szC, &cycleC);
couldDecodeC = isDecodeReady(Varicode::JS8CallTurbo, k, k0, &currentDecodeStartC, &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, &currentDecodeStartE, &nextDecodeStartE, &startE, &szE, &cycleE);
couldDecodeE = isDecodeReady(Varicode::JS8CallSlow, k, k0, &currentDecodeStartE, &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, &currentDecodeStartI, &nextDecodeStartI, &startI, &szI, &cycleI);
couldDecodeI = isDecodeReady(Varicode::JS8CallUltra, k, k0, &currentDecodeStartI, &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<qint32, qint32> m_lastDecodeStartMap;
// TODO: make this non-static field of MainWindow?
// map of submodes to decode + optional alternate decode positions
static QMap<qint32, QList<qint32>> 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<int> 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("<DecodeDebug>") >= 0) {
if(JS8_DEBUG_DECODE) qDebug() << "--> busy?" << m_decoderBusy << "lock exists?" << ( QFile{m_config.temp_dir ().absoluteFilePath (".lock")}.exists());
return;
static QList<int> driftQueue;
static qint32 syncStart = -1;
if(t.indexOf("<DecodeSyncMeta> 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("<DecodeSyncStat>") >= 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("<DecodeStarted>") >= 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("<DecodeDebug>") >= 0) {
return;
}
if(t.indexOf("<DecodeFinished>") >= 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);
}

View File

@ -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<qint32, qint32> 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<DecodeParams> m_decoderQueue;
QMap<QString, int> m_messageDupeCache; // message frame -> freq offset seen
QMap<QString, CachedFrame> m_messageDupeCache; // message frame -> date seen, submode seen, freq offset seen
QMap<QString, QVariant> m_showColumnsCache; // table column:key -> show boolean
QMap<QString, QVariant> m_sortCache; // table key -> sort by
QPriorityQueue<PrioritizedMessage> 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.);

View File

@ -4753,6 +4753,7 @@ list. The list can be maintained in Settings (F2).</string>
<addaction name="actionModeAutoreply"/>
<addaction name="actionModeJS8HB"/>
<addaction name="actionHeartbeatAcknowledgements"/>
<addaction name="separator"/>
</widget>
<addaction name="menuFile"/>
<addaction name="menuConfig"/>
@ -5762,6 +5763,14 @@ list. The list can be maintained in Settings (F2).</string>
<string>Enable Tuning Tone (T&amp;UNE)</string>
</property>
</action>
<action name="actionModeAutoSync">
<property name="checkable">
<bool>true</bool>
</property>
<property name="text">
<string>Enable Automatic Timing Synchronization (S&amp;YNC)</string>
</property>
</action>
</widget>
<layoutdefault spacing="6" margin="11"/>
<customwidgets>

View File

@ -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<QColor> const& CPlotter::colors(){
return g_ColorTbl;
}
void CPlotter::setColours(QVector<QColor> const& cl)
{
g_ColorTbl = cl;

View File

@ -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<QColor> const& colors();
void setColours(QVector<QColor> 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;}

View File

@ -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<QColor> 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){

View File

@ -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<QColor> 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;

View File

@ -179,7 +179,7 @@
<rect>
<x>0</x>
<y>0</y>
<width>270</width>
<width>323</width>
<height>372</height>
</rect>
</property>
@ -427,9 +427,9 @@
<property name="geometry">
<rect>
<x>0</x>
<y>-193</y>
<width>267</width>
<height>723</height>
<y>0</y>
<width>323</width>
<height>742</height>
</rect>
</property>
<layout class="QVBoxLayout" name="verticalLayout_5">
@ -656,92 +656,106 @@
<property name="title">
<string>Waterfall</string>
</property>
<layout class="QFormLayout" name="formLayout">
<item row="1" column="0">
<widget class="QLabel" name="label">
<layout class="QVBoxLayout" name="verticalLayout_4">
<item>
<layout class="QFormLayout" name="formLayout">
<property name="topMargin">
<number>0</number>
</property>
<item row="0" column="0">
<widget class="QLabel" name="label">
<property name="text">
<string>Gain:</string>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QSlider" name="gainSlider">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="minimumSize">
<size>
<width>100</width>
<height>0</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>16777215</width>
<height>16777215</height>
</size>
</property>
<property name="toolTip">
<string>Waterfall gain</string>
</property>
<property name="minimum">
<number>-50</number>
</property>
<property name="maximum">
<number>50</number>
</property>
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="tickPosition">
<enum>QSlider::TicksAbove</enum>
</property>
</widget>
</item>
<item row="1" column="0">
<widget class="QLabel" name="label_2">
<property name="text">
<string>Zero:</string>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="QSlider" name="zeroSlider">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="minimumSize">
<size>
<width>100</width>
<height>0</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>16777215</width>
<height>16777215</height>
</size>
</property>
<property name="toolTip">
<string>Waterfall zero</string>
</property>
<property name="minimum">
<number>-50</number>
</property>
<property name="maximum">
<number>50</number>
</property>
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="tickPosition">
<enum>QSlider::TicksAbove</enum>
</property>
</widget>
</item>
</layout>
</item>
<item>
<widget class="QCheckBox" name="decodeAttemptCheckBox">
<property name="text">
<string>Gain:</string>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="QSlider" name="gainSlider">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="minimumSize">
<size>
<width>100</width>
<height>0</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>16777215</width>
<height>16777215</height>
</size>
</property>
<property name="toolTip">
<string>Waterfall gain</string>
</property>
<property name="minimum">
<number>-50</number>
</property>
<property name="maximum">
<number>50</number>
</property>
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="tickPosition">
<enum>QSlider::TicksAbove</enum>
</property>
</widget>
</item>
<item row="3" column="1">
<widget class="QSlider" name="zeroSlider">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="minimumSize">
<size>
<width>100</width>
<height>0</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>16777215</width>
<height>16777215</height>
</size>
</property>
<property name="toolTip">
<string>Waterfall zero</string>
</property>
<property name="minimum">
<number>-50</number>
</property>
<property name="maximum">
<number>50</number>
</property>
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="tickPosition">
<enum>QSlider::TicksAbove</enum>
</property>
</widget>
</item>
<item row="3" column="0">
<widget class="QLabel" name="label_2">
<property name="text">
<string>Zero:</string>
<string>Display Decode Attempts</string>
</property>
</widget>
</item>
@ -961,8 +975,8 @@
<rect>
<x>0</x>
<y>0</y>
<width>281</width>
<height>198</height>
<width>337</width>
<height>351</height>
</rect>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
@ -1007,67 +1021,114 @@
</widget>
</item>
<item>
<widget class="QPushButton" name="driftSyncMinuteButton">
<property name="minimumSize">
<size>
<width>0</width>
<height>30</height>
</size>
</property>
<property name="toolTip">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Observe signals in the waterfall and click this to synchronize your time drift with the start of a minute.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
<property name="text">
<string>Sync Time Drift to Now (Minute Start)</string>
<widget class="QGroupBox" name="groupBox_10">
<property name="title">
<string>Automatic</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout_13">
<item>
<widget class="QPushButton" name="autoDriftButton">
<property name="minimumSize">
<size>
<width>0</width>
<height>30</height>
</size>
</property>
<property name="toolTip">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Automatically synchronize time drift every second to decodes of NORMAL and SLOW signals observed.&lt;/p&gt;&lt;p&gt;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. &lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
<property name="text">
<string>Start Automatic Time Drift</string>
</property>
<property name="checkable">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<widget class="QCheckBox" name="autoDriftAutoStopCheckBox">
<property name="text">
<string>Stop Automatic Drift After First Decode</string>
</property>
<property name="checked">
<bool>true</bool>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QPushButton" name="driftSyncButton">
<property name="minimumSize">
<size>
<width>0</width>
<height>30</height>
</size>
</property>
<property name="toolTip">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;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.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
<property name="text">
<string>Sync Time Drift to Now (TX Start)</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="driftSyncEndButton">
<property name="minimumSize">
<size>
<width>0</width>
<height>30</height>
</size>
</property>
<property name="toolTip">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;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.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
<property name="text">
<string>Sync Time Drift to Now (TX End)</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="driftSyncResetButton">
<property name="minimumSize">
<size>
<width>0</width>
<height>30</height>
</size>
</property>
<property name="toolTip">
<string>Reset your time drift to zero.</string>
</property>
<property name="text">
<string>Reset Time Drift</string>
<widget class="QGroupBox" name="groupBox_11">
<property name="title">
<string>Manual</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout_14">
<item>
<widget class="QPushButton" name="driftSyncMinuteButton">
<property name="minimumSize">
<size>
<width>0</width>
<height>30</height>
</size>
</property>
<property name="toolTip">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Observe signals in the waterfall and click this to synchronize your time drift with the start of a minute.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
<property name="text">
<string>Set Time Drift to Now (Minute Start)</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="driftSyncEndButton">
<property name="minimumSize">
<size>
<width>0</width>
<height>30</height>
</size>
</property>
<property name="toolTip">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;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.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
<property name="text">
<string>Set Time Drift to Now (TX End)</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="driftSyncButton">
<property name="minimumSize">
<size>
<width>0</width>
<height>30</height>
</size>
</property>
<property name="toolTip">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;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.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
<property name="text">
<string>Set Time Drift to Now (TX Start)</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="driftSyncResetButton">
<property name="minimumSize">
<size>
<width>0</width>
<height>30</height>
</size>
</property>
<property name="toolTip">
<string>Reset your time drift to zero.</string>
</property>
<property name="text">
<string>Reset Time Drift</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item>