2013-12-25 08:59:10 +01:00
|
|
|
|
2013-12-31 00:07:14 +01:00
|
|
|
#include <cassert>
|
2013-12-25 08:59:10 +01:00
|
|
|
#include <cmath>
|
|
|
|
|
|
|
|
#include "FmDecode.h"
|
|
|
|
|
|
|
|
using namespace std;
|
|
|
|
|
|
|
|
|
|
|
|
/** Fast approximation of atan function. */
|
|
|
|
static inline Sample fast_atan(Sample x)
|
|
|
|
{
|
|
|
|
// http://stackoverflow.com/questions/7378187/approximating-inverse-trigonometric-funcions
|
|
|
|
|
|
|
|
Sample y = 1;
|
|
|
|
Sample p = 0;
|
|
|
|
|
|
|
|
if (x < 0) {
|
|
|
|
x = -x;
|
|
|
|
y = -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (x > 1) {
|
|
|
|
p = y;
|
|
|
|
y = -y;
|
|
|
|
x = 1 / x;
|
|
|
|
}
|
|
|
|
|
|
|
|
const Sample b = 0.596227;
|
|
|
|
y *= (b*x + x*x) / (1 + 2*b*x + x*x);
|
|
|
|
|
|
|
|
return (y + p) * Sample(M_PI_2);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/** Compute RMS level over a small prefix of the specified sample vector. */
|
2013-12-29 00:34:13 +01:00
|
|
|
static IQSample::value_type rms_level_approx(const IQSampleVector& samples)
|
2013-12-25 08:59:10 +01:00
|
|
|
{
|
|
|
|
unsigned int n = samples.size();
|
|
|
|
n = (n + 63) / 64;
|
|
|
|
|
2013-12-29 00:34:13 +01:00
|
|
|
IQSample::value_type level = 0;
|
2013-12-25 08:59:10 +01:00
|
|
|
for (unsigned int i = 0; i < n; i++) {
|
|
|
|
const IQSample& s = samples[i];
|
|
|
|
IQSample::value_type re = s.real(), im = s.imag();
|
|
|
|
level += re * re + im * im;
|
|
|
|
}
|
|
|
|
|
|
|
|
return sqrt(level / n);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/* **************** class PhaseDiscriminator **************** */
|
|
|
|
|
|
|
|
// Construct phase discriminator.
|
|
|
|
PhaseDiscriminator::PhaseDiscriminator(double max_freq_dev)
|
|
|
|
: m_freq_scale_factor(1.0 / (max_freq_dev * 2.0 * M_PI))
|
|
|
|
{ }
|
|
|
|
|
|
|
|
|
|
|
|
// Process samples.
|
|
|
|
void PhaseDiscriminator::process(const IQSampleVector& samples_in,
|
|
|
|
SampleVector& samples_out)
|
|
|
|
{
|
|
|
|
unsigned int n = samples_in.size();
|
|
|
|
IQSample s0 = m_last_sample;
|
|
|
|
|
|
|
|
samples_out.resize(n);
|
|
|
|
|
|
|
|
for (unsigned int i = 0; i < n; i++) {
|
|
|
|
IQSample s1(samples_in[i]);
|
|
|
|
IQSample d(conj(s0) * s1);
|
|
|
|
// TODO : implement fast approximation of atan2
|
|
|
|
Sample w = atan2(d.imag(), d.real());
|
|
|
|
samples_out[i] = w * m_freq_scale_factor;
|
|
|
|
s0 = s1;
|
|
|
|
}
|
|
|
|
|
|
|
|
m_last_sample = s0;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2013-12-29 00:34:13 +01:00
|
|
|
/* **************** class PilotPhaseLock **************** */
|
|
|
|
|
|
|
|
// Construct phase-locked loop.
|
|
|
|
PilotPhaseLock::PilotPhaseLock(double freq, double bandwidth, double minsignal)
|
|
|
|
{
|
2013-12-30 11:22:21 +01:00
|
|
|
/*
|
|
|
|
* This is a type-2, 4th order phase-locked loop.
|
|
|
|
*
|
|
|
|
* Open-loop transfer function:
|
2013-12-31 00:07:14 +01:00
|
|
|
* G(z) = K * (z - q1) / ((z - p1) * (z - p2) * (z - 1) * (z - 1))
|
|
|
|
* K = 3.788 * (bandwidth * 2 * Pi)**3
|
|
|
|
* q1 = exp(-0.1153 * bandwidth * 2*Pi)
|
|
|
|
* p1 = exp(-1.146 * bandwidth * 2*Pi)
|
|
|
|
* p2 = exp(-5.331 * bandwidth * 2*Pi)
|
2013-12-30 11:22:21 +01:00
|
|
|
*
|
2013-12-31 00:07:14 +01:00
|
|
|
* I don't understand what I'm doing; hopefully it will work.
|
2013-12-30 11:22:21 +01:00
|
|
|
*/
|
2013-12-29 00:34:13 +01:00
|
|
|
|
|
|
|
// 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;
|
2013-12-31 00:07:14 +01:00
|
|
|
m_pilot_level = 0;
|
2013-12-29 00:34:13 +01:00
|
|
|
|
2013-12-29 00:44:10 +01:00
|
|
|
// Create 2nd order filter for I/Q representation of phase error.
|
2013-12-31 00:07:14 +01:00
|
|
|
// Filter has two poles, unit DC gain.
|
|
|
|
double p1 = exp(-1.146 * bandwidth * 2.0 * M_PI);
|
|
|
|
double p2 = exp(-5.331 * bandwidth * 2.0 * M_PI);
|
|
|
|
m_phasor_a1 = - p1 - p2;
|
|
|
|
m_phasor_a2 = p1 * p2;
|
2013-12-30 11:22:21 +01:00
|
|
|
m_phasor_b0 = 1 + m_phasor_a1 + m_phasor_a2;
|
2013-12-29 00:34:13 +01:00
|
|
|
|
|
|
|
// Create loop filter to stabilize the loop.
|
2013-12-31 00:07:14 +01:00
|
|
|
double q1 = exp(-0.1153 * bandwidth * 2.0 * M_PI);
|
|
|
|
m_loopfilter_b0 = 0.62 * bandwidth * 2.0 * M_PI;
|
|
|
|
m_loopfilter_b1 = - m_loopfilter_b0 * q1;
|
2013-12-30 11:22:21 +01:00
|
|
|
|
|
|
|
// After the loop filter, the phase error is integrated to produce
|
|
|
|
// the frequency. Then the frequency is integrated to produce the phase.
|
2013-12-31 00:07:14 +01:00
|
|
|
// These integrators form the two remaining poles, both at z = 1.
|
2013-12-29 00:34:13 +01:00
|
|
|
|
2013-12-31 00:07:14 +01:00
|
|
|
// Initialize frequency and phase.
|
2013-12-29 00:34:13 +01:00
|
|
|
m_freq = freq * 2.0 * M_PI;
|
|
|
|
m_phase = 0;
|
|
|
|
|
2013-12-29 00:44:10 +01:00
|
|
|
m_phasor_i1 = 0;
|
|
|
|
m_phasor_i2 = 0;
|
|
|
|
m_phasor_q1 = 0;
|
|
|
|
m_phasor_q2 = 0;
|
2013-12-31 00:07:14 +01:00
|
|
|
m_loopfilter_x1 = 0;
|
2014-01-19 12:19:19 +01:00
|
|
|
|
|
|
|
// Initialize PPS generator.
|
|
|
|
m_pilot_periods = 0;
|
|
|
|
m_pps_cnt = 0;
|
|
|
|
m_sample_cnt = 0;
|
2013-12-29 00:34:13 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Process samples.
|
|
|
|
void PilotPhaseLock::process(const SampleVector& samples_in,
|
|
|
|
SampleVector& samples_out)
|
|
|
|
{
|
|
|
|
unsigned int n = samples_in.size();
|
|
|
|
|
|
|
|
samples_out.resize(n);
|
|
|
|
|
2014-01-19 12:19:19 +01:00
|
|
|
bool was_locked = (m_lock_cnt >= m_lock_delay);
|
|
|
|
m_pps_events.clear();
|
|
|
|
|
2013-12-31 00:07:14 +01:00
|
|
|
if (n > 0)
|
|
|
|
m_pilot_level = 1000.0;
|
|
|
|
|
2013-12-29 00:34:13 +01:00
|
|
|
for (unsigned int i = 0; i < n; i++) {
|
|
|
|
|
|
|
|
// Generate locked pilot tone.
|
|
|
|
Sample psin = sin(m_phase);
|
|
|
|
Sample pcos = cos(m_phase);
|
2013-12-31 00:07:14 +01:00
|
|
|
|
|
|
|
// Generate double-frequency output.
|
|
|
|
// sin(2*x) = 2 * sin(x) * cos(x)
|
|
|
|
samples_out[i] = 2 * psin * pcos;
|
2013-12-29 00:34:13 +01:00
|
|
|
|
|
|
|
// Multiply locked tone with input.
|
|
|
|
Sample x = samples_in[i];
|
2013-12-31 00:07:14 +01:00
|
|
|
Sample phasor_i = psin * x;
|
|
|
|
Sample phasor_q = pcos * x;
|
2013-12-29 00:34:13 +01:00
|
|
|
|
|
|
|
// Run IQ phase error through low-pass filter.
|
2013-12-29 00:44:10 +01:00
|
|
|
phasor_i = m_phasor_b0 * phasor_i
|
|
|
|
- m_phasor_a1 * m_phasor_i1
|
|
|
|
- m_phasor_a2 * m_phasor_i2;
|
|
|
|
phasor_q = m_phasor_b0 * phasor_q
|
|
|
|
- m_phasor_a1 * m_phasor_q1
|
|
|
|
- m_phasor_a2 * m_phasor_q2;
|
|
|
|
m_phasor_i2 = m_phasor_i1;
|
|
|
|
m_phasor_i1 = phasor_i;
|
|
|
|
m_phasor_q2 = m_phasor_q1;
|
|
|
|
m_phasor_q1 = phasor_q;
|
2013-12-29 00:34:13 +01:00
|
|
|
|
|
|
|
// 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) {
|
2013-12-31 00:07:14 +01:00
|
|
|
// We are lagging more than 45 degrees behind the input.
|
2013-12-29 00:34:13 +01:00
|
|
|
phase_err = 1;
|
|
|
|
} else {
|
2013-12-31 00:07:14 +01:00
|
|
|
// We are more than 45 degrees ahead of the input.
|
2013-12-29 00:34:13 +01:00
|
|
|
phase_err = -1;
|
|
|
|
}
|
|
|
|
|
2013-12-31 00:07:14 +01:00
|
|
|
// Detect pilot level (conservative).
|
|
|
|
m_pilot_level = min(m_pilot_level, phasor_i);
|
2013-12-29 00:34:13 +01:00
|
|
|
|
|
|
|
// Run phase error through loop filter and update frequency estimate.
|
2013-12-31 00:07:14 +01:00
|
|
|
m_freq += m_loopfilter_b0 * phase_err
|
2013-12-29 00:34:13 +01:00
|
|
|
+ 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;
|
2014-01-19 12:19:19 +01:00
|
|
|
if (m_phase > 2.0 * M_PI) {
|
2013-12-29 00:34:13 +01:00
|
|
|
m_phase -= 2.0 * M_PI;
|
2014-01-19 12:19:19 +01:00
|
|
|
m_pilot_periods++;
|
|
|
|
|
|
|
|
// Generate pulse-per-second.
|
|
|
|
if (m_pilot_periods == pilot_frequency) {
|
|
|
|
m_pilot_periods = 0;
|
|
|
|
if (was_locked) {
|
|
|
|
struct PpsEvent ev;
|
2014-01-19 15:49:43 +01:00
|
|
|
ev.pps_index = m_pps_cnt;
|
|
|
|
ev.sample_index = m_sample_cnt + i;
|
|
|
|
ev.block_position = double(i) / double(n);
|
2014-01-19 12:19:19 +01:00
|
|
|
m_pps_events.push_back(ev);
|
|
|
|
m_pps_cnt++;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2013-12-29 00:34:13 +01:00
|
|
|
}
|
|
|
|
|
2013-12-31 00:07:14 +01:00
|
|
|
// Update lock status.
|
|
|
|
if (2 * m_pilot_level > m_minsignal) {
|
|
|
|
if (m_lock_cnt < m_lock_delay)
|
|
|
|
m_lock_cnt += n;
|
|
|
|
} else {
|
|
|
|
m_lock_cnt = 0;
|
|
|
|
}
|
2014-01-19 12:19:19 +01:00
|
|
|
|
|
|
|
// Drop PPS events when pilot not locked.
|
|
|
|
if (m_lock_cnt < m_lock_delay) {
|
|
|
|
m_pilot_periods = 0;
|
|
|
|
m_pps_cnt = 0;
|
|
|
|
m_pps_events.clear();
|
|
|
|
}
|
|
|
|
|
|
|
|
// Update sample counter.
|
|
|
|
m_sample_cnt += n;
|
2013-12-29 00:34:13 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2013-12-25 08:59:10 +01:00
|
|
|
/* **************** class FmDecoder **************** */
|
|
|
|
|
|
|
|
FmDecoder::FmDecoder(double sample_rate_if,
|
|
|
|
double tuning_offset,
|
|
|
|
double sample_rate_pcm,
|
|
|
|
bool stereo,
|
|
|
|
double deemphasis,
|
|
|
|
double bandwidth_if,
|
|
|
|
double freq_dev,
|
|
|
|
double bandwidth_pcm,
|
|
|
|
unsigned int downsample)
|
2013-12-30 15:58:17 +01:00
|
|
|
|
|
|
|
// Initialize member fields
|
2013-12-25 08:59:10 +01:00
|
|
|
: m_sample_rate_if(sample_rate_if)
|
2013-12-31 00:29:52 +01:00
|
|
|
, m_sample_rate_baseband(sample_rate_if / downsample)
|
2013-12-25 08:59:10 +01:00
|
|
|
, m_tuning_table_size(64)
|
|
|
|
, m_tuning_shift(lrint(-64.0 * tuning_offset / sample_rate_if))
|
|
|
|
, m_freq_dev(freq_dev)
|
|
|
|
, m_downsample(downsample)
|
|
|
|
, m_stereo_enabled(stereo)
|
|
|
|
, m_stereo_detected(false)
|
|
|
|
, m_if_level(0)
|
|
|
|
, m_baseband_mean(0)
|
|
|
|
, m_baseband_level(0)
|
2013-12-30 15:58:17 +01:00
|
|
|
|
|
|
|
// Construct FineTuner
|
2013-12-25 08:59:10 +01:00
|
|
|
, m_finetuner(m_tuning_table_size, m_tuning_shift)
|
2013-12-30 15:58:17 +01:00
|
|
|
|
|
|
|
// Construct LowPassFilterFirIQ
|
2013-12-25 08:59:10 +01:00
|
|
|
, m_iffilter(10, bandwidth_if / sample_rate_if)
|
2013-12-30 15:58:17 +01:00
|
|
|
|
|
|
|
// Construct PhaseDiscriminator
|
2013-12-25 08:59:10 +01:00
|
|
|
, m_phasedisc(freq_dev / sample_rate_if)
|
2013-12-30 15:58:17 +01:00
|
|
|
|
|
|
|
// Construct DownsampleFilter for baseband
|
|
|
|
, m_resample_baseband(8 * downsample, 0.4 / downsample, downsample, true)
|
|
|
|
|
2013-12-31 00:07:14 +01:00
|
|
|
// Construct PilotPhaseLock
|
2013-12-31 00:29:52 +01:00
|
|
|
, m_pilotpll(pilot_freq / m_sample_rate_baseband, // freq
|
|
|
|
50 / m_sample_rate_baseband, // bandwidth
|
2013-12-31 00:07:14 +01:00
|
|
|
0.04) // minsignal
|
|
|
|
|
2013-12-30 15:58:17 +01:00
|
|
|
// Construct DownsampleFilter for mono channel
|
2013-12-31 00:07:14 +01:00
|
|
|
, m_resample_mono(
|
2013-12-31 00:29:52 +01:00
|
|
|
int(m_sample_rate_baseband / 1000.0), // filter_order
|
|
|
|
bandwidth_pcm / m_sample_rate_baseband, // cutoff
|
|
|
|
m_sample_rate_baseband / sample_rate_pcm, // downsample
|
2013-12-31 00:07:14 +01:00
|
|
|
false) // integer_factor
|
|
|
|
|
|
|
|
// Construct DownsampleFilter for stereo channel
|
|
|
|
, m_resample_stereo(
|
2013-12-31 00:29:52 +01:00
|
|
|
int(m_sample_rate_baseband / 1000.0), // filter_order
|
|
|
|
bandwidth_pcm / m_sample_rate_baseband, // cutoff
|
|
|
|
m_sample_rate_baseband / sample_rate_pcm, // downsample
|
2013-12-31 00:07:14 +01:00
|
|
|
false) // integer_factor
|
2013-12-30 15:58:17 +01:00
|
|
|
|
|
|
|
// Construct HighPassFilterIir
|
2013-12-25 08:59:10 +01:00
|
|
|
, m_dcblock_mono(30.0 / sample_rate_pcm)
|
2013-12-31 00:56:53 +01:00
|
|
|
, m_dcblock_stereo(30.0 / sample_rate_pcm)
|
2013-12-30 15:58:17 +01:00
|
|
|
|
|
|
|
// Construct LowwPassFilterRC
|
2013-12-31 00:56:53 +01:00
|
|
|
, m_deemph_mono(
|
|
|
|
(deemphasis == 0) ? 1.0 : (deemphasis * sample_rate_pcm * 1.0e-6))
|
|
|
|
, m_deemph_stereo(
|
|
|
|
(deemphasis == 0) ? 1.0 : (deemphasis * sample_rate_pcm * 1.0e-6))
|
2013-12-30 15:58:17 +01:00
|
|
|
|
2013-12-25 08:59:10 +01:00
|
|
|
{
|
2013-12-30 15:58:17 +01:00
|
|
|
// nothing more to do
|
2013-12-25 08:59:10 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void FmDecoder::process(const IQSampleVector& samples_in,
|
|
|
|
SampleVector& audio)
|
|
|
|
{
|
|
|
|
// Fine tuning.
|
|
|
|
m_finetuner.process(samples_in, m_buf_iftuned);
|
|
|
|
|
|
|
|
// Low pass filter to isolate station.
|
|
|
|
m_iffilter.process(m_buf_iftuned, m_buf_iffiltered);
|
|
|
|
|
|
|
|
// Measure IF level.
|
2013-12-29 00:34:13 +01:00
|
|
|
double if_rms = rms_level_approx(m_buf_iffiltered);
|
2013-12-25 08:59:10 +01:00
|
|
|
m_if_level = 0.95 * m_if_level + 0.05 * if_rms;
|
|
|
|
|
|
|
|
// Extract carrier frequency.
|
|
|
|
m_phasedisc.process(m_buf_iffiltered, m_buf_baseband);
|
|
|
|
|
|
|
|
// Downsample baseband signal to reduce processing.
|
|
|
|
if (m_downsample > 1) {
|
|
|
|
SampleVector tmp(move(m_buf_baseband));
|
|
|
|
m_resample_baseband.process(tmp, m_buf_baseband);
|
|
|
|
}
|
|
|
|
|
|
|
|
// Measure baseband level.
|
2013-12-29 00:34:13 +01:00
|
|
|
double baseband_mean, baseband_rms;
|
2013-12-25 08:59:10 +01:00
|
|
|
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;
|
|
|
|
|
|
|
|
// Extract mono audio signal.
|
|
|
|
m_resample_mono.process(m_buf_baseband, m_buf_mono);
|
|
|
|
|
|
|
|
// DC blocking and de-emphasis.
|
2013-12-31 00:29:52 +01:00
|
|
|
m_dcblock_mono.process_inplace(m_buf_mono);
|
|
|
|
m_deemph_mono.process_inplace(m_buf_mono);
|
2013-12-25 08:59:10 +01:00
|
|
|
|
2013-12-31 00:07:14 +01:00
|
|
|
if (m_stereo_enabled) {
|
|
|
|
|
|
|
|
// Lock on stereo pilot.
|
|
|
|
m_pilotpll.process(m_buf_baseband, m_buf_rawstereo);
|
|
|
|
m_stereo_detected = m_pilotpll.locked();
|
|
|
|
|
2013-12-31 00:56:53 +01:00
|
|
|
// Demodulate stereo signal.
|
|
|
|
demod_stereo(m_buf_baseband, m_buf_rawstereo);
|
2013-12-31 00:07:14 +01:00
|
|
|
|
2013-12-31 00:56:53 +01:00
|
|
|
// Extract audio and downsample.
|
|
|
|
// NOTE: This MUST be done even if no stereo signal is detected yet,
|
|
|
|
// because the downsamplers for mono and stereo signal must be
|
|
|
|
// kept in sync.
|
|
|
|
m_resample_stereo.process(m_buf_rawstereo, m_buf_stereo);
|
2013-12-31 00:07:14 +01:00
|
|
|
|
2013-12-31 00:56:53 +01:00
|
|
|
// DC blocking and de-emphasis.
|
|
|
|
m_dcblock_stereo.process_inplace(m_buf_stereo);
|
|
|
|
m_deemph_stereo.process_inplace(m_buf_stereo);
|
2013-12-31 00:07:14 +01:00
|
|
|
|
2013-12-31 00:56:53 +01:00
|
|
|
if (m_stereo_detected) {
|
|
|
|
|
|
|
|
// Extract left/right channels from mono/stereo signals.
|
|
|
|
stereo_to_left_right(m_buf_mono, m_buf_stereo, audio);
|
|
|
|
|
|
|
|
} else {
|
|
|
|
|
|
|
|
// Duplicate mono signal in left/right channels.
|
|
|
|
mono_to_left_right(m_buf_mono, audio);
|
2013-12-31 00:07:14 +01:00
|
|
|
|
|
|
|
}
|
|
|
|
|
2013-12-31 00:56:53 +01:00
|
|
|
} else {
|
2013-12-31 00:07:14 +01:00
|
|
|
|
2013-12-31 00:56:53 +01:00
|
|
|
// Just return mono channel.
|
|
|
|
audio = move(m_buf_mono);
|
|
|
|
|
|
|
|
}
|
2013-12-25 08:59:10 +01:00
|
|
|
}
|
|
|
|
|
2013-12-31 00:07:14 +01:00
|
|
|
|
|
|
|
// Demodulate stereo L-R signal.
|
|
|
|
void FmDecoder::demod_stereo(const SampleVector& samples_baseband,
|
|
|
|
SampleVector& samples_rawstereo)
|
|
|
|
{
|
|
|
|
// Just multiply the baseband signal with the double-frequency pilot.
|
|
|
|
// And multiply by two to get the full amplitude.
|
|
|
|
// That's all.
|
|
|
|
|
|
|
|
unsigned int n = samples_baseband.size();
|
|
|
|
assert(n == samples_rawstereo.size());
|
|
|
|
|
|
|
|
for (unsigned int i = 0; i < n; i++) {
|
|
|
|
samples_rawstereo[i] *= 2 * samples_baseband[i];
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2013-12-31 00:56:53 +01:00
|
|
|
|
|
|
|
// Duplicate mono signal in left/right channels.
|
|
|
|
void FmDecoder::mono_to_left_right(const SampleVector& samples_mono,
|
|
|
|
SampleVector& audio)
|
|
|
|
{
|
|
|
|
unsigned int n = samples_mono.size();
|
|
|
|
|
|
|
|
audio.resize(2*n);
|
|
|
|
for (unsigned int i = 0; i < n; i++) {
|
|
|
|
Sample m = samples_mono[i];
|
|
|
|
audio[2*i] = m;
|
|
|
|
audio[2*i+1] = m;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Extract left/right channels from mono/stereo signals.
|
|
|
|
void FmDecoder::stereo_to_left_right(const SampleVector& samples_mono,
|
|
|
|
const SampleVector& samples_stereo,
|
|
|
|
SampleVector& audio)
|
|
|
|
{
|
|
|
|
unsigned int n = samples_mono.size();
|
|
|
|
assert(n == samples_stereo.size());
|
|
|
|
|
|
|
|
audio.resize(2*n);
|
|
|
|
for (unsigned int i = 0; i < n; i++) {
|
|
|
|
Sample m = samples_mono[i];
|
|
|
|
Sample s = samples_stereo[i];
|
|
|
|
audio[2*i] = m + s;
|
|
|
|
audio[2*i+1] = m - s;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2013-12-25 08:59:10 +01:00
|
|
|
/* end */
|