kopia lustrzana https://github.com/ogre/habdec
427 wiersze
10 KiB
C++
427 wiersze
10 KiB
C++
#include <iostream>
|
|
#include <iomanip>
|
|
#include <numeric>
|
|
using namespace std;
|
|
|
|
|
|
#include <FL/Fl.H>
|
|
#include <FL/Fl_Window.H>
|
|
#include <FL/Fl_Double_Window.H>
|
|
#include <FL/Fl_Box.H>
|
|
#include <FL/fl_draw.H>
|
|
#include <FL/Fl_Value_Slider.H>
|
|
#include <FL/Fl_Hor_Value_Slider.H>
|
|
#include <FL/Fl_Spinner.H>
|
|
#include <FL/Fl_Group.H>
|
|
#include <FL/Fl_Pack.H>
|
|
#include <FL/Fl_Roller.H>
|
|
#include <FL/Fl_Value_Input.H>
|
|
#include <FL/Fl_Button.H>
|
|
#include <FL/names.h>
|
|
|
|
|
|
#include "IQSource/IQSource_File.h"
|
|
#include "IQSource/IQSource_SoapySDR.h"
|
|
#include "Decoder/Decoder.h"
|
|
|
|
#include "gui_utils.h"
|
|
|
|
using namespace std;
|
|
using namespace habdec;
|
|
|
|
typedef float TReal;
|
|
IQVector<TReal> G_SAMPLES;
|
|
Decoder<TReal> G_DECODER;
|
|
IQSource* G_IQ_SRC_PTR = 0;
|
|
double G_FREQ = 100e6; // 434.274451e6;
|
|
double G_SAMPLING_RATE = 0;
|
|
double G_FREQ_SPAN = 0;
|
|
bool DO_CORRECTION = false;
|
|
|
|
|
|
void SetFrequency(double freq)
|
|
{
|
|
if(G_IQ_SRC_PTR)
|
|
{
|
|
G_FREQ = freq;
|
|
double freq_double = freq;
|
|
G_IQ_SRC_PTR->setOption("frequency_double", &freq_double);
|
|
}
|
|
}
|
|
|
|
void RunDecoder()
|
|
{
|
|
size_t max_samples_num = 256*256; // it doesn't work for small batches and radio ...
|
|
G_SAMPLES.resize(max_samples_num);
|
|
|
|
while(1)
|
|
{
|
|
typedef std::chrono::nanoseconds TDur;
|
|
|
|
auto _start = std::chrono::high_resolution_clock::now();
|
|
size_t count = G_IQ_SRC_PTR->get( G_SAMPLES.data(), G_SAMPLES.size() );
|
|
G_DECODER.pushSamples(G_SAMPLES);
|
|
|
|
G_DECODER();
|
|
TDur _duration = std::chrono::duration_cast<TDur>(std::chrono::high_resolution_clock::now() - _start);
|
|
// std::cout<<"Decoder duration "<<_duration.count()<<" nS "<<int(double(10e9)/_duration.count())<<" / second\n";
|
|
|
|
static auto last_afc_time = std::chrono::high_resolution_clock::now();
|
|
|
|
if( std::chrono::duration_cast< std::chrono::microseconds >
|
|
(std::chrono::high_resolution_clock::now() - last_afc_time).count() > 1000000
|
|
)
|
|
{
|
|
double freq_corr = G_DECODER.getFrequencyCorrection();
|
|
if(DO_CORRECTION)
|
|
{
|
|
if( 100 < abs(freq_corr) )
|
|
{
|
|
G_FREQ += freq_corr;
|
|
G_IQ_SRC_PTR->setOption("frequency_double", &G_FREQ);
|
|
G_DECODER.resetFrequencyCorrection(freq_corr);
|
|
last_afc_time = std::chrono::high_resolution_clock::now();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
int InitProcess(int baud, int a_bits, int a_stops, std::string fname)
|
|
{
|
|
if(fname != "")
|
|
{
|
|
cout<<"File"<<endl;
|
|
|
|
G_SAMPLING_RATE = 10027008 / 256;
|
|
G_SAMPLES.samplingRate(G_SAMPLING_RATE);
|
|
|
|
G_IQ_SRC_PTR = new IQSource_File<TReal>;
|
|
G_IQ_SRC_PTR->setOption("sampling_rate_double", &G_SAMPLING_RATE);
|
|
G_IQ_SRC_PTR->setOption("file_string", &fname);
|
|
bool realtime = false;
|
|
G_IQ_SRC_PTR->setOption("realtime_bool", &realtime);
|
|
bool loop = true;
|
|
G_IQ_SRC_PTR->setOption("loop_bool", &loop);
|
|
|
|
if( !G_IQ_SRC_PTR->init() )
|
|
{
|
|
cout<<"IQSource_File::init failed."<<endl;
|
|
return 1;
|
|
}
|
|
|
|
cout<<"IQSource_File::count: "<<G_IQ_SRC_PTR->count()<<endl;
|
|
}
|
|
else
|
|
{
|
|
cout<<"RADIO"<<endl;
|
|
|
|
// size_t max_samples_num = 256 * 256; // it doesn't work for small batches...
|
|
// G_SAMPLES.resize(max_samples_num);
|
|
|
|
SoapySDR::KwargsList device_list = SoapySDR::Device::enumerate();
|
|
if(!device_list.size())
|
|
{
|
|
cout<<"No SoapySDR devices found. Exit."<<endl;
|
|
return 0;
|
|
}
|
|
auto& device = device_list.back();
|
|
|
|
G_IQ_SRC_PTR = new IQSource_SoapySDR;
|
|
G_IQ_SRC_PTR->setOption("SoapySDR_Kwargs", &device);
|
|
|
|
if( !G_IQ_SRC_PTR->init() )
|
|
{
|
|
cout<<"IQSource_SoapySDR::init failed."<<endl;
|
|
return 1;
|
|
}
|
|
|
|
double sr;
|
|
G_IQ_SRC_PTR->getOption("sampling_rate_double", &sr);
|
|
cout<<C_RED<<"SETTING SR "<<sr<<C_OFF<<endl;
|
|
G_SAMPLES.samplingRate(sr);
|
|
|
|
const double gain = 15;
|
|
G_IQ_SRC_PTR->setOption("gain_double", &gain);
|
|
|
|
G_FREQ = 434.274e6;
|
|
SetFrequency(G_FREQ); // RTL
|
|
}
|
|
|
|
cout<<"G_IQ_SRC_PTR->sampling_rate() "<<G_IQ_SRC_PTR->samplingRate()<<endl;
|
|
|
|
if( !G_IQ_SRC_PTR->start() )
|
|
{
|
|
cout<<"IQSource_File::start failed."<<endl;
|
|
return 1;
|
|
}
|
|
|
|
if( !G_IQ_SRC_PTR->isRunning() )
|
|
{
|
|
cout<<"IQSource_File::isRunning failed."<<endl;
|
|
return 1;
|
|
}
|
|
|
|
// DECODER
|
|
G_DECODER.baud(baud);
|
|
G_DECODER.rtty_bits(a_bits);
|
|
G_DECODER.rtty_stops(a_stops);
|
|
G_DECODER.livePrint(true);
|
|
|
|
// std::this_thread::sleep_for( std::chrono::duration<double, std::milli>(1000) );
|
|
|
|
std::thread* p_thread = new std::thread(RunDecoder);
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
|
|
/////////////////////////////////////////////////////////////////////////
|
|
/////////////////////////////////////////////////////////////////////////
|
|
|
|
|
|
typedef uchar LUMA_T;
|
|
typedef uchar RGB_T;
|
|
#define BG_COLOR 45
|
|
const int RES_X = 1024;
|
|
const int RES_Y = 256;
|
|
|
|
RGB_T SPECTRUM_BITMAP_RGB [RES_X*RES_Y*3]; // FFT
|
|
RGB_T TIME_BITMAP_RGB [RES_X*RES_Y*3]; // Demodulated samples
|
|
|
|
class FreqViewport : public Fl_Box
|
|
{
|
|
public:
|
|
FreqViewport(int X,int Y,int W,int H, const char*L=0)
|
|
: Fl_Box(X,Y,W,H,L), m_x(X), m_y(Y), m_res_x(W), m_res_y(H)
|
|
{
|
|
box(FL_FLAT_BOX);
|
|
color(BG_COLOR);
|
|
Fl::add_timeout(0.1, FreqViewport_CB, (void*)this);
|
|
}
|
|
|
|
private:
|
|
const int m_x;
|
|
const int m_y;
|
|
const int m_res_x;
|
|
const int m_res_y;
|
|
|
|
// frequency drag
|
|
int m_xMB;
|
|
bool m_DRAG;
|
|
int m_xMB_x;
|
|
int m_drag_freq_start;
|
|
|
|
// IQVector<TReal> m_frequencies_;
|
|
std::vector<TReal> m_magnitudesArr;
|
|
|
|
int handle(int event)
|
|
{
|
|
if(event == FL_MOUSEWHEEL)
|
|
{
|
|
size_t decimation_factor = G_DECODER.getDecimationFactor();
|
|
size_t new_decim_exp = log2(decimation_factor) - Fl::event_dy();
|
|
cout<<"Decimation: "<<pow(2,new_decim_exp)<<endl;
|
|
G_DECODER.setupDecimationStagesFactor(pow(2,new_decim_exp));
|
|
return 1;
|
|
}
|
|
if(event ==FL_PUSH)
|
|
{
|
|
m_xMB = Fl::event_button();
|
|
m_xMB_x = Fl::event_x();
|
|
m_drag_freq_start = G_FREQ;
|
|
return 1;
|
|
}
|
|
if(event == FL_RELEASE)
|
|
{
|
|
m_xMB = 0;
|
|
return 1;
|
|
}
|
|
if(m_xMB && event == FL_DRAG)
|
|
{
|
|
double _dx_0_1 = double(Fl::event_x()-m_xMB_x) / w();
|
|
|
|
double freq_change = _dx_0_1 * G_DECODER.getDecimatedSamplingRate();
|
|
if(m_xMB != FL_LEFT_MOUSE)
|
|
freq_change *= 0.1;
|
|
|
|
double _f_new = double(m_drag_freq_start) - freq_change;
|
|
cout<<_f_new<<endl;
|
|
SetFrequency(_f_new);
|
|
|
|
return 1;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
void draw()
|
|
{
|
|
SpectrumInfo<TReal> spectr_inf = G_DECODER.getSpectrumInfo();
|
|
if(!spectr_inf.size())
|
|
return;
|
|
m_magnitudesArr = spectr_inf;
|
|
|
|
if( !m_magnitudesArr.size() )
|
|
{
|
|
cout<<"if( !m_magnitudesArr.size() )"<<endl;
|
|
return;
|
|
}
|
|
|
|
const double resize = double(m_res_x) / spectr_inf.size();
|
|
|
|
int pl = resize * spectr_inf.peak_left_ ;
|
|
int pr = resize * spectr_inf.peak_right_;
|
|
double nf = spectr_inf.noise_floor_;
|
|
double nv = spectr_inf.noise_variance_;
|
|
SimpleDownsampleVector(m_magnitudesArr, 1.0/resize);
|
|
|
|
static Average<double> low_level(25, spectr_inf.min_);
|
|
low_level.add(spectr_inf.min_);
|
|
// double low_level = -300;
|
|
|
|
nf = (nf > (double)low_level) ? nf : (double)low_level;
|
|
|
|
ClearBitmap(SPECTRUM_BITMAP_RGB, m_res_x, m_res_y, 3);
|
|
Histogram2Bitmap(SPECTRUM_BITMAP_RGB, m_res_x, m_res_y,
|
|
m_magnitudesArr,
|
|
low_level, 0, 0,
|
|
.0, .99, true);
|
|
|
|
|
|
if(spectr_inf.peak_left_valid_)
|
|
VerticalLineToBitmap(SPECTRUM_BITMAP_RGB, m_res_x, m_res_y, pl, true, 200, 50, 0);
|
|
else
|
|
VerticalLineToBitmap(SPECTRUM_BITMAP_RGB, m_res_x, m_res_y, pl, true, 50, 12, 0);
|
|
if(spectr_inf.peak_right_valid_)
|
|
VerticalLineToBitmap(SPECTRUM_BITMAP_RGB, m_res_x, m_res_y, pr, true, 0, 80, 250);
|
|
else
|
|
VerticalLineToBitmap(SPECTRUM_BITMAP_RGB, m_res_x, m_res_y, pr, true, 0, 20, 60);
|
|
|
|
HorizontalLineToBitmap(SPECTRUM_BITMAP_RGB, m_res_x, (nf/low_level)*m_res_y, true, 80,80,80);
|
|
HorizontalLineToBitmap(SPECTRUM_BITMAP_RGB, m_res_x, ((nf+nv)/low_level)*m_res_y, true, 80,80,80);
|
|
|
|
|
|
Fl_Box::draw();
|
|
fl_draw_image(SPECTRUM_BITMAP_RGB, m_x, m_y, m_res_x, m_res_y, 3, 0);
|
|
}
|
|
|
|
static void FreqViewport_CB(void *userdata)
|
|
{
|
|
((FreqViewport*)userdata)->redraw();
|
|
Fl::repeat_timeout(1.0f/500, FreqViewport_CB, userdata);
|
|
}
|
|
};
|
|
|
|
|
|
class DemodViewport : public Fl_Box
|
|
{
|
|
|
|
public:
|
|
const int m_x;
|
|
const int m_y;
|
|
const int m_res_x;
|
|
const int m_res_y;
|
|
|
|
DemodViewport(int X,int Y,int W,int H, const char*L=0)
|
|
: Fl_Box(X,Y,W,H,L), m_x(X), m_y(Y), m_res_x(W), m_res_y(H)
|
|
{
|
|
box(FL_FLAT_BOX);
|
|
color(BG_COLOR);
|
|
Fl::add_timeout(0.1, DemodViewport_CB, (void*)this);
|
|
}
|
|
|
|
|
|
private:
|
|
std::vector<TReal> samples_;
|
|
size_t samples_to_show_cnt_ = 0;
|
|
|
|
void draw()
|
|
{
|
|
if(!samples_to_show_cnt_)
|
|
samples_to_show_cnt_ = G_DECODER.getDecimatedSamplingRate() / G_DECODER.getSymbolRate() * 10; // 10 symbols
|
|
|
|
const size_t samples_size_pre_insert_ = samples_.size();
|
|
auto demod = G_DECODER.getDemodulated();
|
|
samples_.insert(
|
|
samples_.end(),
|
|
demod.cbegin(),
|
|
demod.cbegin() + min(samples_to_show_cnt_,demod.size())
|
|
);
|
|
|
|
// if(!samples_.size())
|
|
if(samples_.size() < samples_to_show_cnt_)
|
|
return;
|
|
|
|
ClearBitmap(TIME_BITMAP_RGB, m_res_x, m_res_y, 3);
|
|
VectorToBitmap(TIME_BITMAP_RGB, samples_, m_res_x, m_res_y);
|
|
HorizontalLineToBitmap(TIME_BITMAP_RGB, m_res_x, m_res_y/2, true);
|
|
|
|
Fl_Box::draw();
|
|
fl_draw_image(TIME_BITMAP_RGB, m_x, m_y, m_res_x, m_res_y, 3, 0);
|
|
|
|
if(samples_.size() > samples_to_show_cnt_)
|
|
samples_.erase( samples_.begin(), samples_.end()-samples_size_pre_insert_);
|
|
|
|
}
|
|
|
|
static void DemodViewport_CB(void *userdata)
|
|
{
|
|
((DemodViewport*)userdata)->redraw();
|
|
Fl::repeat_timeout(1.0f/10, DemodViewport_CB, userdata);
|
|
}
|
|
|
|
|
|
};
|
|
/////////////////////////////////
|
|
/////////////////////////////////
|
|
|
|
|
|
void DO_AFC_CB(Fl_Widget* p_w, void* p_user)
|
|
{
|
|
DO_CORRECTION = !DO_CORRECTION;
|
|
}
|
|
|
|
int GUI_MAIN()
|
|
{
|
|
Fl::visual(FL_RGB); // disable dithering
|
|
|
|
Fl_Double_Window* p_window = new Fl_Double_Window(RES_X, 1.5*RES_Y + 50);
|
|
|
|
p_window->begin();
|
|
FreqViewport* p_freqVP = new FreqViewport( 0, 0*RES_Y, RES_X, RES_Y);
|
|
DemodViewport* p_timeVP = new DemodViewport( 0, 1*RES_Y, RES_X, RES_Y/2);
|
|
auto button = new Fl_Button(0, 1*RES_Y + RES_Y/2 + 10, 100, 20, "AFC");
|
|
button->callback( ( Fl_Callback* ) DO_AFC_CB );
|
|
p_window->end();
|
|
p_window->show(0, 0);
|
|
|
|
return Fl::run();
|
|
}
|
|
|
|
|
|
|
|
int main(int argc, char** argv)
|
|
{
|
|
using namespace std;
|
|
|
|
struct thousand_separators : numpunct<char> {
|
|
char do_thousands_sep() const { return ','; }
|
|
string do_grouping() const { return "\3"; }
|
|
};
|
|
cout.imbue( locale(locale(""), new thousand_separators) );
|
|
|
|
|
|
string file("");
|
|
if(argc > 4)
|
|
file = string(argv[4]);
|
|
cout<<"File: "<<file<<endl;
|
|
|
|
InitProcess( std::stoi(argv[1]), std::stoi(argv[2]), std::stoi(argv[3]), file);
|
|
|
|
GUI_MAIN();
|
|
// while(1) this_thread::sleep_for(chrono::duration<double, milli>(3000));
|
|
|
|
}
|