From 5079669e49cd20adbf9fa6dc376933b3a7d24f85 Mon Sep 17 00:00:00 2001 From: Joe Taylor Date: Thu, 19 Jul 2012 18:01:14 +0000 Subject: [PATCH] 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 --- jtmsk.txt | 92 +++++++++------- libm65/jtmsk.f90 | 44 +++----- libm65/msk.f90 | 36 +++--- libm65/rtping.f90 | 147 ++++++++++++------------- libm65/{specjtms.f90 => specjtmsk.f90} | 0 mainwindow.cpp | 2 +- 6 files changed, 154 insertions(+), 167 deletions(-) rename libm65/{specjtms.f90 => specjtmsk.f90} (100%) diff --git a/jtmsk.txt b/jtmsk.txt index 83ff5fb46..fadeb35db 100644 --- a/jtmsk.txt +++ b/jtmsk.txt @@ -1,64 +1,72 @@ 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 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 -padded with blanks up to the next available length from this list. +Message length in characters is automatically selected from among the +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 -with offset equal to half the bit rate. JTMSK sends a "0" bit with -0.5 ms of a sine wave at f0=1000 Hz, and a "1" bit at f1=2000 Hz. -Carrier phase increment over one bit is therefore 0.5 cycles (pi -radians) at f0 and 1.0 cycles (two*pi radians) at f1. With odd parity -there is always an odd number of 1's and even number of 0's in a -character, so the phase increment over a full character is always an -integer number of cycles. + +Modulation is Minimum Shift Keying (MSK) at 2000 baud. A "0" bit is +sent as an audio sine wave at f0=1000 Hz, lasting 0.5 ms; a "1" bit is +a sine wave at f1=2000 Hz. The carrier phase increment over one bit +is thus 0.5 cycles (pi radians) at f0, or 1.0 cycles at f1. Odd +parity assures an odd number of 1's and even number of 0's in every +7-bit character, so the phase increment over a full character is +always an integer number of cycles. 2. Receiving - a. Pings are detected (or mouse-picked data is selected) as in - WSJT9, except that it's done in real time rather than at the end - of an Rx interval. + a. Detect pings and update display in real time. (rtping) - b. Compute real-to-complex FFT. Zap birdies, remove frequency - components outside the range 300 - 2700 Hz, and convert to analytic + b. Compute real-to-complex FFT. Zap birdies, remove frequency + components outside the range 300 - 2700 Hz, convert to analytic time-domain signal. (analytic) - c. Square the complex signal, cx2=cx*cx, and compute FFT. Look for - narrow carriers at frequency 2000 + 2*DF +/- 2*Tol and 2000 Hz - higher. (msdf) + c. Square the complex signal and compute FFT. Look for narrow + spectral lines at frequency 2000 + 2*(DF +/- Tol) Hz and + 4000 + 2*(DF +/- Tol) Hz. (msdf) - d. If carrier is found, measure frequency f and phase phi. Multiply - cx by exp(-twopi*i*(f-1500)*t - phi) to recover the - standard-frequency baseband signal. (tweak1) + d. If a signal is found, measure frequency offset DF and phase Dphi. + (DF and Dphi are measured relative to the original frequencies f0 + 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 - conjg(cwb), where cwb is the MSK waveform for the - character. + conjg(cwb), MSK waveform for the character. - f. Find message length by computing ACF (of what? cdat? soft - symbol values?) + f. Find message length by computing ACF. - g. Cross-correlate the complex standard-frequency signal against - 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 + g. Decode the message by cross-correlating character-length segments of cdat against complex waveforms for each possible character. - i. If msglen is established and long enough, try folding the data and - determining best-fit characters as above. + h. If ping is long enough, try folding the data and determining the + best-fit characters as above. -3. Comparison of modes: +3. Parameters of fast modes: - | 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 -Keying rate (baud) | 441 1378 2000 | 21.5 43.1 -Characters/s | 147 197 286 | 14.3 32.3 -Bandwidth (Hz) | 1764 1378 2000 | 947 1809 ++--------------------+-----------------------+-------------------+ +| | 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 | +| Keying rate (baud) | 441 1378 2000 | 21.5 43.1 | +| Bit rate (bps) | 882 1378 2000 | - - | +| Characters/s | 147 197 286 | 14.3 32.3 | +| Bandwidth (Hz) | 1764 1378 2000 | 947 1809 | ++--------------------+-----------------------+-------------------+ diff --git a/libm65/jtmsk.f90 b/libm65/jtmsk.f90 index f467d76d8..ab3e048f2 100644 --- a/libm65/jtmsk.f90 +++ b/libm65/jtmsk.f90 @@ -1,14 +1,14 @@ -subroutine jtmsk(dat,npts,cfile6,t2,mswidth,ndb,nrpt,Nfreeze, & - ntol,MouseDF,pick) +subroutine jtmsk(dat,npts,cfile6,tpk,mswidth,ndb,nrpt,Nfreeze, & + 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 complex cdat(NZ) !Analytic form of signal character*6 cfile6 !FileID logical pick - character*12 mycall,hiscall + character*12 mycall real s(NZ) !Power spectrum real s2(0:63,400) real r(60000) @@ -16,8 +16,6 @@ subroutine jtmsk(dat,npts,cfile6,t2,mswidth,ndb,nrpt,Nfreeze, & complex cwb(168) !Complex waveform for logical first character msg*400,msg29*29 - character*90 line - common/ccom/nline,tping(100),line(100) data first/.true./ save first,cw,cwb save cdat !Fix its address, for four2 @@ -31,56 +29,44 @@ subroutine jtmsk(dat,npts,cfile6,t2,mswidth,ndb,nrpt,Nfreeze, & nfft1=2**n !FFT length 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 - print*,'b',dfx,snrsq2 sq2lim=7.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 -! 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 s2=0. nchar=(npts-168+1-i1)/168 if(nchar.gt.400) nchar=400 call decodemsk(cdat,npts,cw,i1,nchar,s2,msg) !Decode the message - print*,'B',i1,msglen,nchar - print*,msg(1:nchar) -! ia=1 -! if(nchar.ge.40) ia=min(nchar/3,nchar-28) -! ib=min(ia+28,nchar) !Can better limits ia, ib be found? -! print*,'A',ia,ib,nchar -! print*,msg(1:nchar) -! msg29=adjustl(msg(ia:ib)) + ia=1 + if(nchar.ge.40) ia=min(nchar/3,nchar-28) + ib=min(ia+28,nchar) !Can better limits ia, ib be found? + msg29=adjustl(msg(ia:ib)) msg=adjustl(msg) ib=min(nchar,45) ndf=nint(dfx) nchk=max(20,nint(1.5*msglen)) if(msglen.eq.0 .or. nchar.lt.nchk .or. pick) then - if(nline.le.99) nline=nline+1 - tping(nline)=t2 - write(line(nline),1110) cfile6,t2,mswidth,ndb,nrpt,ndf,msg(1:45) + write(*,1110) cfile6,tpk,mswidth,ndb,nrpt,ndf,msg(1:45) 1110 format(a6,f5.1,i5,i3,1x,i2.2,i5,5x,a45) endif if(msglen.gt.0 .and. nchar.ge.nchk) then call foldmsk(s2,msglen,nchar,mycall,msg,msg29) !Decode folded message - if(nline.le.99) nline=nline+1 - tping(nline)=t2 - write(line(nline),1120) cfile6,t2,mswidth,ndb,nrpt,ndf,msg29 + write(*,1120) cfile6,tpk,mswidth,ndb,nrpt,ndf,msg29 1120 format(a6,f5.1,i5,i3,1x,i2.2,i5,5x,a29,11x,'*') endif 900 continue - return end subroutine jtmsk diff --git a/libm65/msk.f90 b/libm65/msk.f90 index 4465b6146..0e2d3ac73 100644 --- a/libm65/msk.f90 +++ b/libm65/msk.f90 @@ -1,11 +1,12 @@ program msk -! Starting code for a JTMSK decoder. +! Program to test decoding routines for mode JTMSK. parameter (NSMAX=30*48000) character*80 infile character*6 cfile6 character*12 arg + character*12 mycall real dat(NSMAX) real x(NSMAX) complex cx(0:NSMAX/2) @@ -14,46 +15,49 @@ program msk common/mscom/id(NSMAX),s1(215,703),s2(215,703) nargs=iargc() - if(nargs.lt.1) then - print*,'Usage: msk ' + if(nargs.lt.2) then + print*,'Usage: msk nslow snr' go to 999 endif call getarg(1,arg) + read(arg,*) nslow + call getarg(2,arg) read(arg,*) snr +! Read simulated pings from a file open(71,file='dat.71',form='unformatted',status='old') read(71) id - cfile6='123400' + cfile6='123400' npts=30*48000 kstep=2048 - minsigdb=6 + minsigdb=1 mousedf=0 ntol=200 + mycall='W8WN' +! Make some band-limited noise. call random_number(x) nfft=NSMAX call four2a(x,nfft,1,-1,0) df=48000.0/nfft ia=nint(300.0/df) - ib=nint(2800.0/df) + ib=nint(2700.0/df) cx(:ia)=0. cx(ib:)=0. call four2a(cx,nfft,1,1,-1) x(1)=0. - sq=0. - do i=1,NSMAX - sq=sq + x(i)**2 - enddo - rms=sqrt(sq/NSMAX) + rms=sqrt(dot_product(x,x)/NSMAX) 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 - k=k+kstep - call rtping(dat,k,cfile6,MinSigdB,MouseDF,ntol) + k=iblk*kstep + call rtping(dat,k,cfile6,MinSigdB,MouseDF,ntol,mycall) + if(nslow.ne.0) call usleep(42000) enddo 999 end program msk diff --git a/libm65/rtping.f90 b/libm65/rtping.f90 index 386884b76..886c9fd12 100644 --- a/libm65/rtping.f90 +++ b/libm65/rtping.f90 @@ -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, & -! MouseDF,istart,pick,cfile6,mycall,hiscall,mode,ps0) - -! Decode Multi-Tone FSK441 mesages. +! Called from datasink() every 2048 sample intervals (approx 43 ms). +! Detects pings (signal level above MinSigdB). When a ping ends, its +! MSK signal is synchronized and decoded. parameter (NSMAX=30*48000) - parameter (NZMAX=NSMAX/2048) + parameter (NZMAX=703) !703 = NSMAX/2048 real dat(NSMAX) !Raw audio data - logical pick - character*6 cfile6 + character*6 cfile6 !Time hhmmss at start of this Rx interval + character*12 mycall real sig(NZMAX) !Sq-law detected signal, sampled at 43 ms real sigdb(NZMAX) !Signal in dB, sampled at 43 ms - real work(NZMAX) - real pingdat(3,100) -! character msg*40,msg3*3 - character*90 line - common/ccom/nline,tping(100),line(100) - data nping0/0/ + real tmp(NZMAX) + logical inside,pingFound + data k0/9999999/ save + if(k.lt.k0) then + inside=.false. + pingFound=.false. + j0=0 + t1=0. + width=0. + peak=0. + tpk=0. + dt=1.0/48000.0 + kstep=2048 + sdt=dt*kstep + wmin=0.043 + endif + k0=k + slim=MinSigdB -! nf1=-ntol -! nf2=ntol - dt=1.0/48000.0 - kstep=2048 -! pick=.false. - istart=1 - jz=k + snrlim=10.0**(0.1*slim) - 1.0 + sdown=10.0*log10(0.25*snrlim+1.0) ! Find signal power j=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 - call pctile (sig,work,j,50,base1) - do i=1,j - sigdb(i)=db(sig(i)/base1) - if(j.eq.703) write(13,3001) i,sig(i),sigdb(i) -3001 format(i5,2e12.3) - enddo +! Determine baseline noise level + if(mod(j,20).eq.0) call pctile (sig,tmp,j,50,base) + sigdb(j)=db(sig(j)/base) ! (S+N)/N in dB - dtbuf=kstep*dt - wmin=0.040 - call ping(sigdb,j,dtbuf,slim,wmin,pingdat,nping) +! write(72,3001) j*sdt,base,sig(j),sigdb(j) +!3001 format(f10.3,3f12.6) -! If this is a "mouse pick" and no ping was found, force a pseudo-ping -! at center of data. -! if(pick.and.nping.eq.0) then -! if(nping.le.99) nping=nping+1 -! pingdat(1,nping)=0.5*jz*dt -! pingdat(2,nping)=0.16 -! pingdat(3,nping)=1.0 -! endif + if(sigdb(j).ge.slim .and. .not.inside) then + j0=j !Mark the start of a ping + t1=j0*sdt + inside=.true. + peak=0. + endif - do iping=1,nping -! Find starting place and length of data to be analyzed: - tstart=pingdat(1,iping) - width=pingdat(2,iping) - 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) + if(inside .and. sigdb(j).gt.peak) then + peak=sigdb(j) !Save peak strength + tpk=j*sdt ! and time of peak + endif + + if(inside .and. (sigdb(j).lt.sdown .or. j.eq.NZMAX)) then + width=(j-j0)*sdt !Save ping width + if(width.ge.wmin) pingFound=.true. + endif + if(.not.pingFound) return -! Compute average spectrum of this ping. -! call spec441(dat(jj),jjz,ps,f0) +! A ping was found! Assemble a signal report: + nwidth=0 + if(width.ge.0.04) nwidth=1 + if(width.ge.0.12) nwidth=2 + if(width.gt.1.00) nwidth=3 + nstrength=6 + if(peak.ge.11.0) nstrength=7 + if(peak.ge.17.0) nstrength=8 + if(peak.ge.23.0) nstrength=9 + nrpt=10*nwidth + nstrength -! Decode the message. -! msg=' ' -! call longx(dat(jj),jjz,ps,DFTolerance,noffset,msg,msglen,bauderr) + mswidth=10*nint(100.0*width) + i1=(t1-0.02)/dt + if(i1.lt.1) i1=1 + iz=nint((width+0.02)/dt) + 1 + iz=min(iz,k+1-i1,2*48000) !Length of ping in samples -! Assemble a signal report: - nwidth=0 - if(width.ge.0.04) nwidth=1 !These might depend on NSPD - if(width.ge.0.12) nwidth=2 - if(width.gt.1.00) nwidth=3 - nstrength=6 - if(peak.ge.11.0) nstrength=7 - if(peak.ge.17.0) nstrength=8 - if(peak.ge.23.0) nstrength=9 -! nrpt=10*nwidth + nstrength - t2=tstart + dt*(istart-1) + call jtmsk(dat(i1),iz,cfile6,tpk,mswidth,int(peak),nrpt, & + nfreeze,DFTolerance,MouseDF,pick,mycall) - jjzz=min(jjz,2*48000) !Max data size 2 s -!### - jjzz=14400 - jj=jj-200 -!### - - if(nping.gt.nping0) then - print*,'a',jj,jjzz,jj*dt,jjzz*dt,t2,width - call jtmsk(dat(jj),jjzz,cfile6,t2,mswidth,int(peak),nrpt, & - nfreeze,DFTolerance,MouseDF,pick) - nping0=nping - endif - enddo + pingFound=.false. + inside=.false. return end subroutine rtping diff --git a/libm65/specjtms.f90 b/libm65/specjtmsk.f90 similarity index 100% rename from libm65/specjtms.f90 rename to libm65/specjtmsk.f90 diff --git a/mainwindow.cpp b/mainwindow.cpp index ce23348e6..8b017971d 100644 --- a/mainwindow.cpp +++ b/mainwindow.cpp @@ -1,4 +1,4 @@ -//--------------------------------------------------------------- MainWindow +//---------------------------------------------------------------- MainWindow #include "mainwindow.h" #include "ui_mainwindow.h" #include "devsetup.h"