mirror of
https://github.com/cjcliffe/CubicSDR.git
synced 2024-11-22 19:58:39 -05:00
New demodulator thread code
This commit is contained in:
parent
16a9add9ef
commit
2a34fc9cf9
@ -12,11 +12,15 @@
|
|||||||
|
|
||||||
#include <vector>
|
#include <vector>
|
||||||
#include "SDRThread.h"
|
#include "SDRThread.h"
|
||||||
|
#include "DemodulatorThread.h"
|
||||||
|
#include "AudioThread.h"
|
||||||
|
|
||||||
wxBEGIN_EVENT_TABLE(AppFrame, wxFrame)
|
wxBEGIN_EVENT_TABLE(AppFrame, wxFrame)
|
||||||
//EVT_MENU(wxID_NEW, AppFrame::OnNewWindow)
|
//EVT_MENU(wxID_NEW, AppFrame::OnNewWindow)
|
||||||
EVT_MENU(wxID_CLOSE, AppFrame::OnClose)
|
EVT_MENU(wxID_CLOSE, AppFrame::OnClose)
|
||||||
EVT_THREAD(EVENT_SDR_INPUT, AppFrame::OnEventInput)
|
EVT_THREAD(EVENT_SDR_INPUT, AppFrame::OnEventInput)
|
||||||
|
EVT_THREAD(EVENT_DEMOD_INPUT, AppFrame::OnDemodInput)
|
||||||
|
EVT_THREAD(EVENT_AUDIO_INPUT, AppFrame::OnAudioInput)
|
||||||
EVT_IDLE(AppFrame::OnIdle)
|
EVT_IDLE(AppFrame::OnIdle)
|
||||||
wxEND_EVENT_TABLE()
|
wxEND_EVENT_TABLE()
|
||||||
|
|
||||||
@ -53,22 +57,39 @@ AppFrame::AppFrame() :
|
|||||||
Centre();
|
Centre();
|
||||||
Show();
|
Show();
|
||||||
|
|
||||||
m_pQueue = new SDRThreadQueue(this);
|
threadQueueSDR = new SDRThreadQueue(this);
|
||||||
|
t_SDR = new SDRThread(threadQueueSDR);
|
||||||
t_SDR = new SDRThread(m_pQueue);
|
|
||||||
if (t_SDR->Run() != wxTHREAD_NO_ERROR) {
|
if (t_SDR->Run() != wxTHREAD_NO_ERROR) {
|
||||||
wxLogError
|
wxLogError
|
||||||
("Can't create the thread!");
|
("Can't create the SDR thread!");
|
||||||
delete t_SDR;
|
delete t_SDR;
|
||||||
t_SDR = NULL;
|
t_SDR = NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
threadQueueDemod = new DemodulatorThreadQueue(this);
|
||||||
|
t_Demod = new DemodulatorThread(threadQueueDemod);
|
||||||
|
if (t_Demod->Run() != wxTHREAD_NO_ERROR) {
|
||||||
|
wxLogError
|
||||||
|
("Can't create the Demodulator thread!");
|
||||||
|
delete t_Demod;
|
||||||
|
t_Demod = NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
threadQueueAudio = new AudioThreadQueue(this);
|
||||||
|
t_Audio = new AudioThread(threadQueueAudio);
|
||||||
|
if (t_Audio->Run() != wxTHREAD_NO_ERROR) {
|
||||||
|
wxLogError
|
||||||
|
("Can't create the Audio thread!");
|
||||||
|
delete t_Audio;
|
||||||
|
t_Audio = NULL;
|
||||||
|
}
|
||||||
|
|
||||||
// t_IQBuffer = new IQBufferThread(this);
|
// t_IQBuffer = new IQBufferThread(this);
|
||||||
// if (t_IQBuffer->Run() != wxTHREAD_NO_ERROR) {
|
// if (t_IQBuffer->Run() != wxTHREAD_NO_ERROR) {
|
||||||
// wxLogError
|
// wxLogError
|
||||||
// ("Can't create the thread!");
|
// ("Can't create the thread!");
|
||||||
// delete t_IQBuffer;
|
// delete t_IQBuffer;
|
||||||
t_IQBuffer = NULL;
|
// t_IQBuffer = NULL;
|
||||||
// }
|
// }
|
||||||
|
|
||||||
// static const int attribs[] = { WX_GL_RGBA, WX_GL_DOUBLEBUFFER, 0 };
|
// static const int attribs[] = { WX_GL_RGBA, WX_GL_DOUBLEBUFFER, 0 };
|
||||||
@ -99,9 +120,11 @@ AppFrame::~AppFrame() {
|
|||||||
// }
|
// }
|
||||||
// }
|
// }
|
||||||
|
|
||||||
delete t_SDR;
|
// delete t_SDR;
|
||||||
// delete t_IQBuffer;
|
// delete t_IQBuffer;
|
||||||
delete m_pQueue;
|
delete threadQueueAudio;
|
||||||
|
delete threadQueueDemod;
|
||||||
|
delete threadQueueSDR;
|
||||||
}
|
}
|
||||||
|
|
||||||
void AppFrame::OnClose(wxCommandEvent& WXUNUSED(event)) {
|
void AppFrame::OnClose(wxCommandEvent& WXUNUSED(event)) {
|
||||||
@ -114,17 +137,50 @@ void AppFrame::OnNewWindow(wxCommandEvent& WXUNUSED(event)) {
|
|||||||
new AppFrame();
|
new AppFrame();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SDR IQ -> Demodulator
|
||||||
void AppFrame::OnEventInput(wxThreadEvent& event) {
|
void AppFrame::OnEventInput(wxThreadEvent& event) {
|
||||||
std::vector<signed char> *new_buffer = event.GetPayload<std::vector<signed char> *>();
|
std::vector<signed char> *new_buffer = event.GetPayload<std::vector<signed char> *>();
|
||||||
|
// std::cout << new_buffer->size() << std::endl;
|
||||||
test_demod.writeBuffer(new_buffer);
|
if (new_buffer->size()) {
|
||||||
scopeCanvas->setWaveformPoints(test_demod.waveform_points);
|
test_demod.writeBuffer(new_buffer);
|
||||||
spectrumCanvas->setData(new_buffer);
|
scopeCanvas->setWaveformPoints(test_demod.waveform_points);
|
||||||
waterfallCanvas->setData(new_buffer);
|
spectrumCanvas->setData(new_buffer);
|
||||||
|
waterfallCanvas->setData(new_buffer);
|
||||||
|
} else {
|
||||||
|
std::cout << "Incoming IQ data empty?" << std::endl;
|
||||||
|
}
|
||||||
delete new_buffer;
|
delete new_buffer;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Demodulator -> Audio
|
||||||
|
void AppFrame::OnDemodInput(wxThreadEvent& event) {
|
||||||
|
std::vector<float> *new_buffer = event.GetPayload<std::vector<float> *>();
|
||||||
|
|
||||||
|
if (new_buffer->size()) {
|
||||||
|
AudioThreadTask task = AudioThreadTask(AudioThreadTask::AUDIO_THREAD_DATA);
|
||||||
|
task.setData(*new_buffer);
|
||||||
|
threadQueueAudio->addTask(task, AudioThreadQueue::AUDIO_PRIORITY_HIGHEST);
|
||||||
|
} else {
|
||||||
|
std::cout << "Incoming Demod data empty?" << std::endl;
|
||||||
|
}
|
||||||
|
delete new_buffer;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Audio -> Visual
|
||||||
|
void AppFrame::OnAudioInput(wxThreadEvent& event) {
|
||||||
|
// std::vector<float> *new_buffer = event.GetPayload<std::vector<float> *>();
|
||||||
|
//
|
||||||
|
// if (new_buffer->size()) {
|
||||||
|
// AudioThreadTask task = AudioThreadTask(AudioThreadTask::AUDIO_THREAD_DATA);
|
||||||
|
// task.setData(*new_buffer);
|
||||||
|
// threadQueueAudio->addTask(task, AudioThreadQueue::AUDIO_PRIORITY_HIGHEST);
|
||||||
|
// } else {
|
||||||
|
// std::cout << "Incoming Demod data empty?" << std::endl;
|
||||||
|
// }
|
||||||
|
// delete new_buffer;
|
||||||
|
}
|
||||||
|
|
||||||
void AppFrame::OnIdle(wxIdleEvent& event) {
|
void AppFrame::OnIdle(wxIdleEvent& event) {
|
||||||
|
|
||||||
event.Skip();
|
event.Skip();
|
||||||
@ -134,7 +190,7 @@ void AppFrame::setFrequency(unsigned int freq) {
|
|||||||
frequency = freq;
|
frequency = freq;
|
||||||
SDRThreadTask task = SDRThreadTask(SDRThreadTask::SDR_THREAD_TUNING);
|
SDRThreadTask task = SDRThreadTask(SDRThreadTask::SDR_THREAD_TUNING);
|
||||||
task.setUInt(freq);
|
task.setUInt(freq);
|
||||||
m_pQueue->addTask(task, SDRThreadQueue::SDR_PRIORITY_HIGHEST);
|
threadQueueSDR->addTask(task, SDRThreadQueue::SDR_PRIORITY_HIGHEST);
|
||||||
}
|
}
|
||||||
|
|
||||||
int AppFrame::getFrequency() {
|
int AppFrame::getFrequency() {
|
||||||
|
@ -3,6 +3,9 @@
|
|||||||
#include "wx/frame.h"
|
#include "wx/frame.h"
|
||||||
#include "PrimaryGLContext.h"
|
#include "PrimaryGLContext.h"
|
||||||
#include "SDRThread.h"
|
#include "SDRThread.h"
|
||||||
|
#include "AudioThread.h"
|
||||||
|
#include "DemodulatorThread.h"
|
||||||
|
|
||||||
#include "ScopeCanvas.h"
|
#include "ScopeCanvas.h"
|
||||||
#include "SpectrumCanvas.h"
|
#include "SpectrumCanvas.h"
|
||||||
#include "WaterfallCanvas.h"
|
#include "WaterfallCanvas.h"
|
||||||
@ -14,6 +17,9 @@ public:
|
|||||||
AppFrame();
|
AppFrame();
|
||||||
~AppFrame();
|
~AppFrame();
|
||||||
void OnEventInput(wxThreadEvent& event);
|
void OnEventInput(wxThreadEvent& event);
|
||||||
|
void OnDemodInput(wxThreadEvent& event);
|
||||||
|
void OnAudioInput(wxThreadEvent& event);
|
||||||
|
|
||||||
|
|
||||||
void setFrequency(unsigned int freq);
|
void setFrequency(unsigned int freq);
|
||||||
int getFrequency();
|
int getFrequency();
|
||||||
@ -26,10 +32,16 @@ private:
|
|||||||
ScopeCanvas *scopeCanvas;
|
ScopeCanvas *scopeCanvas;
|
||||||
SpectrumCanvas *spectrumCanvas;
|
SpectrumCanvas *spectrumCanvas;
|
||||||
WaterfallCanvas *waterfallCanvas;
|
WaterfallCanvas *waterfallCanvas;
|
||||||
|
|
||||||
SDRThread *t_SDR;
|
SDRThread *t_SDR;
|
||||||
IQBufferThread *t_IQBuffer;
|
SDRThreadQueue* threadQueueSDR;
|
||||||
|
AudioThread *t_Audio;
|
||||||
|
AudioThreadQueue* threadQueueAudio;
|
||||||
|
DemodulatorThread *t_Demod;
|
||||||
|
DemodulatorThreadQueue* threadQueueDemod;
|
||||||
|
|
||||||
|
// IQBufferThread *t_IQBuffer;
|
||||||
wxCriticalSection m_pThreadCS;
|
wxCriticalSection m_pThreadCS;
|
||||||
SDRThreadQueue* m_pQueue;
|
|
||||||
unsigned int frequency;
|
unsigned int frequency;
|
||||||
|
|
||||||
Demodulator test_demod;
|
Demodulator test_demod;
|
||||||
|
@ -5,4 +5,4 @@
|
|||||||
#define FFT_SIZE 2048
|
#define FFT_SIZE 2048
|
||||||
|
|
||||||
#define DEFAULT_FREQ 98900000
|
#define DEFAULT_FREQ 98900000
|
||||||
|
#define AUDIO_FREQUENCY 48000
|
||||||
|
@ -4,40 +4,6 @@
|
|||||||
#include <windows.h>
|
#include <windows.h>
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
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() {
|
Demodulator::Demodulator() {
|
||||||
|
|
||||||
@ -45,53 +11,9 @@ Demodulator::Demodulator() {
|
|||||||
resample_ratio = (float) (bandwidth) / (float) SRATE;
|
resample_ratio = (float) (bandwidth) / (float) SRATE;
|
||||||
wbfm_frequency = 100000;
|
wbfm_frequency = 100000;
|
||||||
wbfm_resample_ratio = (float) (wbfm_frequency) / (float) bandwidth;
|
wbfm_resample_ratio = (float) (wbfm_frequency) / (float) bandwidth;
|
||||||
audio_frequency = 48000;
|
audio_frequency = AUDIO_FREQUENCY;
|
||||||
audio_resample_ratio = (float) (audio_frequency) / (float) wbfm_frequency;
|
audio_resample_ratio = (float) (audio_frequency) / (float) wbfm_frequency;
|
||||||
|
|
||||||
PaError err;
|
|
||||||
err = Pa_Initialize();
|
|
||||||
if (err != paNoError) {
|
|
||||||
std::cout << "Error starting :(\n";
|
|
||||||
}
|
|
||||||
|
|
||||||
int preferred_device = -1;
|
|
||||||
|
|
||||||
#ifdef WIN32
|
|
||||||
wchar_t dev_str[255];
|
|
||||||
memset(dev_str, 0, sizeof(wchar_t) * 255);
|
|
||||||
std::wstring env_name(L"PA_RECOMMENDED_OUTPUT_DEVICE");
|
|
||||||
GetEnvironmentVariable(env_name.c_str(), dev_str, 255);
|
|
||||||
std::wstring env_result(dev_str);
|
|
||||||
|
|
||||||
int env_dev = _wtoi(env_result.c_str());
|
|
||||||
|
|
||||||
if (env_dev || env_result.length()) {
|
|
||||||
std::cout << "Using preferred PortAudio device PA_RECOMMENDED_OUTPUT_DEVICE=" << env_result.c_str() << std::endl;
|
|
||||||
preferred_device = env_dev;
|
|
||||||
} else {
|
|
||||||
std::cout << "Environment variable PA_RECOMMENDED_OUTPUT_DEVICE not set, using PortAudio defaults." << std::endl;
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
outputParameters.device = (preferred_device != -1) ? preferred_device : Pa_GetDefaultOutputDevice();
|
|
||||||
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, audio_frequency, 1024, 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 * ((float)bandwidth / (float)SRATE) * 0.75; // filter cutoff frequency
|
float fc = 0.5f * ((float)bandwidth / (float)SRATE) * 0.75; // filter cutoff frequency
|
||||||
float ft = 0.05f; // filter transition
|
float ft = 0.05f; // filter transition
|
||||||
@ -127,91 +49,88 @@ Demodulator::Demodulator() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Demodulator::~Demodulator() {
|
Demodulator::~Demodulator() {
|
||||||
PaError err;
|
|
||||||
err = Pa_StopStream(stream);
|
|
||||||
err = Pa_CloseStream(stream);
|
|
||||||
Pa_Terminate();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void Demodulator::writeBuffer(std::vector<signed char> *data) {
|
void Demodulator::writeBuffer(std::vector<signed char> *data) {
|
||||||
liquid_float_complex filtered_input[BUF_SIZE / 2];
|
if (data->size()) {
|
||||||
|
liquid_float_complex filtered_input[BUF_SIZE / 2];
|
||||||
|
|
||||||
for (int i = 0; i < BUF_SIZE / 2; i++) {
|
for (int i = 0; i < BUF_SIZE / 2; i++) {
|
||||||
|
|
||||||
liquid_float_complex x;
|
liquid_float_complex x;
|
||||||
liquid_float_complex y;
|
liquid_float_complex y;
|
||||||
|
|
||||||
x.real = (float) (*data)[i * 2] / 127.0f;
|
x.real = (float) (*data)[i * 2] / 127.0f;
|
||||||
x.imag = (float) (*data)[i * 2 + 1] / 127.0f;
|
x.imag = (float) (*data)[i * 2 + 1] / 127.0f;
|
||||||
|
|
||||||
firfilt_crcf_push(fir_filter, x); // push input sample
|
firfilt_crcf_push(fir_filter, x); // push input sample
|
||||||
firfilt_crcf_execute(fir_filter, &y); // compute output
|
firfilt_crcf_execute(fir_filter, &y); // compute output
|
||||||
|
|
||||||
filtered_input[i] = y;
|
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) {
|
int out_size = ceil((float) (BUF_SIZE / 2) * resample_ratio);
|
||||||
waveform_floor = resampled_output[i].real;
|
|
||||||
|
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 wbfm_out_size = ceil((float) (num_written) * wbfm_resample_ratio);
|
int wbfm_out_size = ceil((float) (num_written) * wbfm_resample_ratio);
|
||||||
liquid_float_complex resampled_wbfm_output[wbfm_out_size];
|
liquid_float_complex resampled_wbfm_output[wbfm_out_size];
|
||||||
|
|
||||||
unsigned int num_wbfm_written;
|
unsigned int num_wbfm_written;
|
||||||
msresamp_crcf_execute(wbfm_resampler, resampled_output, num_written, resampled_wbfm_output, &num_wbfm_written);
|
msresamp_crcf_execute(wbfm_resampler, resampled_output, num_written, resampled_wbfm_output, &num_wbfm_written);
|
||||||
|
|
||||||
|
|
||||||
for (int i = 0; i < num_wbfm_written; i++) {
|
for (int i = 0; i < num_wbfm_written; i++) {
|
||||||
firfilt_crcf_push(fir_audio_filter, resampled_wbfm_output[i]);
|
firfilt_crcf_push(fir_audio_filter, resampled_wbfm_output[i]);
|
||||||
firfilt_crcf_execute(fir_audio_filter, &resampled_wbfm_output[i]);
|
firfilt_crcf_execute(fir_audio_filter, &resampled_wbfm_output[i]);
|
||||||
}
|
}
|
||||||
|
|
||||||
int audio_out_size = ceil((float) (num_wbfm_written) * audio_resample_ratio);
|
int audio_out_size = ceil((float) (num_wbfm_written) * audio_resample_ratio);
|
||||||
liquid_float_complex resampled_audio_output[audio_out_size];
|
liquid_float_complex resampled_audio_output[audio_out_size];
|
||||||
|
|
||||||
unsigned int num_audio_written;
|
unsigned int num_audio_written;
|
||||||
msresamp_crcf_execute(audio_resampler, resampled_wbfm_output, num_wbfm_written, resampled_audio_output, &num_audio_written);
|
msresamp_crcf_execute(audio_resampler, resampled_wbfm_output, num_wbfm_written, resampled_audio_output, &num_audio_written);
|
||||||
|
|
||||||
std::vector<float> *newBuffer = new std::vector<float>;
|
// std::vector<float> *newBuffer = new std::vector<float>;
|
||||||
newBuffer->resize(num_audio_written * 2);
|
// newBuffer->resize(num_audio_written * 2);
|
||||||
for (int i = 0; i < num_audio_written; i++) {
|
// for (int i = 0; i < num_audio_written; i++) {
|
||||||
liquid_float_complex y = resampled_audio_output[i];
|
// liquid_float_complex y = resampled_audio_output[i];
|
||||||
|
//
|
||||||
|
// (*newBuffer)[i * 2] = y.real;
|
||||||
|
// (*newBuffer)[i * 2 + 1] = y.real;
|
||||||
|
// }
|
||||||
|
|
||||||
(*newBuffer)[i * 2] = y.real;
|
if (waveform_points.size() != num_audio_written * 2) {
|
||||||
(*newBuffer)[i * 2 + 1] = y.real;
|
waveform_points.resize(num_audio_written * 2);
|
||||||
}
|
}
|
||||||
|
|
||||||
audio_queue.push(newBuffer);
|
for (int i = 0, iMax = waveform_points.size() / 2; i < iMax; i++) {
|
||||||
|
waveform_points[i * 2 + 1] = resampled_audio_output[i].real * 0.5f;
|
||||||
if (waveform_points.size() != num_audio_written * 2) {
|
waveform_points[i * 2] = ((double) i / (double) iMax);
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -9,19 +9,10 @@
|
|||||||
#include "CubicSDRDefs.h"
|
#include "CubicSDRDefs.h"
|
||||||
#include "liquid/liquid.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 {
|
class Demodulator {
|
||||||
public:
|
public:
|
||||||
std::queue<std::vector<float> *> audio_queue;
|
|
||||||
unsigned int audio_queue_ptr;
|
|
||||||
std::vector<float> waveform_points;
|
std::vector<float> waveform_points;
|
||||||
|
|
||||||
Demodulator();
|
Demodulator();
|
||||||
@ -47,7 +38,5 @@ private:
|
|||||||
|
|
||||||
unsigned int audio_frequency;
|
unsigned int audio_frequency;
|
||||||
|
|
||||||
PaStreamParameters outputParameters;
|
|
||||||
PaStream *stream;
|
|
||||||
freqdem fdem;
|
freqdem fdem;
|
||||||
};
|
};
|
||||||
|
173
src/DemodulatorThread.cpp
Normal file
173
src/DemodulatorThread.cpp
Normal file
@ -0,0 +1,173 @@
|
|||||||
|
#include "DemodulatorThread.h"
|
||||||
|
#include "CubicSDRDefs.h"
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
//wxDEFINE_EVENT(wxEVT_COMMAND_DemodulatorThread_INPUT, wxThreadEvent);
|
||||||
|
|
||||||
|
DemodulatorThread::DemodulatorThread(DemodulatorThreadQueue* pQueue, int id) :
|
||||||
|
wxThread(wxTHREAD_DETACHED), m_pQueue(pQueue), m_ID(id) {
|
||||||
|
|
||||||
|
bandwidth = 200000;
|
||||||
|
resample_ratio = (float) (bandwidth) / (float) SRATE;
|
||||||
|
wbfm_frequency = 100000;
|
||||||
|
wbfm_resample_ratio = (float) (wbfm_frequency) / (float) bandwidth;
|
||||||
|
audio_frequency = AUDIO_FREQUENCY;
|
||||||
|
audio_resample_ratio = (float) (audio_frequency) / (float) wbfm_frequency;
|
||||||
|
|
||||||
|
float fc = 0.5f * ((float) bandwidth / (float) SRATE) * 0.75; // 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);
|
||||||
|
|
||||||
|
h_len = estimate_req_filter_len(ft, As);
|
||||||
|
liquid_firdes_kaiser(h_len, 32000.0 / (float) wbfm_frequency, As, mu, h);
|
||||||
|
|
||||||
|
fir_audio_filter = firfilt_crcf_create(h, h_len);
|
||||||
|
|
||||||
|
// create multi-stage arbitrary resampler object
|
||||||
|
resampler = msresamp_crcf_create(resample_ratio, As);
|
||||||
|
msresamp_crcf_print(resampler);
|
||||||
|
|
||||||
|
wbfm_resampler = msresamp_crcf_create(wbfm_resample_ratio, As);
|
||||||
|
msresamp_crcf_print(wbfm_resampler);
|
||||||
|
|
||||||
|
audio_resampler = msresamp_crcf_create(audio_resample_ratio, As);
|
||||||
|
msresamp_crcf_print(audio_resampler);
|
||||||
|
|
||||||
|
float kf = 0.75; // modulation factor
|
||||||
|
|
||||||
|
fdem = freqdem_create(kf);
|
||||||
|
freqdem_print(fdem);
|
||||||
|
}
|
||||||
|
|
||||||
|
DemodulatorThread::~DemodulatorThread() {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
wxThread::ExitCode DemodulatorThread::Entry() {
|
||||||
|
|
||||||
|
while (!TestDestroy()) {
|
||||||
|
|
||||||
|
if (m_pQueue->stackSize()) {
|
||||||
|
|
||||||
|
while (m_pQueue->stackSize()) {
|
||||||
|
DemodulatorThreadTask task = m_pQueue->pop(); // pop a task from the queue. this will block the worker thread if queue is empty
|
||||||
|
switch (task.m_cmd) {
|
||||||
|
case DemodulatorThreadTask::DEMOD_THREAD_DATA:
|
||||||
|
std::vector<unsigned char> *data = &task.getData();
|
||||||
|
if (data->size()) {
|
||||||
|
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 wbfm_out_size = ceil((float) (num_written) * wbfm_resample_ratio);
|
||||||
|
liquid_float_complex resampled_wbfm_output[wbfm_out_size];
|
||||||
|
|
||||||
|
unsigned int num_wbfm_written;
|
||||||
|
msresamp_crcf_execute(wbfm_resampler, resampled_output, num_written, resampled_wbfm_output, &num_wbfm_written);
|
||||||
|
|
||||||
|
for (int i = 0; i < num_wbfm_written; i++) {
|
||||||
|
firfilt_crcf_push(fir_audio_filter, resampled_wbfm_output[i]);
|
||||||
|
firfilt_crcf_execute(fir_audio_filter, &resampled_wbfm_output[i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
int audio_out_size = ceil((float) (num_wbfm_written) * audio_resample_ratio);
|
||||||
|
liquid_float_complex resampled_audio_output[audio_out_size];
|
||||||
|
|
||||||
|
unsigned int num_audio_written;
|
||||||
|
msresamp_crcf_execute(audio_resampler, resampled_wbfm_output, num_wbfm_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++) {
|
||||||
|
liquid_float_complex y = resampled_audio_output[i];
|
||||||
|
|
||||||
|
(*newBuffer)[i * 2] = y.real;
|
||||||
|
(*newBuffer)[i * 2 + 1] = y.real;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
if (!TestDestroy()) {
|
||||||
|
wxThreadEvent event(wxEVT_THREAD, EVENT_DEMOD_INPUT);
|
||||||
|
event.SetPayload(newBuffer);
|
||||||
|
wxQueueEvent(m_pQueue->getHandler(), event.Clone());
|
||||||
|
} else {
|
||||||
|
delete newBuffer;
|
||||||
|
}
|
||||||
|
|
||||||
|
// std::vector<float> *newBuffer = new std::vector<float>;
|
||||||
|
// newBuffer->resize(num_audio_written * 2);
|
||||||
|
// for (int i = 0; i < num_audio_written; i++) {
|
||||||
|
// liquid_float_complex y = resampled_audio_output[i];
|
||||||
|
//
|
||||||
|
// (*newBuffer)[i * 2] = y.real;
|
||||||
|
// (*newBuffer)[i * 2 + 1] = y.real;
|
||||||
|
// }
|
||||||
|
|
||||||
|
// 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);
|
||||||
|
// }
|
||||||
|
}
|
||||||
|
// audio_queue.push(newBuffer);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Sleep(1000);
|
||||||
|
}
|
||||||
|
std::cout << std::endl << "Demodulator Thread Done." << std::endl << std::endl;
|
||||||
|
|
||||||
|
return (wxThread::ExitCode) 0;
|
||||||
|
}
|
||||||
|
|
57
src/DemodulatorThread.h
Normal file
57
src/DemodulatorThread.h
Normal file
@ -0,0 +1,57 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <queue>
|
||||||
|
#include <vector>
|
||||||
|
#include "wx/wxprec.h"
|
||||||
|
|
||||||
|
#ifndef WX_PRECOMP
|
||||||
|
#include "wx/wx.h"
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#include "wx/thread.h"
|
||||||
|
|
||||||
|
#include "DemodulatorThreadQueue.h"
|
||||||
|
|
||||||
|
#include "liquid/liquid.h"
|
||||||
|
|
||||||
|
// declare a new type of event, to be used by our DemodulatorThread class:
|
||||||
|
//wxDECLARE_EVENT(wxEVT_COMMAND_DemodulatorThread_COMPLETED, wxThreadEvent);
|
||||||
|
//wxDECLARE_EVENT(wxEVT_COMMAND_DemodulatorThread_UPDATE, wxThreadEvent);
|
||||||
|
//wxDECLARE_EVENT(wxEVT_COMMAND_DemodulatorThread_INPUT, wxThreadEvent);
|
||||||
|
|
||||||
|
enum {
|
||||||
|
EVENT_DEMOD_INPUT = wxID_HIGHEST + 1
|
||||||
|
};
|
||||||
|
|
||||||
|
class DemodulatorThread: public wxThread {
|
||||||
|
public:
|
||||||
|
std::queue<std::vector<float> *> audio_queue;
|
||||||
|
unsigned int audio_queue_ptr;
|
||||||
|
|
||||||
|
DemodulatorThread(DemodulatorThreadQueue* pQueue, int id = 0);
|
||||||
|
~DemodulatorThread();
|
||||||
|
|
||||||
|
protected:
|
||||||
|
virtual ExitCode Entry();
|
||||||
|
DemodulatorThreadQueue* m_pQueue;
|
||||||
|
int m_ID;
|
||||||
|
|
||||||
|
firfilt_crcf fir_filter;
|
||||||
|
firfilt_crcf fir_audio_filter;
|
||||||
|
|
||||||
|
unsigned int bandwidth;
|
||||||
|
|
||||||
|
msresamp_crcf resampler;
|
||||||
|
float resample_ratio;
|
||||||
|
|
||||||
|
msresamp_crcf wbfm_resampler;
|
||||||
|
float wbfm_resample_ratio;
|
||||||
|
unsigned int wbfm_frequency;
|
||||||
|
|
||||||
|
msresamp_crcf audio_resampler;
|
||||||
|
float audio_resample_ratio;
|
||||||
|
|
||||||
|
unsigned int audio_frequency;
|
||||||
|
|
||||||
|
freqdem fdem;
|
||||||
|
};
|
43
src/DemodulatorThreadQueue.cpp
Normal file
43
src/DemodulatorThreadQueue.cpp
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
#include "DemodulatorThreadQueue.h"
|
||||||
|
|
||||||
|
#include "wx/wxprec.h"
|
||||||
|
|
||||||
|
#ifndef WX_PRECOMP
|
||||||
|
#include "wx/wx.h"
|
||||||
|
#endif
|
||||||
|
|
||||||
|
DemodulatorThreadQueue::DemodulatorThreadQueue(wxEvtHandler* pParent) :
|
||||||
|
m_pParent(pParent) {
|
||||||
|
}
|
||||||
|
|
||||||
|
void DemodulatorThreadQueue::addTask(const DemodulatorThreadTask& task, const DEMOD_PRIORITY& priority) {
|
||||||
|
wxMutexLocker lock(m_MutexQueue);
|
||||||
|
m_Tasks.insert(std::make_pair(priority, task));
|
||||||
|
m_QueueCount.Post();
|
||||||
|
}
|
||||||
|
|
||||||
|
DemodulatorThreadTask DemodulatorThreadQueue::pop() {
|
||||||
|
DemodulatorThreadTask element;
|
||||||
|
m_QueueCount.Wait();
|
||||||
|
m_MutexQueue.Lock();
|
||||||
|
element = (m_Tasks.begin())->second;
|
||||||
|
m_Tasks.erase(m_Tasks.begin());
|
||||||
|
m_MutexQueue.Unlock();
|
||||||
|
return element;
|
||||||
|
}
|
||||||
|
|
||||||
|
void DemodulatorThreadQueue::report(const DemodulatorThreadTask::DEMOD_THREAD_COMMAND& cmd, const wxString& sArg, int iArg) {
|
||||||
|
wxCommandEvent evt(wxEVT_THREAD, cmd);
|
||||||
|
evt.SetString(sArg);
|
||||||
|
evt.SetInt(iArg);
|
||||||
|
m_pParent->AddPendingEvent(evt);
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t DemodulatorThreadQueue::stackSize() {
|
||||||
|
wxMutexLocker lock(m_MutexQueue);
|
||||||
|
return m_Tasks.size();
|
||||||
|
}
|
||||||
|
|
||||||
|
wxEvtHandler* DemodulatorThreadQueue::getHandler() {
|
||||||
|
return m_pParent;
|
||||||
|
}
|
28
src/DemodulatorThreadQueue.h
Normal file
28
src/DemodulatorThreadQueue.h
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <map>
|
||||||
|
#include "DemodulatorThreadTask.h"
|
||||||
|
|
||||||
|
#include "wx/event.h"
|
||||||
|
|
||||||
|
class DemodulatorThreadQueue {
|
||||||
|
public:
|
||||||
|
enum DEMOD_PRIORITY {
|
||||||
|
DEMOD_PRIORITY_HIGHEST, DEMOD_PRIORITY_HIGHER, DEMOD_PRIORITY_NORMAL, DEMOD_PRIORITY_BELOW_NORMAL, DEMOD_PRIORITY_LOW, DEMOD_PRIORITY_IDLE
|
||||||
|
};
|
||||||
|
DemodulatorThreadQueue(wxEvtHandler* pParent);
|
||||||
|
|
||||||
|
void addTask(const DemodulatorThreadTask& task, const DEMOD_PRIORITY& priority = DEMOD_PRIORITY_NORMAL);
|
||||||
|
void report(const DemodulatorThreadTask::DEMOD_THREAD_COMMAND& cmd, const wxString& sArg = wxEmptyString, int iArg = 0);
|
||||||
|
|
||||||
|
DemodulatorThreadTask pop();
|
||||||
|
size_t stackSize();
|
||||||
|
|
||||||
|
wxEvtHandler* getHandler();
|
||||||
|
|
||||||
|
private:
|
||||||
|
wxEvtHandler* m_pParent;
|
||||||
|
std::multimap<DEMOD_PRIORITY, DemodulatorThreadTask> m_Tasks;
|
||||||
|
wxMutex m_MutexQueue;
|
||||||
|
wxSemaphore m_QueueCount;
|
||||||
|
};
|
8
src/DemodulatorThreadTask.cpp
Normal file
8
src/DemodulatorThreadTask.cpp
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
#include "DemodulatorThreadTask.h"
|
||||||
|
|
||||||
|
void DemodulatorThreadTask::setData(std::vector<unsigned char> &data_in) {
|
||||||
|
data = data_in;
|
||||||
|
}
|
||||||
|
std::vector<unsigned char> &DemodulatorThreadTask::getData() {
|
||||||
|
return data;
|
||||||
|
}
|
26
src/DemodulatorThreadTask.h
Normal file
26
src/DemodulatorThreadTask.h
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <vector>
|
||||||
|
#include "wx/defs.h"
|
||||||
|
#include "wx/string.h"
|
||||||
|
|
||||||
|
class DemodulatorThreadTask {
|
||||||
|
public:
|
||||||
|
enum DEMOD_THREAD_COMMAND {
|
||||||
|
DEMOD_THREAD_EXIT = wxID_EXIT, DEMOD_THREAD_NULL = wxID_HIGHEST + 1, DEMOD_THREAD_STARTED, DEMOD_THREAD_PROCESS, DEMOD_THREAD_ERROR, DEMOD_THREAD_DATA
|
||||||
|
};
|
||||||
|
|
||||||
|
DemodulatorThreadTask() :
|
||||||
|
m_cmd(DEMOD_THREAD_NULL) {
|
||||||
|
}
|
||||||
|
DemodulatorThreadTask(DEMOD_THREAD_COMMAND cmd) :
|
||||||
|
m_cmd(cmd) {
|
||||||
|
}
|
||||||
|
|
||||||
|
void setData(std::vector<unsigned char> &data_in);
|
||||||
|
std::vector<unsigned char> &getData();
|
||||||
|
|
||||||
|
DEMOD_THREAD_COMMAND m_cmd;
|
||||||
|
|
||||||
|
std::vector<unsigned char> data;
|
||||||
|
};
|
Loading…
Reference in New Issue
Block a user