Clean exit on Ctrl-C or SIGTERM.
Use double instead of float for real-valued samples. Start implementation of stereo pilot PLL.
This commit is contained in:
		
							parent
							
								
									0ec6475902
								
							
						
					
					
						commit
						70ba5520ca
					
				|  | @ -11,8 +11,9 @@ using namespace std; | |||
| 
 | ||||
| 
 | ||||
| /** Prepare Lanczos FIR filter coefficients. */ | ||||
| template <class T> | ||||
| static void make_lanczos_coeff(unsigned int filter_order, double cutoff, | ||||
|                                SampleVector& coeff) | ||||
|                                vector<T>& coeff) | ||||
| { | ||||
|     coeff.resize(filter_order + 1); | ||||
| 
 | ||||
|  |  | |||
							
								
								
									
										2
									
								
								Filter.h
								
								
								
								
							
							
						
						
									
										2
									
								
								Filter.h
								
								
								
								
							|  | @ -48,7 +48,7 @@ public: | |||
|     void process(const IQSampleVector& samples_in, IQSampleVector& samples_out); | ||||
| 
 | ||||
| private: | ||||
|     CoeffVector     m_coeff; | ||||
|     std::vector<IQSample::value_type> m_coeff; | ||||
|     IQSampleVector  m_state; | ||||
| }; | ||||
| 
 | ||||
|  |  | |||
							
								
								
									
										122
									
								
								FmDecode.cc
								
								
								
								
							
							
						
						
									
										122
									
								
								FmDecode.cc
								
								
								
								
							|  | @ -33,12 +33,12 @@ static inline Sample fast_atan(Sample x) | |||
| 
 | ||||
| 
 | ||||
