mirror of
				https://github.com/AlexandreRouma/SDRPlusPlus.git
				synced 2025-10-31 00:48:11 +01:00 
			
		
		
		
	Added basic RDS support, no error correction yet
This commit is contained in:
		
							
								
								
									
										189
									
								
								core/src/dsp/clock_recovery/fd.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										189
									
								
								core/src/dsp/clock_recovery/fd.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,189 @@ | ||||
| #pragma once | ||||
| #include "../processor.h" | ||||
| #include "../loop/phase_control_loop.h" | ||||
| #include "../taps/windowed_sinc.h" | ||||
| #include "../multirate/polyphase_bank.h" | ||||
| #include "../math/step.h" | ||||
|  | ||||
| namespace dsp::clock_recovery { | ||||
|     class FD : public Processor<float, float> { | ||||
|         using base_type = Processor<float, float> ; | ||||
|     public: | ||||
|         FD() {} | ||||
|  | ||||
|         FD(stream<float>* in, double omega, double omegaGain, double muGain, double omegaRelLimit, int interpPhaseCount = 128, int interpTapCount = 8) { init(in, omega, omegaGain, muGain, omegaRelLimit, interpPhaseCount, interpTapCount); } | ||||
|  | ||||
|         ~FD() { | ||||
|             if (!base_type::_block_init) { return; } | ||||
|             base_type::stop(); | ||||
|             dsp::multirate::freePolyphaseBank(interpBank); | ||||
|             buffer::free(buffer); | ||||
|         } | ||||
|  | ||||
|         void init(stream<float>* in, double omega, double omegaGain, double muGain, double omegaRelLimit, int interpPhaseCount = 128, int interpTapCount = 8) { | ||||
|             _omega = omega; | ||||
|             _omegaGain = omegaGain; | ||||
|             _muGain = muGain; | ||||
|             _omegaRelLimit = omegaRelLimit; | ||||
|             _interpPhaseCount = interpPhaseCount; | ||||
|             _interpTapCount = interpTapCount; | ||||
|  | ||||
|             pcl.init(_muGain, _omegaGain, 0.0, 0.0, 1.0, _omega, _omega * (1.0 - omegaRelLimit), _omega * (1.0 + omegaRelLimit)); | ||||
|             generateInterpTaps(); | ||||
|             buffer = buffer::alloc<float>(STREAM_BUFFER_SIZE + _interpTapCount); | ||||
|             bufStart = &buffer[_interpTapCount - 1]; | ||||
|          | ||||
|             base_type::init(in); | ||||
|         } | ||||
|  | ||||
|         void setOmega(double omega) { | ||||
|             assert(base_type::_block_init); | ||||
|             std::lock_guard<std::recursive_mutex> lck(base_type::ctrlMtx); | ||||
|             base_type::tempStop(); | ||||
|             _omega = omega; | ||||
|             offset = 0; | ||||
|             pcl.phase = 0.0f; | ||||
|             pcl.freq = _omega; | ||||
|             pcl.setFreqLimits(_omega * (1.0 - _omegaRelLimit), _omega * (1.0 + _omegaRelLimit)); | ||||
|             base_type::tempStart(); | ||||
|         } | ||||
|  | ||||
|         void setOmegaGain(double omegaGain) { | ||||
|             assert(base_type::_block_init); | ||||
|             std::lock_guard<std::recursive_mutex> lck(base_type::ctrlMtx); | ||||
|             _omegaGain = omegaGain; | ||||
|             pcl.setCoefficients(_muGain, _omegaGain); | ||||
|         } | ||||
|  | ||||
|         void setMuGain(double muGain) { | ||||
|             assert(base_type::_block_init); | ||||
|             std::lock_guard<std::recursive_mutex> lck(base_type::ctrlMtx); | ||||
|             _muGain = muGain; | ||||
|             pcl.setCoefficients(_muGain, _omegaGain); | ||||
|         } | ||||
|  | ||||
|         void setOmegaRelLimit(double omegaRelLimit) { | ||||
|             assert(base_type::_block_init); | ||||
|             std::lock_guard<std::recursive_mutex> lck(base_type::ctrlMtx); | ||||
|             _omegaRelLimit = omegaRelLimit; | ||||
|             pcl.setFreqLimits(_omega * (1.0 - _omegaRelLimit), _omega * (1.0 + _omegaRelLimit)); | ||||
|         } | ||||
|  | ||||
|         void setInterpParams(int interpPhaseCount, int interpTapCount) { | ||||
|             assert(base_type::_block_init); | ||||
|             std::lock_guard<std::recursive_mutex> lck(base_type::ctrlMtx); | ||||
|             base_type::tempStop(); | ||||
|             _interpPhaseCount = interpPhaseCount; | ||||
|             _interpTapCount = interpTapCount; | ||||
|             dsp::multirate::freePolyphaseBank(interpBank); | ||||
|             buffer::free(buffer); | ||||
|             generateInterpTaps(); | ||||
|             buffer = buffer::alloc<float>(STREAM_BUFFER_SIZE + _interpTapCount); | ||||
|             bufStart = &buffer[_interpTapCount - 1]; | ||||
|             base_type::tempStart(); | ||||
|         } | ||||
|  | ||||
|         void reset() { | ||||
|             assert(base_type::_block_init); | ||||
|             std::lock_guard<std::recursive_mutex> lck(base_type::ctrlMtx); | ||||
|             base_type::tempStop(); | ||||
|             offset = 0; | ||||
|             pcl.phase = 0.0f; | ||||
|             pcl.freq = _omega; | ||||
|             base_type::tempStart(); | ||||
|         } | ||||
|  | ||||
|         inline int process(int count, const float* in, float* out) { | ||||
|             // Copy data to work buffer | ||||
|             memcpy(bufStart, in, count * sizeof(float)); | ||||
|  | ||||
|             // Process all samples | ||||
|             int outCount = 0; | ||||
|             while (offset < count) { | ||||
|                 float error; | ||||
|                 float outVal; | ||||
|                 float dfdt; | ||||
|  | ||||
|                 // Calculate new output value | ||||
|                 int phase = std::clamp<int>(floorf(pcl.phase * (float)_interpPhaseCount), 0, _interpPhaseCount - 1); | ||||
|                 volk_32f_x2_dot_prod_32f(&outVal, &buffer[offset], interpBank.phases[phase], _interpTapCount); | ||||
|                 out[outCount++] = outVal; | ||||
|  | ||||
|                 // Calculate derivative of the signal | ||||
|                 if (phase == 0) { | ||||
|                     float fT1; | ||||
|                     volk_32f_x2_dot_prod_32f(&fT1, &buffer[offset], interpBank.phases[phase+1], _interpTapCount); | ||||
|                     dfdt = fT1 - outVal; | ||||
|                 } | ||||
|                 else if (phase == _interpPhaseCount - 1) { | ||||
|                     float fT_1; | ||||
|                     volk_32f_x2_dot_prod_32f(&fT_1, &buffer[offset], interpBank.phases[phase-1], _interpTapCount); | ||||
|                     dfdt = outVal - fT_1; | ||||
|                 } | ||||
|                 else { | ||||
|                     float fT_1; | ||||
|                     float fT1; | ||||
|                     volk_32f_x2_dot_prod_32f(&fT_1, &buffer[offset], interpBank.phases[phase-1], _interpTapCount); | ||||
|                     volk_32f_x2_dot_prod_32f(&fT1, &buffer[offset], interpBank.phases[phase+1], _interpTapCount); | ||||
|                     dfdt = (fT1 - fT_1) * 0.5f; | ||||
|                 } | ||||
|                  | ||||
|                 // Calculate error | ||||
|                 error = dfdt * math::step(outVal); | ||||
|  | ||||
|                 // Clamp symbol phase error | ||||
|                 if (error > 1.0f) { error = 1.0f; } | ||||
|                 if (error < -1.0f) { error = -1.0f; } | ||||
|  | ||||
|                 // Advance symbol offset and phase | ||||
|                 pcl.advance(error); | ||||
|                 float delta = floorf(pcl.phase); | ||||
|                 offset += delta; | ||||
|                 pcl.phase -= delta; | ||||
|             } | ||||
|             offset -= count; | ||||
|  | ||||
|             // Update delay buffer | ||||
|             memmove(buffer, &buffer[count], (_interpTapCount - 1) * sizeof(float)); | ||||
|  | ||||
|             return outCount; | ||||
|         } | ||||
|  | ||||
|         int run() { | ||||
|             int count = base_type::_in->read(); | ||||
|             if (count < 0) { return -1; } | ||||
|  | ||||
|             int outCount = process(count, base_type::_in->readBuf, base_type::out.writeBuf); | ||||
|  | ||||
|             // Swap if some data was generated | ||||
|             base_type::_in->flush(); | ||||
|             if (outCount) { | ||||
|                 if (!base_type::out.swap(outCount)) { return -1; } | ||||
|             } | ||||
|             return outCount; | ||||
|         } | ||||
|  | ||||
|         loop::PhaseControlLoop<float, false> pcl; | ||||
|  | ||||
|     protected: | ||||
|         void generateInterpTaps() { | ||||
|             double bw = 0.5 / (double)_interpPhaseCount; | ||||
|             dsp::tap<float> lp = dsp::taps::windowedSinc<float>(_interpPhaseCount * _interpTapCount, dsp::math::freqToOmega(bw, 1.0), dsp::window::nuttall, _interpPhaseCount); | ||||
|             interpBank = dsp::multirate::buildPolyphaseBank<float>(_interpPhaseCount, lp); | ||||
|             taps::free(lp); | ||||
|         } | ||||
|  | ||||
|         dsp::multirate::PolyphaseBank<float> interpBank; | ||||
|  | ||||
|         double _omega; | ||||
|         double _omegaGain; | ||||
|         double _muGain; | ||||
|         double _omegaRelLimit; | ||||
|         int _interpPhaseCount; | ||||
|         int _interpTapCount; | ||||
|  | ||||
|         int offset = 0; | ||||
|         float* buffer; | ||||
|         float* bufStart; | ||||
|     }; | ||||
| } | ||||
| @@ -178,7 +178,7 @@ namespace dsp::clock_recovery { | ||||
|         } | ||||
|  | ||||
|         dsp::multirate::PolyphaseBank<float> interpBank; | ||||
|         loop::PhaseControlLoop<double, false> pcl; | ||||
|         loop::PhaseControlLoop<float, false> pcl; | ||||
|  | ||||
|         double _omega; | ||||
|         double _omegaGain; | ||||
|   | ||||
| @@ -12,6 +12,7 @@ | ||||
| #include "../math/multiply.h" | ||||
| #include "../math/add.h" | ||||
| #include "../math/subtract.h" | ||||
| #include "../multirate/rational_resampler.h" | ||||
|  | ||||
| namespace dsp::demod { | ||||
|     class BroadcastFM : public Processor<complex_t, stereo_t> { | ||||
| @@ -19,7 +20,7 @@ namespace dsp::demod { | ||||
|     public: | ||||
|         BroadcastFM() {} | ||||
|  | ||||
|         BroadcastFM(stream<complex_t>* in, double deviation, double samplerate, bool stereo = true, bool lowPass = true) { init(in, deviation, samplerate, stereo, lowPass); } | ||||
|         BroadcastFM(stream<complex_t>* in, double deviation, double samplerate, bool stereo = true, bool lowPass = true, bool rdsOut = false) { init(in, deviation, samplerate, stereo, lowPass); } | ||||
|  | ||||
|         ~BroadcastFM() { | ||||
|             if (!base_type::_block_init) { return; } | ||||
| @@ -31,11 +32,12 @@ namespace dsp::demod { | ||||
|             taps::free(audioFirTaps); | ||||
|         } | ||||
|  | ||||
|         virtual void init(stream<complex_t>* in, double deviation, double samplerate, bool stereo = true, bool lowPass = true) { | ||||
|         virtual void init(stream<complex_t>* in, double deviation, double samplerate, bool stereo = true, bool lowPass = true, bool rdsOut = false) { | ||||
|             _deviation = deviation; | ||||
|             _samplerate = samplerate; | ||||
|             _stereo = stereo; | ||||
|             _lowPass = lowPass; | ||||
|             _rdsOut = rdsOut; | ||||
|              | ||||
|             demod.init(NULL, _deviation, _samplerate); | ||||
|             pilotFirTaps = taps::bandPass<complex_t>(18750.0, 19250.0, 3000.0, _samplerate, true); | ||||
| @@ -47,6 +49,7 @@ namespace dsp::demod { | ||||
|             audioFirTaps = taps::lowPass(15000.0, 4000.0, _samplerate); | ||||
|             alFir.init(NULL, audioFirTaps); | ||||
|             arFir.init(NULL, audioFirTaps); | ||||
|             rdsResamp.init(NULL, samplerate, 5000.0); | ||||
|  | ||||
|             lmr = buffer::alloc<float>(STREAM_BUFFER_SIZE); | ||||
|             l = buffer::alloc<float>(STREAM_BUFFER_SIZE); | ||||
| @@ -56,6 +59,7 @@ namespace dsp::demod { | ||||
|             lmrDelay.out.free(); | ||||
|             arFir.out.free(); | ||||
|             alFir.out.free(); | ||||
|             rdsResamp.out.free(); | ||||
|  | ||||
|             base_type::init(in); | ||||
|         } | ||||
| @@ -88,6 +92,8 @@ namespace dsp::demod { | ||||
|             alFir.setTaps(audioFirTaps); | ||||
|             arFir.setTaps(audioFirTaps); | ||||
|  | ||||
|             rdsResamp.setInSamplerate(samplerate); | ||||
|  | ||||
|             reset(); | ||||
|             base_type::tempStart(); | ||||
|         } | ||||
| @@ -110,6 +116,15 @@ namespace dsp::demod { | ||||
|             base_type::tempStart(); | ||||
|         } | ||||
|  | ||||
|         void setRDSOut(bool rdsOut) { | ||||
|             assert(base_type::_block_init); | ||||
|             std::lock_guard<std::recursive_mutex> lck(base_type::ctrlMtx); | ||||
|             base_type::tempStop(); | ||||
|             _rdsOut = rdsOut; | ||||
|             reset(); | ||||
|             base_type::tempStart(); | ||||
|         } | ||||
|  | ||||
|         void reset() { | ||||
|             assert(base_type::_block_init); | ||||
|             std::lock_guard<std::recursive_mutex> lck(base_type::ctrlMtx); | ||||
| @@ -124,7 +139,7 @@ namespace dsp::demod { | ||||
|             base_type::tempStart(); | ||||
|         } | ||||
|  | ||||
|         inline int process(int count, complex_t* in, stereo_t* out) { | ||||
|         inline int process(int count, complex_t* in, stereo_t* out, int& rdsOutCount, float* rdsout = NULL) { | ||||
|             // Demodulate | ||||
|             demod.process(count, in, demod.out.writeBuf); | ||||
|             if (_stereo) { | ||||
| @@ -139,10 +154,19 @@ namespace dsp::demod { | ||||
|                 lprDelay.process(count, demod.out.writeBuf, demod.out.writeBuf); | ||||
|                 lmrDelay.process(count, rtoc.out.writeBuf, rtoc.out.writeBuf); | ||||
|                  | ||||
|                 // Double and conjugate PLL output to down convert the L-R signal | ||||
|                 math::Multiply<dsp::complex_t>::process(count, pilotPLL.out.writeBuf, pilotPLL.out.writeBuf, pilotPLL.out.writeBuf); | ||||
|                 // conjugate PLL output to down convert twice the L-R signal | ||||
|                 math::Conjugate::process(count, pilotPLL.out.writeBuf, pilotPLL.out.writeBuf); | ||||
|                 math::Multiply<dsp::complex_t>::process(count, rtoc.out.writeBuf, pilotPLL.out.writeBuf, rtoc.out.writeBuf); | ||||
|                 math::Multiply<dsp::complex_t>::process(count, rtoc.out.writeBuf, pilotPLL.out.writeBuf, rtoc.out.writeBuf); | ||||
|  | ||||
|                 // Do RDS demod | ||||
|                 if (_rdsOut) { | ||||
|                     // Since the PLL output is no longer needed after this, use it as the output | ||||
|                     math::Multiply<dsp::complex_t>::process(count, rtoc.out.writeBuf, pilotPLL.out.writeBuf, pilotPLL.out.writeBuf); | ||||
|                     convert::ComplexToReal::process(count, pilotPLL.out.writeBuf, rdsout); | ||||
|                     volk_32f_s32f_multiply_32f(rdsout, rdsout, 100.0, count); | ||||
|                     rdsOutCount = rdsResamp.process(count, rdsout, rdsout); | ||||
|                 } | ||||
|  | ||||
|                 // Convert output back to real for further processing | ||||
|                 convert::ComplexToReal::process(count, rtoc.out.writeBuf, lmr); | ||||
| @@ -180,18 +204,25 @@ namespace dsp::demod { | ||||
|             int count = base_type::_in->read(); | ||||
|             if (count < 0) { return -1; } | ||||
|  | ||||
|             process(count, base_type::_in->readBuf, base_type::out.writeBuf); | ||||
|             int rdsOutCount = 0; | ||||
|             process(count, base_type::_in->readBuf, base_type::out.writeBuf, rdsOutCount, rdsOut.writeBuf); | ||||
|  | ||||
|             base_type::_in->flush(); | ||||
|             if (!base_type::out.swap(count)) { return -1; } | ||||
|             if (rdsOutCount && _rdsOut) { | ||||
|                 if (!rdsOut.swap(rdsOutCount)) { return -1; } | ||||
|             } | ||||
|             return count; | ||||
|         } | ||||
|  | ||||
|         stream<float> rdsOut; | ||||
|  | ||||
|     protected: | ||||
|         double _deviation; | ||||
|         double _samplerate; | ||||
|         bool _stereo; | ||||
|         bool _lowPass = true; | ||||
|         bool _lowPass; | ||||
|         bool _rdsOut; | ||||
|  | ||||
|         Quadrature demod; | ||||
|         tap<complex_t> pilotFirTaps; | ||||
| @@ -203,6 +234,7 @@ namespace dsp::demod { | ||||
|         tap<float> audioFirTaps; | ||||
|         filter::FIR<float, float> arFir; | ||||
|         filter::FIR<float, float> alFir; | ||||
|         multirate::RationalResampler<float> rdsResamp; | ||||
|  | ||||
|         float* lmr; | ||||
|         float* l; | ||||
|   | ||||
							
								
								
									
										31
									
								
								core/src/dsp/digital/binary_slicer.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										31
									
								
								core/src/dsp/digital/binary_slicer.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,31 @@ | ||||
| #pragma once | ||||
| #include "../processor.h" | ||||
|  | ||||
| namespace dsp::digital { | ||||
|     class BinarySlicer : public Processor<float, uint8_t> { | ||||
|         using base_type = Processor<float, uint8_t>; | ||||
|     public: | ||||
|         BinarySlicer() {} | ||||
|  | ||||
|         BinarySlicer(stream<float> *in) { base_type::init(in); } | ||||
|  | ||||
|         static inline int process(int count, const float* in, uint8_t* out) { | ||||
|             // TODO: Switch to volk | ||||
|             for (int i = 0; i < count; i++) { | ||||
|                 out[i] = in[i] > 0.0f; | ||||
|             } | ||||
|             return count; | ||||
|         } | ||||
|  | ||||
|         int run() { | ||||
|             int count = base_type::_in->read(); | ||||
|             if (count < 0) { return -1; } | ||||
|  | ||||
|             process(count, base_type::_in->readBuf, base_type::out.writeBuf); | ||||
|  | ||||
|             base_type::_in->flush(); | ||||
|             if (!base_type::out.swap(count)) { return -1; } | ||||
|             return count; | ||||
|         } | ||||
|     }; | ||||
| } | ||||
							
								
								
									
										64
									
								
								core/src/dsp/digital/differentia_decoder.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										64
									
								
								core/src/dsp/digital/differentia_decoder.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,64 @@ | ||||
| #pragma once | ||||
| #include "../processor.h" | ||||
|  | ||||
| namespace dsp::digital { | ||||
|     class DifferentialDecoder : public Processor<uint8_t, uint8_t> { | ||||
|         using base_type = Processor<uint8_t, uint8_t>; | ||||
|     public: | ||||
|         DifferentialDecoder() {} | ||||
|  | ||||
|         DifferentialDecoder(stream<uint8_t> *in) { base_type::init(in); } | ||||
|  | ||||
|         void init(stream<uint8_t> *in, uint8_t modulus, uint8_t initSym = 0) { | ||||
|             _modulus = modulus; | ||||
|             _initSym = initSym; | ||||
|  | ||||
|             last = _initSym; | ||||
|  | ||||
|             base_type::init(in); | ||||
|         } | ||||
|  | ||||
|         void setModulus(uint8_t modulus) { | ||||
|             assert(base_type::_block_init); | ||||
|             std::lock_guard<std::recursive_mutex> lck(base_type::ctrlMtx); | ||||
|             _modulus = modulus; | ||||
|         } | ||||
|  | ||||
|         void setInitSym(uint8_t initSym) { | ||||
|             assert(base_type::_block_init); | ||||
|             std::lock_guard<std::recursive_mutex> lck(base_type::ctrlMtx); | ||||
|             _initSym = initSym; | ||||
|         } | ||||
|  | ||||
|         void reset() { | ||||
|             assert(base_type::_block_init); | ||||
|             std::lock_guard<std::recursive_mutex> lck(base_type::ctrlMtx); | ||||
|             base_type::tempStop(); | ||||
|             last = _initSym; | ||||
|             base_type::tempStart(); | ||||
|         } | ||||
|          | ||||
|         inline int process(int count, const uint8_t* in, uint8_t* out) { | ||||
|             for (int i = 0; i < count; i++) { | ||||
|                 out[i] = (in[i] - last) % _modulus; | ||||
|             } | ||||
|             return count; | ||||
|         } | ||||
|  | ||||
|         int run() { | ||||
|             int count = base_type::_in->read(); | ||||
|             if (count < 0) { return -1; } | ||||
|  | ||||
|             process(count, base_type::_in->readBuf, base_type::out.writeBuf); | ||||
|  | ||||
|             base_type::_in->flush(); | ||||
|             if (!base_type::out.swap(count)) { return -1; } | ||||
|             return count; | ||||
|         } | ||||
|  | ||||
|     protected: | ||||
|         uint8_t last; | ||||
|         uint8_t _initSym; | ||||
|         uint8_t _modulus; | ||||
|     }; | ||||
| } | ||||
							
								
								
									
										65
									
								
								core/src/dsp/digital/differential_decoder.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										65
									
								
								core/src/dsp/digital/differential_decoder.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,65 @@ | ||||
| #pragma once | ||||
| #include "../processor.h" | ||||
|  | ||||
| namespace dsp::digital { | ||||
|     class DifferentialDecoder : public Processor<uint8_t, uint8_t> { | ||||
|         using base_type = Processor<uint8_t, uint8_t>; | ||||
|     public: | ||||
|         DifferentialDecoder() {} | ||||
|  | ||||
|         DifferentialDecoder(stream<uint8_t> *in) { base_type::init(in); } | ||||
|  | ||||
|         void init(stream<uint8_t> *in, uint8_t modulus, uint8_t initSym = 0) { | ||||
|             _modulus = modulus; | ||||
|             _initSym = initSym; | ||||
|  | ||||
|             last = _initSym; | ||||
|  | ||||
|             base_type::init(in); | ||||
|         } | ||||
|  | ||||
|         void setModulus(uint8_t modulus) { | ||||
|             assert(base_type::_block_init); | ||||
|             std::lock_guard<std::recursive_mutex> lck(base_type::ctrlMtx); | ||||
|             _modulus = modulus; | ||||
|         } | ||||
|  | ||||
|         void setInitSym(uint8_t initSym) { | ||||
|             assert(base_type::_block_init); | ||||
|             std::lock_guard<std::recursive_mutex> lck(base_type::ctrlMtx); | ||||
|             _initSym = initSym; | ||||
|         } | ||||
|  | ||||
|         void reset() { | ||||
|             assert(base_type::_block_init); | ||||
|             std::lock_guard<std::recursive_mutex> lck(base_type::ctrlMtx); | ||||
|             base_type::tempStop(); | ||||
|             last = _initSym; | ||||
|             base_type::tempStart(); | ||||
|         } | ||||
|          | ||||
|         inline int process(int count, const uint8_t* in, uint8_t* out) { | ||||
|             for (int i = 0; i < count; i++) { | ||||
|                 out[i] = (in[i] - last + _modulus) % _modulus; | ||||
|                 last = in[i]; | ||||
|             } | ||||
|             return count; | ||||
|         } | ||||
|  | ||||
|         int run() { | ||||
|             int count = base_type::_in->read(); | ||||
|             if (count < 0) { return -1; } | ||||
|  | ||||
|             process(count, base_type::_in->readBuf, base_type::out.writeBuf); | ||||
|  | ||||
|             base_type::_in->flush(); | ||||
|             if (!base_type::out.swap(count)) { return -1; } | ||||
|             return count; | ||||
|         } | ||||
|  | ||||
|     protected: | ||||
|         uint8_t last; | ||||
|         uint8_t _initSym; | ||||
|         uint8_t _modulus; | ||||
|     }; | ||||
| } | ||||
							
								
								
									
										47
									
								
								core/src/dsp/digital/manchester_decoder.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										47
									
								
								core/src/dsp/digital/manchester_decoder.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,47 @@ | ||||
| #pragma once | ||||
| #include "../processor.h" | ||||
|  | ||||
| namespace dsp::digital { | ||||
|     class ManchesterDecoder : public Processor<uint8_t, uint8_t> { | ||||
|         using base_type = Processor<uint8_t, uint8_t>; | ||||
|     public: | ||||
|         ManchesterDecoder() {} | ||||
|  | ||||
|         ManchesterDecoder(stream<uint8_t> *in) { base_type::init(in); } | ||||
|  | ||||
|         void reset() { | ||||
|             assert(base_type::_block_init); | ||||
|             std::lock_guard<std::recursive_mutex> lck(base_type::ctrlMtx); | ||||
|             base_type::tempStop(); | ||||
|             offset = 0; | ||||
|             base_type::tempStart(); | ||||
|         } | ||||
|  | ||||
|         inline int process(int count, const uint8_t* in, uint8_t* out) { | ||||
|             // TODO: NOT THIS BULLSHIT | ||||
|             int outCount = 0; | ||||
|             for (; offset < count; offset += 2) { | ||||
|                 out[outCount++] = in[offset]; | ||||
|             } | ||||
|             offset -= count; | ||||
|             return outCount; | ||||
|         } | ||||
|  | ||||
|         int run() { | ||||
|             int count = base_type::_in->read(); | ||||
|             if (count < 0) { return -1; } | ||||
|  | ||||
|             int outCount = process(count, base_type::_in->readBuf, base_type::out.writeBuf); | ||||
|  | ||||
|             // Swap if some data was generated | ||||
|             base_type::_in->flush(); | ||||
|             if (outCount) { | ||||
|                 if (!base_type::out.swap(outCount)) { return -1; } | ||||
|             } | ||||
|             return outCount; | ||||
|         } | ||||
|  | ||||
|     protected: | ||||
|         int offset = 0; | ||||
|     }; | ||||
| } | ||||
| @@ -1,13 +1,13 @@ | ||||
| #pragma once | ||||
| #include <imgui.h> | ||||
| #include <string> | ||||
| #include <module.h> | ||||
|  | ||||
| namespace style { | ||||
|     extern ImFont* baseFont; | ||||
|     extern ImFont* bigFont; | ||||
|     extern ImFont* hugeFont; | ||||
|  | ||||
|     extern float uiScale; | ||||
|     SDRPP_EXPORT ImFont* baseFont; | ||||
|     SDRPP_EXPORT ImFont* bigFont; | ||||
|     SDRPP_EXPORT ImFont* hugeFont; | ||||
|     SDRPP_EXPORT float uiScale; | ||||
|  | ||||
|     bool setDefaultStyle(std::string resDir); | ||||
|     bool loadFonts(std::string resDir); | ||||
|   | ||||
| @@ -56,7 +56,7 @@ public: | ||||
|         config.release(created); | ||||
|  | ||||
|         vfo = sigpath::vfoManager.createVFO(name, ImGui::WaterfallVFO::REF_CENTER, 0, 150000, INPUT_SAMPLE_RATE, 150000, 150000, true); | ||||
|         demod.init(vfo->output, 72000.0f, INPUT_SAMPLE_RATE, 33, 0.6f, 0.1f, 0.005f, (0.01 * 0.01) / 4.0, 0.01); | ||||
|         demod.init(vfo->output, 72000.0f, INPUT_SAMPLE_RATE, 33, 0.6f, 0.1f, 0.005f, 1e-6, 0.01); | ||||
|         split.init(&demod.out); | ||||
|         split.bindStream(&symSinkStream); | ||||
|         split.bindStream(&sinkStream); | ||||
|   | ||||
| @@ -1,6 +1,15 @@ | ||||
| #pragma once | ||||
| #include "../demod.h" | ||||
| #include <dsp/demod/broadcast_fm.h> | ||||
| #include <dsp/clock_recovery/mm.h> | ||||
| #include <dsp/clock_recovery/fd.h> | ||||
| #include <dsp/taps/root_raised_cosine.h> | ||||
| #include <dsp/digital/binary_slicer.h> | ||||
| #include <dsp/digital/manchester_decoder.h> | ||||
| #include <dsp/digital/differential_decoder.h> | ||||
| #include <gui/widgets/symbol_diagram.h> | ||||
| #include <fstream> | ||||
| #include <rds.h> | ||||
|  | ||||
| namespace demod { | ||||
|     class WFM : public Demodulator { | ||||
| @@ -13,12 +22,17 @@ namespace demod { | ||||
|  | ||||
|         ~WFM() { | ||||
|             stop(); | ||||
|             gui::waterfall.onFFTRedraw.unbindHandler(&fftRedrawHandler); | ||||
|         } | ||||
|  | ||||
|         void init(std::string name, ConfigManager* config, dsp::stream<dsp::complex_t>* input, double bandwidth, double audioSR) { | ||||
|             this->name = name; | ||||
|             _config = config; | ||||
|  | ||||
|             fftRedrawHandler.handler = fftRedraw; | ||||
|             fftRedrawHandler.ctx = this; | ||||
|             gui::waterfall.onFFTRedraw.bindHandler(&fftRedrawHandler); | ||||
|  | ||||
|             // Load config | ||||
|             _config->acquire(); | ||||
|             bool modified = false; | ||||
| @@ -28,18 +42,36 @@ namespace demod { | ||||
|             if (config->conf[name][getName()].contains("lowPass")) { | ||||
|                 _lowPass = config->conf[name][getName()]["lowPass"]; | ||||
|             } | ||||
|             if (config->conf[name][getName()].contains("rds")) { | ||||
|                 _rds = config->conf[name][getName()]["rds"]; | ||||
|             } | ||||
|             _config->release(modified); | ||||
|  | ||||
|             // Define structure | ||||
|             demod.init(input, bandwidth / 2.0f, getIFSampleRate(), _stereo, _lowPass); | ||||
|             demod.init(input, bandwidth / 2.0f, getIFSampleRate(), _stereo, _lowPass, _rds); | ||||
|             recov.init(&demod.rdsOut, 5000.0 / 2375, omegaGain, muGain, 0.01); | ||||
|             slice.init(&recov.out); | ||||
|             manch.init(&slice.out); | ||||
|             diff.init(&manch.out, 2); | ||||
|             hs.init(&diff.out, rdsHandler, this); | ||||
|         } | ||||
|  | ||||
|         void start() { | ||||
|             demod.start(); | ||||
|             recov.start(); | ||||
|             slice.start(); | ||||
|             manch.start(); | ||||
|             diff.start(); | ||||
|             hs.start(); | ||||
|         } | ||||
|  | ||||
|         void stop() { | ||||
|             demod.stop(); | ||||
|             recov.stop(); | ||||
|             slice.stop(); | ||||
|             manch.stop(); | ||||
|             diff.stop(); | ||||
|             hs.stop(); | ||||
|         } | ||||
|  | ||||
|         void showMenu() { | ||||
| @@ -55,6 +87,21 @@ namespace demod { | ||||
|                 _config->conf[name][getName()]["lowPass"] = _lowPass; | ||||
|                 _config->release(true); | ||||
|             } | ||||
|             if (ImGui::Checkbox(("Decode RDS##_radio_wfm_rds_" + name).c_str(), &_rds)) { | ||||
|                 demod.setRDSOut(_rds); | ||||
|                 _config->acquire(); | ||||
|                 _config->conf[name][getName()]["rds"] = _rds; | ||||
|                 _config->release(true); | ||||
|             } | ||||
|  | ||||
|             // if (_rds) { | ||||
|             //     if (rdsDecode.countryCodeValid()) { ImGui::Text("Country code: %d", rdsDecode.getCountryCode()); } | ||||
|             //     if (rdsDecode.programCoverageValid()) { ImGui::Text("Program coverage: %d", rdsDecode.getProgramCoverage()); } | ||||
|             //     if (rdsDecode.programRefNumberValid()) { ImGui::Text("Reference number: %d", rdsDecode.getProgramRefNumber()); } | ||||
|             //     if (rdsDecode.programTypeValid()) { ImGui::Text("Program type: %d", rdsDecode.getProgramType()); } | ||||
|             //     if (rdsDecode.PSNameValid()) { ImGui::Text("Program name: [%s]", rdsDecode.getPSName().c_str()); } | ||||
|             //     if (rdsDecode.radioTextValid()) { ImGui::Text("Radiotext: [%s]", rdsDecode.getRadioText().c_str()); } | ||||
|             // } | ||||
|         } | ||||
|  | ||||
|         void setBandwidth(double bandwidth) { | ||||
| @@ -93,12 +140,68 @@ namespace demod { | ||||
|         } | ||||
|  | ||||
|     private: | ||||
|         static void rdsHandler(uint8_t* data, int count, void* ctx) { | ||||
|             WFM* _this = (WFM*)ctx; | ||||
|             _this->rdsDecode.process(data, count); | ||||
|         } | ||||
|  | ||||
|         static void fftRedraw(ImGui::WaterFall::FFTRedrawArgs args, void* ctx) { | ||||
|             WFM* _this = (WFM*)ctx; | ||||
|             if (!_this->_rds) { return; } | ||||
|  | ||||
|             // Generate string depending on RDS mode | ||||
|             char buf[256]; | ||||
|             if (_this->rdsDecode.PSNameValid() && _this->rdsDecode.radioTextValid()) { | ||||
|                 sprintf(buf, "RDS: %s - %s", _this->rdsDecode.getPSName().c_str(), _this->rdsDecode.getRadioText().c_str()); | ||||
|             } | ||||
|             else if (_this->rdsDecode.PSNameValid()) { | ||||
|                 sprintf(buf, "RDS: %s", _this->rdsDecode.getPSName().c_str()); | ||||
|             } | ||||
|             else if (_this->rdsDecode.radioTextValid()) { | ||||
|                 sprintf(buf, "RDS: %s", _this->rdsDecode.getRadioText().c_str()); | ||||
|             } | ||||
|             else { | ||||
|                 return; | ||||
|             } | ||||
|  | ||||
|             // Calculate paddings | ||||
|             ImVec2 min = args.min; | ||||
|             min.x += 5.0f * style::uiScale; | ||||
|             min.y += 5.0f * style::uiScale; | ||||
|             ImVec2 tmin = min; | ||||
|             tmin.x += 5.0f * style::uiScale; | ||||
|             tmin.y += 5.0f * style::uiScale; | ||||
|             ImVec2 tmax = ImGui::CalcTextSize(buf); | ||||
|             tmax.x += tmin.x; | ||||
|             tmax.y += tmin.y; | ||||
|             ImVec2 max = tmax; | ||||
|             max.x += 5.0f * style::uiScale; | ||||
|             max.y += 5.0f * style::uiScale; | ||||
|  | ||||
|             // Draw back drop | ||||
|             args.window->DrawList->AddRectFilled(min, max, IM_COL32(0, 0, 0, 128)); | ||||
|  | ||||
|             // Draw text | ||||
|             args.window->DrawList->AddText(tmin, IM_COL32(255, 255, 0, 255), buf); | ||||
|         } | ||||
|  | ||||
|         dsp::demod::BroadcastFM demod; | ||||
|         dsp::clock_recovery::FD recov; | ||||
|         dsp::digital::BinarySlicer slice; | ||||
|         dsp::digital::ManchesterDecoder manch; | ||||
|         dsp::digital::DifferentialDecoder diff; | ||||
|         dsp::sink::Handler<uint8_t> hs; | ||||
|         EventHandler<ImGui::WaterFall::FFTRedrawArgs> fftRedrawHandler; | ||||
|  | ||||
|         rds::RDSDecoder rdsDecode; | ||||
|  | ||||
|         ConfigManager* _config = NULL; | ||||
|  | ||||
|         bool _stereo = false; | ||||
|         bool _lowPass = true; | ||||
|         bool _rds = false; | ||||
|         float muGain = 0.01; | ||||
|         float omegaGain = (0.01*0.01)/4.0; | ||||
|  | ||||
|         std::string name; | ||||
|     }; | ||||
|   | ||||
| @@ -232,16 +232,19 @@ private: | ||||
|         } | ||||
|  | ||||
|         // Noise blanker | ||||
|         if (ImGui::Checkbox(("Noise blanker (W.I.P.)##_radio_nb_ena_" + _this->name).c_str(), &_this->nbEnabled)) { | ||||
|             _this->setNBEnabled(_this->nbEnabled); | ||||
|         if (_this->nbAllowed) { | ||||
|             if (ImGui::Checkbox(("Noise blanker (W.I.P.)##_radio_nb_ena_" + _this->name).c_str(), &_this->nbEnabled)) { | ||||
|                 _this->setNBEnabled(_this->nbEnabled); | ||||
|             } | ||||
|             if (!_this->nbEnabled && _this->enabled) { style::beginDisabled(); } | ||||
|             ImGui::SameLine(); | ||||
|             ImGui::SetNextItemWidth(menuWidth - ImGui::GetCursorPosX()); | ||||
|             if (ImGui::SliderFloat(("##_radio_nb_lvl_" + _this->name).c_str(), &_this->nbLevel, _this->MIN_NB, _this->MAX_NB, "%.3fdB")) { | ||||
|                 _this->setNBLevel(_this->nbLevel); | ||||
|             } | ||||
|             if (!_this->nbEnabled && _this->enabled) { style::endDisabled(); } | ||||
|         } | ||||
|         if (!_this->nbEnabled && _this->enabled) { style::beginDisabled(); } | ||||
|         ImGui::SameLine(); | ||||
|         ImGui::SetNextItemWidth(menuWidth - ImGui::GetCursorPosX()); | ||||
|         if (ImGui::SliderFloat(("##_radio_nb_lvl_" + _this->name).c_str(), &_this->nbLevel, _this->MIN_NB, _this->MAX_NB, "%.3fdB")) { | ||||
|             _this->setNBLevel(_this->nbLevel); | ||||
|         } | ||||
|         if (!_this->nbEnabled && _this->enabled) { style::endDisabled(); } | ||||
|          | ||||
|  | ||||
|         // Squelch | ||||
|         if (ImGui::Checkbox(("Squelch##_radio_sqelch_ena_" + _this->name).c_str(), &_this->squelchEnabled)) { | ||||
| @@ -422,7 +425,7 @@ private: | ||||
|         // Configure noise blanker | ||||
|         nb.setRate(500.0 / ifSamplerate); | ||||
|         setNBLevel(nbLevel); | ||||
|         setNBEnabled(nbEnabled); | ||||
|         setNBEnabled(nbAllowed&& nbEnabled); | ||||
|  | ||||
|         // Configure FM IF Noise Reduction | ||||
|         setIFNRPreset((selectedDemodID == RADIO_DEMOD_NFM) ? ifnrPresets[fmIFPresetId] : IFNR_PRESET_BROADCAST); | ||||
|   | ||||
							
								
								
									
										251
									
								
								decoder_modules/radio/src/rds.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										251
									
								
								decoder_modules/radio/src/rds.cpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,251 @@ | ||||
| #include "rds.h" | ||||
| #include <string.h> | ||||
| #include <map> | ||||
| #include <algorithm> | ||||
|  | ||||
| namespace rds { | ||||
|     std::map<uint16_t, BlockType> SYNDROMES = { | ||||
|         { 0b1111011000, BLOCK_TYPE_A  }, | ||||
|         { 0b1111010100, BLOCK_TYPE_B  }, | ||||
|         { 0b1001011100, BLOCK_TYPE_C  }, | ||||
|         { 0b1111001100, BLOCK_TYPE_CP }, | ||||
|         { 0b1001011000, BLOCK_TYPE_D  } | ||||
|     }; | ||||
|  | ||||
|     std::map<BlockType, uint16_t> OFFSETS = { | ||||
|         { BLOCK_TYPE_A,  0b0011111100 }, | ||||
|         { BLOCK_TYPE_B,  0b0110011000 }, | ||||
|         { BLOCK_TYPE_C,  0b0101101000 }, | ||||
|         { BLOCK_TYPE_CP, 0b1101010000 }, | ||||
|         { BLOCK_TYPE_D,  0b0110110100 } | ||||
|     }; | ||||
|  | ||||
|     // This parity check matrix is given in annex B2.1 of the specificiation | ||||
|     const uint16_t PARITY_CHECK_MAT[] = { | ||||
|         0b1000000000, | ||||
|         0b0100000000, | ||||
|         0b0010000000, | ||||
|         0b0001000000, | ||||
|         0b0000100000, | ||||
|         0b0000010000, | ||||
|         0b0000001000, | ||||
|         0b0000000100, | ||||
|         0b0000000010, | ||||
|         0b0000000001, | ||||
|         0b1011011100, | ||||
|         0b0101101110, | ||||
|         0b0010110111, | ||||
|         0b1010000111, | ||||
|         0b1110011111, | ||||
|         0b1100010011, | ||||
|         0b1101010101, | ||||
|         0b1101110110, | ||||
|         0b0110111011, | ||||
|         0b1000000001, | ||||
|         0b1111011100, | ||||
|         0b0111101110, | ||||
|         0b0011110111, | ||||
|         0b1010100111, | ||||
|         0b1110001111, | ||||
|         0b1100011011 | ||||
|     }; | ||||
|  | ||||
|     const int BLOCK_LEN = 26; | ||||
|     const int DATA_LEN = 16; | ||||
|     const int POLY_LEN = 10; | ||||
|     const uint16_t LFSR_POLY = 0x5B9; | ||||
|     const uint16_t IN_POLY = 0x31B; | ||||
|  | ||||
|     void RDSDecoder::process(uint8_t* symbols, int count) { | ||||
|         for (int i = 0; i < count; i++) { | ||||
|             // Shift in the bit | ||||
|             shiftReg = ((shiftReg << 1) & 0x3FFFFFF) | (symbols[i] & 1); | ||||
|  | ||||
|             // Skip if we need to shift in new data | ||||
|             if (--skip > 0) { | ||||
|                 continue; | ||||
|             } | ||||
|  | ||||
|             // Calculate the syndrome and update sync status | ||||
|             uint16_t syn = calcSyndrome(shiftReg); | ||||
|             auto synIt = SYNDROMES.find(syn); | ||||
|             bool knownSyndrome = synIt != SYNDROMES.end(); | ||||
|             sync = std::clamp<int>(knownSyndrome ? ++sync : --sync, 0, 4); | ||||
|  | ||||
|             // if (knownSyndrome) { | ||||
|             //     printf("Found known syn: %04X\n", syn); | ||||
|             // } | ||||
|              | ||||
|             // If we're still no longer in sync, try to resync | ||||
|             if (!sync) { continue; } | ||||
|  | ||||
|             // Figure out which block we've got | ||||
|             BlockType type; | ||||
|             if (knownSyndrome) { | ||||
|                 type = SYNDROMES[syn]; | ||||
|             } | ||||
|             else { | ||||
|                 type = (BlockType)((lastType + 1) % _BLOCK_TYPE_COUNT); | ||||
|             } | ||||
|  | ||||
|             // Save block while correcting errors (NOT YET) | ||||
|             blocks[type] = shiftReg;//correctErrors(shiftReg, type); | ||||
|  | ||||
|             //printf("Block type: %d, Sync: %d, KnownSyndrome: %s, contGroup: %d, offset: %d\n", type, sync, knownSyndrome ? "yes" : "no", contGroup, i); | ||||
|  | ||||
|             // Update continous group count | ||||
|             if (type == BLOCK_TYPE_A) { contGroup = 1; } | ||||
|             else if (type == BLOCK_TYPE_B && lastType == BLOCK_TYPE_A) { contGroup++; } | ||||
|             else if ((type == BLOCK_TYPE_C || type == BLOCK_TYPE_CP) && lastType == BLOCK_TYPE_B) { contGroup++; } | ||||
|             else if (type == BLOCK_TYPE_D && (lastType == BLOCK_TYPE_C || lastType == BLOCK_TYPE_CP)) { contGroup++; } | ||||
|             else { contGroup = 0; } | ||||
|  | ||||
|             // If we've got an entire group, process it | ||||
|             if (contGroup >= 4) { | ||||
|                 contGroup = 0; | ||||
|                 decodeGroup(); | ||||
|             } | ||||
|  | ||||
|             // // Remember the last block type and skip to new block | ||||
|             lastType = type; | ||||
|             skip = BLOCK_LEN; | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     uint16_t RDSDecoder::calcSyndrome(uint32_t block) { | ||||
|         // Perform vector/matrix dot product between block and parity matrix | ||||
|         uint16_t syn = 0; | ||||
|         for(int i = 0; i < BLOCK_LEN; i++) { | ||||
|             syn ^= PARITY_CHECK_MAT[BLOCK_LEN - 1 - i] * ((block >> i) & 1); | ||||
|         } | ||||
|         return syn; | ||||
|     } | ||||
|  | ||||
|     uint32_t RDSDecoder::correctErrors(uint32_t block, BlockType type) { | ||||
|         // Init the syndrome and output | ||||
|         uint16_t syn = 0; | ||||
|         uint32_t out = block; | ||||
|  | ||||
|         // Subtract the offset from block | ||||
|         block ^= (uint32_t)OFFSETS[type]; | ||||
|  | ||||
|         // Feed in the data | ||||
|         for (int i = BLOCK_LEN - 1; i >= 0; i--) { | ||||
|             // Shift the syndrome and keep the output | ||||
|             uint8_t outBit = (syn >> (POLY_LEN - 1)) & 1; | ||||
|             syn = (syn << 1) & 0b1111111111; | ||||
|  | ||||
|             // Apply LFSR polynomial | ||||
|             syn ^= LFSR_POLY * outBit; | ||||
|  | ||||
|             // Apply input polynomial. | ||||
|             syn ^= IN_POLY * ((block >> i) & 1); | ||||
|         } | ||||
|  | ||||
|         // Use the syndrome register to do error correction | ||||
|         // TODO: For some reason there's always zeros in the syn when starting | ||||
|         uint8_t errorFound = 0; | ||||
|         for (int i = DATA_LEN - 1; i >= 0; i--) { | ||||
|             // Check if the 5 leftmost bits are all zero | ||||
|             errorFound |= !(syn & 0b11111); | ||||
|  | ||||
|             // Write output | ||||
|             uint8_t outBit = (syn >> (POLY_LEN - 1)) & 1; | ||||
|             out ^= (errorFound & outBit) << (i + POLY_LEN); | ||||
|  | ||||
|             // Shift syndrome | ||||
|             syn = (syn << 1) & 0b1111111111; | ||||
|             syn ^= LFSR_POLY * outBit * !errorFound; | ||||
|         } | ||||
|  | ||||
|         // TODO: mark block as irrecoverable if too damaged | ||||
|  | ||||
|         if (errorFound) { | ||||
|             printf("Error found\n"); | ||||
|         } | ||||
|  | ||||
|         return out; | ||||
|     } | ||||
|  | ||||
|     void RDSDecoder::decodeGroup() { | ||||
|         std::lock_guard<std::mutex> lck(groupMtx); | ||||
|         auto now = std::chrono::high_resolution_clock::now(); | ||||
|         anyGroupLastUpdate = now; | ||||
|  | ||||
|         // Decode PI code | ||||
|         countryCode = (blocks[BLOCK_TYPE_A] >> 22) & 0xF; | ||||
|         programCoverage = (AreaCoverage)((blocks[BLOCK_TYPE_A] >> 18) & 0xF); | ||||
|         programRefNumber = (blocks[BLOCK_TYPE_A] >> 10) & 0xFF; | ||||
|  | ||||
|         // Decode group type and version | ||||
|         uint8_t groupType = (blocks[BLOCK_TYPE_B] >> 22) & 0xF; | ||||
|         GroupVersion groupVer = (GroupVersion)((blocks[BLOCK_TYPE_B] >> 21) & 1); | ||||
|  | ||||
|         // Decode traffic program and program type | ||||
|         trafficProgram = (blocks[BLOCK_TYPE_B] >> 20) & 1; | ||||
|         programType = (ProgramType)((blocks[BLOCK_TYPE_B] >> 15) & 0x1F); | ||||
|          | ||||
|         if (groupType == 0) { | ||||
|             group0LastUpdate = now; | ||||
|             trafficAnnouncement = (blocks[BLOCK_TYPE_B] >> 14) & 1; | ||||
|             music = (blocks[BLOCK_TYPE_B] >> 13) & 1; | ||||
|             uint8_t diBit = (blocks[BLOCK_TYPE_B] >> 12) & 1; | ||||
|             uint8_t offset = ((blocks[BLOCK_TYPE_B] >> 10) & 0b11); | ||||
|             uint8_t diOffset = 3 - offset; | ||||
|             uint8_t psOffset = offset * 2; | ||||
|  | ||||
|             if (groupVer == GROUP_VER_A) { | ||||
|                 alternateFrequency = (blocks[BLOCK_TYPE_C] >> 10) & 0xFFFF; | ||||
|             } | ||||
|  | ||||
|             // Write DI bit to the decoder identification | ||||
|             decoderIdent &= ~(1 << diOffset); | ||||
|             decoderIdent |= (diBit << diOffset); | ||||
|  | ||||
|             // Write chars at offset the PSName | ||||
|             programServiceName[psOffset] = (blocks[BLOCK_TYPE_D] >> 18) & 0xFF; | ||||
|             programServiceName[psOffset + 1] = (blocks[BLOCK_TYPE_D] >> 10) & 0xFF; | ||||
|         } | ||||
|         else if (groupType == 2) { | ||||
|             group2LastUpdate = now; | ||||
|             // Get char offset and write chars in the Radiotext | ||||
|             bool nAB = (blocks[BLOCK_TYPE_B] >> 14) & 1; | ||||
|             uint8_t offset = (blocks[BLOCK_TYPE_B] >> 10) & 0xF; | ||||
|  | ||||
|             // Clear text field if the A/B flag changed | ||||
|             if (nAB != rtAB) { | ||||
|                 radioText = "                                                                "; | ||||
|             } | ||||
|             rtAB = nAB; | ||||
|  | ||||
|             // Write char at offset in Radiotext | ||||
|             if (groupVer == GROUP_VER_A) { | ||||
|                 uint8_t rtOffset = offset * 4; | ||||
|                 radioText[rtOffset] = (blocks[BLOCK_TYPE_C] >> 18) & 0xFF; | ||||
|                 radioText[rtOffset + 1] = (blocks[BLOCK_TYPE_C] >> 10) & 0xFF; | ||||
|                 radioText[rtOffset + 2] = (blocks[BLOCK_TYPE_D] >> 18) & 0xFF; | ||||
|                 radioText[rtOffset + 3] = (blocks[BLOCK_TYPE_D] >> 10) & 0xFF; | ||||
|             } | ||||
|             else { | ||||
|                 uint8_t rtOffset = offset * 2; | ||||
|                 radioText[rtOffset] = (blocks[BLOCK_TYPE_D] >> 18) & 0xFF; | ||||
|                 radioText[rtOffset + 1] = (blocks[BLOCK_TYPE_D] >> 10) & 0xFF; | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     bool RDSDecoder::anyGroupValid() { | ||||
|         auto now = std::chrono::high_resolution_clock::now(); | ||||
|         return (std::chrono::duration_cast<std::chrono::milliseconds>(now - anyGroupLastUpdate)).count() < 5000.0; | ||||
|     } | ||||
|  | ||||
|     bool RDSDecoder::group0Valid() { | ||||
|         auto now = std::chrono::high_resolution_clock::now(); | ||||
|         return (std::chrono::duration_cast<std::chrono::milliseconds>(now - group0LastUpdate)).count() < 5000.0; | ||||
|     } | ||||
|  | ||||
|     bool RDSDecoder::group2Valid() { | ||||
|         auto now = std::chrono::high_resolution_clock::now(); | ||||
|         return (std::chrono::duration_cast<std::chrono::milliseconds>(now - group2LastUpdate)).count() < 5000.0; | ||||
|     } | ||||
| } | ||||
							
								
								
									
										179
									
								
								decoder_modules/radio/src/rds.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										179
									
								
								decoder_modules/radio/src/rds.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,179 @@ | ||||
| #pragma once | ||||
| #include <stdint.h> | ||||
| #include <string> | ||||
| #include <chrono> | ||||
| #include <mutex> | ||||
|  | ||||
| namespace rds { | ||||
|     enum BlockType { | ||||
|         BLOCK_TYPE_A, | ||||
|         BLOCK_TYPE_B, | ||||
|         BLOCK_TYPE_C, | ||||
|         BLOCK_TYPE_CP, | ||||
|         BLOCK_TYPE_D, | ||||
|         _BLOCK_TYPE_COUNT | ||||
|     }; | ||||
|  | ||||
|     enum GroupVersion { | ||||
|         GROUP_VER_A, | ||||
|         GROUP_VER_B | ||||
|     }; | ||||
|  | ||||
|     enum AreaCoverage { | ||||
|         AREA_COVERAGE_LOCAL, | ||||
|         AREA_COVERAGE_INTERNATIONAL, | ||||
|         AREA_COVERAGE_NATIONAL, | ||||
|         AREA_COVERAGE_SUPRA_NATIONAL, | ||||
|         AREA_COVERAGE_REGIONAL1, | ||||
|         AREA_COVERAGE_REGIONAL2, | ||||
|         AREA_COVERAGE_REGIONAL3, | ||||
|         AREA_COVERAGE_REGIONAL4, | ||||
|         AREA_COVERAGE_REGIONAL5, | ||||
|         AREA_COVERAGE_REGIONAL6, | ||||
|         AREA_COVERAGE_REGIONAL7, | ||||
|         AREA_COVERAGE_REGIONAL8, | ||||
|         AREA_COVERAGE_REGIONAL9, | ||||
|         AREA_COVERAGE_REGIONAL10, | ||||
|         AREA_COVERAGE_REGIONAL11, | ||||
|         AREA_COVERAGE_REGIONAL12 | ||||
|     }; | ||||
|  | ||||
|     enum ProgramType { | ||||
|         // US Types | ||||
|         PROGRAM_TYPE_US_NONE = 0, | ||||
|         PROGRAM_TYPE_US_NEWS = 1, | ||||
|         PROGRAM_TYPE_US_INFOMATION = 2, | ||||
|         PROGRAM_TYPE_US_SPORTS = 3, | ||||
|         PROGRAM_TYPE_US_TALK = 4, | ||||
|         PROGRAM_TYPE_US_ROCK = 5, | ||||
|         PROGRAM_TYPE_US_CLASSIC_ROCK = 6, | ||||
|         PROGRAM_TYPE_US_ADULT_HITS = 7, | ||||
|         PROGRAM_TYPE_US_SOFT_ROCK = 8, | ||||
|         PROGRAM_TYPE_US_TOP_40 = 9, | ||||
|         PROGRAM_TYPE_US_COUNTRY = 10, | ||||
|         PROGRAM_TYPE_US_OLDIES = 11, | ||||
|         PROGRAM_TYPE_US_SOFT = 12, | ||||
|         PROGRAM_TYPE_US_NOSTALGIA = 13, | ||||
|         PROGRAM_TYPE_US_JAZZ = 14, | ||||
|         PROGRAM_TYPE_US_CLASSICAL = 15, | ||||
|         PROGRAM_TYPE_US_RHYTHM_AND_BLUES = 16, | ||||
|         PROGRAM_TYPE_US_SOFT_RHYTHM_AND_BLUES = 17, | ||||
|         PROGRAM_TYPE_US_FOREIGN_LANGUAGE = 18, | ||||
|         PROGRAM_TYPE_US_RELIGIOUS_MUSIC = 19, | ||||
|         PROGRAM_TYPE_US_RELIGIOUS_TALK = 20, | ||||
|         PROGRAM_TYPE_US_PERSONALITY = 21, | ||||
|         PROGRAM_TYPE_US_PUBLIC = 22, | ||||
|         PROGRAM_TYPE_US_COLLEGE = 23, | ||||
|         PROGRAM_TYPE_US_UNASSIGNED0 = 24, | ||||
|         PROGRAM_TYPE_US_UNASSIGNED1 = 25, | ||||
|         PROGRAM_TYPE_US_UNASSIGNED2 = 26, | ||||
|         PROGRAM_TYPE_US_UNASSIGNED3 = 27, | ||||
|         PROGRAM_TYPE_US_UNASSIGNED4 = 28, | ||||
|         PROGRAM_TYPE_US_WEATHER = 29, | ||||
|         PROGRAM_TYPE_US_EMERGENCY_TEST = 30, | ||||
|         PROGRAM_TYPE_US_EMERGENCY = 31, | ||||
|  | ||||
|         // EU Types | ||||
|         PROGRAM_TYPE_EU_NONE = 0, | ||||
|         PROGRAM_TYPE_EU_NEWS = 1, | ||||
|         PROGRAM_TYPE_EU_CURRENT_AFFAIRS = 2, | ||||
|         PROGRAM_TYPE_EU_INFORMATION = 3, | ||||
|         PROGRAM_TYPE_EU_SPORTS = 4, | ||||
|         PROGRAM_TYPE_EU_EDUCATION = 5, | ||||
|         PROGRAM_TYPE_EU_DRAMA = 6, | ||||
|         PROGRAM_TYPE_EU_CULTURE = 7, | ||||
|         PROGRAM_TYPE_EU_SCIENCE = 8, | ||||
|         PROGRAM_TYPE_EU_VARIED = 9, | ||||
|         PROGRAM_TYPE_EU_POP_MUSIC = 10, | ||||
|         PROGRAM_TYPE_EU_ROCK_MUSIC = 11, | ||||
|         PROGRAM_TYPE_EU_EASY_LISTENING_MUSIC = 12, | ||||
|         PROGRAM_TYPE_EU_LIGHT_CLASSICAL = 13, | ||||
|         PROGRAM_TYPE_EU_SERIOUS_CLASSICAL = 14, | ||||
|         PROGRAM_TYPE_EU_OTHER_MUSIC = 15, | ||||
|         PROGRAM_TYPE_EU_WEATHER = 16, | ||||
|         PROGRAM_TYPE_EU_FINANCE = 17, | ||||
|         PROGRAM_TYPE_EU_CHILDRENS_PROGRAM = 18, | ||||
|         PROGRAM_TYPE_EU_SOCIAL_AFFAIRS = 19, | ||||
|         PROGRAM_TYPE_EU_RELIGION = 20, | ||||
|         PROGRAM_TYPE_EU_PHONE_IN = 21, | ||||
|         PROGRAM_TYPE_EU_TRAVEL = 22, | ||||
|         PROGRAM_TYPE_EU_LEISURE = 23, | ||||
|         PROGRAM_TYPE_EU_JAZZ_MUSIC = 24, | ||||
|         PROGRAM_TYPE_EU_COUNTRY_MUSIC = 25, | ||||
|         PROGRAM_TYPE_EU_NATIONAL_MUSIC = 26, | ||||
|         PROGRAM_TYPE_EU_OLDIES_MUSIC = 27, | ||||
|         PROGRAM_TYPE_EU_FOLK_MUSIC = 28, | ||||
|         PROGRAM_TYPE_EU_DOCUMENTARY = 29, | ||||
|         PROGRAM_TYPE_EU_ALARM_TEST = 30, | ||||
|         PROGRAM_TYPE_EU_ALARM = 31 | ||||
|     }; | ||||
|  | ||||
|     enum DecoderIdentification { | ||||
|         DECODER_IDENT_STEREO = (1 << 0), | ||||
|         DECODER_IDENT_ARTIFICIAL_HEAD = (1 << 1), | ||||
|         DECODER_IDENT_COMPRESSED = (1 << 2), | ||||
|         DECODER_IDENT_VARIABLE_PTY = (1 << 0) | ||||
|     }; | ||||
|  | ||||
|     class RDSDecoder { | ||||
|     public: | ||||
|         void process(uint8_t* symbols, int count); | ||||
|  | ||||
|         bool countryCodeValid() { std::lock_guard<std::mutex> lck(groupMtx); return anyGroupValid(); } | ||||
|         uint8_t getCountryCode() { std::lock_guard<std::mutex> lck(groupMtx); return countryCode; } | ||||
|         bool programCoverageValid() { std::lock_guard<std::mutex> lck(groupMtx); return anyGroupValid(); } | ||||
|         uint8_t getProgramCoverage() { std::lock_guard<std::mutex> lck(groupMtx); return programCoverage; } | ||||
|         bool programRefNumberValid() { std::lock_guard<std::mutex> lck(groupMtx); return anyGroupValid(); } | ||||
|         uint8_t getProgramRefNumber() { std::lock_guard<std::mutex> lck(groupMtx); return programRefNumber; } | ||||
|         bool programTypeValid() { std::lock_guard<std::mutex> lck(groupMtx); return anyGroupValid(); } | ||||
|         ProgramType getProgramType() { std::lock_guard<std::mutex> lck(groupMtx); return programType; } | ||||
|  | ||||
|         bool musicValid() { std::lock_guard<std::mutex> lck(groupMtx); return group0Valid(); } | ||||
|         bool getMusic() { std::lock_guard<std::mutex> lck(groupMtx); return music; } | ||||
|         bool PSNameValid() { std::lock_guard<std::mutex> lck(groupMtx); return group0Valid(); } | ||||
|         std::string getPSName() { std::lock_guard<std::mutex> lck(groupMtx); return programServiceName; } | ||||
|  | ||||
|         bool radioTextValid() { std::lock_guard<std::mutex> lck(groupMtx); return group2Valid(); } | ||||
|         std::string getRadioText() { std::lock_guard<std::mutex> lck(groupMtx); return radioText; } | ||||
|  | ||||
|     private: | ||||
|         static uint16_t calcSyndrome(uint32_t block); | ||||
|         static uint32_t correctErrors(uint32_t block, BlockType type); | ||||
|         void decodeGroup(); | ||||
|  | ||||
|         bool anyGroupValid(); | ||||
|         bool group0Valid(); | ||||
|         bool group2Valid(); | ||||
|  | ||||
|         // State machine | ||||
|         uint32_t shiftReg = 0; | ||||
|         int sync = 0; | ||||
|         int skip = 0; | ||||
|         BlockType lastType = BLOCK_TYPE_A; | ||||
|         int contGroup = 0; | ||||
|         uint32_t blocks[_BLOCK_TYPE_COUNT]; | ||||
|  | ||||
|         // All groups | ||||
|         std::mutex groupMtx; | ||||
|         std::chrono::steady_clock::time_point anyGroupLastUpdate; | ||||
|         uint8_t countryCode; | ||||
|         AreaCoverage programCoverage; | ||||
|         uint8_t programRefNumber; | ||||
|         bool trafficProgram; | ||||
|         ProgramType programType; | ||||
|  | ||||
|         // Group type 0 | ||||
|         std::chrono::steady_clock::time_point group0LastUpdate; | ||||
|         bool trafficAnnouncement; | ||||
|         bool music; | ||||
|         uint8_t decoderIdent; | ||||
|         uint16_t alternateFrequency; | ||||
|         std::string programServiceName = "        "; | ||||
|  | ||||
|         // Group type 2 | ||||
|         std::chrono::steady_clock::time_point group2LastUpdate; | ||||
|         bool rtAB = false; | ||||
|         std::string radioText = "                                                                "; | ||||
|  | ||||
|     }; | ||||
| } | ||||
| @@ -56,10 +56,10 @@ cp $build_dir/sink_modules/network_sink/Release/network_sink.dll sdrpp_windows_x | ||||
|  | ||||
|  | ||||
| # Copy decoder modules | ||||
| # cp $build_dir/decoder_modules/m17_decoder/Release/m17_decoder.dll sdrpp_windows_x64/modules/ | ||||
| # cp "C:/Program Files/codec2/lib/libcodec2.dll" sdrpp_windows_x64/ | ||||
| cp $build_dir/decoder_modules/m17_decoder/Release/m17_decoder.dll sdrpp_windows_x64/modules/ | ||||
| cp "C:/Program Files/codec2/lib/libcodec2.dll" sdrpp_windows_x64/ | ||||
|  | ||||
| # cp $build_dir/decoder_modules/meteor_demodulator/Release/meteor_demodulator.dll sdrpp_windows_x64/modules/ | ||||
| cp $build_dir/decoder_modules/meteor_demodulator/Release/meteor_demodulator.dll sdrpp_windows_x64/modules/ | ||||
| cp $build_dir/decoder_modules/radio/Release/radio.dll sdrpp_windows_x64/modules/ | ||||
|  | ||||
|  | ||||
|   | ||||
		Reference in New Issue
	
	Block a user