#include /* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 4; tab-width: 4 -*- */ /* * main.cpp * Copyright (C) 2016 David Shah * */ #include "FFTRenderer.hpp" #include "dsp/DemodThread.hpp" #include "rftool/RFThread.hpp" #include #include extern "C" { #define complex #ifdef CUDA_FFT #include #else #include #endif } #include #include #include #include #include #include #include #include using namespace std; /* For testing propose use the local (not installed) ui file */ /* #define UI_FILE PACKAGE_DATA_DIR"/ui/sa_demo.ui" */ #define UI_FILE "sa_demo.ui" RFThread *rft; DemodThread *dmt; FFTRenderer *fftr; mutex fft_lock; vector fft_points; Glib::RefPtr dispMaxAmp, dispMinAmp, rxDispZoom, rxOffset, rxCenterFreq, rxInputGain, demodOffset, demodIFBW, demodAFBW, demodAFGain, txOffset, txPower; Gtk::ComboBox *fftLengthSel, *rxBwSel, *rxInputSel, *demodModeSel, *txModeSel; Gtk::ToggleButton *txEnableButton; Gtk::CheckButton *rxAgcEnable, *fftPeakScale; Gtk::DrawingArea *fft_area, *waterfallArea; Gtk::SpinButton *rxCenterFreqSpinner; Gtk::Button *autoscaleAmp, *txMorseKey; Gtk::Scale *rxGainSet; Gtk::Label *rxResolution, *cursorFreq; ulong last_fftLength = -1; fftwf_plan fftplan = nullptr; const size_t max_fftLength = 16777216; _Complex float *x_buf, *y_buf; atomic draw_done{false}; atomic fftLength{524288}; float *fft_window; // Waterfall display points const int waterfall_wmax = 3840, waterfall_hmax = 2160; float waterfall_points[waterfall_hmax][waterfall_wmax] = {{0.0}}; int waterfall_yptr = 0; int fft_count = 0; chrono::system_clock::time_point tmr; void update_fft() { fft_count = 0; tmr = chrono::system_clock::now(); while (true) { rft->getSamples(x_buf, fftLength); if ((fftplan == nullptr) || (fftLength != last_fftLength)) { // Build/rebuild window const float a0 = 0.42, a1 = 0.5, a2 = 0.08, pi = 3.1415; for (int ii = 0; ii < fftLength; ii++) { fft_window[ii] = a0 - a1 * cos((2 * pi * ii) / (fftLength - 1)) + a2 * cos((4 * pi * ii) / (fftLength - 1)); } } for (int ii = 0; ii < fftLength; ii++) { // Multiplying by -1^ii puts centre at origin if ((ii % 2) == 1) { x_buf[ii] *= -1; } x_buf[ii] *= fft_window[ii]; } if ((fftplan == nullptr) || (fftLength != last_fftLength)) { if (fftplan != nullptr) fftwf_destroy_plan(fftplan); fftplan = fftwf_plan_dft_1d(int(fftLength), x_buf, y_buf, -1, FFTW_ESTIMATE); } { lock_guard fft_lock_acquire(fft_lock); fftwf_execute(fftplan); } last_fftLength = fftLength; this_thread::sleep_for(chrono::milliseconds(2)); fft_count++; if ((chrono::system_clock::now() - tmr) > chrono::seconds(2)) { float speed = fft_count / float(chrono::duration_cast( chrono::system_clock::now() - tmr) .count()); speed *= 1000; cout << "fft speed: " << speed << "/s" << endl; fft_count = 0; tmr = chrono::system_clock::now(); } } } bool redraw_fft() { fft_area->queue_draw(); return true; } bool fftClickHandler(GdkEventButton *event) { if (event->button == 3) { rxDispZoom->set_value(1); rxOffset->set_value(0); } else if (event->button == 1) { double clickFreq = fftr->GetStartFrequencyOff() + (event->x / double(fftr->viewWidth)) * fftr->GetFrequencySpan(); rxOffset->set_value(clickFreq / 1.0e6); rxDispZoom->set_value(fftr->zoom * 4); } return true; } bool fftMouseMoveHandler(GdkEventMotion *event) { double ptrFreq = fftr->GetStartFrequencyOff() + (event->x / double(fftr->viewWidth)) * fftr->GetFrequencySpan(); stringstream temp; temp << "Cursor = "; temp << fixed << setprecision(3) << (ptrFreq / 1.0e6) + rxCenterFreq->get_value(); temp << "MHz"; cursorFreq->set_text(temp.str()); return false; } void centerFreqChanged() { rft->setCenterFreq(uint64_t(rxCenterFreq->get_value() * 1e6)); } void inputGainChanged() { rft->setGain(uint64_t(rxInputGain->get_value())); } void inputSelChanged() { int input_sel; rxInputSel->get_active()->get_value(1, input_sel); rft->setInputPort(input_sel); }; void rxBandwidthChanged() { double bw; rxBwSel->get_active()->get_value(1, bw); rft->setBandwidth(bw); }; void rxAgcChanged() { rft->setAgcEnable(rxAgcEnable->get_active()); } void autoscale_vert() { double minamp = *min_element(fft_points.begin(), fft_points.end()); double maxamp = *max_element(fft_points.begin(), fft_points.end()); dispMinAmp->set_value(floor(minamp - 1)); dispMaxAmp->set_value(ceil(maxamp + 20)); } void demod_setting_changed() { if (dmt != nullptr) { // gchararray dmMode; // demodModeSel->get_active()->get_value(1, dmMode); dmt->demodMode = string(demodModeSel->get_active_id()); dmt->demodOffset = demodOffset->get_value() * 1e6; dmt->ifBandwidth = demodIFBW->get_value() * 1e3; dmt->afBandwidth = demodAFBW->get_value() * 1e3; dmt->afGain = demodAFGain->get_value(); } }; void tx_enable_changed() { rft->setTxEnable(txEnableButton->get_active()); } void morse_key_pressed() { rft->setTxEnable(true); } void morse_key_released() { if (!txEnableButton->get_active()) rft->setTxEnable(false); } void tx_mode_changed() { string mode = string(txModeSel->get_active_id()); if (mode == "CW") { rft->setTxMode(TxMode::TX_SINE); } else if (mode == "Noise") { rft->setTxMode(TxMode::TX_NOISE); } } void tx_offset_changed() { rft->setTxOffset(int32_t(1e6 * txOffset->get_value())); } void tx_power_changed() { rft->setTxPower(int32_t(txPower->get_value())); } string to_string_prec(double val, int prec) { stringstream s; string suffix = ""; if (val >= 1e9) { val /= 1e9; suffix = "G"; } else if (val >= 1e6) { val /= 1e6; suffix = "M"; } else if (val >= 1e3) { val /= 1e3; suffix = "k"; } s << setprecision(prec) << val << suffix; return s.str(); } bool on_fft_draw(const Cairo::RefPtr &cr) { { lock_guard fft_lock_acquire(fft_lock); Gtk::Allocation allocation = fft_area->get_allocation(); fftr->viewWidth = allocation.get_width(); fftr->viewHeight = allocation.get_height(); fftr->centerFrequency = rxCenterFreq->get_value() * 1e6; fftr->maxAmplitude = dispMaxAmp->get_value(); fftr->minAmplitude = dispMinAmp->get_value(); fftr->zoom = rxDispZoom->get_value(); fftr->offset = rxOffset->get_value() * 1e6; fftr->sampleRate = rft->getCurrentSampleRate(); rxBwSel->get_active()->get_value(1, fftr->bandwidth); fftr->peakScale = fftPeakScale->get_active(); double resolution = double(fftr->sampleRate) / double(fftLength); double disp_res = double(fftr->bandwidth) / (double(fftr->viewWidth) * fftr->zoom); rxResolution->set_text("Resolution = " + to_string_prec(resolution, 3) + "Hz (" + to_string_prec(disp_res, 3) + "Hz disp)"); ulong fftLengthT; fftLengthSel->get_active()->get_value(1, fftLengthT); fftLength = fftLengthT; fftr->FitFFTToView(2048, 0, y_buf, fftLength, fft_points); for (int i = 0; i < fft_points.size(); i++) { if (i < waterfall_wmax) { waterfall_points[waterfall_yptr][i] = (fft_points[i] - fftr->minAmplitude) / (fftr->maxAmplitude - fftr->minAmplitude); } } waterfall_yptr++; if (waterfall_yptr >= waterfall_hmax) waterfall_yptr = 0; fftr->RenderToContext(fft_points, cr); } waterfallArea->queue_draw(); draw_done = true; return true; } bool on_wf_draw(const Cairo::RefPtr &cr) { Gtk::Allocation allocation = waterfallArea->get_allocation(); int width = allocation.get_width(); int height = allocation.get_height(); uint32_t *argb_bitmap = new uint32_t[height * width]; for (int y = 0; y < height; y++) { for (int x = 0; x < width; x++) { if (x >= waterfall_wmax) break; int waterfall_y = ((waterfall_yptr - y) + waterfall_hmax) % waterfall_hmax; float fval = waterfall_points[waterfall_y][x]; uint8_t pixval; if (fval >= 1) pixval = 255; else if (fval <= 0) pixval = 0; else pixval = uint8_t(fval * 255); argb_bitmap[y * width + x] = 0xff000000 | (uint16_t(pixval) << 8); } if (y >= waterfall_hmax) break; } Cairo::RefPtr tmpSurface = Cairo::ImageSurface::create( reinterpret_cast(argb_bitmap), Cairo::Format::FORMAT_ARGB32, width, height, width * 4); cr->set_source(tmpSurface, 0, 0); cr->paint(); tmpSurface->finish(); delete[] argb_bitmap; return true; } int main(int argc, char *argv[]) { #ifdef CUDA_FFT x_buf = (_Complex float *)malloc(max_fftLength * sizeof(_Complex float)); y_buf = (_Complex float *)malloc(max_fftLength * sizeof(_Complex float)); #else x_buf = fftwf_alloc_complex(max_fftLength); y_buf = fftwf_alloc_complex(max_fftLength); #endif fft_window = new float[max_fftLength]; fftr = new FFTRenderer(); Gtk::Main kit(argc, argv); // Load the Glade file and instiate its widgets: Glib::RefPtr builder; try { builder = Gtk::Builder::create_from_file(UI_FILE); } catch (const Glib::FileError &ex) { std::cerr << ex.what() << std::endl; return 1; } Gtk::Window *main_win = 0; builder->get_widget("main_window", main_win); builder->get_widget("fftArea", fft_area); builder->get_widget("waterfallArea", waterfallArea); dispMaxAmp = Glib::RefPtr::cast_dynamic( builder->get_object("dispMaxAmp")); dispMinAmp = Glib::RefPtr::cast_dynamic( builder->get_object("dispMinAmp")); rxDispZoom = Glib::RefPtr::cast_dynamic( builder->get_object("rxDispZoom")); rxOffset = Glib::RefPtr::cast_dynamic( builder->get_object("rxOffset")); rxCenterFreq = Glib::RefPtr::cast_dynamic( builder->get_object("rxCenterFreq")); rxInputGain = Glib::RefPtr::cast_dynamic( builder->get_object("rxInputGain")); demodOffset = Glib::RefPtr::cast_dynamic( builder->get_object("demodOffset")); demodIFBW = Glib::RefPtr::cast_dynamic( builder->get_object("demodIFBW")); demodAFBW = Glib::RefPtr::cast_dynamic( builder->get_object("demodAFBW")); demodAFGain = Glib::RefPtr::cast_dynamic( builder->get_object("demodAFGain")); txOffset = Glib::RefPtr::cast_dynamic( builder->get_object("txOffset")); txPower = Glib::RefPtr::cast_dynamic( builder->get_object("txPower")); builder->get_widget("fftLengthSel", fftLengthSel); builder->get_widget("rxBwSel", rxBwSel); builder->get_widget("rxInputSel", rxInputSel); builder->get_widget("rxAgcEnable", rxAgcEnable); builder->get_widget("rxCenterFreqSpinner", rxCenterFreqSpinner); builder->get_widget("rxGainSet", rxGainSet); builder->get_widget("rxResolution", rxResolution); builder->get_widget("cursorFreq", cursorFreq); builder->get_widget("autoscaleAmp", autoscaleAmp); builder->get_widget("fftPeakScale", fftPeakScale); builder->get_widget("demodModeSel", demodModeSel); builder->get_widget("txEnableButton", txEnableButton); builder->get_widget("txModeSel", txModeSel); builder->get_widget("txMorseKey", txMorseKey); if (main_win) { rft = new RFThread(); dmt = new DemodThread(); fft_area->signal_draw().connect(sigc::ptr_fun(on_fft_draw)); waterfallArea->signal_draw().connect(sigc::ptr_fun(on_wf_draw)); main_win->set_events(Gdk::BUTTON_PRESS_MASK | Gdk::POINTER_MOTION_MASK); fft_area->set_events(Gdk::BUTTON_PRESS_MASK | Gdk::POINTER_MOTION_MASK); fft_area->signal_button_press_event().connect( sigc::ptr_fun(fftClickHandler)); fft_area->signal_motion_notify_event().connect( sigc::ptr_fun(fftMouseMoveHandler)); rxCenterFreqSpinner->signal_changed().connect( sigc::ptr_fun(centerFreqChanged)); rxInputGain->signal_value_changed().connect( sigc::ptr_fun(inputGainChanged)); rxInputSel->signal_changed().connect(sigc::ptr_fun(inputSelChanged)); rxBwSel->signal_changed().connect(sigc::ptr_fun(rxBandwidthChanged)); rxAgcEnable->signal_toggled().connect(sigc::ptr_fun(rxAgcChanged)); demodModeSel->signal_changed().connect( sigc::ptr_fun(demod_setting_changed)); demodOffset->signal_value_changed().connect( sigc::ptr_fun(demod_setting_changed)); demodIFBW->signal_value_changed().connect( sigc::ptr_fun(demod_setting_changed)); demodAFBW->signal_value_changed().connect( sigc::ptr_fun(demod_setting_changed)); demodAFGain->signal_value_changed().connect( sigc::ptr_fun(demod_setting_changed)); txOffset->signal_value_changed().connect(sigc::ptr_fun(tx_offset_changed)); txPower->signal_value_changed().connect(sigc::ptr_fun(tx_power_changed)); txEnableButton->signal_toggled().connect(sigc::ptr_fun(tx_enable_changed)); txModeSel->signal_changed().connect(sigc::ptr_fun(tx_mode_changed)); txMorseKey->signal_pressed().connect(sigc::ptr_fun(morse_key_pressed)); txMorseKey->signal_released().connect(sigc::ptr_fun(morse_key_released)); autoscaleAmp->signal_clicked().connect(sigc::ptr_fun(autoscale_vert)); rft->start(); Glib::signal_timeout().connect(sigc::ptr_fun(redraw_fft), 17); thread uth(update_fft); dmt->start(rft); kit.run(*main_win); dmt->stop(); rft->stop(); } return 0; }