| /** Compute RMS level over a small prefix of the specified sample vector. */ | ||||
| static Sample rms_level_approx(const IQSampleVector& samples) | ||||
| static IQSample::value_type rms_level_approx(const IQSampleVector& samples) | ||||
| { | ||||
|     unsigned int n = samples.size(); | ||||
|     n = (n + 63) / 64; | ||||
| 
 | ||||
|     Sample level = 0; | ||||
|     IQSample::value_type level = 0; | ||||
|     for (unsigned int i = 0; i < n; i++) { | ||||
|         const IQSample& s = samples[i]; | ||||
|         IQSample::value_type re = s.real(), im = s.imag(); | ||||
|  | @ -79,6 +79,120 @@ void PhaseDiscriminator::process(const IQSampleVector& samples_in, | |||
| } | ||||
| 
 | ||||
| 
 | ||||
| /* ****************  class PilotPhaseLock  **************** */ | ||||
| 
 | ||||
| // Construct phase-locked loop.
 | ||||
| PilotPhaseLock::PilotPhaseLock(double freq, double bandwidth, double minsignal) | ||||
| { | ||||
|     // This is a type-2, 4th order phase-locked loop.
 | ||||
|     // I don't understand what I'm doing; hopefully it just works.
 | ||||
| 
 | ||||
|     // Set min/max locking frequencies.
 | ||||
|     m_minfreq = (freq - bandwidth) * 2.0 * M_PI; | ||||
|     m_maxfreq = (freq + bandwidth) * 2.0 * M_PI; | ||||
| 
 | ||||
|     // Set valid signal threshold.
 | ||||
|     m_minsignal  = minsignal; | ||||
|     m_lock_delay = int(10.0 / bandwidth); | ||||
|     m_lock_cnt   = 0; | ||||
| 
 | ||||
|     // Create 2nd order filter for IQ representation of phase error
 | ||||
|     // with both poles at z = exp(-2.5 * bandwidth * 2*PI).
 | ||||
|     double t = exp(-2.5 * bandwidth * 2.0 * M_PI); | ||||
|     m_iqfilter_a1 = -2.0 * t; | ||||
|     m_iqfilter_a2 = t * t; | ||||
|     m_iqfilter_b0 = m_iqfilter_a1 + m_iqfilter_a2; | ||||
| 
 | ||||
|     // Create loop filter to stabilize the loop.
 | ||||
|     // Zero at z = exp(-0.2 * bandwidth * 2*PI), 
 | ||||
|     m_loopfilter_b0 = 1.0; | ||||
|     m_loopfilter_b1 = - exp(-0.2 * bandwidth * 2.0 * M_PI); | ||||
| // TODO : loop gain 
 | ||||
| 
 | ||||
|     // Reset frequency and phase.
 | ||||
|     m_freq  = freq * 2.0 * M_PI; | ||||
|     m_phase = 0; | ||||
| 
 | ||||
|     m_iqfilter_i1 = 0; | ||||
|     m_iqfilter_i2 = 0; | ||||
|     m_iqfilter_q1 = 0; | ||||
|     m_iqfilter_q2 = 0; | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| // Process samples.
 | ||||
| void PilotPhaseLock::process(const SampleVector& samples_in, | ||||
|                              SampleVector& samples_out) | ||||
| { | ||||
|     unsigned int n = samples_in.size(); | ||||
| 
 | ||||
|     samples_out.resize(n); | ||||
| 
 | ||||
|     for (unsigned int i = 0; i < n; i++) { | ||||
| 
 | ||||
|         // Generate locked pilot tone.
 | ||||
|         Sample psin = sin(m_phase); | ||||
|         Sample pcos = cos(m_phase); | ||||
|         samples_out[i] = pcos; | ||||
| 
 | ||||
|         // Multiply locked tone with input.
 | ||||
|         Sample x = samples_in[i]; | ||||
|         Sample phasor_i = pcos * x; | ||||
|         Sample phasor_q = psin * x; | ||||
| 
 | ||||
|         // Run IQ phase error through low-pass filter.
 | ||||
|         phasor_i = m_iqfilter_b0 * phasor_i | ||||
|                    - m_iqfilter_a1 * m_iqfilter_i1 | ||||
|                    - m_iqfilter_a2 * m_iqfilter_i2; | ||||
|         phasor_q = m_iqfilter_b0 * phasor_q | ||||
|                    - m_iqfilter_a1 * m_iqfilter_q1 | ||||
|                    - m_iqfilter_a2 * m_iqfilter_q2; | ||||
|         m_iqfilter_i2 = m_iqfilter_i1; | ||||
|         m_iqfilter_i1 = phasor_i; | ||||
|         m_iqfilter_q2 = m_iqfilter_q1; | ||||
|         m_iqfilter_q1 = phasor_q; | ||||
| 
 | ||||
|         // Convert I/Q ratio to estimate of phase error.
 | ||||
|         Sample phase_err; | ||||
|         if (phasor_i > abs(phasor_q)) { | ||||
|             // We are within +/- 45 degrees from lock.
 | ||||
|             // Use simple linear approximation of arctan.
 | ||||
|             phase_err = phasor_q / phasor_i; | ||||
|         } else if (phasor_q > 0) { | ||||
|             // We are lagging more than 45 degrees behind the input.
 | ||||
|             phase_err = 1; | ||||
|         } else { | ||||
|             // We are more than 45 degrees ahead of the input.
 | ||||
|             phase_err = -1; | ||||
|         } | ||||
| 
 | ||||
|         // Detect signal threshold.
 | ||||
|         if (phasor_i > m_minsignal) { | ||||
|             m_lock_cnt++; | ||||
|         } else { | ||||
|             m_lock_cnt = 0; | ||||
|         } | ||||
| 
 | ||||
|         // Run phase error through loop filter and update frequency estimate.
 | ||||
|         m_freq += m_loopfilter_b0 * phase_err | ||||
|                   + m_loopfilter_b1 * m_loopfilter_x1; | ||||
|         m_loopfilter_x1 = phase_err; | ||||
| 
 | ||||
|         // Limit frequency to allowable range.
 | ||||
|         m_freq = max(m_minfreq, min(m_maxfreq, m_freq)); | ||||
| 
 | ||||
|         // Update locked phase.
 | ||||
|         m_phase += m_freq; | ||||
|         if (m_phase < -2.0 * M_PI) | ||||
|             m_phase += 2.0 * M_PI; | ||||
|         else if (m_phase > 2.0 * M_PI) | ||||
|             m_phase -= 2.0 * M_PI; | ||||
|     } | ||||
| 
 | ||||
|     m_lock_cnt = min(m_lock_delay, m_lock_cnt); | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| /* ****************  class FmDecoder  **************** */ | ||||
| 
 | ||||
| FmDecoder::FmDecoder(double sample_rate_if, | ||||
|  | @ -125,7 +239,7 @@ void FmDecoder::process(const IQSampleVector& samples_in, | |||
|     m_iffilter.process(m_buf_iftuned, m_buf_iffiltered); | ||||
| 
 | ||||
|     // Measure IF level.
 | ||||
|     Sample if_rms = rms_level_approx(m_buf_iffiltered); | ||||
|     double if_rms = rms_level_approx(m_buf_iffiltered); | ||||
|     m_if_level = 0.95 * m_if_level + 0.05 * if_rms; | ||||
| 
 | ||||
|     // Extract carrier frequency.
 | ||||
|  | @ -138,7 +252,7 @@ void FmDecoder::process(const IQSampleVector& samples_in, | |||
|     } | ||||
| 
 | ||||
|     // Measure baseband level.
 | ||||
|     Sample baseband_mean, baseband_rms; | ||||
|     double baseband_mean, baseband_rms; | ||||
|     samples_mean_rms(m_buf_baseband, baseband_mean, baseband_rms); | ||||
|     m_baseband_mean  = 0.95 * m_baseband_mean + 0.05 * baseband_mean; | ||||
|     m_baseband_level = 0.95 * m_baseband_level + 0.05 * baseband_rms; | ||||
|  |  | |||
							
								
								
									
										46
									
								
								FmDecode.h
								
								
								
								
							
							
						
						
									
										46
									
								
								FmDecode.h
								
								
								
								
							|  | @ -26,12 +26,46 @@ public: | |||
|     void process(const IQSampleVector& samples_in, SampleVector& samples_out); | ||||
| 
 | ||||
| private: | ||||
|     const Sample    m_freq_scale_factor; | ||||
|     IQSample        m_last_sample; | ||||
|     const Coeff m_freq_scale_factor; | ||||
|     IQSample    m_last_sample; | ||||
| }; | ||||
| 
 | ||||
| 
 | ||||
| // TODO : maybe CIC downsampling from 1 MS/s to ~ 250 kS/s
 | ||||
| /** Phase-locked loop for stereo pilot. */ | ||||
| class PilotPhaseLock | ||||
| { | ||||
| public: | ||||
| 
 | ||||
|     /**
 | ||||
|      * Construct phase-locked loop. | ||||
|      * | ||||
|      * freq       :: center frequency of capture range relative to sample freq | ||||
|      *               (0.5 is Nyquist) | ||||
|      * bandwidth  :: approximate bandwidth relative to sample frequency | ||||
|      * minsignal  :: minimum pilot amplitude | ||||
|      */ | ||||
|     PilotPhaseLock(double freq, double bandwidth, double minsignal); | ||||
| 
 | ||||
|     /** Process samples and extract pilot tone at unit amplitude. */ | ||||
|     void process(const SampleVector& samples_in, SampleVector& samples_out); | ||||
| 
 | ||||
|     /** Return true if the phase-locked loop is locked. */ | ||||
|     bool locked() const | ||||
|     { | ||||
|         return m_lock_cnt >= m_lock_delay; | ||||
|     } | ||||
| 
 | ||||
| private: | ||||
|     Coeff   m_minfreq, m_maxfreq; | ||||
|     Coeff   m_iqfilter_b0, m_iqfilter_a1, m_iqfilter_a2; | ||||
|     Coeff   m_loopfilter_b0, m_loopfilter_b1; | ||||
|     Sample  m_iqfilter_i1, m_iqfilter_i2, m_iqfilter_q1, m_iqfilter_q2; | ||||
|     Sample  m_loopfilter_x1; | ||||
|     Sample  m_freq, m_phase; | ||||
|     Sample  m_minsignal; | ||||
|     int     m_lock_delay; | ||||
|     int     m_lock_cnt; | ||||
| }; | ||||
| 
 | ||||
| 
 | ||||
| /** Complete decoder for FM broadcast signal. */ | ||||
|  | @ -120,9 +154,9 @@ private: | |||
|     const unsigned int m_downsample; | ||||
|     const bool      m_stereo_enabled; | ||||
|     bool            m_stereo_detected; | ||||
|     Sample          m_if_level; | ||||
|     Sample          m_baseband_mean; | ||||
|     Sample          m_baseband_level; | ||||
|     double          m_if_level; | ||||
|     double          m_baseband_mean; | ||||
|     double          m_baseband_level; | ||||
| 
 | ||||
|     IQSampleVector  m_buf_iftuned; | ||||
|     IQSampleVector  m_buf_iffiltered; | ||||
|  |  | |||
							
								
								
									
										6
									
								
								SoftFM.h
								
								
								
								
							
							
						
						
									
										6
									
								
								SoftFM.h
								
								
								
								
							|  | @ -7,15 +7,15 @@ | |||
| typedef std::complex<float> IQSample; | ||||
| typedef std::vector<IQSample> IQSampleVector; | ||||
| 
 | ||||
| typedef float Sample; | ||||
| typedef double Sample; | ||||
| typedef std::vector<Sample> SampleVector; | ||||
| 
 | ||||
| typedef float Coeff; | ||||
| typedef double Coeff; | ||||
| typedef std::vector<Coeff> CoeffVector; | ||||
| 
 | ||||
| 
 | ||||
| /** Compute mean and RMS over a sample vector. */ | ||||
| inline void samples_mean_rms(const SampleVector& samples, Sample& mean, Sample& rms) | ||||
| inline void samples_mean_rms(const SampleVector& samples, double& mean, double& rms) | ||||
| { | ||||
|     Sample vsum = 0; | ||||
|     Sample vsumsq = 0; | ||||
|  |  | |||
							
								
								
									
										40
									
								
								main.cc
								
								
								
								
							
							
						
						
									
										40
									
								
								main.cc
								
								
								
								
							|  | @ -5,14 +5,18 @@ | |||
|  */ | ||||
| 
 | ||||
| #include <cstdlib> | ||||
| #include <cstdio> | ||||
| #include <climits> | ||||
| #include <cmath> | ||||
| #include <csignal> | ||||
| #include <cstring> | ||||
| #include <atomic> | ||||
| #include <condition_variable> | ||||
| #include <memory> | ||||
| #include <mutex> | ||||
| #include <queue> | ||||
| #include <thread> | ||||
| #include <unistd.h> | ||||
| #include <getopt.h> | ||||
| 
 | ||||
| #include "SoftFM.h" | ||||
|  | @ -178,6 +182,20 @@ void write_output_data(AudioOutput *output, DataBuffer<Sample> *buf, | |||
| } | ||||
| 
 | ||||
| 
 | ||||
| /** Handle Ctrl-C and SIGTERM. */ | ||||
| static void handle_sigterm(int sig) | ||||
| { | ||||
|     stop_flag.store(true); | ||||
| 
 | ||||
|     string msg = "Got signal "; | ||||
|     msg += strsignal(sig); | ||||
|     msg += ", stopping ...\n"; | ||||
| 
 | ||||
|     const char *s = msg.c_str(); | ||||
|     write(STDERR_FILENO, s, strlen(s)); | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| void usage() | ||||
| { | ||||
|     fprintf(stderr, | ||||
|  | @ -315,7 +333,19 @@ int main(int argc, char **argv) | |||
|         exit(1); | ||||
|     } | ||||
| 
 | ||||
| // TODO : catch Ctrl-C 
 | ||||
|     // Catch Ctrl-C and SIGTERM
 | ||||
|     struct sigaction sigact; | ||||
|     sigact.sa_handler = handle_sigterm; | ||||
|     sigemptyset(&sigact.sa_mask); | ||||
|     sigact.sa_flags = SA_RESETHAND; | ||||
|     if (sigaction(SIGINT, &sigact, NULL) < 0) { | ||||
|         fprintf(stderr, "WARNING: can not install SIGINT handler (%s)\n", | ||||
|                 strerror(errno)); | ||||
|     } | ||||
|     if (sigaction(SIGTERM, &sigact, NULL) < 0) { | ||||
|         fprintf(stderr, "WARNING: can not install SIGTERM handler (%s)\n", | ||||
|                 strerror(errno)); | ||||
|     } | ||||
| 
 | ||||
|     // Intentionally tune at a higher frequency to avoid DC offset.
 | ||||
|     double tuner_freq = freq; | ||||
|  | @ -422,7 +452,7 @@ int main(int argc, char **argv) | |||
|     double audio_level = 0; | ||||
| 
 | ||||
|     // Main loop.
 | ||||
|     for (unsigned int block = 0; ; block++) { | ||||
|     for (unsigned int block = 0; !stop_flag.load(); block++) { | ||||
| 
 | ||||
|         // Check for overflow of source buffer.
 | ||||
|         if (!inbuf_length_warning && | ||||
|  | @ -434,12 +464,14 @@ int main(int argc, char **argv) | |||
| 
 | ||||
|         // Pull next block from source buffer.
 | ||||
|         IQSampleVector iqsamples = source_buffer.pull(); | ||||
|         if (iqsamples.empty()) | ||||
|             break; | ||||
| 
 | ||||
|         // Decode FM signal.
 | ||||
|         fm.process(iqsamples, audiosamples); | ||||
| 
 | ||||
|         // Measure audio level.
 | ||||
|         Sample audio_mean, audio_rms; | ||||
|         double audio_mean, audio_rms; | ||||
|         samples_mean_rms(audiosamples, audio_mean, audio_rms); | ||||
|         audio_level = 0.95 * audio_level + 0.05 * audio_rms; | ||||
| 
 | ||||
|  | @ -482,6 +514,8 @@ int main(int argc, char **argv) | |||
| 
 | ||||
|     } | ||||
| 
 | ||||
|     fprintf(stderr, "\n"); | ||||
| 
 | ||||
|     // Join background threads.
 | ||||
|     source_thread.join(); | ||||
|     if (outputbuf_samples > 0) { | ||||
|  |  | |||
		Loading…
	
		Reference in New Issue