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