Now have a properly functioning stand-along decoder for JTMSK.

git-svn-id: svn+ssh://svn.code.sf.net/p/wsjt/wsjt/branches/jtms3@2529 ab8295b8-cf94-4d9e-aec4-7959e3be5d79
This commit is contained in:
Joe Taylor 2012-07-19 18:01:14 +00:00
parent 160009e621
commit 5079669e49
6 changed files with 154 additions and 167 deletions

View File

@ -1,64 +1,72 @@
JTMSK: Possible New Mode for Meteor Scatter JTMSK: Possible New Mode for Meteor Scatter
------------------------------------------- -------------------------------------------
JTMSK is a digital mode designed for amateur meteor-scatter
communication. The "MSK" part of the name could mean "Meteor Scatter
King": the mode works at the same signal levels as the popular mode
FSK441, but at about twice the character transmission rate, so it
makes much better use of short pings. MSK also means "Minimum Shift
Keying", which is the modulation technique used in JTMSK. It is a
form of continuous-phase frequency-shift keying that achieves high
transmission rate with a narrow bandwidth, while maintaining a
constant-envelope waveform.
1. Transmitting 1. Transmitting
Messages are sent character-by character, 6 bits plus odd parity. Messages are sent character-by character, 6 bits plus odd parity.
Message length can be one of {5 7 9 11 13 17 19 23 29}; messages are Message length in characters is automatically selected from among the
padded with blanks up to the next available length from this list. possibilities 5 7 9 11 13 17 19 23 29. (Note that these lengths share
no common factor.) Messages are padded with blanks when required, up
to the next available length.
Modulation is Minimum Shift Keying (MSK) at 2000 baud. This is a form
of constant-envelope, continuous-phase modulation, equivalent to FSK Modulation is Minimum Shift Keying (MSK) at 2000 baud. A "0" bit is
with offset equal to half the bit rate. JTMSK sends a "0" bit with sent as an audio sine wave at f0=1000 Hz, lasting 0.5 ms; a "1" bit is
0.5 ms of a sine wave at f0=1000 Hz, and a "1" bit at f1=2000 Hz. a sine wave at f1=2000 Hz. The carrier phase increment over one bit
Carrier phase increment over one bit is therefore 0.5 cycles (pi is thus 0.5 cycles (pi radians) at f0, or 1.0 cycles at f1. Odd
radians) at f0 and 1.0 cycles (two*pi radians) at f1. With odd parity parity assures an odd number of 1's and even number of 0's in every
there is always an odd number of 1's and even number of 0's in a 7-bit character, so the phase increment over a full character is
character, so the phase increment over a full character is always an always an integer number of cycles.
integer number of cycles.
2. Receiving 2. Receiving
a. Pings are detected (or mouse-picked data is selected) as in a. Detect pings and update display in real time. (rtping)
WSJT9, except that it's done in real time rather than at the end
of an Rx interval.
b. Compute real-to-complex FFT. Zap birdies, remove frequency b. Compute real-to-complex FFT. Zap birdies, remove frequency
components outside the range 300 - 2700 Hz, and convert to analytic components outside the range 300 - 2700 Hz, convert to analytic
time-domain signal. (analytic) time-domain signal. (analytic)
c. Square the complex signal, cx2=cx*cx, and compute FFT. Look for c. Square the complex signal and compute FFT. Look for narrow
narrow carriers at frequency 2000 + 2*DF +/- 2*Tol and 2000 Hz spectral lines at frequency 2000 + 2*(DF +/- Tol) Hz and
higher. (msdf) 4000 + 2*(DF +/- Tol) Hz. (msdf)
d. If carrier is found, measure frequency f and phase phi. Multiply d. If a signal is found, measure frequency offset DF and phase Dphi.
cx by exp(-twopi*i*(f-1500)*t - phi) to recover the (DF and Dphi are measured relative to the original frequencies f0
standard-frequency baseband signal. (tweak1) and f1, not their doubled values.) Multiply cx by
exp(-twopi*i*DF*t - Dphi) to recover the standard-frequency
baseband signal. (tweak1)
e. Establish symbol and character sync by cross-correlating with e. Establish symbol and character sync by cross-correlating with
conjg(cwb), where cwb is the MSK waveform for the <space> conjg(cwb), MSK waveform for the <space> character.
character.
f. Find message length by computing ACF (of what? cdat? soft f. Find message length by computing ACF.
symbol values?)
g. Cross-correlate the complex standard-frequency signal against g. Decode the message by cross-correlating character-length segments
conjugated templates for each character in the JTMSK alphabet.
Best match yields the decoded character.
h. Decode the message by cross-correlating character-length segments
of cdat against complex waveforms for each possible character. of cdat against complex waveforms for each possible character.
i. If msglen is established and long enough, try folding the data and h. If ping is long enough, try folding the data and determining the
determining best-fit characters as above. best-fit characters as above.
3. Comparison of modes: 3. Parameters of fast modes:
| Meteor Scatter | IonoScatter +--------------------+-----------------------+-------------------+
| FSK441 JTMS JTMSK | JT6M ISCAT | | Meteor Scatter | IonoScatter |
-------------------+-----------------------+------------------- | | FSK441 JTMS JTMSK | JT6M ISCAT |
T/R period (s) | 30 30 30 | 30 30 +--------------------+-----------------------+-------------------|
Modulation type | 4-FSK MSK* MSK* | 44-FSK 41-FSK | T/R period (s) | 30 30 30 | 30 30 |
Keying rate (baud) | 441 1378 2000 | 21.5 43.1 | Modulation type | 4-FSK MSK MSK | 44-FSK 41-FSK |
Characters/s | 147 197 286 | 14.3 32.3 | Keying rate (baud) | 441 1378 2000 | 21.5 43.1 |
Bandwidth (Hz) | 1764 1378 2000 | 947 1809 | Bit rate (bps) | 882 1378 2000 | - - |
| Characters/s | 147 197 286 | 14.3 32.3 |
| Bandwidth (Hz) | 1764 1378 2000 | 947 1809 |
+--------------------+-----------------------+-------------------+

