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 () void Detector::clear ()
{ {
QMutexLocker mutex(&m_lock);
#if JS8_RING_BUFFER #if JS8_RING_BUFFER
// set index to roughly where we are in time (1ms resolution) resetBufferPosition();
qint64 now (DriftingDateTime::currentMSecsSinceEpoch ()); resetBufferContent();
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;
#else #else
dec_data.params.kin = 0; dec_data.params.kin = 0;
m_bufferPos = 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); // 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) qint64 Detector::writeData (char const * data, qint64 maxSize)
{ {
QMutexLocker mutex(&m_lock); 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 // we take the time of the data as the following assuming no latency
// delivering it to us (not true but close enough for us) // delivering it to us (not true but close enough for us)
qint64 now (DriftingDateTime::currentMSecsSinceEpoch ()); qint64 now (DriftingDateTime::currentMSecsSinceEpoch ());
unsigned secondInToday ((now % 86400000LL) / 1000); unsigned secondInToday ((now % 86400000LL) / 1000);
return secondInToday % m_period; return secondInToday % m_period;
} }

View File

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

View File

@ -13,6 +13,7 @@
#define JS8_DECODE_THREAD 1 // use a separate thread for decode process handling #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_ALLOW_EXTENDED 1 // allow extended latin-1 capital charset
#define JS8_SAVE_AUDIO 0 // enable the save menu #define JS8_SAVE_AUDIO 0 // enable the save menu
#define JS8_AUTO_SYNC 1 // enable the experimental auto sync feature
#ifdef QT_DEBUG #ifdef QT_DEBUG
#define JS8_DEBUG_DECODE 0 // emit debug statements for the decode pipeline #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 nfSplit; // JT65 | JT9 split frequency
int nfb; // High decode limit (Hz) (filter max) int nfb; // High decode limit (Hz) (filter max)
int ntol; // +/- decoding range around fQSO (Hz) int ntol; // +/- decoding range around fQSO (Hz)
bool syncStats; // only compute sync candidates
int kin; // number of frames written to d2 int kin; // number of frames written to d2
int kposA; // starting position of decode for submode A int kposA; // starting position of decode for submode A
int kposB; // starting position of decode for submode B 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) , isAlt_(false)
, bits_{0} , bits_{0}
, submode_{ string_.mid(column_mode + padding_, 3).trimmed().at(0).cell() - 'A' } , submode_{ string_.mid(column_mode + padding_, 3).trimmed().at(0).cell() - 'A' }
, frame_ { string_.mid (column_qsoText + padding_, 12).trimmed () }
{ {
if(message_.length() >= 1) { if(message_.length() >= 1) {
message_ = message_.left (21).remove (QRegularExpression {"[<>]"}); message_ = message_.left (21).remove (QRegularExpression {"[<>]"});
@ -71,7 +72,8 @@ DecodedText::DecodedText (QString const& js8callmessage, int bits, int submode):
isHeartbeat_(false), isHeartbeat_(false),
isAlt_(false), isAlt_(false),
bits_(bits), bits_(bits),
submode_(submode) submode_(submode),
frame_(js8callmessage)
{ {
is_standard_ = QRegularExpression("^(CQ|DE|QRZ)\\s").match(message_).hasMatch(); is_standard_ = QRegularExpression("^(CQ|DE|QRZ)\\s").match(message_).hasMatch();

View File

@ -41,6 +41,7 @@ public:
bool tryUnpackFastData(); bool tryUnpackFastData();
quint8 frameType() const { return frameType_; } quint8 frameType() const { return frameType_; }
QString frame() const { return frame_; }
QString extra() const { return extra_; } QString extra() const { return extra_; }
QString compoundCall() const { return compound_; } QString compoundCall() const { return compound_; }
@ -113,6 +114,7 @@ private:
bool is_standard_; bool is_standard_;
int bits_; int bits_;
int submode_; int submode_;
QString frame_;
}; };
#endif // DECODEDTEXT_H #endif // DECODEDTEXT_H

View File

@ -88,13 +88,28 @@ subroutine multimode_decoder(ss,id2,params,nfsample)
pos = max(0,params%kposI) pos = max(0,params%kposI)
sz = max(0,params%kszI) sz = max(0,params%kszI)
id0=0 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, & call my_js8i%decode(js8i_decoded,id0,params%nQSOProgress,params%nfqso, &
params%nftx,newdat,params%nutc,params%nfa,params%nfb, & params%nftx,newdat,params%nutc,params%nfa,params%nfb, &
params%nexp_decode,params%ndepth,logical(params%nagain), & params%nexp_decode,params%ndepth,logical(params%nagain), &
logical(params%lft8apon),logical(params%lapcqonly),params%napwid, & 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' write(*,*) '<DecodeDebug> mode I decode finished'
@ -111,13 +126,28 @@ subroutine multimode_decoder(ss,id2,params,nfsample)
pos = max(0,params%kposE) pos = max(0,params%kposE)
sz = max(0,params%kszE) sz = max(0,params%kszE)
id0=0 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, & call my_js8e%decode(js8e_decoded,id0,params%nQSOProgress,params%nfqso, &
params%nftx,newdat,params%nutc,params%nfa,params%nfb, & params%nftx,newdat,params%nutc,params%nfa,params%nfb, &
params%nexp_decode,params%ndepth,logical(params%nagain), & params%nexp_decode,params%ndepth,logical(params%nagain), &
logical(params%lft8apon),logical(params%lapcqonly),params%napwid, & 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' write(*,*) '<DecodeDebug> mode E decode finished'
@ -134,13 +164,28 @@ subroutine multimode_decoder(ss,id2,params,nfsample)
pos = max(0,params%kposC) pos = max(0,params%kposC)
sz = max(0,params%kszC) sz = max(0,params%kszC)
id0=0 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, & call my_js8c%decode(js8c_decoded,id0,params%nQSOProgress,params%nfqso, &
params%nftx,newdat,params%nutc,params%nfa,params%nfb, & params%nftx,newdat,params%nutc,params%nfa,params%nfb, &
params%nexp_decode,params%ndepth,logical(params%nagain), & params%nexp_decode,params%ndepth,logical(params%nagain), &
logical(params%lft8apon),logical(params%lapcqonly),params%napwid, & 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' write(*,*) '<DecodeDebug> mode C decode finished'
@ -157,13 +202,28 @@ subroutine multimode_decoder(ss,id2,params,nfsample)
pos = max(0,params%kposB) pos = max(0,params%kposB)
sz = max(0,params%kszB) sz = max(0,params%kszB)
id0=0 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, & call my_js8b%decode(js8b_decoded,id0,params%nQSOProgress,params%nfqso, &
params%nftx,newdat,params%nutc,params%nfa,params%nfb, & params%nftx,newdat,params%nutc,params%nfa,params%nfb, &
params%nexp_decode,params%ndepth,logical(params%nagain), & params%nexp_decode,params%ndepth,logical(params%nagain), &
logical(params%lft8apon),logical(params%lapcqonly),params%napwid, & 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' write(*,*) '<DecodeDebug> mode B decode finished'
@ -177,16 +237,31 @@ subroutine multimode_decoder(ss,id2,params,nfsample)
write(*,*) '<DecodeDebug> mode A decode started' write(*,*) '<DecodeDebug> mode A decode started'
! copy the relevant frames for decoding ! copy the relevant frames for decoding
pos = max(0,params%kposA) pos = int(max(0,params%kposA))
sz = max(0,params%kszA) sz = int(max(0,params%kszA))
id0=0 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, & call my_js8a%decode(js8a_decoded,id0,params%nQSOProgress,params%nfqso, &
params%nftx,newdat,params%nutc,params%nfa,params%nfb, & params%nftx,newdat,params%nutc,params%nfa,params%nfb, &
params%nexp_decode,params%ndepth,logical(params%nagain), & params%nexp_decode,params%ndepth,logical(params%nagain), &
logical(params%lft8apon),logical(params%lapcqonly),params%napwid, & 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' write(*,*) '<DecodeDebug> mode A decode finished'
@ -196,7 +271,7 @@ subroutine multimode_decoder(ss,id2,params,nfsample)
write(*,*) '<DecodeDebug> finished' write(*,*) '<DecodeDebug> finished'
call flush(6) 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) !call sleep_msec(3000)
write(*,1010) ndecoded write(*,1010) ndecoded
1010 format('<DecodeFinished>',i4) 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. ! 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 (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 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 (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 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 (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 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, & napwid,lsubtract,nagain,iaptype,mycall12,mygrid6,hiscall12,bcontest, &
sync0,f1,xdt,xbase,apsym,nharderrors,dmin,nbadcrc,ipass,iera,msg37,xsnr) 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*12 mycall12,hiscall12
character*6 mycall6,mygrid6,hiscall6,c1,c2 character*6 mycall6,mygrid6,hiscall6,c1,c2
character*87 cbits character*87 cbits
logical bcontest logical bcontest,syncStats
real a(5) real a(5)
real s1(0:7,ND),s2(0:7,NN),s1sort(8*ND) real s1(0:7,ND),s2(0:7,NN),s1sort(8*ND)
real ps(0:7),psl(0:7) real ps(0:7),psl(0:7)
@ -224,6 +224,11 @@ subroutine js8dec(dd0,icos,newdat,nQSOProgress,nfqso,nftx,ndepth,lapon,lapcqonly
return return
endif endif
if(syncStats) then
write(*,*) '<DecodeSyncStat> candidate ', NSUBMODE, 'f1', f1, 'sync', nsync, 'xdt', xdt
flush(6)
endif
j=0 j=0
do k=1,NN do k=1,NN
if(k.le.7) cycle 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) i3bit=4*decoded(73) + 2*decoded(74) + decoded(75)
if(nbadcrc.eq.0) then if(nbadcrc.eq.0) then
if(syncStats) then
write(*,*) '<DecodeSyncStat> decode ', NSUBMODE, 'f1', f1, 'sync', (sync*10), 'xdt', xdt2
flush(6)
endif
decoded0=decoded decoded0=decoded
call extractmessage174(decoded,origmsg,ncrcflag) call extractmessage174(decoded,origmsg,ncrcflag)
decoded=decoded0 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 (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 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 (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 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, & subroutine decode(this,callback,iwave,nQSOProgress,nfqso,nftx,newdat, &
nutc,nfa,nfb,nexp_decode,ndepth,nagain,lft8apon,lapcqonly,napwid, & nutc,nfa,nfb,nexp_decode,ndepth,nagain,lft8apon,lapcqonly,napwid, &
mycall12,mygrid6,hiscall12,hisgrid6) mycall12,mygrid6,hiscall12,hisgrid6,syncStats)
! use wavhdr ! use wavhdr
use timer_module, only: timer use timer_module, only: timer
! type(hdr) h ! type(hdr) h
@ -38,7 +38,7 @@ contains
real candidate(3,NMAXCAND) real candidate(3,NMAXCAND)
real dd(NMAX) real dd(NMAX)
logical, intent(in) :: lft8apon,lapcqonly,nagain logical, intent(in) :: lft8apon,lapcqonly,nagain
logical newdat,lsubtract,ldupe,bcontest logical newdat,lsubtract,ldupe,bcontest,syncStats
character*12 mycall12, hiscall12 character*12 mycall12, hiscall12
character*6 mygrid6,hisgrid6 character*6 mygrid6,hisgrid6
integer*2 iwave(NMAX) integer*2 iwave(NMAX)
@ -93,25 +93,15 @@ contains
lsubtract=.false. lsubtract=.false.
endif endif
if(NWRITELOG.eq.1) then
write(*,*) '<DecodeDebug> pass', ipass, 'of', npass, 'subtract', lsubtract
flush(6)
endif
call timer('syncjs8 ',0) call timer('syncjs8 ',0)
call syncjs8(dd,icos,ifa,ifb,syncmin,nfqso,s,candidate,ncand,sbase) call syncjs8(dd,icos,ifa,ifb,syncmin,nfqso,s,candidate,ncand,sbase)
call timer('syncjs8 ',1) 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 do icand=1,ncand
sync=candidate(3,icand) sync=candidate(3,icand)
f1=candidate(1,icand) f1=candidate(1,icand)
@ -124,7 +114,7 @@ contains
endif endif
call timer('js8dec ',0) 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, & lapcqonly,napwid,lsubtract,nagain,iaptype,mycall12,mygrid6, &
hiscall12,bcontest,sync,f1,xdt,xbase,apsym,nharderrors,dmin, & hiscall12,bcontest,sync,f1,xdt,xbase,apsym,nharderrors,dmin, &
nbadcrc,iappass,iera,msg37,xsnr) nbadcrc,iappass,iera,msg37,xsnr)
@ -137,7 +127,7 @@ contains
write(*,*) '<DecodeDebug> candidate', icand, 'hard', hd, 'nbadcrc', nbadcrc write(*,*) '<DecodeDebug> candidate', icand, 'hard', hd, 'nbadcrc', nbadcrc
flush(6) flush(6)
endif endif
call timer('js8dec ',1) call timer('js8dec ',1)
if(nbadcrc.eq.0) then if(nbadcrc.eq.0) then
ldupe=.false. ldupe=.false.

View File

@ -25,7 +25,7 @@ contains
subroutine decode(this,callback,iwave,nQSOProgress,nfqso,nftx,newdat, & subroutine decode(this,callback,iwave,nQSOProgress,nfqso,nftx,newdat, &
nutc,nfa,nfb,nexp_decode,ndepth,nagain,lft8apon,lapcqonly,napwid, & nutc,nfa,nfb,nexp_decode,ndepth,nagain,lft8apon,lapcqonly,napwid, &
mycall12,mygrid6,hiscall12,hisgrid6) mycall12,mygrid6,hiscall12,hisgrid6,syncStats)
! use wavhdr ! use wavhdr
use timer_module, only: timer use timer_module, only: timer
! type(hdr) h ! type(hdr) h
@ -38,7 +38,7 @@ contains
real candidate(3,NMAXCAND) real candidate(3,NMAXCAND)
real dd(NMAX) real dd(NMAX)
logical, intent(in) :: lft8apon,lapcqonly,nagain logical, intent(in) :: lft8apon,lapcqonly,nagain
logical newdat,lsubtract,ldupe,bcontest logical newdat,lsubtract,ldupe,bcontest,syncStats
character*12 mycall12, hiscall12 character*12 mycall12, hiscall12
character*6 mygrid6,hisgrid6 character*6 mygrid6,hisgrid6
integer*2 iwave(NMAX) integer*2 iwave(NMAX)
@ -93,6 +93,11 @@ contains
lsubtract=.false. lsubtract=.false.
endif endif
if(NWRITELOG.eq.1) then
write(*,*) '<DecodeDebug> pass', ipass, 'of', npass, 'subtract', lsubtract
flush(6)
endif
call timer('syncjs8 ',0) call timer('syncjs8 ',0)
call syncjs8(dd,icos,ifa,ifb,syncmin,nfqso,s,candidate,ncand,sbase) call syncjs8(dd,icos,ifa,ifb,syncmin,nfqso,s,candidate,ncand,sbase)
call timer('syncjs8 ',1) call timer('syncjs8 ',1)
@ -114,7 +119,7 @@ contains
endif endif
call timer('js8dec ',0) 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, & lapcqonly,napwid,lsubtract,nagain,iaptype,mycall12,mygrid6, &
hiscall12,bcontest,sync,f1,xdt,xbase,apsym,nharderrors,dmin, & hiscall12,bcontest,sync,f1,xdt,xbase,apsym,nharderrors,dmin, &
nbadcrc,iappass,iera,msg37,xsnr) nbadcrc,iappass,iera,msg37,xsnr)

View File

@ -25,7 +25,7 @@ contains
subroutine decode(this,callback,iwave,nQSOProgress,nfqso,nftx,newdat, & subroutine decode(this,callback,iwave,nQSOProgress,nfqso,nftx,newdat, &
nutc,nfa,nfb,nexp_decode,ndepth,nagain,lft8apon,lapcqonly,napwid, & nutc,nfa,nfb,nexp_decode,ndepth,nagain,lft8apon,lapcqonly,napwid, &
mycall12,mygrid6,hiscall12,hisgrid6) mycall12,mygrid6,hiscall12,hisgrid6,syncStats)
! use wavhdr ! use wavhdr
use timer_module, only: timer use timer_module, only: timer
! type(hdr) h ! type(hdr) h
@ -38,7 +38,7 @@ contains
real candidate(3,NMAXCAND) real candidate(3,NMAXCAND)
real dd(NMAX) real dd(NMAX)
logical, intent(in) :: lft8apon,lapcqonly,nagain logical, intent(in) :: lft8apon,lapcqonly,nagain
logical newdat,lsubtract,ldupe,bcontest logical newdat,lsubtract,ldupe,bcontest,syncStats
character*12 mycall12, hiscall12 character*12 mycall12, hiscall12
character*6 mygrid6,hisgrid6 character*6 mygrid6,hisgrid6
integer*2 iwave(NMAX) integer*2 iwave(NMAX)
@ -93,6 +93,11 @@ contains
lsubtract=.false. lsubtract=.false.
endif endif
if(NWRITELOG.eq.1) then
write(*,*) '<DecodeDebug> pass', ipass, 'of', npass, 'subtract', lsubtract
flush(6)
endif
call timer('syncjs8 ',0) call timer('syncjs8 ',0)
call syncjs8(dd,icos,ifa,ifb,syncmin,nfqso,s,candidate,ncand,sbase) call syncjs8(dd,icos,ifa,ifb,syncmin,nfqso,s,candidate,ncand,sbase)
call timer('syncjs8 ',1) call timer('syncjs8 ',1)
@ -114,7 +119,7 @@ contains
endif endif
call timer('js8dec ',0) 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, & lapcqonly,napwid,lsubtract,nagain,iaptype,mycall12,mygrid6, &
hiscall12,bcontest,sync,f1,xdt,xbase,apsym,nharderrors,dmin, & hiscall12,bcontest,sync,f1,xdt,xbase,apsym,nharderrors,dmin, &
nbadcrc,iappass,iera,msg37,xsnr) nbadcrc,iappass,iera,msg37,xsnr)

View File

@ -25,7 +25,7 @@ contains
subroutine decode(this,callback,iwave,nQSOProgress,nfqso,nftx,newdat, & subroutine decode(this,callback,iwave,nQSOProgress,nfqso,nftx,newdat, &
nutc,nfa,nfb,nexp_decode,ndepth,nagain,lft8apon,lapcqonly,napwid, & nutc,nfa,nfb,nexp_decode,ndepth,nagain,lft8apon,lapcqonly,napwid, &
mycall12,mygrid6,hiscall12,hisgrid6) mycall12,mygrid6,hiscall12,hisgrid6,syncStats)
! use wavhdr ! use wavhdr
use timer_module, only: timer use timer_module, only: timer
! type(hdr) h ! type(hdr) h
@ -38,7 +38,7 @@ contains
real candidate(3,NMAXCAND) real candidate(3,NMAXCAND)
real dd(NMAX) real dd(NMAX)
logical, intent(in) :: lft8apon,lapcqonly,nagain logical, intent(in) :: lft8apon,lapcqonly,nagain
logical newdat,lsubtract,ldupe,bcontest logical newdat,lsubtract,ldupe,bcontest,syncStats
character*12 mycall12, hiscall12 character*12 mycall12, hiscall12
character*6 mygrid6,hisgrid6 character*6 mygrid6,hisgrid6
integer*2 iwave(NMAX) integer*2 iwave(NMAX)
@ -93,6 +93,11 @@ contains
lsubtract=.false. lsubtract=.false.
endif endif
if(NWRITELOG.eq.1) then
write(*,*) '<DecodeDebug> pass', ipass, 'of', npass, 'subtract', lsubtract
flush(6)
endif
call timer('syncjs8 ',0) call timer('syncjs8 ',0)
call syncjs8(dd,icos,ifa,ifb,syncmin,nfqso,s,candidate,ncand,sbase) call syncjs8(dd,icos,ifa,ifb,syncmin,nfqso,s,candidate,ncand,sbase)
call timer('syncjs8 ',1) call timer('syncjs8 ',1)
@ -114,7 +119,7 @@ contains
endif endif
call timer('js8dec ',0) 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, & lapcqonly,napwid,lsubtract,nagain,iaptype,mycall12,mygrid6, &
hiscall12,bcontest,sync,f1,xdt,xbase,apsym,nharderrors,dmin, & hiscall12,bcontest,sync,f1,xdt,xbase,apsym,nharderrors,dmin, &
nbadcrc,iappass,iera,msg37,xsnr) nbadcrc,iappass,iera,msg37,xsnr)

View File

@ -25,7 +25,7 @@ contains
subroutine decode(this,callback,iwave,nQSOProgress,nfqso,nftx,newdat, & subroutine decode(this,callback,iwave,nQSOProgress,nfqso,nftx,newdat, &
nutc,nfa,nfb,nexp_decode,ndepth,nagain,lft8apon,lapcqonly,napwid, & nutc,nfa,nfb,nexp_decode,ndepth,nagain,lft8apon,lapcqonly,napwid, &
mycall12,mygrid6,hiscall12,hisgrid6) mycall12,mygrid6,hiscall12,hisgrid6,syncStats)
! use wavhdr ! use wavhdr
use timer_module, only: timer use timer_module, only: timer
! type(hdr) h ! type(hdr) h
@ -38,7 +38,7 @@ contains
real candidate(3,NMAXCAND) real candidate(3,NMAXCAND)
real dd(NMAX) real dd(NMAX)
logical, intent(in) :: lft8apon,lapcqonly,nagain logical, intent(in) :: lft8apon,lapcqonly,nagain
logical newdat,lsubtract,ldupe,bcontest logical newdat,lsubtract,ldupe,bcontest,syncStats
character*12 mycall12, hiscall12 character*12 mycall12, hiscall12
character*6 mygrid6,hisgrid6 character*6 mygrid6,hisgrid6
integer*2 iwave(NMAX) integer*2 iwave(NMAX)
@ -93,6 +93,11 @@ contains
lsubtract=.false. lsubtract=.false.
endif endif
if(NWRITELOG.eq.1) then
write(*,*) '<DecodeDebug> pass', ipass, 'of', npass, 'subtract', lsubtract
flush(6)
endif
call timer('syncjs8 ',0) call timer('syncjs8 ',0)
call syncjs8(dd,icos,ifa,ifb,syncmin,nfqso,s,candidate,ncand,sbase) call syncjs8(dd,icos,ifa,ifb,syncmin,nfqso,s,candidate,ncand,sbase)
call timer('syncjs8 ',1) call timer('syncjs8 ',1)
@ -114,7 +119,7 @@ contains
endif endif
call timer('js8dec ',0) 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, & lapcqonly,napwid,lsubtract,nagain,iaptype,mycall12,mygrid6, &
hiscall12,bcontest,sync,f1,xdt,xbase,apsym,nharderrors,dmin, & hiscall12,bcontest,sync,f1,xdt,xbase,apsym,nharderrors,dmin, &
nbadcrc,iappass,iera,msg37,xsnr) nbadcrc,iappass,iera,msg37,xsnr)

View File

@ -21,8 +21,8 @@ program jt9
!### ndepth was defined as 60001. Why??? !### ndepth was defined as 60001. Why???
integer :: arglen,stat,offset,remain,mode=0,flow=200,fsplit=2700, & integer :: arglen,stat,offset,remain,mode=0,flow=200,fsplit=2700, &
fhigh=4000,nrxfreq=1500,ntrperiod=1,ndepth=1,nexp_decode=0 fhigh=4000,nrxfreq=1500,ntrperiod=1,ndepth=1,nexp_decode=0
logical :: read_files = .true., tx9 = .false., display_help = .false. logical :: read_files = .true., tx9 = .false., display_help = .false., syncStats = .false.
type (option) :: long_options(21) = [ & type (option) :: long_options(22) = [ &
option ('help', .false., 'h', 'Display this help message', ''), & option ('help', .false., 'h', 'Display this help message', ''), &
option ('shmem',.true.,'s','Use shared memory for sample data','KEY'), & option ('shmem',.true.,'s','Use shared memory for sample data','KEY'), &
option ('tr-period', .true., 'p', 'Tx/Rx period, default MINUTES=1', & option ('tr-period', .true., 'p', 'Tx/Rx period, default MINUTES=1', &
@ -50,6 +50,7 @@ program jt9
!option ('jt65', .false., '6', 'JT65 mode', ''), & !option ('jt65', .false., '6', 'JT65 mode', ''), &
!option ('jt9', .false., '9', 'JT9 mode', ''), & !option ('jt9', .false., '9', 'JT9 mode', ''), &
option ('js8', .false., '8', 'JS8 mode', ''), & option ('js8', .false., '8', 'JS8 mode', ''), &
option ('syncStats', .false., 'y', 'Sync only', ''), &
!option ('jt4', .false., '4', 'JT4 mode', ''), & !option ('jt4', .false., '4', 'JT4 mode', ''), &
!option ('qra64', .false., 'q', 'QRA64 mode', ''), & !option ('qra64', .false., 'q', 'QRA64 mode', ''), &
option ('sub-mode', .true., 'b', 'Sub mode, default SUBMODE=A', 'A'), & 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 ! if (mode.lt.9.or.mode.eq.65) mode = mode + 9
case ('8') case ('8')
mode = 8 mode = 8
case ('y')
syncStats = .true.
case ('T') case ('T')
tx9 = .true. tx9 = .true.
case ('w') case ('w')
@ -254,6 +257,7 @@ program jt9
shared_data%params%ljt65apon=.true. shared_data%params%ljt65apon=.true.
shared_data%params%napwid=75 shared_data%params%napwid=75
shared_data%params%dttol=3. shared_data%params%dttol=3.
shared_data%params%syncStats=syncStats
! shared_data%params%minsync=0 !### TEST ONLY ! shared_data%params%minsync=0 !### TEST ONLY
! shared_data%params%nfqso=1500 !### TEST ONLY ! shared_data%params%nfqso=1500 !### TEST ONLY
@ -290,10 +294,12 @@ program jt9
shared_data%params%kposB=0 shared_data%params%kposB=0
shared_data%params%kposC=0 shared_data%params%kposC=0
shared_data%params%kposE=0 shared_data%params%kposE=0
shared_data%params%kposI=0
shared_data%params%kszA=NMAX-1 shared_data%params%kszA=NMAX-1
shared_data%params%kszB=NMAX-1 shared_data%params%kszB=NMAX-1
shared_data%params%kszC=NMAX-1 shared_data%params%kszC=NMAX-1
shared_data%params%kszE=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) call multimode_decoder(shared_data%ss,shared_data%id2,shared_data%params,nfsample)
enddo enddo

View File

@ -18,6 +18,7 @@
integer(c_int) :: nfsplit integer(c_int) :: nfsplit
integer(c_int) :: nfb integer(c_int) :: nfb
integer(c_int) :: ntol integer(c_int) :: ntol
logical(c_bool) :: syncStats
integer(c_int) :: kin integer(c_int) :: kin
integer(c_int) :: kposA integer(c_int) :: kposA
integer(c_int) :: kposB 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::qsy, this, &MainWindow::qsy);
connect(m_wideGraph.data(), &WideGraph::drifted, this, &MainWindow::drifted);
decodeBusy(false); decodeBusy(false);
QString t1[28]={"1 uW","2 uW","5 uW","10 uW","20 uW","50 uW","100 uW","200 uW","500 uW", 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", "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; return stop;
} }
// int MainWindow::computeCurrentCycle(int period){ /**
// return m_detector->secondInPeriod() / period; * @brief MainWindow::computeCycleForDecode
// } *
// * compute which cycle we are currently in based on a submode frames per cycle and our current k position
// int MainWindow::computeCycleStartForDecode(int cycle, int period){ *
// qint32 samplesPerCycle = period * RX_SAMPLE_RATE; * @param submode
// return cycle * samplesPerCycle; * @param k
// } * @return
*/
int MainWindow::computeCycleForDecode(int submode, int k){ 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 cycleFrames = computeFramesPerCycleForDecode(submode);
qint32 currentCycle = (k / cycleFrames) % (maxFrames / cycleFrames); // we mod here so we loop back to zero correctly qint32 currentCycle = (k / cycleFrames) % (maxFrames / cycleFrames); // we mod here so we loop back to zero correctly
return currentCycle; 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){ int MainWindow::computeFramesPerCycleForDecode(int submode){
return computePeriodForSubmode(submode) * RX_SAMPLE_RATE; return computePeriodForSubmode(submode) * RX_SAMPLE_RATE;
} }
int MainWindow::computeFramesNeededForDecode(int submode){ int MainWindow::computePeriodStartDelayForDecode(int submode){
int symbolSamples = 0; int delay = 0;
float threshold = 0.0;
switch(submode){ switch(submode){
case Varicode::JS8CallNormal: symbolSamples = JS8A_SYMBOL_SAMPLES; threshold = JS8A_START_DELAY_MS/1000.0 + 0.5; break; case Varicode::JS8CallNormal: delay = JS8A_START_DELAY_MS; break;
case Varicode::JS8CallFast: symbolSamples = JS8B_SYMBOL_SAMPLES; threshold = JS8B_START_DELAY_MS/1000.0 + 0.5; break; case Varicode::JS8CallFast: delay = JS8B_START_DELAY_MS; break;
case Varicode::JS8CallTurbo: symbolSamples = JS8C_SYMBOL_SAMPLES; threshold = JS8C_START_DELAY_MS/1000.0 + 0.5; break; case Varicode::JS8CallTurbo: delay = JS8C_START_DELAY_MS; break;
case Varicode::JS8CallSlow: symbolSamples = JS8E_SYMBOL_SAMPLES; threshold = JS8E_START_DELAY_MS/1000.0 + 0.5; break; case Varicode::JS8CallSlow: delay = JS8E_START_DELAY_MS; break;
case Varicode::JS8CallUltra: symbolSamples = JS8I_SYMBOL_SAMPLES; threshold = JS8I_START_DELAY_MS/1000.0 + 0.5; 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))); return int(qFloor(float(symbolSamples*JS8_NUM_SYMBOLS + threshold*RX_SAMPLE_RATE)));
} }
@ -2686,6 +2725,8 @@ void MainWindow::dataSink(qint64 frames)
k0 = k; k0 = k;
} }
//qDebug() << "k" << k << "k0" << k0 << "delta" << k-k0;
#if JS8_USE_REFSPEC #if JS8_USE_REFSPEC
QString fname {QDir::toNativeSeparators(m_config.writeable_data_dir ().absoluteFilePath ("refspec.dat"))}; QString fname {QDir::toNativeSeparators(m_config.writeable_data_dir ().absoluteFilePath ("refspec.dat"))};
QByteArray bafname = fname.toLatin1(); QByteArray bafname = fname.toLatin1();
@ -4214,10 +4255,19 @@ bool MainWindow::decode(qint32 k){
return false; 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(ready || !m_decoderQueue.isEmpty()){
if(JS8_DEBUG_DECODE) qDebug() << "--> decoder is ready to be run with" << m_decoderQueue.count() << "decode periods"; 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 // TODO: what follows can likely be pulled out to an async process
@ -4232,6 +4282,11 @@ bool MainWindow::decode(qint32 k){
return false; 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 int threshold = m_nSubMode == Varicode::JS8CallSlow ? 4000 : 2000; // two seconds
if(isInDecodeDelayThreshold(threshold)){ if(isInDecodeDelayThreshold(threshold)){
if(JS8_DEBUG_DECODE) qDebug() << "--> decoder paused for" << threshold << "ms after transmit stop"; 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 // compute the next decode for each submode
// enqueue those decodes that are "ready" // enqueue those decodes that are "ready"
// on an interval, issue a decode // on an interval, issue a decode
int decodes = 0;
static qint32 currentDecodeStartA = -1; bool couldDecodeA = false;
static qint32 nextDecodeStartA = -1;
qint32 startA = -1; qint32 startA = -1;
qint32 szA = -1; qint32 szA = -1;
qint32 cycleA = -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; 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){ if(m_diskData){
startA = 0; startA = 0;
szA = NTMAX*RX_SAMPLE_RATE-1; szA = NTMAX*RX_SAMPLE_RATE-1;
@ -4281,11 +4363,8 @@ bool MainWindow::decodeEnqueueReady(qint32 k, qint32 k0){
static qint32 currentDecodeStartB = -1; static qint32 currentDecodeStartB = -1;
static qint32 nextDecodeStartB = -1; static qint32 nextDecodeStartB = -1;
qint32 startB = -1;
qint32 szB = -1;
qint32 cycleB = -1;
if(JS8_DEBUG_DECODE) qDebug() << "? FAST " << currentDecodeStartB << nextDecodeStartB; 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){ if(m_diskData){
startB = 0; startB = 0;
szB = NTMAX*RX_SAMPLE_RATE-1; szB = NTMAX*RX_SAMPLE_RATE-1;
@ -4294,11 +4373,8 @@ bool MainWindow::decodeEnqueueReady(qint32 k, qint32 k0){
static qint32 currentDecodeStartC = -1; static qint32 currentDecodeStartC = -1;
static qint32 nextDecodeStartC = -1; static qint32 nextDecodeStartC = -1;
qint32 startC = -1;
qint32 szC = -1;
qint32 cycleC = -1;
if(JS8_DEBUG_DECODE) qDebug() << "? TURBO " << currentDecodeStartC << nextDecodeStartC; 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){ if(m_diskData){
startC = 0; startC = 0;
szC = NTMAX*RX_SAMPLE_RATE-1; szC = NTMAX*RX_SAMPLE_RATE-1;
@ -4308,11 +4384,8 @@ bool MainWindow::decodeEnqueueReady(qint32 k, qint32 k0){
#if JS8_ENABLE_JS8E #if JS8_ENABLE_JS8E
static qint32 currentDecodeStartE = -1; static qint32 currentDecodeStartE = -1;
static qint32 nextDecodeStartE = -1; static qint32 nextDecodeStartE = -1;
qint32 startE = -1;
qint32 szE = -1;
qint32 cycleE = -1;
if(JS8_DEBUG_DECODE) qDebug() << "? SLOW " << currentDecodeStartE << nextDecodeStartE; 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){ if(m_diskData){
startE = 0; startE = 0;
szE = NTMAX*RX_SAMPLE_RATE-1; szE = NTMAX*RX_SAMPLE_RATE-1;
@ -4323,11 +4396,8 @@ bool MainWindow::decodeEnqueueReady(qint32 k, qint32 k0){
#if JS8_ENABLE_JS8I #if JS8_ENABLE_JS8I
static qint32 currentDecodeStartI = -1; static qint32 currentDecodeStartI = -1;
static qint32 nextDecodeStartI = -1; static qint32 nextDecodeStartI = -1;
qint32 startI = -1;
qint32 szI = -1;
qint32 cycleI = -1;
if(JS8_DEBUG_DECODE) qDebug() << "? ULTRA " << currentDecodeStartI << nextDecodeStartI; 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){ if(m_diskData){
startI = 0; startI = 0;
szI = NTMAX*RX_SAMPLE_RATE-1; szI = NTMAX*RX_SAMPLE_RATE-1;
@ -4335,12 +4405,9 @@ bool MainWindow::decodeEnqueueReady(qint32 k, qint32 k0){
} }
#endif #endif
int decodes = 0;
if(couldDecodeA){ if(couldDecodeA){
DecodeParams d; DecodeParams d;
d.submode = Varicode::JS8CallNormal; d.submode = Varicode::JS8CallNormal;
d.cycle = cycleA;
d.start = startA; d.start = startA;
d.sz = szA; d.sz = szA;
m_decoderQueue.append(d); m_decoderQueue.append(d);
@ -4350,7 +4417,6 @@ bool MainWindow::decodeEnqueueReady(qint32 k, qint32 k0){
if(couldDecodeB){ if(couldDecodeB){
DecodeParams d; DecodeParams d;
d.submode = Varicode::JS8CallFast; d.submode = Varicode::JS8CallFast;
d.cycle = cycleB;
d.start = startB; d.start = startB;
d.sz = szB; d.sz = szB;
m_decoderQueue.append(d); m_decoderQueue.append(d);
@ -4360,7 +4426,6 @@ bool MainWindow::decodeEnqueueReady(qint32 k, qint32 k0){
if(couldDecodeC){ if(couldDecodeC){
DecodeParams d; DecodeParams d;
d.submode = Varicode::JS8CallTurbo; d.submode = Varicode::JS8CallTurbo;
d.cycle = cycleC;
d.start = startC; d.start = startC;
d.sz = szC; d.sz = szC;
m_decoderQueue.append(d); m_decoderQueue.append(d);
@ -4371,7 +4436,6 @@ bool MainWindow::decodeEnqueueReady(qint32 k, qint32 k0){
if(couldDecodeE){ if(couldDecodeE){
DecodeParams d; DecodeParams d;
d.submode = Varicode::JS8CallSlow; d.submode = Varicode::JS8CallSlow;
d.cycle = cycleE;
d.start = startE; d.start = startE;
d.sz = szE; d.sz = szE;
m_decoderQueue.append(d); m_decoderQueue.append(d);
@ -4383,7 +4447,6 @@ bool MainWindow::decodeEnqueueReady(qint32 k, qint32 k0){
if(couldDecodeI){ if(couldDecodeI){
DecodeParams d; DecodeParams d;
d.submode = Varicode::JS8CallUltra; d.submode = Varicode::JS8CallUltra;
d.cycle = cycleI;
d.start = startI; d.start = startI;
d.sz = szI; d.sz = szI;
m_decoderQueue.append(d); m_decoderQueue.append(d);
@ -4394,6 +4457,311 @@ bool MainWindow::decodeEnqueueReady(qint32 k, qint32 k0){
return decodes > 0; 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 * @brief MainWindow::decodeProcessQueue
* process the decode queue by merging available decode ranges * process the decode queue by merging available decode ranges
@ -4406,7 +4774,7 @@ bool MainWindow::decodeProcessQueue(qint32 *pSubmode){
QMutexLocker mutex(m_detector->getMutex()); QMutexLocker mutex(m_detector->getMutex());
if(m_decoderBusy){ if(m_decoderBusy){
int seconds = m_decoderBusyStartTime.secsTo(DriftingDateTime::currentDateTimeUtc()); int seconds = m_decoderBusyStartTime.secsTo(QDateTime::currentDateTimeUtc());
if(seconds > 60){ if(seconds > 60){
if(JS8_DEBUG_DECODE) qDebug() << "--> decoder should be killed!" << QString("(%1 seconds)").arg(seconds); if(JS8_DEBUG_DECODE) qDebug() << "--> decoder should be killed!" << QString("(%1 seconds)").arg(seconds);
} else if(seconds > 30){ } 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; 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; dec_data.params.nsubmodes = 0;
while(!m_decoderQueue.isEmpty()){ while(!m_decoderQueue.isEmpty()){
@ -4494,6 +4863,7 @@ bool MainWindow::decodeProcessQueue(qint32 *pSubmode){
int period = computePeriodForSubmode(submode); 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.npts8=(m_ihsym*m_nsps)/16;
dec_data.params.newdat=1; dec_data.params.newdat=1;
dec_data.params.nagain=0; dec_data.params.nagain=0;
@ -4673,7 +5043,7 @@ void MainWindow::decodeBusy(bool b) //decodeBusy()
m_decoderBusy=b; m_decoderBusy=b;
if(m_decoderBusy){ if(m_decoderBusy){
tx_status_label.setText("Decoding"); tx_status_label.setText("Decoding");
m_decoderBusyStartTime = DriftingDateTime::currentDateTimeUtc(); m_decoderBusyStartTime = QDateTime::currentDateTimeUtc(); //DriftingDateTime::currentDateTimeUtc();
m_decoderBusyFreq = dialFrequency(); m_decoderBusyFreq = dialFrequency();
m_decoderBusyBand = m_config.bands()->find (m_decoderBusyFreq); m_decoderBusyBand = m_config.bands()->find (m_decoderBusyFreq);
} }
@ -4699,7 +5069,16 @@ void MainWindow::decodeDone ()
m_nclearave=0; m_nclearave=0;
m_RxLog=0; m_RxLog=0;
m_blankLine=true; 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); decodeBusy(false);
} }
@ -4750,7 +5129,7 @@ void MainWindow::decodeCheckHangingDecoder(){
return; return;
} }
if(!m_decoderBusyStartTime.isValid() || m_decoderBusyStartTime.secsTo(DriftingDateTime::currentDateTimeUtc()) < 60){ if(!m_decoderBusyStartTime.isValid() || m_decoderBusyStartTime.secsTo(QDateTime::currentDateTimeUtc()) < 60){
return; return;
} }
@ -4836,22 +5215,186 @@ void MainWindow::readFromStdout(QProcess * proc) //r
} }
void MainWindow::processDecodedLine(QByteArray t){ void MainWindow::processDecodedLine(QByteArray t){
qDebug() << "JS8: " << QString(t); if(JS8_DEBUG_DECODE) qDebug() << "JS8: " << QString(t);
bool bAvgMsg=false; bool bAvgMsg=false;
int navg=0; 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()); static QList<int> driftQueue;
return;
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(t.indexOf("<DecodeStarted>") >= 0) {
if(JS8_DEBUG_DECODE) qDebug() << "--> busy?" << m_decoderBusy << "lock exists?" << ( QFile{m_config.temp_dir ().absoluteFilePath (".lock")}.exists()); if(m_wideGraph->shouldDisplayDecodeAttempts()){
return; 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(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; m_bDecoded = t.mid(16).trimmed().toInt() > 0;
int mswait=3*1000*m_TRperiod/4; int mswait=3*1000*m_TRperiod/4;
if(!m_diskData) killFileTimer.start(mswait); //Kill in 3/4 period 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"}); auto rawText = QString::fromUtf8 (t.constData ()).remove (QRegularExpression {"\r|\n"});
DecodedText decodedtext {rawText, "FT8" == m_mode && DecodedText decodedtext {rawText, "FT8" == m_mode &&
ui->cbVHFcontest->isChecked(), m_config.my_grid ()}; ui->cbVHFcontest->isChecked(), m_config.my_grid ()};
bool bValidFrame = decodedtext.snr() >= rxSnrThreshold(decodedtext.submode());
// dupe check // frames are also valid if they pass our dupe check (haven't seen the same frame in the past 1/2 decode period)
auto frame = decodedtext.message();
auto frameOffset = decodedtext.frequencyOffset(); auto frameOffset = decodedtext.frequencyOffset();
if(m_messageDupeCache.contains(frame)){ auto frameDedupeKey = QString("%1:%2").arg(decodedtext.submode()).arg(decodedtext.frame());
// check to see if the frequency is near our previous frame if(m_messageDupeCache.contains(frameDedupeKey)){
auto cachedFreq = m_messageDupeCache.value(frame, 0); auto cached = m_messageDupeCache.value(frameDedupeKey);
if(qAbs(cachedFreq - frameOffset) <= rxThreshold(decodedtext.submode())){
qDebug() << "duplicate frame from" << cachedFreq << "and" << frameOffset; // check to see if the time since last seen is > 1/2 decode period
bValidFrame = false; 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 // check to see if the frequency is near our previous frame
m_messageDupeCache[frame] = frameOffset; 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(); qDebug() << "valid" << bValidFrame << submodeName(decodedtext.submode()) << "decoded text" << decodedtext.message();
// skip if invalid // skip if invalid
@ -4906,6 +5461,9 @@ void MainWindow::processDecodedLine(QByteArray t){
return; 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) // log valid frames to ALL.txt (and correct their timestamp format)
auto freq = dialFrequency(); auto freq = dialFrequency();
@ -4954,7 +5512,7 @@ void MainWindow::processDecodedLine(QByteArray t){
d.utcTimestamp = DriftingDateTime::currentDateTimeUtc(); d.utcTimestamp = DriftingDateTime::currentDateTimeUtc();
d.snr = decodedtext.snr(); d.snr = decodedtext.snr();
d.isBuffered = false; d.isBuffered = false;
d.tdrift = decodedtext.dt(); d.tdrift = m_wideGraph->shouldAutoSync() ? DriftingDateTime::drift()/1000.0 : decodedtext.dt();
d.submode = decodedtext.submode(); d.submode = decodedtext.submode();
// if we have any "first" frame, and a buffer is already established, clear it... // 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.offset = decodedtext.frequencyOffset();
cd.utcTimestamp = DriftingDateTime::currentDateTimeUtc(); cd.utcTimestamp = DriftingDateTime::currentDateTimeUtc();
cd.bits = decodedtext.bits(); cd.bits = decodedtext.bits();
cd.tdrift = decodedtext.dt(); cd.tdrift = m_wideGraph->shouldAutoSync() ? DriftingDateTime::drift()/1000.0 : decodedtext.dt();
cd.submode = decodedtext.submode(); cd.submode = decodedtext.submode();
// Only respond to HEARTBEATS...remember that CQ messages are "Alt" pings // 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.utcTimestamp = DriftingDateTime::currentDateTimeUtc();
cmd.bits = decodedtext.bits(); cmd.bits = decodedtext.bits();
cmd.extra = parts.length() > 2 ? parts.mid(3).join(" ") : ""; 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(); 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) // 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(); auto drift = DriftingDateTime::drift();
QDateTime t = DriftingDateTime::currentDateTimeUtc(); QDateTime t = DriftingDateTime::currentDateTimeUtc();
QStringList parts; 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"); parts << t.date().toString("yyyy MMM dd");
ui->labUTC->setText(parts.join("\n")); ui->labUTC->setText(parts.join("\n"));
@ -7803,19 +8361,11 @@ void MainWindow::on_actionJS8_triggered()
if(m_isWideGraphMDI) m_wideGraph->show(); if(m_isWideGraphMDI) m_wideGraph->show();
ui->decodedTextLabel2->setText(" UTC dB DT Freq Message"); ui->decodedTextLabel2->setText(" UTC dB DT Freq Message");
m_modulator->setTRPeriod(m_TRperiod); // TODO - not thread safe m_modulator->setTRPeriod(m_TRperiod); // TODO - not thread safe
#if JS8_RING_BUFFER
Q_ASSERT(NTMAX == 60); Q_ASSERT(NTMAX == 60);
m_wideGraph->setPeriod(m_TRperiod, m_nsps); m_wideGraph->setPeriod(m_TRperiod, m_nsps);
#if JS8_ENABLE_JS8E && !JS8E_IS_ULTRA
m_detector->setTRPeriod(NTMAX); // TODO - not thread safe 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"); ui->label_7->setText("Rx Frequency");
if(m_config.bFox()) { if(m_config.bFox()) {
ui->label_6->setText("Stations calling DXpedition " + m_config.my_callsign()); ui->label_6->setText("Stations calling DXpedition " + m_config.my_callsign());
@ -9623,6 +10173,12 @@ void MainWindow::qsy(int hzDelta){
displayActivity(true); 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){ void MainWindow::setFreqOffsetForRestore(int freq, bool shouldRestore){
setFreq4(freq, freq); setFreq4(freq, freq);
if(shouldRestore){ if(shouldRestore){
@ -10137,6 +10693,7 @@ void MainWindow::updateModeButtonText(){
modeText += QString("+HB"); modeText += QString("+HB");
} }
} }
ui->modeButton->setText(modeText); ui->modeButton->setText(modeText);
} }

View File

@ -132,6 +132,7 @@ public slots:
void readFromStdout(QProcess * proc); void readFromStdout(QProcess * proc);
void setXIT(int n, Frequency base = 0u); void setXIT(int n, Frequency base = 0u);
void qsy(int hzDelta); void qsy(int hzDelta);
void drifted(int prev, int cur);
void setFreqOffsetForRestore(int freq, bool shouldRestore); void setFreqOffsetForRestore(int freq, bool shouldRestore);
bool tryRestoreFreqOffset(); bool tryRestoreFreqOffset();
void setFreq4(int rxFreq, int txFreq); void setFreq4(int rxFreq, int txFreq);
@ -244,6 +245,7 @@ private slots:
bool decode(qint32 k); bool decode(qint32 k);
bool isDecodeReady(int submode, qint32 k, qint32 k0, qint32 *pCurrentDecodeStart, qint32 *pNextDecodeStart, qint32 *pStart, qint32 *pSz, qint32 *pCycle); 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 decodeEnqueueReady(qint32 k, qint32 k0);
bool decodeEnqueueReadyExperiment(qint32 k, qint32 k0);
bool decodeProcessQueue(qint32 *pSubmode); bool decodeProcessQueue(qint32 *pSubmode);
void decodeStart(); void decodeStart();
void decodePrepareSaveAudio(int submode); void decodePrepareSaveAudio(int submode);
@ -614,6 +616,7 @@ private:
bool m_loopall; bool m_loopall;
bool m_decoderBusy; bool m_decoderBusy;
QString m_decoderBusyBand; QString m_decoderBusyBand;
QMap<qint32, qint32> m_lastDecodeStartMap; // submode, decode k start position
Radio::Frequency m_decoderBusyFreq; Radio::Frequency m_decoderBusyFreq;
QDateTime m_decoderBusyStartTime; QDateTime m_decoderBusyStartTime;
bool m_auto; bool m_auto;
@ -853,13 +856,18 @@ private:
struct DecodeParams { struct DecodeParams {
int submode; int submode;
int cycle;
int start; int start;
int sz; int sz;
}; };
struct CachedFrame {
QDateTime date;
int submode;
int freq;
};
QQueue<DecodeParams> m_decoderQueue; 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_showColumnsCache; // table column:key -> show boolean
QMap<QString, QVariant> m_sortCache; // table key -> sort by QMap<QString, QVariant> m_sortCache; // table key -> sort by
QPriorityQueue<PrioritizedMessage> m_txMessageQueue; // messages to be sent QPriorityQueue<PrioritizedMessage> m_txMessageQueue; // messages to be sent
@ -983,7 +991,10 @@ private:
//int computeCurrentCycle(int period); //int computeCurrentCycle(int period);
//int computeCycleStartForDecode(int cycle, int period); //int computeCycleStartForDecode(int cycle, int period);
int computeCycleForDecode(int submode, int k); int computeCycleForDecode(int submode, int k);
int computeAltCycleForDecode(int submode, int k, int offsetFrames);
int computeFramesPerCycleForDecode(int submode); int computeFramesPerCycleForDecode(int submode);
int computePeriodStartDelayForDecode(int submode);
int computeFramesPerSymbolForDecode(int submode);
int computeFramesNeededForDecode(int submode); int computeFramesNeededForDecode(int submode);
bool shortList(QString callsign); bool shortList(QString callsign);
void transmit (double snr = 99.); 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="actionModeAutoreply"/>
<addaction name="actionModeJS8HB"/> <addaction name="actionModeJS8HB"/>
<addaction name="actionHeartbeatAcknowledgements"/> <addaction name="actionHeartbeatAcknowledgements"/>
<addaction name="separator"/>
</widget> </widget>
<addaction name="menuFile"/> <addaction name="menuFile"/>
<addaction name="menuConfig"/> <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> <string>Enable Tuning Tone (T&amp;UNE)</string>
</property> </property>
</action> </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> </widget>
<layoutdefault spacing="6" margin="11"/> <layoutdefault spacing="6" margin="11"/>
<customwidgets> <customwidgets>

View File

@ -277,42 +277,31 @@ void CPlotter::draw(float swide[], bool bScroll, bool bRed)
painter2D.drawText(x1-4,y,"73"); 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 update(); //trigger a new paintEvent
m_bScaleOK=true; 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; int x1=XfromFreq(ia);
m_ib=ib; int x2=XfromFreq(ib);
draw(swide,false,true);
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() void CPlotter::replot()
@ -890,6 +879,10 @@ void CPlotter::setTol(int n) //setTol()
DrawOverlay(); DrawOverlay();
} }
QVector<QColor> const& CPlotter::colors(){
return g_ColorTbl;
}
void CPlotter::setColours(QVector<QColor> const& cl) void CPlotter::setColours(QVector<QColor> const& cl)
{ {
g_ColorTbl = cl; g_ColorTbl = cl;

View File

@ -77,6 +77,7 @@ public:
void setBreadth(qint32 w) {m_w = w;} void setBreadth(qint32 w) {m_w = w;}
qint32 breadth() const {return m_w;} qint32 breadth() const {return m_w;}
float fSpan() const {return m_fSpan;} float fSpan() const {return m_fSpan;}
QVector<QColor> const& colors();
void setColours(QVector<QColor> const& cl); void setColours(QVector<QColor> const& cl);
void setFlatten(bool b1, bool b2); void setFlatten(bool b1, bool b2);
void setTol(int n); void setTol(int n);
@ -90,7 +91,8 @@ public:
void setReference(bool b) {m_bReference = b;} void setReference(bool b) {m_bReference = b;}
bool Reference() const {return m_bReference;} bool Reference() const {return m_bReference;}
#endif #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 setVHF(bool bVHF);
void setRedFile(QString fRed); void setRedFile(QString fRed);
bool scaleOK () const {return m_bScaleOK;} 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->controls_widget->setVisible(!m_settings->value("HideControls", false).toBool());
ui->cbControls->setChecked(!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->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(); auto splitState = m_settings->value("SplitState").toByteArray();
if(!splitState.isEmpty()){ if(!splitState.isEmpty()){
@ -242,11 +244,80 @@ void WideGraph::saveSettings() //saveS
m_settings->setValue ("FilterOpacityPercent", ui->filterOpacitySpinBox->value()); m_settings->setValue ("FilterOpacityPercent", ui->filterOpacitySpinBox->value());
m_settings->setValue ("SplitState", ui->splitter->saveState()); m_settings->setValue ("SplitState", ui->splitter->saveState());
m_settings->setValue ("WaterfallFPS", ui->fpsSpinBox->value()); 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 void WideGraph::dataSink2(float s[], float df3, int ihsym, int ndiskdata) //dataSink2
@ -356,6 +427,8 @@ void WideGraph::drawSwide(){
swideLocal[i] = flagValue; swideLocal[i] = flagValue;
} }
ui->widePlot->draw(swideLocal,true,false); ui->widePlot->draw(swideLocal,true,false);
} else if(lastSecondInPeriod != secondInPeriod) {
//ui->widePlot->drawHorizontalLine(Qt::white, 0, 5);
} }
lastSecondInPeriod=secondInPeriod; 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 void WideGraph::on_paletteComboBox_activated (QString const& palette) //palette selector
{ {
m_waterfallPalette = palette; m_waterfallPalette = palette;
@ -905,6 +982,8 @@ void WideGraph::on_driftSyncResetButton_clicked(){
} }
void WideGraph::setDrift(int n){ void WideGraph::setDrift(int n){
int prev = drift();
DriftingDateTime::setDrift(n); DriftingDateTime::setDrift(n);
qDebug() << qSetRealNumberPrecision(12) << "Drift milliseconds:" << n; qDebug() << qSetRealNumberPrecision(12) << "Drift milliseconds:" << n;
@ -914,6 +993,12 @@ void WideGraph::setDrift(int n){
if(ui->driftSpinBox->value() != n){ if(ui->driftSpinBox->value() != n){
ui->driftSpinBox->setValue(n); ui->driftSpinBox->setValue(n);
} }
emit drifted(prev, n);
}
int WideGraph::drift(){
return DriftingDateTime::drift();
} }
void WideGraph::setQSYEnabled(bool enabled){ void WideGraph::setQSYEnabled(bool enabled){

View File

@ -63,10 +63,14 @@ public:
int smoothYellow(); int smoothYellow();
void setRxBand (QString const& band); void setRxBand (QString const& band);
void setWSPRtransmitted(); 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 setVHF(bool bVHF);
void setRedFile(QString fRed); void setRedFile(QString fRed);
void setTurbo(bool turbo); void setTurbo(bool turbo);
bool shouldDisplayDecodeAttempts();
bool shouldAutoSync();
QVector<QColor> const& colors();
signals: signals:
void freezeDecode2(int n); void freezeDecode2(int n);
@ -74,6 +78,7 @@ signals:
void setXIT2(int n); void setXIT2(int n);
void setFreq3(int rxFreq, int txFreq); void setFreq3(int rxFreq, int txFreq);
void qsy(int hzDelta); void qsy(int hzDelta);
void drifted(int prev, int cur);
public slots: public slots:
void wideFreezeDecode(int n); void wideFreezeDecode(int n);
@ -82,8 +87,10 @@ public slots:
void setControlsVisible(bool visible); void setControlsVisible(bool visible);
bool controlsVisible(); bool controlsVisible();
void setDrift(int n); void setDrift(int n);
int drift();
void setQSYEnabled(bool enabled); void setQSYEnabled(bool enabled);
void setPaused(bool paused){ m_paused = paused; } void setPaused(bool paused){ m_paused = paused; }
void notifyDriftedSignalsDecoded(int signalsDecoded);
protected: protected:
void keyPressEvent (QKeyEvent *e) override; void keyPressEvent (QKeyEvent *e) override;
@ -117,6 +124,7 @@ private slots:
void on_filterCheckBox_toggled(bool b); void on_filterCheckBox_toggled(bool b);
void on_filterOpacitySpinBox_valueChanged(int n); void on_filterOpacitySpinBox_valueChanged(int n);
void on_autoDriftButton_toggled(bool checked);
void on_driftSpinBox_valueChanged(int n); void on_driftSpinBox_valueChanged(int n);
void on_driftSyncButton_clicked(); void on_driftSyncButton_clicked();
void on_driftSyncEndButton_clicked(); void on_driftSyncEndButton_clicked();
@ -158,6 +166,9 @@ private:
bool m_bRef; bool m_bRef;
bool m_bHaveTransmitted; //Set true at end of a WSPR transmission bool m_bHaveTransmitted; //Set true at end of a WSPR transmission
QTimer m_autoSyncTimer;
int m_autoSyncTimeLeft;
QTimer m_drawTimer; QTimer m_drawTimer;
QMutex m_drawLock; QMutex m_drawLock;

View File

@ -179,7 +179,7 @@
<rect> <rect>
<x>0</x> <x>0</x>
<y>0</y> <y>0</y>
<width>270</width> <width>323</width>
<height>372</height> <height>372</height>
</rect> </rect>
</property> </property>
@ -427,9 +427,9 @@
<property name="geometry"> <property name="geometry">
<rect> <rect>
<x>0</x> <x>0</x>
<y>-193</y> <y>0</y>
<width>267</width> <width>323</width>
<height>723</height> <height>742</height>
</rect> </rect>
</property> </property>
<layout class="QVBoxLayout" name="verticalLayout_5"> <layout class="QVBoxLayout" name="verticalLayout_5">
@ -656,92 +656,106 @@
<property name="title"> <property name="title">
<string>Waterfall</string> <string>Waterfall</string>
</property> </property>
<layout class="QFormLayout" name="formLayout"> <layout class="QVBoxLayout" name="verticalLayout_4">
<item row="1" column="0"> <item>
<widget class="QLabel" name="label"> <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"> <property name="text">
<string>Gain:</string> <string>Display Decode Attempts</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>
</property> </property>
</widget> </widget>
</item> </item>
@ -961,8 +975,8 @@
<rect> <rect>
<x>0</x> <x>0</x>
<y>0</y> <y>0</y>
<width>281</width> <width>337</width>
<height>198</height> <height>351</height>
</rect> </rect>
</property> </property>
<layout class="QVBoxLayout" name="verticalLayout"> <layout class="QVBoxLayout" name="verticalLayout">
@ -1007,67 +1021,114 @@
</widget> </widget>
</item> </item>
<item> <item>
<widget class="QPushButton" name="driftSyncMinuteButton"> <widget class="QGroupBox" name="groupBox_10">
<property name="minimumSize"> <property name="title">
<size> <string>Automatic</string>
<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>
</property> </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> </widget>
</item> </item>
<item> <item>
<widget class="QPushButton" name="driftSyncButton"> <widget class="QGroupBox" name="groupBox_11">
<property name="minimumSize"> <property name="title">
<size> <string>Manual</string>
<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>
</property> </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> </widget>
</item> </item>
<item> <item>