From a78a862cbae8edab308c6993a4157062fd44379f Mon Sep 17 00:00:00 2001 From: "Charles J. Cliffe" Date: Sun, 9 Nov 2014 18:12:02 -0500 Subject: [PATCH] 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) --- CMakeLists.txt | 2 + src/Demodulator.cpp | 169 +++++++++++++++++++++++++++++++++++++++ src/Demodulator.h | 46 +++++++++++ src/PrimaryGLContext.cpp | 168 ++------------------------------------ src/PrimaryGLContext.h | 33 +------- 5 files changed, 227 insertions(+), 191 deletions(-) create mode 100644 src/Demodulator.cpp create mode 100644 src/Demodulator.h diff --git a/CMakeLists.txt b/CMakeLists.txt index ce0d95a..72ad941 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -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) diff --git a/src/Demodulator.cpp b/src/Demodulator.cpp new file mode 100644 index 0000000..68372a7 --- /dev/null +++ b/src/Demodulator.cpp @@ -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 *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 *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 *newBuffer = new std::vector; + 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); + } +} diff --git a/src/Demodulator.h b/src/Demodulator.h new file mode 100644 index 0000000..5c51b70 --- /dev/null +++ b/src/Demodulator.h @@ -0,0 +1,46 @@ +#pragma once + +#include +#include +#include +#include +#include + + +#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 *> audio_queue; + unsigned int audio_queue_ptr; + std::vector waveform_points; + + Demodulator(); + ~Demodulator(); + + void writeBuffer(std::vector *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; +}; diff --git a/src/PrimaryGLContext.cpp b/src/PrimaryGLContext.cpp index 7c86dc3..636a852 100644 --- a/src/PrimaryGLContext.cpp +++ b/src/PrimaryGLContext.cpp @@ -15,10 +15,6 @@ #include "AppFrame.h" #include -#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 *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 null_pts; + + canvas.Plot(spectrum_points, test_demod.waveform_points); SwapBuffers(); } @@ -277,35 +187,14 @@ void TestGLCanvas::setData(std::vector *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 *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 *newBuffer = new std::vector; - 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); } } diff --git a/src/PrimaryGLContext.h b/src/PrimaryGLContext.h index c38d489..9d25844 100644 --- a/src/PrimaryGLContext.h +++ b/src/PrimaryGLContext.h @@ -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 *data); - std::queue< std::vector * > 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 spectrum_points; - std::vector 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 fft_result; std::vector fft_result_ma; std::vector fft_result_maa; - unsigned int bandwidth; - unsigned int audio_frequency; - float audio_resample_ratio; - - PaStreamParameters outputParameters; - PaStream *stream; - - + Demodulator test_demod; wxDECLARE_EVENT_TABLE(); };