mirror of
https://github.com/cjcliffe/CubicSDR.git
synced 2024-11-04 16:31:15 -05:00
Moved demodulation code into it's own class
Preparing to make it more dynamic, able to tune multiple frequencies at once and support remaining demodulation modes (AM, USB, LSB)
This commit is contained in:
parent
14d7e431bd
commit
a78a862cba
@ -90,6 +90,7 @@ SET (cubicsdr_sources
|
||||
src/AppFrame.cpp
|
||||
src/SDRThreadQueue.cpp
|
||||
src/SDRThreadTask.cpp
|
||||
src/Demodulator.cpp
|
||||
)
|
||||
|
||||
SET (cubicsdr_headers
|
||||
@ -101,6 +102,7 @@ SET (cubicsdr_headers
|
||||
src/CubicSDRDefs.h
|
||||
src/SDRThreadQueue.h
|
||||
src/SDRThreadTask.h
|
||||
src/Demodulator.h
|
||||
)
|
||||
#configure_files(${PROJECT_SOURCE_DIR}/shaders ${PROJECT_BINARY_DIR}/shaders COPYONLY)
|
||||
#configure_files(${PROJECT_SOURCE_DIR}/png ${PROJECT_BINARY_DIR}/png COPYONLY)
|
||||
|
169
src/Demodulator.cpp
Normal file
169
src/Demodulator.cpp
Normal file
@ -0,0 +1,169 @@
|
||||
#include "Demodulator.h"
|
||||
|
||||
static int patestCallback(const void *inputBuffer, void *outputBuffer, unsigned long framesPerBuffer, const PaStreamCallbackTimeInfo* timeInfo,
|
||||
PaStreamCallbackFlags statusFlags, void *userData) {
|
||||
|
||||
Demodulator *src = (Demodulator *) userData;
|
||||
|
||||
float *out = (float*) outputBuffer;
|
||||
|
||||
if (!src->audio_queue.size()) {
|
||||
for (int i = 0; i < framesPerBuffer * 2; i++) {
|
||||
out[i] = 0;
|
||||
}
|
||||
return paContinue;
|
||||
}
|
||||
|
||||
std::vector<float> *nextBuffer = src->audio_queue.front();
|
||||
|
||||
for (int i = 0; i < framesPerBuffer * 2; i++) {
|
||||
out[i] = (*nextBuffer)[src->audio_queue_ptr];
|
||||
|
||||
src->audio_queue_ptr++;
|
||||
|
||||
if (src->audio_queue_ptr == nextBuffer->size()) {
|
||||
src->audio_queue.pop();
|
||||
delete nextBuffer;
|
||||
src->audio_queue_ptr = 0;
|
||||
if (!src->audio_queue.size()) {
|
||||
break;
|
||||
}
|
||||
nextBuffer = src->audio_queue.front();
|
||||
}
|
||||
}
|
||||
|
||||
return paContinue;
|
||||
}
|
||||
|
||||
Demodulator::Demodulator() {
|
||||
|
||||
bandwidth = 800000;
|
||||
resample_ratio = (float) (bandwidth) / (float) SRATE;
|
||||
audio_frequency = 44100;
|
||||
audio_resample_ratio = (float) (audio_frequency) / (float) bandwidth;
|
||||
|
||||
PaError err;
|
||||
err = Pa_Initialize();
|
||||
if (err != paNoError) {
|
||||
std::cout << "Error starting :(\n";
|
||||
}
|
||||
|
||||
outputParameters.device = Pa_GetDefaultOutputDevice(); /* default output device */
|
||||
if (outputParameters.device == paNoDevice) {
|
||||
std::cout << "Error: No default output device.\n";
|
||||
}
|
||||
|
||||
outputParameters.channelCount = 2; /* Stereo output, most likely supported. */
|
||||
outputParameters.sampleFormat = paFloat32; /* 32 bit floating point output. */
|
||||
outputParameters.suggestedLatency = Pa_GetDeviceInfo(outputParameters.device)->defaultLowOutputLatency;
|
||||
outputParameters.hostApiSpecificStreamInfo = NULL;
|
||||
|
||||
stream = NULL;
|
||||
|
||||
err = Pa_OpenStream(&stream, NULL, &outputParameters, 44100, 256, paClipOff, &patestCallback, this);
|
||||
|
||||
err = Pa_StartStream(stream);
|
||||
if (err != paNoError) {
|
||||
std::cout << "Error starting stream: " << Pa_GetErrorText(err) << std::endl;
|
||||
std::cout << "\tPortAudio error: " << Pa_GetErrorText(err) << std::endl;
|
||||
}
|
||||
|
||||
float fc = 0.5f * (bandwidth / SRATE); // filter cutoff frequency
|
||||
float ft = 0.05f;// filter transition
|
||||
float As = 60.0f;// stop-band attenuation [dB]
|
||||
float mu = 0.0f;// fractional timing offset
|
||||
|
||||
// estimate required filter length and generate filter
|
||||
unsigned int h_len = estimate_req_filter_len(ft, As);
|
||||
float h[h_len];
|
||||
liquid_firdes_kaiser(h_len, fc, As, mu, h);
|
||||
|
||||
fir_filter = firfilt_crcf_create(h, h_len);
|
||||
|
||||
// create multi-stage arbitrary resampler object
|
||||
resampler = msresamp_crcf_create(resample_ratio, As);
|
||||
msresamp_crcf_print(resampler);
|
||||
|
||||
audio_resampler = msresamp_crcf_create(audio_resample_ratio, As);
|
||||
msresamp_crcf_print(audio_resampler);
|
||||
|
||||
float kf = 0.1f;// modulation factor
|
||||
|
||||
fdem = freqdem_create(kf);
|
||||
freqdem_print(fdem);
|
||||
}
|
||||
|
||||
Demodulator::~Demodulator() {
|
||||
PaError err;
|
||||
err = Pa_StopStream(stream);
|
||||
err = Pa_CloseStream(stream);
|
||||
Pa_Terminate();
|
||||
}
|
||||
|
||||
void Demodulator::writeBuffer(std::vector<signed char> *data) {
|
||||
liquid_float_complex filtered_input[BUF_SIZE / 2];
|
||||
|
||||
for (int i = 0; i < BUF_SIZE / 2; i++) {
|
||||
|
||||
liquid_float_complex x;
|
||||
liquid_float_complex y;
|
||||
|
||||
x.real = (float) (*data)[i * 2] / 127.0f;
|
||||
x.imag = (float) (*data)[i * 2 + 1] / 127.0f;
|
||||
|
||||
firfilt_crcf_push(fir_filter, x); // push input sample
|
||||
firfilt_crcf_execute(fir_filter, &y); // compute output
|
||||
|
||||
filtered_input[i] = y;
|
||||
}
|
||||
|
||||
int out_size = ceil((float) (BUF_SIZE / 2) * resample_ratio);
|
||||
|
||||
liquid_float_complex resampled_output[out_size];
|
||||
|
||||
unsigned int num_written; // number of values written to buffer
|
||||
msresamp_crcf_execute(resampler, filtered_input, (BUF_SIZE / 2), resampled_output, &num_written);
|
||||
|
||||
float waveform_ceil = 0, waveform_floor = 0;
|
||||
|
||||
float pcm = 0;
|
||||
|
||||
for (int i = 0; i < num_written; i++) {
|
||||
freqdem_demodulate(fdem, resampled_output[i], &pcm);
|
||||
|
||||
resampled_output[i].real = (float) pcm;
|
||||
resampled_output[i].imag = 0;
|
||||
|
||||
if (waveform_ceil < resampled_output[i].real) {
|
||||
waveform_ceil = resampled_output[i].real;
|
||||
}
|
||||
|
||||
if (waveform_floor > resampled_output[i].real) {
|
||||
waveform_floor = resampled_output[i].real;
|
||||
}
|
||||
}
|
||||
|
||||
int audio_out_size = ceil((float) (num_written) * audio_resample_ratio);
|
||||
liquid_float_complex resampled_audio_output[audio_out_size];
|
||||
|
||||
unsigned int num_audio_written; // number of values written to buffer
|
||||
msresamp_crcf_execute(audio_resampler, resampled_output, num_written, resampled_audio_output, &num_audio_written);
|
||||
|
||||
std::vector<float> *newBuffer = new std::vector<float>;
|
||||
newBuffer->resize(num_audio_written * 2);
|
||||
for (int i = 0; i < num_audio_written; i++) {
|
||||
(*newBuffer)[i * 2] = resampled_audio_output[i].real;
|
||||
(*newBuffer)[i * 2 + 1] = resampled_audio_output[i].real;
|
||||
}
|
||||
|
||||
audio_queue.push(newBuffer);
|
||||
|
||||
if (waveform_points.size() != num_audio_written * 2) {
|
||||
waveform_points.resize(num_audio_written * 2);
|
||||
}
|
||||
|
||||
for (int i = 0, iMax = waveform_points.size() / 2; i < iMax; i++) {
|
||||
waveform_points[i * 2 + 1] = resampled_audio_output[i].real * 0.5f;
|
||||
waveform_points[i * 2] = ((double) i / (double) iMax);
|
||||
}
|
||||
}
|
46
src/Demodulator.h
Normal file
46
src/Demodulator.h
Normal file
@ -0,0 +1,46 @@
|
||||
#pragma once
|
||||
|
||||
#include <vector>
|
||||
#include <queue>
|
||||
#include <cstring>
|
||||
#include <iostream>
|
||||
#include <math.h>
|
||||
|
||||
|
||||
#include "CubicSDRDefs.h"
|
||||
#include "liquid/liquid.h"
|
||||
|
||||
#include "portaudio.h"
|
||||
#ifdef WIN32
|
||||
#include "pa_stream.h"
|
||||
#include "pa_debugprint.h"
|
||||
#endif
|
||||
|
||||
static int patestCallback(const void *inputBuffer, void *outputBuffer, unsigned long framesPerBuffer, const PaStreamCallbackTimeInfo* timeInfo,
|
||||
PaStreamCallbackFlags statusFlags, void *userData);
|
||||
|
||||
class Demodulator {
|
||||
public:
|
||||
std::queue<std::vector<float> *> audio_queue;
|
||||
unsigned int audio_queue_ptr;
|
||||
std::vector<float> waveform_points;
|
||||
|
||||
Demodulator();
|
||||
~Demodulator();
|
||||
|
||||
void writeBuffer(std::vector<signed char> *data);
|
||||
|
||||
private:
|
||||
firfilt_crcf fir_filter;
|
||||
|
||||
msresamp_crcf resampler;
|
||||
msresamp_crcf audio_resampler;
|
||||
float resample_ratio;
|
||||
unsigned int bandwidth;
|
||||
unsigned int audio_frequency;
|
||||
float audio_resample_ratio;
|
||||
|
||||
PaStreamParameters outputParameters;
|
||||
PaStream *stream;
|
||||
freqdem fdem;
|
||||
};
|
@ -15,10 +15,6 @@
|
||||
#include "AppFrame.h"
|
||||
#include <algorithm>
|
||||
|
||||
#ifdef WIN32
|
||||
#include "pa_debugprint.h"
|
||||
#endif
|
||||
|
||||
wxString glGetwxString(GLenum name) {
|
||||
const GLubyte *v = glGetString(name);
|
||||
if (v == 0) {
|
||||
@ -106,49 +102,12 @@ EVT_KEY_DOWN(TestGLCanvas::OnKeyDown)
|
||||
EVT_IDLE(TestGLCanvas::OnIdle)
|
||||
wxEND_EVENT_TABLE()
|
||||
|
||||
static int patestCallback(const void *inputBuffer, void *outputBuffer, unsigned long framesPerBuffer, const PaStreamCallbackTimeInfo* timeInfo,
|
||||
PaStreamCallbackFlags statusFlags, void *userData) {
|
||||
|
||||
TestGLCanvas *src = (TestGLCanvas *) userData;
|
||||
|
||||
float *out = (float*) outputBuffer;
|
||||
|
||||
if (!src->audio_queue.size()) {
|
||||
for (int i = 0; i < framesPerBuffer * 2; i++) {
|
||||
out[i] = 0;
|
||||
}
|
||||
return paContinue;
|
||||
}
|
||||
|
||||
std::vector<float> *nextBuffer = src->audio_queue.front();
|
||||
|
||||
for (int i = 0; i < framesPerBuffer * 2; i++) {
|
||||
out[i] = (*nextBuffer)[src->audio_queue_ptr];
|
||||
|
||||
src->audio_queue_ptr++;
|
||||
|
||||
if (src->audio_queue_ptr == nextBuffer->size()) {
|
||||
src->audio_queue.pop();
|
||||
delete nextBuffer;
|
||||
src->audio_queue_ptr = 0;
|
||||
if (!src->audio_queue.size()) {
|
||||
break;
|
||||
}
|
||||
nextBuffer = src->audio_queue.front();
|
||||
}
|
||||
}
|
||||
|
||||
return paContinue;
|
||||
}
|
||||
|
||||
TestGLCanvas::TestGLCanvas(wxWindow *parent, int *attribList) :
|
||||
wxGLCanvas(parent, wxID_ANY, attribList, wxDefaultPosition, wxDefaultSize,
|
||||
wxFULL_REPAINT_ON_RESIZE), parent(parent) {
|
||||
|
||||
bandwidth = 800000;
|
||||
resample_ratio = (float) (bandwidth) / (float) SRATE;
|
||||
audio_frequency = 44100;
|
||||
audio_resample_ratio = (float) (audio_frequency) / (float) bandwidth;
|
||||
|
||||
int in_block_size = BUF_SIZE / 2;
|
||||
int out_block_size = FFT_SIZE;
|
||||
@ -161,62 +120,11 @@ TestGLCanvas::TestGLCanvas(wxWindow *parent, int *attribList) :
|
||||
|
||||
fft_ceil_ma = fft_ceil_maa = 1.0;
|
||||
|
||||
PaError err;
|
||||
err = Pa_Initialize();
|
||||
if (err != paNoError) {
|
||||
std::cout << "Error starting :(\n";
|
||||
}
|
||||
|
||||
outputParameters.device = Pa_GetDefaultOutputDevice(); /* default output device */
|
||||
if (outputParameters.device == paNoDevice) {
|
||||
std::cout << "Error: No default output device.\n";
|
||||
}
|
||||
|
||||
outputParameters.channelCount = 2; /* Stereo output, most likely supported. */
|
||||
outputParameters.sampleFormat = paFloat32; /* 32 bit floating point output. */
|
||||
outputParameters.suggestedLatency = Pa_GetDeviceInfo(outputParameters.device)->defaultLowOutputLatency;
|
||||
outputParameters.hostApiSpecificStreamInfo = NULL;
|
||||
|
||||
stream = NULL;
|
||||
|
||||
err = Pa_OpenStream(&stream, NULL, &outputParameters, 44100, 256, paClipOff, &patestCallback, this);
|
||||
|
||||
err = Pa_StartStream(stream);
|
||||
if (err != paNoError) {
|
||||
std::cout << "Error starting stream: " << Pa_GetErrorText(err) << std::endl;
|
||||
std::cout << "\tPortAudio error: " << Pa_GetErrorText(err) << std::endl;
|
||||
}
|
||||
|
||||
float fc = 0.5f * (bandwidth / SRATE); // filter cutoff frequency
|
||||
float ft = 0.05f; // filter transition
|
||||
float As = 60.0f; // stop-band attenuation [dB]
|
||||
float mu = 0.0f; // fractional timing offset
|
||||
|
||||
// estimate required filter length and generate filter
|
||||
unsigned int h_len = estimate_req_filter_len(ft, As);
|
||||
float h[h_len];
|
||||
liquid_firdes_kaiser(h_len, fc, As, mu, h);
|
||||
|
||||
fir_filter = firfilt_crcf_create(h, h_len);
|
||||
|
||||
// create multi-stage arbitrary resampler object
|
||||
resampler = msresamp_crcf_create(resample_ratio, As);
|
||||
msresamp_crcf_print(resampler);
|
||||
|
||||
audio_resampler = msresamp_crcf_create(audio_resample_ratio, As);
|
||||
msresamp_crcf_print(audio_resampler);
|
||||
|
||||
float kf = 0.1f; // modulation factor
|
||||
|
||||
fdem = freqdem_create(kf);
|
||||
freqdem_print(fdem);
|
||||
}
|
||||
|
||||
TestGLCanvas::~TestGLCanvas() {
|
||||
PaError err;
|
||||
err = Pa_StopStream(stream);
|
||||
err = Pa_CloseStream(stream);
|
||||
Pa_Terminate();
|
||||
|
||||
}
|
||||
|
||||
void TestGLCanvas::OnPaint(wxPaintEvent& WXUNUSED(event)) {
|
||||
@ -226,7 +134,9 @@ void TestGLCanvas::OnPaint(wxPaintEvent& WXUNUSED(event)) {
|
||||
PrimaryGLContext& canvas = wxGetApp().GetContext(this);
|
||||
glViewport(0, 0, ClientSize.x, ClientSize.y);
|
||||
|
||||
canvas.Plot(spectrum_points, waveform_points);
|
||||
std::vector<float> null_pts;
|
||||
|
||||
canvas.Plot(spectrum_points, test_demod.waveform_points);
|
||||
|
||||
SwapBuffers();
|
||||
}
|
||||
@ -277,35 +187,14 @@ void TestGLCanvas::setData(std::vector<signed char> *data) {
|
||||
spectrum_points.resize(FFT_SIZE * 2);
|
||||
}
|
||||
|
||||
fftw_execute(plan[0]);
|
||||
|
||||
liquid_float_complex filtered_input[BUF_SIZE / 2];
|
||||
|
||||
for (int i = 0; i < BUF_SIZE / 2; i++) {
|
||||
|
||||
liquid_float_complex x;
|
||||
liquid_float_complex y;
|
||||
|
||||
x.real = (float) (*data)[i * 2] / 127.0f;
|
||||
x.imag = (float) (*data)[i * 2 + 1] / 127.0f;
|
||||
|
||||
firfilt_crcf_push(fir_filter, x); // push input sample
|
||||
firfilt_crcf_execute(fir_filter, &y); // compute output
|
||||
|
||||
filtered_input[i] = y;
|
||||
in[i][0] = x.real;
|
||||
in[i][1] = x.imag;
|
||||
in[i][0] = (float) (*data)[i * 2] / 127.0f;
|
||||
in[i][1] = (float) (*data)[i * 2 + 1] / 127.0f;
|
||||
}
|
||||
|
||||
int out_size = ceil((float) (BUF_SIZE / 2) * resample_ratio);
|
||||
|
||||
liquid_float_complex resampled_output[out_size];
|
||||
|
||||
unsigned int num_written; // number of values written to buffer
|
||||
msresamp_crcf_execute(resampler, filtered_input, (BUF_SIZE / 2), resampled_output, &num_written);
|
||||
fftw_execute(plan[0]);
|
||||
|
||||
double fft_ceil = 0;
|
||||
// fft_floor,
|
||||
|
||||
if (fft_result.size() < FFT_SIZE) {
|
||||
fft_result.resize(FFT_SIZE);
|
||||
@ -350,48 +239,7 @@ void TestGLCanvas::setData(std::vector<signed char> *data) {
|
||||
spectrum_points[i * 2] = ((double) i / (double) iMax);
|
||||
}
|
||||
|
||||
float waveform_ceil = 0, waveform_floor = 0;
|
||||
|
||||
float pcm = 0;
|
||||
|
||||
for (int i = 0; i < num_written; i++) {
|
||||
freqdem_demodulate(fdem, resampled_output[i], &pcm);
|
||||
|
||||
resampled_output[i].real = (float) pcm;
|
||||
resampled_output[i].imag = 0;
|
||||
|
||||
if (waveform_ceil < resampled_output[i].real) {
|
||||
waveform_ceil = resampled_output[i].real;
|
||||
}
|
||||
|
||||
if (waveform_floor > resampled_output[i].real) {
|
||||
waveform_floor = resampled_output[i].real;
|
||||
}
|
||||
}
|
||||
|
||||
int audio_out_size = ceil((float) (num_written) * audio_resample_ratio);
|
||||
liquid_float_complex resampled_audio_output[audio_out_size];
|
||||
|
||||
unsigned int num_audio_written; // number of values written to buffer
|
||||
msresamp_crcf_execute(audio_resampler, resampled_output, num_written, resampled_audio_output, &num_audio_written);
|
||||
|
||||
if (waveform_points.size() != num_audio_written * 2) {
|
||||
waveform_points.resize(num_audio_written * 2);
|
||||
}
|
||||
|
||||
for (int i = 0, iMax = waveform_points.size() / 2; i < iMax; i++) {
|
||||
waveform_points[i * 2 + 1] = resampled_audio_output[i].real * 0.5f;
|
||||
waveform_points[i * 2] = ((double) i / (double) iMax);
|
||||
}
|
||||
|
||||
std::vector<float> *newBuffer = new std::vector<float>;
|
||||
newBuffer->resize(num_audio_written * 2);
|
||||
for (int i = 0; i < num_audio_written; i++) {
|
||||
(*newBuffer)[i * 2] = resampled_audio_output[i].real;
|
||||
(*newBuffer)[i * 2 + 1] = resampled_audio_output[i].real;
|
||||
}
|
||||
|
||||
audio_queue.push(newBuffer);
|
||||
test_demod.writeBuffer(data);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -8,13 +8,7 @@
|
||||
|
||||
#include "CubicSDRDefs.h"
|
||||
#include "fftw3.h"
|
||||
|
||||
#include "liquid/liquid.h"
|
||||
|
||||
#include "portaudio.h"
|
||||
#ifdef WIN32
|
||||
#include "pa_stream.h"
|
||||
#endif
|
||||
#include "Demodulator.h"
|
||||
|
||||
class PrimaryGLContext: public wxGLContext {
|
||||
public:
|
||||
@ -32,9 +26,6 @@ public:
|
||||
|
||||
void setData(std::vector<signed char> *data);
|
||||
|
||||
std::queue< std::vector <float> * > audio_queue;
|
||||
unsigned int audio_queue_ptr;
|
||||
|
||||
private:
|
||||
void OnPaint(wxPaintEvent& event);
|
||||
void OnKeyDown(wxKeyEvent& event);
|
||||
@ -43,36 +34,16 @@ private:
|
||||
|
||||
wxWindow *parent;
|
||||
std::vector<float> spectrum_points;
|
||||
std::vector<float> waveform_points;
|
||||
|
||||
fftw_complex *in, *out[2];
|
||||
fftw_plan plan[2];
|
||||
|
||||
firfilt_crcf fir_filter;
|
||||
|
||||
float pre_r;
|
||||
float pre_j;
|
||||
float droop_ofs, droop_ofs_ma, droop_ofs_maa;
|
||||
|
||||
msresamp_crcf resampler;
|
||||
msresamp_crcf audio_resampler;
|
||||
float resample_ratio;
|
||||
|
||||
freqdem fdem;
|
||||
|
||||
float fft_ceil_ma, fft_ceil_maa;
|
||||
|
||||
std::vector<float> fft_result;
|
||||
std::vector<float> fft_result_ma;
|
||||
std::vector<float> fft_result_maa;
|
||||
|
||||
unsigned int bandwidth;
|
||||
unsigned int audio_frequency;
|
||||
float audio_resample_ratio;
|
||||
|
||||
PaStreamParameters outputParameters;
|
||||
PaStream *stream;
|
||||
|
||||
|
||||
Demodulator test_demod;
|
||||
wxDECLARE_EVENT_TABLE();
|
||||
};
|
||||
|
Loading…
Reference in New Issue
Block a user