// // Derived from fldigi rdid.cxx by John G8BPQ September 22 // // ---------------------------------------------------------------------------- // // rsid.cxx // // Copyright (C) 2008-2012 // Dave Freese, W1HKJ // Copyright (C) 2009-2012 // Stelios Bounanos, M0GLD // Copyright (C) 2012 // John Douyere, VK2ETA // // This file is part of fldigi. // // Fldigi is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. // // Fldigi is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS for (A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // // You should have received a copy of the GNU General Public License // along with fldigi. If not, see . // ---------------------------------------------------------------------------- #include #include "rsid.h" #include #include "fftw3.h" #include "UZ7HOStuff.h" #define M_PI 3.1415926f #define true 1 #define false 0 #define TRUE 1 #define FALSE 0 #define WORD unsigned int #define BYTE unsigned char #define byte unsigned char void SampleSink(int LR, short Sample); void Flush(); void extSetOffset(int rxOffset); void mon_rsid(int snd_ch, char * RSID); extern int RSID_SABM[4]; extern int RSID_UI[4]; extern int RSID_SetModem[4]; struct RSIDs { unsigned short rs; trx_mode mode; const char* name; }; extern int SampleNo; extern int Number; int len; int symlen; #include "rsid_defs.cxx" #define RSWINDOW 1 const int Squares[] = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9,10,11,12,13,14,15, 0, 2, 4, 6, 8,10,12,14, 9,11,13,15, 1, 3, 5, 7, 0, 3, 6, 5,12,15,10, 9, 1, 2, 7, 4,13,14,11, 8, 0, 4, 8,12, 9,13, 1, 5,11,15, 3, 7, 2, 6,10,14, 0, 5,10,15,13, 8, 7, 2, 3, 6, 9,12,14,11, 4, 1, 0, 6,12,10, 1, 7,13,11, 2, 4,14, 8, 3, 5,15, 9, 0, 7,14, 9, 5, 2,11,12,10,13, 4, 3,15, 8, 1, 6, 0, 8, 9, 1,11, 3, 2,10,15, 7, 6,14, 4,12,13, 5, 0, 9,11, 2,15, 6, 4,13, 7,14,12, 5, 8, 1, 3,10, 0,10,13, 7, 3, 9,14, 4, 6,12,11, 1, 5,15, 8, 2, 0,11,15, 4, 7,12, 8, 3,14, 5, 1,10, 9, 2, 6,13, 0,12, 1,13, 2,14, 3,15, 4, 8, 5, 9, 6,10, 7,11, 0,13, 3,14, 6,11, 5, 8,12, 1,15, 2,10, 7, 9, 4, 0,14, 5,11,10, 4,15, 1,13, 3, 8, 6, 7, 9, 2,12, 0,15, 7, 8,14, 1, 9, 6, 5,10, 2,13,11, 4,12, 3 }; const int indices[] = { 2, 4, 8, 9, 11, 15, 7, 14, 5, 10, 13, 3 }; int rmode, rmode2; void Encode(int code, unsigned char *rsid) { rsid[0] = code >> 8; rsid[1] = (code >> 4) & 0x0f; rsid[2] = code & 0x0f; for (int i = 3; i < RSID_NSYMBOLS; i++) rsid[i] = 0; for (int i = 0; i < 12; i++) { for (int j = RSID_NSYMBOLS - 1; j > 0; j--) rsid[j] = rsid[j - 1] ^ Squares[(rsid[j] << 4) + indices[i]]; rsid[0] = Squares[(rsid[0] << 4) + indices[i]]; } } //============================================================================= // transmit rsid code for current mode //============================================================================= float sampd; short samps; // Each symbol is transmitted using MFSK modulation.There are 16 possibilities of frequencies separated by // 11025 / 1024 = 10.766 Hz. Each symbol transmission being done on only one frequency for a duration equal // to 1024 / 11025 x 1000 = 92.88 ms.The entire RSID sequence of 15 symbols is transmitted in 15 x 1024 / 11025 = 1393 ms. // The analysis is based on a Fast Fourier transform of 2048 points at 11025 samples / sec, regularly done at each // semi - step of time(46.44 ms). // For each semi - step of time(46.44 ms) and for each semi - step of frequency(5.38 Hz),the program attempts to detect // an RSID extending for the last 1.393 seconds.So each second, about 8500 possible RSID // (depending on the selected bandwidth) are tested // But we are working at 12000 samples/sec so 92.88 mS = 1114.56 samples, so I think we run fft every 557.28 samples (46.44 ms) // How do we get 5.28 Hz buckets at 12000? Can we run fft of length 2,272.727 (say 2273) length? // Actually not sure we need to. We can interpolate freq and so long as we can get within a few Hz should be ok // Spec says tone spacing ia 10.766 (11025 / 1024) double toneinterval = RSID_SAMPLE_RATE / 1024; // fftw library interface static fftwf_complex * in = 0, *out; static fftwf_plan p; #define N 4096 short savedSamples[N + 1000]; // At least N + max input length (currently uses 512); int savedSampLen = 0; int firstBin = (300 * 2048) / 12000; // Search Lowest (300 Hz) int lastBin = (3000 * 2048) / 12000;; // Seach Highest (3000 Hz) double avmag; // Average magnitude over spectrum int fft_buckets[RSID_NTIMES][RSID_FFT_SIZE]; // This seems to have last 30 sets of values float aFFTAmpl[RSID_FFT_SIZE]; // Amplitude samples from fft // Table of precalculated Reed Solomon symbols unsigned char pCodes1[256][16]; unsigned char pCodes2[256][16]; int found1; int found2; double rsid_secondary_time_out; int bPrevTimeSliceValid; int iPrevDistance; int iPrevBin; int iPrevSymbol; int fft_buckets[RSID_NTIMES][RSID_FFT_SIZE]; int bPrevTimeSliceValid2; int iPrevDistance2; int iPrevBin2; int iPrevSymbol2; int hamming_resolution = 2; int needRSID[4] = { 0,0,0,0 }; // Request TX scheduler to send RSID int needSetOffset[4] = { 0,0,0,0 }; void CalculateBuckets(const float *pSpectrum, int iBegin, int iEnd); void Search(); void RSIDinitfft() { unsigned char * c; in = (fftwf_complex*)fftwf_malloc(sizeof(fftwf_complex) * N); out = (fftwf_complex*)fftwf_malloc(sizeof(fftwf_complex) * N); p = fftwf_plan_dft_1d(N, in, out, FFTW_FORWARD, FFTW_MEASURE); // Initialization of assigned mode/submode IDs. for (int i = 0; i < rsid_ids_size1; i++) { c = &pCodes1[i][0]; Encode(rsid_ids_1[i].rs, c); } } void reset() { iPrevDistance = iPrevDistance2 = 99; bPrevTimeSliceValid = bPrevTimeSliceValid2 = false; found1 = found2 = false; rsid_secondary_time_out = 0; } // Compute fft of last 557 * 2 values and return bucket number of peak static int dofft(short * inp, float * mags) { int i; float mag; float max = 0; int maxindex = 0; memset(in, 0, sizeof(fftwf_complex) * N); avmag = 0; for (i = 0; i < 512 * 2; i++) { // in[i][0] = inp[i] * 1.0f; // Hamming Window in[i][0] = inp[i]; in[i][0] = (float)((0.53836f - (0.46164f * cos(2 * M_PI * i / (float)(557.0 * 2.0 - 1)))) * inp[i]); in[i][1] = 0; } fftwf_execute(p); for (i = firstBin; i < lastBin; i++) // only need buckets up to 3000 { // Convert Real/Imag to amplitude mag = powf(out[i][0], 2); mag += powf(out[i][1], 2); mag = sqrtf(mag); mags[i] = mag; avmag += mag; if (mag > max) { max = mag; maxindex = i; } } avmag /= (lastBin - firstBin); return maxindex; } void RSIDProcessSamples(short * Samples, int nSamples) { // Add to saved samples, and if we have more than 557 run FFT and save remaining // We process last 557 + new 557 + zero padding // Trying with 512 @ 12000 // savedSampLen is number of shorts not bytes if (in == 0) // Not initialised return; memcpy(&savedSamples[savedSampLen], Samples, nSamples * sizeof(short)); savedSampLen += nSamples; if (savedSampLen >= 512 * 2) // Old + New { int peakBucket; peakBucket = dofft(savedSamples, aFFTAmpl); if (peakBucket > firstBin && peakBucket < lastBin) { // float freq; // freq = peakBucket * 12000.0f / 2048; // Debugprintf("%d %f %f %f", peakBucket, freq, aFFTAmpl[peakBucket], avmag); } savedSampLen -= 512; memmove(savedSamples, &savedSamples[512 * sizeof(short)], savedSampLen * sizeof(short)); Search(); } } int HammingDistance(int iBucket, unsigned char *p2) { int dist = 0; for (int i = 0, j = 0; i < RSID_NSYMBOLS; i++, j += 2) { if (fft_buckets[j][iBucket] != p2[i]) { ++dist; if (dist > hamming_resolution) break; } } return dist; } int iDistanceMin = 1000; // infinity int search_amp(int *bin_out, int *symbol_out) { int i, j; int iDistance = 1000; int iBin = -1; int iSymbol = -1; int tblsize; iDistanceMin = 1000; // infinity tblsize = rsid_ids_size1; unsigned char *pc = 0; for (i = 0; i < tblsize; i++) { pc = &pCodes1[i][0]; for (j = firstBin; j < lastBin - RSID_NTIMES; j++) { iDistance = HammingDistance(j, pc); if (iDistance < iDistanceMin) { iDistanceMin = iDistance; iSymbol = i; iBin = j; if (iDistanceMin == 0) break; } } } if (iDistanceMin <= hamming_resolution) { *symbol_out = iSymbol; *bin_out = iBin; return true; } return false; } void apply(int iBin, int iSymbol) { // Does something with the found id const struct RSIDs *prsid = &rsid_ids_1[iSymbol]; int Freq = (int)(iBin + 15) * 12000.0f / 2048; char Msg[128]; int Offset = Freq - rx_freq[0]; int i; int nearest = -1, minOffset = 9999, absOffset; // If there is more than one decoder with update from rsid set update the nearest for (i = 0; i < 4; i++) { if (RSID_SetModem[i]) { absOffset = abs(Freq - rx_freq[i]); if (absOffset < minOffset) { // Nearer nearest = i; minOffset = absOffset; } } } // We don't run this unless at least one modem has RSID_SetModem set. Offset = Freq - rx_freq[nearest]; sprintf(Msg, "RSID %s %d %d Nearest Modem %c Offset %d", prsid->name, iDistanceMin, Freq, nearest + 'A', Offset); mon_rsid(0, Msg); // Set Modem RX Offset to match received freq chanOffset[nearest] = Offset; needSetOffset[nearest] = 1; // Update GUI } void Search() { int symbol = -1; int bin = -1; // We have just calculated a new set of fft amplitude bins in aFFTAmpl // we find peak bin, and store in fft_buckets array. This has 30 sets of 1024 buckets (though we only use first 512, as we limit search to 3 KHz) // We move previous 29 entries to start of array and add new values on end so samples corresponding to first bit of rsid msg are at start memmove(fft_buckets, &(fft_buckets[1][0]), (RSID_NTIMES - 1) * RSID_FFT_SIZE * sizeof(int)); memset(&(fft_buckets[RSID_NTIMES - 1][0]), 0, RSID_FFT_SIZE * sizeof(int)); // We process even then odd bins, using alternate bins to get resolution to 1/2 bin CalculateBuckets(aFFTAmpl, firstBin, lastBin - RSID_NTIMES); CalculateBuckets(aFFTAmpl, firstBin + 1, lastBin - RSID_NTIMES); // Now have 30 sets of 512 bit valies (0-15). We look for a set of 15 that match an ID found1 = search_amp(&bin, &symbol); if (found1) { apply(bin, symbol); reset(); } } void CalculateBuckets(const float *pSpectrum, int iBegin, int iEnd) { float Amp = 0.0f, AmpMax = 0.0f; int iBucketMax = iBegin - 2; int j; // This searches odd and even pairs of amps, hence the += 2 for (int i = iBegin; i < iEnd; i += 2) { if (iBucketMax == i - 2) { // if max was first in grooup of 15 redo full search AmpMax = pSpectrum[i]; iBucketMax = i; for (j = i + 2; j < i + RSID_NTIMES + 2; j += 2) { Amp = pSpectrum[j]; if (Amp > AmpMax) { AmpMax = Amp; iBucketMax = j; } } } else { // Max wasn't first, so must be in next 14, so we can just check if new last is > max j = i + RSID_NTIMES; Amp = pSpectrum[j]; if (Amp > AmpMax) { AmpMax = Amp; iBucketMax = j; } } fft_buckets[RSID_NTIMES - 1][i] = (iBucketMax - i) >> 1; } } void sendRSID(int Chan, int dropTX) { unsigned char rsid[RSID_NSYMBOLS]; float sr; int iTone; float freq, phaseincr; float fr; float phase; int Mode = speed[Chan]; int Freq = rx_freq[Chan]; rmode2 = 687; rmode = 35 + Mode; // Packet 300 or 1200 Encode(rmode, rsid); sr = 12000; symlen = (size_t)floor(RSID_SYMLEN * sr); SampleNo = 0; SoundIsPlaying = TRUE; RadioPTT(Chan, 1); Number = 0; // transmit 5 symbol periods of silence at beginning of rsid for (int i = 0; i < 5 * symlen; i++) SampleSink(0, 0); // transmit sequence of 15 symbols (tones) fr = 1.0f * Freq - (RSID_SAMPLE_RATE * 7 / 1024); phase = 0.0f; for (int i = 0; i < 15; i++) { iTone = rsid[i]; freq = fr + iTone * RSID_SAMPLE_RATE / 1024; phaseincr = 2.0f * M_PI * freq / sr; for (int j = 0; j < symlen; j++) { phase += phaseincr; if (phase > 2.0 * M_PI) phase -= 2.0 * M_PI; sampd = sinf(phase); sampd = sampd * 32767.0f; samps = (short)sampd; SampleSink(0, samps); } } // 5 symbol periods of silence at end of transmission // and between RsID and the data signal int nperiods = 5; for (int i = 0; i < nperiods * symlen; i++) SampleSink(modemtoSoundLR[Chan], 0); tx_status[Chan] = TX_SILENCE; // Stop TX Flush(); if (dropTX) RadioPTT(Chan, 0); } // Experimental Busy Detect, based on ARDOP code static int LastBusyCheck = 0; static int BusyCount = 0; static int BusyStatus = 0; static int LastBusyStatus = 0; static int BusyDet = 5; // DCD Threshold int LastStart = 0; int LastStop = 0; extern int LastBusyOn; extern int LastBusyOff; static int LastBusy = FALSE; extern float dblAvgStoNSlowNarrow; extern float dblAvgStoNFastNarrow; extern float dblAvgStoNSlowWide; extern float dblAvgStoNFastWide; int BusyOnCnt = 0; // used to filter Busy ON detections int BusyOffCnt = 0; // used to filter Busy OFF detections unsigned int LastBusyTrip = 0; unsigned int PriorLastBusyTrip = 0; unsigned int LastBusyClear = 0; unsigned int LastTrip; void SortSignals(float * dblMag, int intStartBin, int intStopBin, int intNumBins, float * dblAVGSignalPerBin, float * dblAVGBaselinePerBin); void SortSignals2(float * dblMag, int intStartBin, int intStopBin, int intNumBins, float * dblAVGSignalPerBin, float * dblAVGBaselinePerBin); static BOOL BusyDetect3(float * dblMag, int intStart, int intStop) // this only called while searching for leader ...once leader detected, no longer called. { // each bin is about 12000/2048 or 5.86 Hz // First sort signals and look at highes signals:baseline ratio.. float dblAVGSignalPerBinNarrow, dblAVGSignalPerBinWide, dblAVGBaselineNarrow, dblAVGBaselineWide; float dblSlowAlpha = 0.2f; float dblAvgStoNNarrow = 0, dblAvgStoNWide = 0; int intNarrow = 16; // 16 x 5.86 about 94 z int intWide = ((intStop - intStart) * 2) / 3; //* 0.66); int blnBusy = FALSE; int BusyDet4th = BusyDet * BusyDet * BusyDet * BusyDet; int BusyDet = 5; unsigned int HoldMs = 5000; // First sort signals and look at highest signals:baseline ratio.. // First narrow band (~94Hz) SortSignals2(dblMag, intStart, intStop, intNarrow, &dblAVGSignalPerBinNarrow, &dblAVGBaselineNarrow); if (LastStart == intStart && LastStop == intStop) dblAvgStoNNarrow = (1 - dblSlowAlpha) * dblAvgStoNNarrow + dblSlowAlpha * dblAVGSignalPerBinNarrow / dblAVGBaselineNarrow; else { // This initializes the Narrow average after a bandwidth change dblAvgStoNNarrow = dblAVGSignalPerBinNarrow / dblAVGBaselineNarrow; LastStart = intStart; LastStop = intStop; } // Wide band (66% of current bandwidth) SortSignals2(dblMag, intStart, intStop, intWide, &dblAVGSignalPerBinWide, &dblAVGBaselineWide); if (LastStart == intStart && LastStop == intStop) dblAvgStoNWide = (1 - dblSlowAlpha) * dblAvgStoNWide + dblSlowAlpha * dblAVGSignalPerBinWide / dblAVGBaselineWide; else { // This initializes the Wide average after a bandwidth change dblAvgStoNWide = dblAVGSignalPerBinWide / dblAVGBaselineWide; LastStart = intStart; LastStop = intStop; } // Preliminary calibration...future a function of bandwidth and BusyDet. blnBusy = (dblAvgStoNNarrow > (3 + 0.008 * BusyDet4th)) || (dblAvgStoNWide > (5 + 0.016 * BusyDet4th)); if (BusyDet == 0) blnBusy = FALSE; // 0 Disables check ?? Is this the best place to do this? // WriteDebugLog(LOGDEBUG, "Busy %d Wide %f Narrow %f", blnBusy, dblAvgStoNWide, dblAvgStoNNarrow); if (blnBusy) { // This requires multiple adjacent busy conditions to skip over one nuisance Busy trips. // Busy must be present at least 3 consecutive times ( ~250 ms) to be reported BusyOnCnt += 1; BusyOffCnt = 0; if (BusyOnCnt > 3) LastTrip = Now; } else { BusyOffCnt += 1; BusyOnCnt = 0; } if (LastBusy == 0 && BusyOnCnt >= 3) { PriorLastBusyTrip = LastBusyTrip; // save old dttLastBusyTrip for use in BUSYBLOCKING function LastBusyTrip = Now; LastBusy = TRUE; } else { if (LastBusy && (Now - LastTrip) > HoldMs && BusyOffCnt >= 3) { LastBusyClear = Now; LastBusy = FALSE; } } return LastBusy; } static void UpdateBusyDetector() { // Use applitude bins in aFFTAmpl float dblMagAvg = 0; int intTuneLineLow, intTuneLineHi; int i; int BusyFlag; if (Now - LastBusyCheck < 100) return; LastBusyCheck = Now; for (i = 52; i < 512; i++) { // starting at ~300 Hz to ~3000 Hz Which puts the center of the signal in the center of the window (~1500Hz) dblMagAvg += aFFTAmpl[i]; } intTuneLineLow = 52; intTuneLineHi = 512; BusyFlag = BusyDetect3(aFFTAmpl, intTuneLineLow, intTuneLineHi); if (BusyFlag == 0) { if (BusyCount == 0) BusyStatus = 0; else BusyCount--; } else { BusyStatus = 1; BusyCount = 10; // Try delaying busy off a bit } if (BusyStatus && !LastBusyStatus) { Debugprintf("BUSY TRUE"); } // stcStatus.Text = "True" // queTNCStatus.Enqueue(stcStatus) // 'Debug.WriteLine("BUSY TRUE @ " & Format(DateTime.UtcNow, "HH:mm:ss")) else if (LastBusyStatus && !BusyStatus) { Debugprintf("BUSY FALSE"); } LastBusyStatus = BusyStatus; }