| 
									
										
										
										
											2021-07-09 12:06:23 +01:00
										 |  |  | ///////////////////////////////////////////////////////////////////////////////////
 | 
					
						
							|  |  |  | // Copyright (C) 2019 Edouard Griffiths, F4EXB                                   //
 | 
					
						
							|  |  |  | // Copyright (C) 2021 Jon Beniston, M7RCE                                        //
 | 
					
						
							|  |  |  | //                                                                               //
 | 
					
						
							|  |  |  | // This program 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 as version 3 of the License, or                  //
 | 
					
						
							|  |  |  | // (at your option) any later version.                                           //
 | 
					
						
							|  |  |  | //                                                                               //
 | 
					
						
							|  |  |  | // This program 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 V3 for more details.                               //
 | 
					
						
							|  |  |  | //                                                                               //
 | 
					
						
							|  |  |  | // You should have received a copy of the GNU General Public License             //
 | 
					
						
							|  |  |  | // along with this program. If not, see <http://www.gnu.org/licenses/>.          //
 | 
					
						
							|  |  |  | ///////////////////////////////////////////////////////////////////////////////////
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | #include <QDebug>
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | #include <complex.h>
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | #include "dsp/dspengine.h"
 | 
					
						
							|  |  |  | #include "dsp/datafifo.h"
 | 
					
						
							|  |  |  | #include "dsp/scopevis.h"
 | 
					
						
							|  |  |  | #include "util/db.h"
 | 
					
						
							|  |  |  | #include "util/popcount.h"
 | 
					
						
							|  |  |  | #include "maincore.h"
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | #include "pagerdemod.h"
 | 
					
						
							|  |  |  | #include "pagerdemodsink.h"
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | PagerDemodSink::PagerDemodSink(PagerDemod *pagerDemod) : | 
					
						
							|  |  |  |         m_scopeSink(nullptr), | 
					
						
							|  |  |  |         m_pagerDemod(pagerDemod), | 
					
						
							|  |  |  |         m_channelSampleRate(PagerDemodSettings::m_channelSampleRate), | 
					
						
							|  |  |  |         m_channelFrequencyOffset(0), | 
					
						
							|  |  |  |         m_magsqSum(0.0f), | 
					
						
							|  |  |  |         m_magsqPeak(0.0f), | 
					
						
							|  |  |  |         m_magsqCount(0), | 
					
						
							|  |  |  |         m_messageQueueToChannel(nullptr), | 
					
						
							|  |  |  |         m_dcOffset(0.0f), | 
					
						
							|  |  |  |         m_dataPrev(0), | 
					
						
							|  |  |  |         m_inverted(false), | 
					
						
							|  |  |  |         m_bit(0), | 
					
						
							|  |  |  |         m_gotSOP(false), | 
					
						
							|  |  |  |         m_bits(0), | 
					
						
							|  |  |  |         m_bitCount(0), | 
					
						
							|  |  |  |         m_syncCount(75), | 
					
						
							|  |  |  |         m_batchNumber(0), | 
					
						
							|  |  |  |         m_wordCount(0), | 
					
						
							| 
									
										
										
										
											2021-07-13 18:03:12 +02:00
										 |  |  |         m_addressValid(0), | 
					
						
							|  |  |  |         m_sampleBufferIndex(0) | 
					
						
							| 
									
										
										
										
											2021-07-09 12:06:23 +01:00
										 |  |  | { | 
					
						
							|  |  |  |     m_magsq = 0.0; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     m_demodBuffer.resize(1<<12); | 
					
						
							|  |  |  |     m_demodBufferFill = 0; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     applySettings(m_settings, true); | 
					
						
							|  |  |  |     applyChannelSettings(m_channelSampleRate, m_channelFrequencyOffset, true); | 
					
						
							| 
									
										
										
										
											2021-07-13 18:03:12 +02:00
										 |  |  |     m_sampleBuffer.resize(m_sampleBufferSize); | 
					
						
							| 
									
										
										
										
											2021-07-09 12:06:23 +01:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | PagerDemodSink::~PagerDemodSink() | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | void PagerDemodSink::sampleToScope(Complex sample) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  |     if (m_scopeSink) | 
					
						
							|  |  |  |     { | 
					
						
							| 
									
										
										
										
											2021-07-13 18:03:12 +02:00
										 |  |  |         m_sampleBuffer[m_sampleBufferIndex++] = sample; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         if (m_sampleBufferIndex == m_sampleBufferSize) | 
					
						
							|  |  |  |         { | 
					
						
							|  |  |  |             std::vector<ComplexVector::const_iterator> vbegin; | 
					
						
							|  |  |  |             vbegin.push_back(m_sampleBuffer.begin()); | 
					
						
							|  |  |  |             m_scopeSink->feed(vbegin, m_sampleBufferSize); | 
					
						
							|  |  |  |             m_sampleBufferIndex = 0; | 
					
						
							|  |  |  |         } | 
					
						
							| 
									
										
										
										
											2021-07-09 12:06:23 +01:00
										 |  |  |     } | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | void PagerDemodSink::feed(const SampleVector::const_iterator& begin, const SampleVector::const_iterator& end) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  |     Complex ci; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     for (SampleVector::const_iterator it = begin; it != end; ++it) | 
					
						
							|  |  |  |     { | 
					
						
							|  |  |  |         Complex c(it->real(), it->imag()); | 
					
						
							|  |  |  |         c *= m_nco.nextIQ(); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         if (m_interpolatorDistance < 1.0f) // interpolate
 | 
					
						
							|  |  |  |         { | 
					
						
							|  |  |  |             while (!m_interpolator.interpolate(&m_interpolatorDistanceRemain, c, &ci)) | 
					
						
							|  |  |  |             { | 
					
						
							|  |  |  |                 processOneSample(ci); | 
					
						
							|  |  |  |                 m_interpolatorDistanceRemain += m_interpolatorDistance; | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |         else // decimate
 | 
					
						
							|  |  |  |         { | 
					
						
							|  |  |  |             if (m_interpolator.decimate(&m_interpolatorDistanceRemain, c, &ci)) | 
					
						
							|  |  |  |             { | 
					
						
							|  |  |  |                 processOneSample(ci); | 
					
						
							|  |  |  |                 m_interpolatorDistanceRemain += m_interpolatorDistance; | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // XOR bits together for parity check
 | 
					
						
							|  |  |  | int PagerDemodSink::xorBits(quint32 word, int firstBit, int lastBit) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  |     int x = 0; | 
					
						
							|  |  |  |     for (int i = firstBit; i <= lastBit; i++) | 
					
						
							|  |  |  |     { | 
					
						
							|  |  |  |         x ^= (word >> i) & 1; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |     return x; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // Check for even parity
 | 
					
						
							|  |  |  | bool PagerDemodSink::evenParity(quint32 word, int firstBit, int lastBit, int parityBit) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  |     return xorBits(word, firstBit, lastBit) == parityBit; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // Reverse order of bits
 | 
					
						
							|  |  |  | quint32 PagerDemodSink::reverse(quint32 x) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  |     x = (((x & 0xaaaaaaaa) >> 1) | ((x & 0x55555555) << 1)); | 
					
						
							|  |  |  |     x = (((x & 0xcccccccc) >> 2) | ((x & 0x33333333) << 2)); | 
					
						
							|  |  |  |     x = (((x & 0xf0f0f0f0) >> 4) | ((x & 0x0f0f0f0f) << 4)); | 
					
						
							|  |  |  |     x = (((x & 0xff00ff00) >> 8) | ((x & 0x00ff00ff) << 8)); | 
					
						
							|  |  |  |     return((x >> 16) | (x << 16)); | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // Calculate BCH parity and even parity bits
 | 
					
						
							|  |  |  | quint32 PagerDemodSink::bchEncode(const quint32 cw) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  | 	quint32 bit = 0; | 
					
						
							|  |  |  | 	quint32 localCW = cw & 0xFFFFF800;	// Mask off BCH parity and even parity bits
 | 
					
						
							|  |  |  | 	quint32 cwE = localCW; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// Calculate BCH bits
 | 
					
						
							|  |  |  | 	for (bit = 1; bit <= 21; bit++) | 
					
						
							|  |  |  |     { | 
					
						
							|  |  |  | 		if (cwE & 0x80000000) { | 
					
						
							|  |  |  | 			cwE ^= 0xED200000; | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 		cwE <<= 1; | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	localCW |= (cwE >> 21); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	return localCW; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // Use BCH decoding to try to fix any bit errors
 | 
					
						
							|  |  |  | // Returns true if able to be decode/repair successfull
 | 
					
						
							|  |  |  | // See: https://www.eevblog.com/forum/microcontrollers/practical-guides-to-bch-fec/
 | 
					
						
							|  |  |  | bool PagerDemodSink::bchDecode(const quint32 cw, quint32& correctedCW) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  | 	// Calculate syndrome
 | 
					
						
							|  |  |  | 	// We do this by recalculating the BCH parity bits and XORing them against the received ones
 | 
					
						
							|  |  |  | 	quint32 syndrome = ((bchEncode(cw) ^ cw) >> 1) & 0x3FF; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	if (syndrome == 0) | 
					
						
							|  |  |  |     { | 
					
						
							|  |  |  | 		// Syndrome of zero indicates no repair required
 | 
					
						
							|  |  |  |         correctedCW = cw; | 
					
						
							|  |  |  | 		return true; | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// Meggitt decoder
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	quint32 result = 0; | 
					
						
							|  |  |  | 	quint32 damagedCW = cw; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// Calculate BCH bits
 | 
					
						
							|  |  |  | 	for (quint32 xbit = 0; xbit < 31; xbit++) | 
					
						
							|  |  |  |     { | 
					
						
							|  |  |  | 		// Produce the next corrected bit in the high bit of the result
 | 
					
						
							|  |  |  | 		result <<= 1; | 
					
						
							|  |  |  | 		if ((syndrome == 0x3B4) ||		// 0x3B4: Syndrome when a single error is detected in the MSB
 | 
					
						
							|  |  |  | 			(syndrome == 0x26E)	||		// 0x26E: Two adjacent errors
 | 
					
						
							|  |  |  | 			(syndrome == 0x359) ||		// 0x359: Two errors, one OK bit between
 | 
					
						
							|  |  |  | 			(syndrome == 0x076) ||		// 0x076: Two errors, two OK bits between
 | 
					
						
							|  |  |  | 			(syndrome == 0x255) ||		// 0x255: Two errors, three OK bits between
 | 
					
						
							|  |  |  | 			(syndrome == 0x0F0) ||		// 0x0F0: Two errors, four OK bits between
 | 
					
						
							|  |  |  | 			(syndrome == 0x216) || | 
					
						
							|  |  |  | 			(syndrome == 0x365) || | 
					
						
							|  |  |  | 			(syndrome == 0x068) || | 
					
						
							|  |  |  | 			(syndrome == 0x25A) || | 
					
						
							|  |  |  | 			(syndrome == 0x343) || | 
					
						
							|  |  |  | 			(syndrome == 0x07B) || | 
					
						
							|  |  |  | 			(syndrome == 0x1E7) || | 
					
						
							|  |  |  | 			(syndrome == 0x129) || | 
					
						
							|  |  |  | 			(syndrome == 0x14E) || | 
					
						
							|  |  |  | 			(syndrome == 0x2C9) || | 
					
						
							|  |  |  | 			(syndrome == 0x0BE) || | 
					
						
							|  |  |  | 			(syndrome == 0x231) || | 
					
						
							|  |  |  | 			(syndrome == 0x0C2) || | 
					
						
							|  |  |  | 			(syndrome == 0x20F) || | 
					
						
							|  |  |  | 			(syndrome == 0x0DD) || | 
					
						
							|  |  |  | 			(syndrome == 0x1B4) || | 
					
						
							|  |  |  | 			(syndrome == 0x2B4) || | 
					
						
							|  |  |  | 			(syndrome == 0x334) || | 
					
						
							|  |  |  | 			(syndrome == 0x3F4) || | 
					
						
							|  |  |  | 			(syndrome == 0x394) || | 
					
						
							|  |  |  | 			(syndrome == 0x3A4) || | 
					
						
							|  |  |  | 			(syndrome == 0x3BC) || | 
					
						
							|  |  |  | 			(syndrome == 0x3B0) || | 
					
						
							|  |  |  | 			(syndrome == 0x3B6) || | 
					
						
							|  |  |  | 			(syndrome == 0x3B5) | 
					
						
							|  |  |  | 		   ) | 
					
						
							|  |  |  |         { | 
					
						
							|  |  |  | 			// Syndrome matches an error in the MSB
 | 
					
						
							|  |  |  | 			// Correct that error and adjust the syndrome to account for it
 | 
					
						
							|  |  |  | 			syndrome ^= 0x3B4; | 
					
						
							|  |  |  | 			result |= (~damagedCW & 0x80000000) >> 30; | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  |         else | 
					
						
							|  |  |  |         { | 
					
						
							|  |  |  | 			// No error
 | 
					
						
							|  |  |  | 			result |= (damagedCW & 0x80000000) >> 30; | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 		damagedCW <<= 1; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		// Handle syndrome shift register feedback
 | 
					
						
							|  |  |  | 		if (syndrome & 0x200) | 
					
						
							|  |  |  |         { | 
					
						
							|  |  |  | 			syndrome <<= 1; | 
					
						
							|  |  |  | 			syndrome ^= 0x769;  // 0x769 = POCSAG generator polynomial -- x^10 + x^9 + x^8 + x^6 + x^5 + x^3 + 1
 | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  |         else | 
					
						
							|  |  |  |         { | 
					
						
							|  |  |  | 			syndrome <<= 1; | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 		// Mask off bits which fall off the end of the syndrome shift register
 | 
					
						
							|  |  |  | 		syndrome &= 0x3FF; | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// Check if error correction was successful
 | 
					
						
							|  |  |  | 	if (syndrome != 0) | 
					
						
							|  |  |  |     { | 
					
						
							|  |  |  | 		// Syndrome nonzero at end indicates uncorrectable errors
 | 
					
						
							|  |  |  |         correctedCW = cw; | 
					
						
							|  |  |  | 		return false; | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	correctedCW = result; | 
					
						
							|  |  |  | 	return true; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // Decode a batch of codewords to addresses and messages
 | 
					
						
							|  |  |  | // Messages may be spreadout over multiple batches
 | 
					
						
							|  |  |  | // https://www.itu.int/dms_pubrec/itu-r/rec/m/R-REC-M.584-1-198607-S!!PDF-E.pdf
 | 
					
						
							|  |  |  | // https://www.itu.int/dms_pubrec/itu-r/rec/m/R-REC-M.584-2-199711-I!!PDF-E.pdf
 | 
					
						
							|  |  |  | void PagerDemodSink::decodeBatch() | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  |     int i = 1; | 
					
						
							|  |  |  |     for (int frame = 0; frame < PAGERDEMOD_FRAMES_PER_BATCH; frame++) | 
					
						
							|  |  |  |     { | 
					
						
							|  |  |  |         for (int word = 0; word < PAGERDEMOD_CODEWORDS_PER_FRAME; word++) | 
					
						
							|  |  |  |         { | 
					
						
							|  |  |  |             bool addressCodeWord = ((m_codeWords[i] >> 31) & 1) == 0; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             // Stop decoding current message if we receive a new address
 | 
					
						
							|  |  |  |             if (addressCodeWord && m_addressValid) | 
					
						
							|  |  |  |             { | 
					
						
							|  |  |  |                 m_numericMessage = m_numericMessage.trimmed(); // Remove trailing spaces
 | 
					
						
							|  |  |  |                 if (getMessageQueueToChannel()) | 
					
						
							|  |  |  |                 { | 
					
						
							| 
									
										
										
										
											2021-07-19 17:57:13 +01:00
										 |  |  |                     // Convert from 7-bit to UTF-8 using user specified encoding
 | 
					
						
							|  |  |  |                     for (int i = 0; i < m_alphaMessage; i++) | 
					
						
							|  |  |  |                     { | 
					
						
							|  |  |  |                         QChar c = m_alphaMessage[i]; | 
					
						
							|  |  |  |                         int idx = m_settings.m_sevenbit.indexOf(c.toLatin1()); | 
					
						
							|  |  |  |                         if (idx >= 0) { | 
					
						
							|  |  |  |                             c = m_settings.m_unicode[idx]; | 
					
						
							|  |  |  |                         } | 
					
						
							|  |  |  |                         m_alphaMessage[i] = c; | 
					
						
							|  |  |  |                     } | 
					
						
							|  |  |  |                     // Reverse reading order, if required
 | 
					
						
							| 
									
										
										
										
											2021-07-28 15:00:43 +01:00
										 |  |  |                     if (m_settings.m_reverse) { | 
					
						
							| 
									
										
										
										
											2021-07-19 17:57:13 +01:00
										 |  |  |                         std::reverse(m_alphaMessage.begin(), m_alphaMessage.end()); | 
					
						
							|  |  |  |                     } | 
					
						
							|  |  |  |                     // Send to channel and GUI
 | 
					
						
							| 
									
										
										
										
											2021-07-09 12:06:23 +01:00
										 |  |  |                     PagerDemod::MsgPagerMessage *msg = PagerDemod::MsgPagerMessage::create(m_address, m_functionBits, m_alphaMessage, m_numericMessage, m_parityErrors, m_bchErrors); | 
					
						
							|  |  |  |                     getMessageQueueToChannel()->push(msg); | 
					
						
							|  |  |  |                 } | 
					
						
							|  |  |  |                 m_addressValid = false; | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             // Check parity bit
 | 
					
						
							|  |  |  |             bool parityError = !evenParity(m_codeWords[i], 1, 31, m_codeWords[i] & 0x1); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             if (m_codeWords[i] == PAGERDEMOD_POCSAG_IDLECODE) | 
					
						
							|  |  |  |             { | 
					
						
							|  |  |  |                 // Idle
 | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  |             else if (addressCodeWord) | 
					
						
							|  |  |  |             { | 
					
						
							|  |  |  |                 // Address
 | 
					
						
							|  |  |  |                 m_functionBits = (m_codeWords[i] >> 11) & 0x3; | 
					
						
							|  |  |  |                 int addressBits = (m_codeWords[i] >> 13) & 0x3ffff; | 
					
						
							|  |  |  |                 m_address = (addressBits << 3) | frame; | 
					
						
							|  |  |  |                 m_numericMessage = ""; | 
					
						
							|  |  |  |                 m_alphaMessage = ""; | 
					
						
							|  |  |  |                 m_alphaBitBufferBits = 0; | 
					
						
							|  |  |  |                 m_alphaBitBuffer = 0; | 
					
						
							|  |  |  |                 m_parityErrors = parityError ? 1 : 0; | 
					
						
							|  |  |  |                 m_bchErrors = m_codeWordsBCHError[i] ? 1 : 0; | 
					
						
							|  |  |  |                 m_addressValid = true; | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  |             else | 
					
						
							|  |  |  |             { | 
					
						
							|  |  |  |                 // Message - decode as both numeric and ASCII - not all operators use functionBits to indidcate encoding
 | 
					
						
							|  |  |  |                 int messageBits = (m_codeWords[i] >> 11) & 0xfffff; | 
					
						
							|  |  |  |                 if (parityError) { | 
					
						
							|  |  |  |                     m_parityErrors++; | 
					
						
							|  |  |  |                 } | 
					
						
							|  |  |  |                 if (m_codeWordsBCHError[i]) { | 
					
						
							|  |  |  |                     m_bchErrors++; | 
					
						
							|  |  |  |                 } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |                 // Numeric format
 | 
					
						
							|  |  |  |                 for (int j = 16; j >= 0; j -= 4) | 
					
						
							|  |  |  |                 { | 
					
						
							|  |  |  |                     quint32 numericBits = (messageBits >> j) & 0xf; | 
					
						
							|  |  |  |                     numericBits = reverse(numericBits) >> (32-4); | 
					
						
							|  |  |  |                     // Spec has 0xa as 'spare', but other decoders treat is as .
 | 
					
						
							|  |  |  |                     const char numericChars[] = { | 
					
						
							|  |  |  |                         '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '.', 'U', ' ', '-', ')', '(' | 
					
						
							|  |  |  |                     }; | 
					
						
							|  |  |  |                     char numericChar = numericChars[numericBits]; | 
					
						
							|  |  |  |                     m_numericMessage.append(numericChar); | 
					
						
							|  |  |  |                 } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |                 // 7-bit ASCII alpnanumeric format
 | 
					
						
							|  |  |  |                 m_alphaBitBuffer = (m_alphaBitBuffer << 20) | messageBits; | 
					
						
							|  |  |  |                 m_alphaBitBufferBits += 20; | 
					
						
							|  |  |  |                 while (m_alphaBitBufferBits >= 7) | 
					
						
							|  |  |  |                 { | 
					
						
							|  |  |  |                     // Extract next 7-bit character from bit buffer
 | 
					
						
							|  |  |  |                     char c = (m_alphaBitBuffer >> (m_alphaBitBufferBits-7)) & 0x7f; | 
					
						
							|  |  |  |                     // Reverse bit ordering
 | 
					
						
							|  |  |  |                     c = reverse(c) >> (32-7); | 
					
						
							|  |  |  |                     // Add to received message string (excluding, null, end of text, end ot transmission)
 | 
					
						
							|  |  |  |                     if (c != 0 && c != 0x3 && c != 0x4) { | 
					
						
							|  |  |  |                         m_alphaMessage.append(c); | 
					
						
							|  |  |  |                     } | 
					
						
							|  |  |  |                     // Remove from bit buffer
 | 
					
						
							|  |  |  |                     m_alphaBitBufferBits -= 7; | 
					
						
							|  |  |  |                     if (m_alphaBitBufferBits == 0) { | 
					
						
							|  |  |  |                         m_alphaBitBuffer = 0; | 
					
						
							|  |  |  |                     } else { | 
					
						
							|  |  |  |                         m_alphaBitBuffer &= (1 << m_alphaBitBufferBits) - 1; | 
					
						
							|  |  |  |                     } | 
					
						
							|  |  |  |                 } | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             // Move to next codeword
 | 
					
						
							|  |  |  |             i++; | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | void PagerDemodSink::processOneSample(Complex &ci) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  |     Complex ca; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     // FM demodulation
 | 
					
						
							|  |  |  |     double magsqRaw; | 
					
						
							|  |  |  |     Real deviation; | 
					
						
							|  |  |  |     Real fmDemod = m_phaseDiscri.phaseDiscriminatorDelta(ci, magsqRaw, deviation); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     // Calculate average and peak levels for level meter
 | 
					
						
							|  |  |  |     Real magsq = magsqRaw / (SDR_RX_SCALED*SDR_RX_SCALED); | 
					
						
							|  |  |  |     m_movingAverage(magsq); | 
					
						
							|  |  |  |     m_magsq = m_movingAverage.asDouble(); | 
					
						
							|  |  |  |     m_magsqSum += magsq; | 
					
						
							| 
									
										
										
										
											2021-11-09 21:57:56 +01:00
										 |  |  | 
 | 
					
						
							|  |  |  |     if (magsq > m_magsqPeak) { | 
					
						
							| 
									
										
										
										
											2021-07-09 12:06:23 +01:00
										 |  |  |         m_magsqPeak = magsq; | 
					
						
							|  |  |  |     } | 
					
						
							| 
									
										
										
										
											2021-11-09 21:57:56 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-07-09 12:06:23 +01:00
										 |  |  |     m_magsqCount++; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     // Low pass filter
 | 
					
						
							|  |  |  |     Real filt = m_lowpassBaud.filter(fmDemod); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     // An input frequency offset corresponds to a DC offset after FM demodulation
 | 
					
						
							|  |  |  |     // To calculate what it is, we average part of the preamble, which should be zero
 | 
					
						
							|  |  |  |     if (!m_gotSOP) | 
					
						
							|  |  |  |     { | 
					
						
							|  |  |  |         m_preambleMovingAverage(filt); | 
					
						
							|  |  |  |         m_dcOffset = m_preambleMovingAverage.asDouble(); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     bool sample = false; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     // Slice data
 | 
					
						
							|  |  |  |     int data = (filt - m_dcOffset) >= 0.0; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     // Look for edge - A PLL here would be less susceptible to noise
 | 
					
						
							|  |  |  |     if (data != m_dataPrev) | 
					
						
							|  |  |  |     { | 
					
						
							|  |  |  |         // Center in middle of bit
 | 
					
						
							|  |  |  |         m_syncCount = m_samplesPerSymbol/2; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |     else | 
					
						
							|  |  |  |     { | 
					
						
							|  |  |  |         // Wait until centre of bit to sample it
 | 
					
						
							|  |  |  |         m_syncCount--; | 
					
						
							| 
									
										
										
										
											2021-11-09 21:57:56 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-07-09 12:06:23 +01:00
										 |  |  |         if (m_syncCount <= 0) | 
					
						
							|  |  |  |         { | 
					
						
							|  |  |  |             // According to a variety of places on the web, high frequency is a 0, low is 1.
 | 
					
						
							|  |  |  |             // While this seems to be correct in the UK, some IQ files I've obtained seem
 | 
					
						
							|  |  |  |             // to be reversed, so we support both.
 | 
					
						
							|  |  |  |             if (m_inverted) { | 
					
						
							|  |  |  |                 m_bit = data; | 
					
						
							|  |  |  |             } else { | 
					
						
							|  |  |  |                 m_bit = !data; | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-11-09 21:57:56 +01:00
										 |  |  |             sample = true; | 
					
						
							| 
									
										
										
										
											2021-07-09 12:06:23 +01:00
										 |  |  |             // Store in shift reg. MSB transmitted first
 | 
					
						
							|  |  |  |             m_bits = (m_bits << 1) | m_bit; | 
					
						
							|  |  |  |             m_bitCount++; | 
					
						
							| 
									
										
										
										
											2021-11-09 21:57:56 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-07-09 12:06:23 +01:00
										 |  |  |             if (m_bitCount > 32) { | 
					
						
							|  |  |  |                 m_bitCount = 32; | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             if ((m_bitCount == 32) && !m_gotSOP) | 
					
						
							|  |  |  |             { | 
					
						
							|  |  |  |                 // Look for synccode that starts a batch - allow three errors that can be corrected
 | 
					
						
							|  |  |  |                 if (m_bits == PAGERDEMOD_POCSAG_SYNCCODE) | 
					
						
							|  |  |  |                 { | 
					
						
							|  |  |  |                     m_gotSOP = true; | 
					
						
							|  |  |  |                     m_inverted = false; | 
					
						
							|  |  |  |                 } | 
					
						
							|  |  |  |                 else if (m_bits == PAGERDEMOD_POCSAG_SYNCCODE_INV) | 
					
						
							|  |  |  |                 { | 
					
						
							|  |  |  |                     m_gotSOP = true; | 
					
						
							|  |  |  |                     m_inverted = true; | 
					
						
							|  |  |  |                 } | 
					
						
							|  |  |  |                 else if (popcount(m_bits ^ PAGERDEMOD_POCSAG_SYNCCODE) >= 29) | 
					
						
							|  |  |  |                 { | 
					
						
							|  |  |  |                     quint32 correctedCW; | 
					
						
							|  |  |  |                     if (bchDecode(m_bits, correctedCW) && (correctedCW == PAGERDEMOD_POCSAG_SYNCCODE)) | 
					
						
							|  |  |  |                     { | 
					
						
							|  |  |  |                         m_gotSOP = true; | 
					
						
							|  |  |  |                         m_inverted = false; | 
					
						
							|  |  |  |                     } | 
					
						
							|  |  |  |                 } | 
					
						
							|  |  |  |                 else if (popcount(m_bits ^ PAGERDEMOD_POCSAG_SYNCCODE_INV) >= 29) | 
					
						
							|  |  |  |                 { | 
					
						
							|  |  |  |                     quint32 correctedCW; | 
					
						
							| 
									
										
										
										
											2021-07-09 13:23:57 +01:00
										 |  |  |                     if (bchDecode(~m_bits, correctedCW) && (correctedCW == PAGERDEMOD_POCSAG_SYNCCODE)) | 
					
						
							| 
									
										
										
										
											2021-07-09 12:06:23 +01:00
										 |  |  |                     { | 
					
						
							|  |  |  |                         m_gotSOP = true; | 
					
						
							|  |  |  |                         m_inverted = true; | 
					
						
							|  |  |  |                     } | 
					
						
							|  |  |  |                 } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |                 if (m_gotSOP) | 
					
						
							|  |  |  |                 { | 
					
						
							|  |  |  |                     // Reset demod state
 | 
					
						
							|  |  |  |                     m_bits = 0; | 
					
						
							|  |  |  |                     m_bitCount = 0; | 
					
						
							|  |  |  |                     m_codeWords[0] = PAGERDEMOD_POCSAG_SYNCCODE; | 
					
						
							|  |  |  |                     m_wordCount = 1; | 
					
						
							|  |  |  |                     m_addressValid = false; | 
					
						
							|  |  |  |                 } | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  |             else if ((m_bitCount == 32) && m_gotSOP) | 
					
						
							|  |  |  |             { | 
					
						
							|  |  |  |                 // Got a complete codeword - use BCH decoding to fix any bit errors
 | 
					
						
							|  |  |  |                 quint32 correctedCW; | 
					
						
							|  |  |  |                 m_codeWordsBCHError[m_wordCount] = !bchDecode(m_bits, correctedCW); | 
					
						
							|  |  |  |                 m_codeWords[m_wordCount] = correctedCW; | 
					
						
							|  |  |  |                 m_wordCount++; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |                 // Check for sync code at start of batch
 | 
					
						
							|  |  |  |                 if ((m_wordCount == 1) && (correctedCW != PAGERDEMOD_POCSAG_SYNCCODE)) | 
					
						
							|  |  |  |                 { | 
					
						
							|  |  |  |                     m_gotSOP = false; | 
					
						
							|  |  |  |                     //m_thresholdMet = false;
 | 
					
						
							|  |  |  |                     m_addressValid = false; | 
					
						
							|  |  |  |                     m_inverted = false; | 
					
						
							|  |  |  |                 } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |                 // Have we received a complete batch
 | 
					
						
							|  |  |  |                 if (m_wordCount == PAGERDEMOD_BATCH_WORDS) | 
					
						
							|  |  |  |                 { | 
					
						
							|  |  |  |                     // Decode it to addresses and messages
 | 
					
						
							|  |  |  |                     decodeBatch(); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |                     // Start a new batch
 | 
					
						
							|  |  |  |                     m_batchNumber++; | 
					
						
							|  |  |  |                     m_wordCount = 0; | 
					
						
							|  |  |  |                 } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |                 m_bits = 0; | 
					
						
							|  |  |  |                 m_bitCount = 0; | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             m_syncCount = m_samplesPerSymbol; | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     // Save data for edge detection
 | 
					
						
							|  |  |  |     m_dataPrev = data; | 
					
						
							|  |  |  |     // Select signals to feed to scope
 | 
					
						
							|  |  |  |     Complex scopeSample; | 
					
						
							| 
									
										
										
										
											2021-11-09 21:57:56 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-07-09 12:06:23 +01:00
										 |  |  |     switch (m_settings.m_scopeCh1) | 
					
						
							|  |  |  |     { | 
					
						
							|  |  |  |     case 0: | 
					
						
							|  |  |  |         scopeSample.real(ci.real() / SDR_RX_SCALEF); | 
					
						
							|  |  |  |         break; | 
					
						
							|  |  |  |     case 1: | 
					
						
							|  |  |  |         scopeSample.real(ci.imag() / SDR_RX_SCALEF); | 
					
						
							|  |  |  |         break; | 
					
						
							|  |  |  |     case 2: | 
					
						
							|  |  |  |         scopeSample.real(magsq); | 
					
						
							|  |  |  |         break; | 
					
						
							|  |  |  |     case 3: | 
					
						
							|  |  |  |         scopeSample.real(fmDemod); | 
					
						
							|  |  |  |         break; | 
					
						
							|  |  |  |     case 4: | 
					
						
							|  |  |  |         scopeSample.real(filt); | 
					
						
							|  |  |  |         break; | 
					
						
							|  |  |  |     case 5: | 
					
						
							|  |  |  |         scopeSample.real(m_dcOffset); | 
					
						
							|  |  |  |         break; | 
					
						
							|  |  |  |     case 6: | 
					
						
							|  |  |  |         scopeSample.real(data); | 
					
						
							|  |  |  |         break; | 
					
						
							|  |  |  |     case 7: | 
					
						
							|  |  |  |         scopeSample.real(sample); | 
					
						
							|  |  |  |         break; | 
					
						
							|  |  |  |     case 8: | 
					
						
							|  |  |  |         scopeSample.real(m_bit); | 
					
						
							|  |  |  |         break; | 
					
						
							|  |  |  |     case 9: | 
					
						
							|  |  |  |         scopeSample.real(m_gotSOP); | 
					
						
							|  |  |  |         break; | 
					
						
							|  |  |  |     } | 
					
						
							| 
									
										
										
										
											2021-11-09 21:57:56 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-07-09 12:06:23 +01:00
										 |  |  |     switch (m_settings.m_scopeCh2) | 
					
						
							|  |  |  |     { | 
					
						
							|  |  |  |     case 0: | 
					
						
							|  |  |  |         scopeSample.imag(ci.real() / SDR_RX_SCALEF); | 
					
						
							|  |  |  |         break; | 
					
						
							|  |  |  |     case 1: | 
					
						
							|  |  |  |         scopeSample.imag(ci.imag() / SDR_RX_SCALEF); | 
					
						
							|  |  |  |         break; | 
					
						
							|  |  |  |     case 2: | 
					
						
							|  |  |  |         scopeSample.imag(magsq); | 
					
						
							|  |  |  |         break; | 
					
						
							|  |  |  |     case 3: | 
					
						
							|  |  |  |         scopeSample.imag(fmDemod); | 
					
						
							|  |  |  |         break; | 
					
						
							|  |  |  |     case 4: | 
					
						
							|  |  |  |         scopeSample.imag(filt); | 
					
						
							|  |  |  |         break; | 
					
						
							|  |  |  |     case 5: | 
					
						
							|  |  |  |         scopeSample.imag(m_dcOffset); | 
					
						
							|  |  |  |         break; | 
					
						
							|  |  |  |     case 6: | 
					
						
							|  |  |  |         scopeSample.imag(data); | 
					
						
							|  |  |  |         break; | 
					
						
							|  |  |  |     case 7: | 
					
						
							|  |  |  |         scopeSample.imag(sample); | 
					
						
							|  |  |  |         break; | 
					
						
							|  |  |  |     case 8: | 
					
						
							|  |  |  |         scopeSample.imag(m_bit); | 
					
						
							|  |  |  |         break; | 
					
						
							|  |  |  |     case 9: | 
					
						
							|  |  |  |         scopeSample.imag(m_gotSOP); | 
					
						
							|  |  |  |         break; | 
					
						
							|  |  |  |     } | 
					
						
							| 
									
										
										
										
											2021-11-09 21:57:56 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-07-09 12:06:23 +01:00
										 |  |  |     sampleToScope(scopeSample); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     // Send demod signal to Demod Analzyer feature
 | 
					
						
							|  |  |  |     m_demodBuffer[m_demodBufferFill++] = fmDemod * std::numeric_limits<int16_t>::max(); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     if (m_demodBufferFill >= m_demodBuffer.size()) | 
					
						
							|  |  |  |     { | 
					
						
							|  |  |  |         QList<DataFifo*> *dataFifos = MainCore::instance()->getDataPipes().getFifos(m_channel, "demod"); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         if (dataFifos) | 
					
						
							|  |  |  |         { | 
					
						
							|  |  |  |             QList<DataFifo*>::iterator it = dataFifos->begin(); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             for (; it != dataFifos->end(); ++it) { | 
					
						
							|  |  |  |                 (*it)->write((quint8*) &m_demodBuffer[0], m_demodBuffer.size() * sizeof(qint16), DataFifo::DataTypeI16); | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         m_demodBufferFill = 0; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | void PagerDemodSink::applyChannelSettings(int channelSampleRate, int channelFrequencyOffset, bool force) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  |     qDebug() << "PagerDemodSink::applyChannelSettings:" | 
					
						
							|  |  |  |             << " channelSampleRate: " << channelSampleRate | 
					
						
							|  |  |  |             << " channelFrequencyOffset: " << channelFrequencyOffset; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     if ((m_channelFrequencyOffset != channelFrequencyOffset) || | 
					
						
							|  |  |  |         (m_channelSampleRate != channelSampleRate) || force) | 
					
						
							|  |  |  |     { | 
					
						
							|  |  |  |         m_nco.setFreq(-channelFrequencyOffset, channelSampleRate); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     if ((m_channelSampleRate != channelSampleRate) || force) | 
					
						
							|  |  |  |     { | 
					
						
							|  |  |  |         m_interpolator.create(16, channelSampleRate, m_settings.m_rfBandwidth / 2.2); | 
					
						
							|  |  |  |         m_interpolatorDistance = (Real) channelSampleRate / (Real) PagerDemodSettings::m_channelSampleRate; | 
					
						
							|  |  |  |         m_interpolatorDistanceRemain = m_interpolatorDistance; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     m_channelSampleRate = channelSampleRate; | 
					
						
							|  |  |  |     m_channelFrequencyOffset = channelFrequencyOffset; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | void PagerDemodSink::applySettings(const PagerDemodSettings& settings, bool force) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  |     qDebug() << "PagerDemodSink::applySettings:" | 
					
						
							|  |  |  |             << " rfBandwidth: " << settings.m_rfBandwidth | 
					
						
							|  |  |  |             << " fmDeviation: " << settings.m_fmDeviation | 
					
						
							|  |  |  |             << " baud: " << settings.m_baud | 
					
						
							|  |  |  |             << " force: " << force; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     if ((settings.m_rfBandwidth != m_settings.m_rfBandwidth) || force) | 
					
						
							|  |  |  |     { | 
					
						
							|  |  |  |         m_interpolator.create(16, m_channelSampleRate, settings.m_rfBandwidth / 2.2); | 
					
						
							|  |  |  |         m_interpolatorDistance = (Real) m_channelSampleRate / (Real) PagerDemodSettings::m_channelSampleRate; | 
					
						
							|  |  |  |         m_interpolatorDistanceRemain = m_interpolatorDistance; | 
					
						
							|  |  |  |         m_lowpass.create(301, PagerDemodSettings::m_channelSampleRate, settings.m_rfBandwidth / 2.0f); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     if ((settings.m_fmDeviation != m_settings.m_fmDeviation) || force) | 
					
						
							|  |  |  |     { | 
					
						
							|  |  |  |         m_phaseDiscri.setFMScaling(PagerDemodSettings::m_channelSampleRate / (2.0f * settings.m_fmDeviation)); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     if ((settings.m_baud != m_settings.m_baud) || force) | 
					
						
							|  |  |  |     { | 
					
						
							|  |  |  |         m_samplesPerSymbol = PagerDemodSettings::m_channelSampleRate / settings.m_baud; | 
					
						
							|  |  |  |         qDebug() << "PagerDemodSink::applySettings: m_samplesPerSymbol: " << m_samplesPerSymbol; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         // Signal is a square wave - so include several harmonics
 | 
					
						
							|  |  |  |         m_lowpassBaud.create(301, PagerDemodSettings::m_channelSampleRate, settings.m_baud * 5.0f); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     m_settings = settings; | 
					
						
							|  |  |  | } |