View File

@ -1,14 +1,14 @@
subroutine jtmsk(dat,npts,cfile6,t2,mswidth,ndb,nrpt,Nfreeze, & subroutine jtmsk(dat,npts,cfile6,tpk,mswidth,ndb,nrpt,Nfreeze, &
ntol,MouseDF,pick) ntol,MouseDF,pick,mycall)
! Decode a JTMS ping ! Decode a JTMSK ping
parameter (NZ=30*48000) parameter (NZ=2*48000)
real dat(npts) !Raw data real dat(npts) !Raw data
complex cdat(NZ) !Analytic form of signal complex cdat(NZ) !Analytic form of signal
character*6 cfile6 !FileID character*6 cfile6 !FileID
logical pick logical pick
character*12 mycall,hiscall character*12 mycall
real s(NZ) !Power spectrum real s(NZ) !Power spectrum
real s2(0:63,400) real s2(0:63,400)
real r(60000) real r(60000)
@ -16,8 +16,6 @@ subroutine jtmsk(dat,npts,cfile6,t2,mswidth,ndb,nrpt,Nfreeze, &
complex cwb(168) !Complex waveform for <space> complex cwb(168) !Complex waveform for <space>
logical first logical first
character msg*400,msg29*29 character msg*400,msg29*29
character*90 line
common/ccom/nline,tping(100),line(100)
data first/.true./ data first/.true./
save first,cw,cwb save first,cw,cwb
save cdat !Fix its address, for four2 save cdat !Fix its address, for four2
@ -31,17 +29,14 @@ subroutine jtmsk(dat,npts,cfile6,t2,mswidth,ndb,nrpt,Nfreeze, &
nfft1=2**n !FFT length nfft1=2**n !FFT length
call analytic(dat,npts,nfft1,s,cdat) !Convert to analytic signal call analytic(dat,npts,nfft1,s,cdat) !Convert to analytic signal
call mskdf(cdat,npts,t2,nfft1,f0,nfreeze,mousedf,ntol, & call mskdf(cdat,npts,tpk,nfft1,f0,nfreeze,mousedf,ntol, &
dfx,snrsq2) !Get DF dfx,snrsq2) !Get DF
print*,'b',dfx,snrsq2
sq2lim=7.0 sq2lim=7.0
if(pick) sq2lim=5.0 if(pick) sq2lim=5.0
if(snrsq2.lt.sq2lim) go to 900 !Reject non-JTMS signals if(snrsq2.lt.sq2lim) go to 900 !Reject non-JTMS signals
call tweak1(cdat,npts,-dfx,cdat) !Mix to standard frequency call tweak1(cdat,npts,-dfx,cdat) !Mix to standard frequency
! DF is known, now establish character sync.
call syncmsk(cdat,npts,cwb,r,i1) !Get character sync call syncmsk(cdat,npts,cwb,r,i1) !Get character sync
call lenmsk(r,npts,msglen) !Find message length call lenmsk(r,npts,msglen) !Find message length
@ -51,36 +46,27 @@ subroutine jtmsk(dat,npts,cfile6,t2,mswidth,ndb,nrpt,Nfreeze, &
if(nchar.gt.400) nchar=400 if(nchar.gt.400) nchar=400
call decodemsk(cdat,npts,cw,i1,nchar,s2,msg) !Decode the message call decodemsk(cdat,npts,cw,i1,nchar,s2,msg) !Decode the message
print*,'B',i1,msglen,nchar
print*,msg(1:nchar)
! ia=1 ia=1
! if(nchar.ge.40) ia=min(nchar/3,nchar-28) if(nchar.ge.40) ia=min(nchar/3,nchar-28)
! ib=min(ia+28,nchar) !Can better limits ia, ib be found? ib=min(ia+28,nchar) !Can better limits ia, ib be found?
! print*,'A',ia,ib,nchar msg29=adjustl(msg(ia:ib))
! print*,msg(1:nchar)
! msg29=adjustl(msg(ia:ib))
msg=adjustl(msg) msg=adjustl(msg)
ib=min(nchar,45) ib=min(nchar,45)
ndf=nint(dfx) ndf=nint(dfx)
nchk=max(20,nint(1.5*msglen)) nchk=max(20,nint(1.5*msglen))
if(msglen.eq.0 .or. nchar.lt.nchk .or. pick) then if(msglen.eq.0 .or. nchar.lt.nchk .or. pick) then
if(nline.le.99) nline=nline+1 write(*,1110) cfile6,tpk,mswidth,ndb,nrpt,ndf,msg(1:45)
tping(nline)=t2
write(line(nline),1110) cfile6,t2,mswidth,ndb,nrpt,ndf,msg(1:45)
1110 format(a6,f5.1,i5,i3,1x,i2.2,i5,5x,a45) 1110 format(a6,f5.1,i5,i3,1x,i2.2,i5,5x,a45)
endif endif
if(msglen.gt.0 .and. nchar.ge.nchk) then if(msglen.gt.0 .and. nchar.ge.nchk) then
call foldmsk(s2,msglen,nchar,mycall,msg,msg29) !Decode folded message call foldmsk(s2,msglen,nchar,mycall,msg,msg29) !Decode folded message
if(nline.le.99) nline=nline+1 write(*,1120) cfile6,tpk,mswidth,ndb,nrpt,ndf,msg29
tping(nline)=t2
write(line(nline),1120) cfile6,t2,mswidth,ndb,nrpt,ndf,msg29
1120 format(a6,f5.1,i5,i3,1x,i2.2,i5,5x,a29,11x,'*') 1120 format(a6,f5.1,i5,i3,1x,i2.2,i5,5x,a29,11x,'*')
endif endif
900 continue 900 continue
return return
end subroutine jtmsk end subroutine jtmsk

View File

@ -1,11 +1,12 @@
program msk program msk
! Starting code for a JTMSK decoder. ! Program to test decoding routines for mode JTMSK.
parameter (NSMAX=30*48000) parameter (NSMAX=30*48000)
character*80 infile character*80 infile
character*6 cfile6 character*6 cfile6
character*12 arg character*12 arg
character*12 mycall
real dat(NSMAX) real dat(NSMAX)
real x(NSMAX) real x(NSMAX)
complex cx(0:NSMAX/2) complex cx(0:NSMAX/2)
@ -14,46 +15,49 @@ program msk
common/mscom/id(NSMAX),s1(215,703),s2(215,703) common/mscom/id(NSMAX),s1(215,703),s2(215,703)
nargs=iargc() nargs=iargc()
if(nargs.lt.1) then if(nargs.lt.2) then
print*,'Usage: msk <snr>' print*,'Usage: msk nslow snr'
go to 999 go to 999
endif endif
call getarg(1,arg) call getarg(1,arg)
read(arg,*) nslow
call getarg(2,arg)
read(arg,*) snr read(arg,*) snr
! Read simulated pings from a file
open(71,file='dat.71',form='unformatted',status='old') open(71,file='dat.71',form='unformatted',status='old')
read(71) id read(71) id
cfile6='123400'
cfile6='123400'
npts=30*48000 npts=30*48000
kstep=2048 kstep=2048
minsigdb=6 minsigdb=1
mousedf=0 mousedf=0
ntol=200 ntol=200
mycall='W8WN'
! Make some band-limited noise.
call random_number(x) call random_number(x)
nfft=NSMAX nfft=NSMAX
call four2a(x,nfft,1,-1,0) call four2a(x,nfft,1,-1,0)
df=48000.0/nfft df=48000.0/nfft
ia=nint(300.0/df) ia=nint(300.0/df)
ib=nint(2800.0/df) ib=nint(2700.0/df)
cx(:ia)=0. cx(:ia)=0.
cx(ib:)=0. cx(ib:)=0.
call four2a(cx,nfft,1,1,-1) call four2a(cx,nfft,1,1,-1)
x(1)=0. x(1)=0.
sq=0. rms=sqrt(dot_product(x,x)/NSMAX)
do i=1,NSMAX
sq=sq + x(i)**2
enddo
rms=sqrt(sq/NSMAX)
x=x/rms x=x/rms
sig=(10.0**(0.05*snr))/32768.0
dat=sig*id + x
k=0 sig=(10.0**(0.05*snr))/32768.0 !Scaled signal strength
dat=sig*id + x !Add pings to noise
! This loop simulates being called from "datasink()" in program JTMSK.
do iblk=1,npts/kstep do iblk=1,npts/kstep
k=k+kstep k=iblk*kstep
call rtping(dat,k,cfile6,MinSigdB,MouseDF,ntol) call rtping(dat,k,cfile6,MinSigdB,MouseDF,ntol,mycall)
if(nslow.ne.0) call usleep(42000)
enddo enddo
999 end program msk 999 end program msk

View File

@ -1,103 +1,92 @@
subroutine rtping(dat,k,cfile6,MinSigdB,MouseDF,ntol) subroutine rtping(dat,k,cfile6,MinSigdB,MouseDF,ntol,mycall)
!subroutine rtping(dat,jz,nz,MinSigdB,MinWidth,NFreeze,DFTolerance, & ! Called from datasink() every 2048 sample intervals (approx 43 ms).
! MouseDF,istart,pick,cfile6,mycall,hiscall,mode,ps0) ! Detects pings (signal level above MinSigdB). When a ping ends, its
! MSK signal is synchronized and decoded.
! Decode Multi-Tone FSK441 mesages.
parameter (NSMAX=30*48000) parameter (NSMAX=30*48000)
parameter (NZMAX=NSMAX/2048) parameter (NZMAX=703) !703 = NSMAX/2048
real dat(NSMAX) !Raw audio data real dat(NSMAX) !Raw audio data
logical pick character*6 cfile6 !Time hhmmss at start of this Rx interval
character*6 cfile6 character*12 mycall
real sig(NZMAX) !Sq-law detected signal, sampled at 43 ms real sig(NZMAX) !Sq-law detected signal, sampled at 43 ms
real sigdb(NZMAX) !Signal in dB, sampled at 43 ms real sigdb(NZMAX) !Signal in dB, sampled at 43 ms
real work(NZMAX) real tmp(NZMAX)
real pingdat(3,100) logical inside,pingFound
! character msg*40,msg3*3 data k0/9999999/
character*90 line
common/ccom/nline,tping(100),line(100)
data nping0/0/
save save
slim=MinSigdB if(k.lt.k0) then
! nf1=-ntol inside=.false.
! nf2=ntol pingFound=.false.
j0=0
t1=0.
width=0.
peak=0.
tpk=0.
dt=1.0/48000.0 dt=1.0/48000.0
kstep=2048 kstep=2048
! pick=.false. sdt=dt*kstep
istart=1 wmin=0.043
jz=k endif
k0=k
slim=MinSigdB
snrlim=10.0**(0.1*slim) - 1.0
sdown=10.0*log10(0.25*snrlim+1.0)
! Find signal power ! Find signal power
j=k/kstep j=k/kstep
sig(j)=dot_product(dat(k-kstep+1:k),dat(k-kstep+1:k))/kstep sig(j)=dot_product(dat(k-kstep+1:k),dat(k-kstep+1:k))/kstep
if(j.lt.10) return if(j.lt.20) return
! Remove baseline, compute signal level in dB ! Determine baseline noise level
call pctile (sig,work,j,50,base1) if(mod(j,20).eq.0) call pctile (sig,tmp,j,50,base)
do i=1,j sigdb(j)=db(sig(j)/base) ! (S+N)/N in dB
sigdb(i)=db(sig(i)/base1)
if(j.eq.703) write(13,3001) i,sig(i),sigdb(i)
3001 format(i5,2e12.3)
enddo
dtbuf=kstep*dt ! write(72,3001) j*sdt,base,sig(j),sigdb(j)
wmin=0.040 !3001 format(f10.3,3f12.6)
call ping(sigdb,j,dtbuf,slim,wmin,pingdat,nping)
! If this is a "mouse pick" and no ping was found, force a pseudo-ping if(sigdb(j).ge.slim .and. .not.inside) then
! at center of data. j0=j !Mark the start of a ping
! if(pick.and.nping.eq.0) then t1=j0*sdt
! if(nping.le.99) nping=nping+1 inside=.true.
! pingdat(1,nping)=0.5*jz*dt peak=0.
! pingdat(2,nping)=0.16 endif
! pingdat(3,nping)=1.0
! endif
do iping=1,nping if(inside .and. sigdb(j).gt.peak) then
! Find starting place and length of data to be analyzed: peak=sigdb(j) !Save peak strength
tstart=pingdat(1,iping) tpk=j*sdt ! and time of peak
width=pingdat(2,iping) endif
peak=pingdat(3,iping)
! mswidth=10*nint(100.0*width)
jj=(tstart-0.02)/dt
if(jj.lt.1) jj=1
jjz=nint((width+0.02)/dt)+1
jjz=min(jjz,jz+1-jj)
! Compute average spectrum of this ping. if(inside .and. (sigdb(j).lt.sdown .or. j.eq.NZMAX)) then
! call spec441(dat(jj),jjz,ps,f0) width=(j-j0)*sdt !Save ping width
if(width.ge.wmin) pingFound=.true.
endif
if(.not.pingFound) return
! Decode the message. ! A ping was found! Assemble a signal report:
! msg=' '
! call longx(dat(jj),jjz,ps,DFTolerance,noffset,msg,msglen,bauderr)
! Assemble a signal report:
nwidth=0 nwidth=0
if(width.ge.0.04) nwidth=1 !These might depend on NSPD if(width.ge.0.04) nwidth=1
if(width.ge.0.12) nwidth=2 if(width.ge.0.12) nwidth=2
if(width.gt.1.00) nwidth=3 if(width.gt.1.00) nwidth=3
nstrength=6 nstrength=6
if(peak.ge.11.0) nstrength=7 if(peak.ge.11.0) nstrength=7
if(peak.ge.17.0) nstrength=8 if(peak.ge.17.0) nstrength=8
if(peak.ge.23.0) nstrength=9 if(peak.ge.23.0) nstrength=9
! nrpt=10*nwidth + nstrength nrpt=10*nwidth + nstrength
t2=tstart + dt*(istart-1)
jjzz=min(jjz,2*48000) !Max data size 2 s mswidth=10*nint(100.0*width)
!### i1=(t1-0.02)/dt
jjzz=14400 if(i1.lt.1) i1=1
jj=jj-200 iz=nint((width+0.02)/dt) + 1
!### iz=min(iz,k+1-i1,2*48000) !Length of ping in samples
if(nping.gt.nping0) then call jtmsk(dat(i1),iz,cfile6,tpk,mswidth,int(peak),nrpt, &
print*,'a',jj,jjzz,jj*dt,jjzz*dt,t2,width nfreeze,DFTolerance,MouseDF,pick,mycall)
call jtmsk(dat(jj),jjzz,cfile6,t2,mswidth,int(peak),nrpt, &
nfreeze,DFTolerance,MouseDF,pick) pingFound=.false.
nping0=nping inside=.false.
endif
enddo
return return
end subroutine rtping end subroutine rtping

View File

@ -1,4 +1,4 @@
//--------------------------------------------------------------- MainWindow //---------------------------------------------------------------- MainWindow
#include "mainwindow.h" #include "mainwindow.h"
#include "ui_mainwindow.h" #include "ui_mainwindow.h"
#include "devsetup.h" #include "devsetup.h"