diff --git a/src/applications/gqrx/mainwindow.cpp b/src/applications/gqrx/mainwindow.cpp index 784564158..f263d6be3 100644 --- a/src/applications/gqrx/mainwindow.cpp +++ b/src/applications/gqrx/mainwindow.cpp @@ -5,6 +5,7 @@ * * Copyright 2011-2014 Alexandru Csete OZ9AEC. * Copyright (C) 2013 by Elias Oenal + * Copyright 2022 Marc CAPDEVILLE F4JMZ * * Gqrx is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -147,6 +148,8 @@ MainWindow::MainWindow(const QString& cfgfile, bool edit_conf, QWidget *parent) /* create dock widgets */ uiDockRxOpt = new DockRxOpt(); uiDockRDS = new DockRDS(); + uiDockFAX = new DockFAX(); + uiDockRTTY = new DockRTTY(); uiDockAudio = new DockAudio(); uiDockInputCtl = new DockInputCtl(); uiDockFft = new DockFft(); @@ -196,7 +199,12 @@ MainWindow::MainWindow(const QString& cfgfile, bool edit_conf, QWidget *parent) addDockWidget(Qt::RightDockWidgetArea, uiDockAudio); addDockWidget(Qt::RightDockWidgetArea, uiDockRDS); + addDockWidget(Qt::RightDockWidgetArea, uiDockFAX); + addDockWidget(Qt::RightDockWidgetArea, uiDockRTTY); + tabifyDockWidget(uiDockAudio, uiDockRDS); + tabifyDockWidget(uiDockRDS, uiDockFAX); + tabifyDockWidget(uiDockFAX, uiDockRTTY); uiDockAudio->raise(); addDockWidget(Qt::BottomDockWidgetArea, uiDockBookmarks); @@ -204,6 +212,8 @@ MainWindow::MainWindow(const QString& cfgfile, bool edit_conf, QWidget *parent) /* hide docks that we don't want to show initially */ uiDockBookmarks->hide(); uiDockRDS->hide(); + uiDockFAX->hide(); + uiDockRTTY->hide(); /* Add dock widget actions to View menu. By doing it this way all signal/slot connections will be established automagially. @@ -211,6 +221,8 @@ MainWindow::MainWindow(const QString& cfgfile, bool edit_conf, QWidget *parent) ui->menu_View->addAction(uiDockInputCtl->toggleViewAction()); ui->menu_View->addAction(uiDockRxOpt->toggleViewAction()); ui->menu_View->addAction(uiDockRDS->toggleViewAction()); + ui->menu_View->addAction(uiDockFAX->toggleViewAction()); + ui->menu_View->addAction(uiDockRTTY->toggleViewAction()); ui->menu_View->addAction(uiDockAudio->toggleViewAction()); ui->menu_View->addAction(uiDockFft->toggleViewAction()); ui->menu_View->addAction(uiDockBookmarks->toggleViewAction()); @@ -299,6 +311,30 @@ MainWindow::MainWindow(const QString& cfgfile, bool edit_conf, QWidget *parent) connect(uiDockFft, SIGNAL(fftMinHoldToggled(bool)), ui->plotter, SLOT(enableMinHold(bool))); connect(uiDockFft, SIGNAL(peakDetectToggled(bool)), ui->plotter, SLOT(enablePeakDetect(bool))); connect(uiDockRDS, SIGNAL(rdsDecoderToggled(bool)), this, SLOT(setRdsDecoder(bool))); + connect(uiDockFAX, SIGNAL(fax_start_decoder()), this, SLOT(start_fax_decoder())); + connect(uiDockFAX, SIGNAL(fax_stop_decoder()), this, SLOT(stop_fax_decoder())); + connect(uiDockFAX, SIGNAL(fax_reset_Clicked()), this, SLOT(reset_fax_decoder())); + connect(uiDockFAX, SIGNAL(fax_black_freq_Changed(float)), this, SLOT(set_fax_black_freq(float))); + connect(uiDockFAX, SIGNAL(fax_white_freq_Changed(float)), this, SLOT(set_fax_white_freq(float))); + connect(uiDockFAX, SIGNAL(fax_lpm_Changed(float)), this, SLOT(set_fax_lpm(float))); + connect(uiDockFAX, SIGNAL(fax_ioc_Changed(float)), this, SLOT(set_fax_ioc(float))); + connect(uiDockFAX, SIGNAL(fax_reset_Clicked()), this, SLOT(force_fax_reset())); + connect(uiDockFAX, SIGNAL(fax_sync_Clicked()), this, SLOT(force_fax_sync())); + connect(uiDockFAX, SIGNAL(fax_start_Clicked()), this, SLOT(force_fax_start())); + connect(uiDockFAX, SIGNAL(fax_save_Clicked()), this, SLOT(save_fax())); + connect(uiDockRTTY, SIGNAL(rtty_start_decoder()), this, SLOT(start_rtty_decoder())); + connect(uiDockRTTY, SIGNAL(rtty_stop_decoder()), this, SLOT(stop_rtty_decoder())); + connect(uiDockRTTY, SIGNAL(rtty_reset_clicked()), this, SLOT(reset_rtty_decoder())); + connect(uiDockRTTY, SIGNAL(rtty_baud_rate_Changed(float)), this, SLOT(set_rtty_baud_rate(float))); + connect(uiDockRTTY, SIGNAL(rtty_mark_freq_Changed(float)), this, SLOT(set_rtty_mark_freq(float))); + connect(uiDockRTTY, SIGNAL(rtty_space_freq_Changed(float)), this, SLOT(set_rtty_space_freq(float))); + connect(uiDockRTTY, SIGNAL(rtty_threshold_Changed(float)), this, SLOT(set_rtty_threshold(float))); + connect(uiDockRTTY, SIGNAL(rtty_bandwidth_Changed(float)), this, SLOT(set_rtty_bandwidth(float))); + connect(uiDockRTTY, SIGNAL(rtty_transwidth_Changed(float)), this, SLOT(set_rtty_transwidth(float))); + connect(uiDockRTTY, SIGNAL(rtty_filterlen_Changed(float)), this, SLOT(set_rtty_filterlen(float))); + connect(uiDockRTTY, SIGNAL(rtty_mode_Changed(int)), this, SLOT(set_rtty_mode(int))); + connect(uiDockRTTY, SIGNAL(rtty_parity_Changed(int)), this, SLOT(set_rtty_parity(int))); + connect(uiDockRTTY, SIGNAL(rtty_save_clicked()), this, SLOT(save_rtty())); // Plotter connect(ui->plotter, SIGNAL(pandapterRangeChanged(float,float)), @@ -348,6 +384,12 @@ MainWindow::MainWindow(const QString& cfgfile, bool edit_conf, QWidget *parent) rds_timer = new QTimer(this); connect(rds_timer, SIGNAL(timeout()), this, SLOT(rdsTimeout())); + fax_timer = new QTimer(this); + connect(fax_timer, SIGNAL(timeout()), this, SLOT(faxTimeout())); + + rtty_timer = new QTimer(this); + connect(rtty_timer, SIGNAL(timeout()), this, SLOT(rttyTimeout())); + // enable frequency tooltips on FFT plot ui->plotter->setTooltipsEnabled(true); @@ -440,6 +482,8 @@ MainWindow::~MainWindow() delete uiDockFft; delete uiDockInputCtl; delete uiDockRDS; + delete uiDockFAX; + delete uiDockRTTY; delete rx; delete remote; delete qsvg_dummy; @@ -659,6 +703,8 @@ bool MainWindow::loadConfig(const QString& cfgfile, bool check_crash, uiDockRxOpt->readSettings(m_settings); uiDockFft->readSettings(m_settings); uiDockAudio->readSettings(m_settings); + uiDockRTTY->readSettings(m_settings); + uiDockFAX->readSettings(m_settings); dxc_options->readSettings(m_settings); { @@ -785,6 +831,8 @@ void MainWindow::storeSession() uiDockRxOpt->saveSettings(m_settings); uiDockFft->saveSettings(m_settings); uiDockAudio->saveSettings(m_settings); + uiDockFAX->saveSettings(m_settings); + uiDockRTTY->saveSettings(m_settings); remote->saveSettings(m_settings); iq_tool->saveSettings(m_settings); @@ -1025,8 +1073,8 @@ void MainWindow::setFilterOffset(qint64 freq_hz) auto rx_freq = d_hw_freq + d_lnb_lo + freq_hz; ui->freqCtrl->setFrequency(rx_freq); - if (rx->is_rds_decoder_active()) { - rx->reset_rds_parser(); + if (rx->is_decoder_active(receiver_base_cf::RX_DECODER_RDS)) { + rx->reset_decoder(receiver_base_cf::RX_DECODER_RDS); } } @@ -1167,7 +1215,9 @@ void MainWindow::selectDemod(int mode_idx) double cwofs = 0.0; int filter_preset = uiDockRxOpt->currentFilter(); int flo=0, fhi=0, click_res=100; - bool rds_enabled; + bool rds_decoder_enabled; + bool fax_decoder_enabled; + bool rtty_decoder_enabled; // validate mode_idx if (mode_idx < DockRxOpt::MODE_OFF || mode_idx >= DockRxOpt::MODE_LAST) @@ -1180,11 +1230,21 @@ void MainWindow::selectDemod(int mode_idx) uiDockRxOpt->getFilterPreset(mode_idx, filter_preset, &flo, &fhi); d_filter_shape = (receiver::filter_shape)uiDockRxOpt->currentFilterShape(); - rds_enabled = rx->is_rds_decoder_active(); - if (rds_enabled) + rds_decoder_enabled = rx->is_decoder_active(receiver_base_cf::RX_DECODER_RDS); + if (rds_decoder_enabled) setRdsDecoder(false); uiDockRDS->setDisabled(); + fax_decoder_enabled = rx->is_decoder_active(receiver_base_cf::RX_DECODER_FAX); + if (fax_decoder_enabled) + stop_fax_decoder(); + uiDockFAX->set_Disabled(); + + rtty_decoder_enabled = rx->is_decoder_active(receiver_base_cf::RX_DECODER_RTTY); + if (rtty_decoder_enabled) + stop_rtty_decoder(); + uiDockRTTY->set_Disabled(); + switch (mode_idx) { case DockRxOpt::MODE_OFF: @@ -1251,7 +1311,7 @@ void MainWindow::selectDemod(int mode_idx) rx->set_demod(receiver::RX_DEMOD_WFM_S); uiDockRDS->setEnabled(); - if (rds_enabled) + if (rds_decoder_enabled) setRdsDecoder(true); break; @@ -1297,6 +1357,16 @@ void MainWindow::selectDemod(int mode_idx) break; } + if (rx->get_rx_chain() == receiver::RX_CHAIN_NBRX) { + uiDockFAX->set_Enabled(); + if (fax_decoder_enabled) + start_fax_decoder(); + + uiDockRTTY->set_Enabled(); + if (rtty_decoder_enabled) + start_rtty_decoder(); + } + qDebug() << "Filter preset for mode" << mode_idx << "LO:" << flo << "HI:" << fhi; ui->plotter->setHiLowCutFrequencies(flo, fhi); ui->plotter->setClickResolution(click_res); @@ -1532,10 +1602,10 @@ void MainWindow::rdsTimeout() std::string buffer; int num; - rx->get_rds_data(buffer, num); + rx->get_decoder_data(receiver_base_cf::RX_DECODER_RDS, &buffer, num); while(num!=-1) { uiDockRDS->updateRDS(QString::fromStdString(buffer), num); - rx->get_rds_data(buffer, num); + rx->get_decoder_data(receiver_base_cf::RX_DECODER_RDS, &buffer, num); } } @@ -1965,6 +2035,8 @@ void MainWindow::on_actionDSP_triggered(bool checked) iq_fft_timer->stop(); audio_fft_timer->stop(); rds_timer->stop(); + fax_timer->stop(); + rtty_timer->stop(); /* stop receiver */ rx->stop(); @@ -2100,8 +2172,8 @@ void MainWindow::on_plotter_newDemodFreq(qint64 freq, qint64 delta) uiDockRxOpt->setFilterOffset(delta); ui->freqCtrl->setFrequency(freq); - if (rx->is_rds_decoder_active()) - rx->reset_rds_parser(); + if (rx->is_decoder_active(receiver_base_cf::RX_DECODER_RDS)) + rx->reset_decoder(receiver_base_cf::RX_DECODER_RDS); } /* CPlotter::NewfilterFreq() is emitted or bookmark activated */ @@ -2250,20 +2322,268 @@ void MainWindow::setRdsDecoder(bool checked) { qDebug() << "Starting RDS decoder."; uiDockRDS->showEnabled(); - rx->start_rds_decoder(); - rx->reset_rds_parser(); + rx->start_decoder(receiver_base_cf::RX_DECODER_RDS); + rx->reset_decoder(receiver_base_cf::RX_DECODER_RDS); rds_timer->start(250); } else { qDebug() << "Stopping RDS decoder."; uiDockRDS->showDisabled(); - rx->stop_rds_decoder(); + rx->stop_decoder(receiver_base_cf::RX_DECODER_RDS); rds_timer->stop(); } remote->setRDSstatus(checked); } +/** FAX image update timeout. */ +void MainWindow::faxTimeout() { + int num = 0; + unsigned char * data = NULL; + + + while (rx->get_decoder_data(receiver_base_cf::RX_DECODER_FAX,data, num) != -1) { + if (!fax_running && num) { // New Image is starting + qint64 freq = ui->freqCtrl->getFrequency(); + fax_name = QDateTime::currentDateTimeUtc().toString("'gqrx_'yyyyMMdd_hhmmss_%1_'fax.png'").arg(freq); + fprintf(stderr,"FAX : Startting image %s\n",fax_name.toLocal8Bit().data()); + fax_image=QImage(1,1,QImage::Format::Format_Grayscale8); + fax_running = true; + } + + if (num) { + if (fax_image.width() < num) + fax_image = fax_image.copy(0,0,num,fax_image.height()+1); + else + fax_image = fax_image.copy(0,0,fax_image.width(),fax_image.height()+1); + + data = fax_image.scanLine(fax_image.height()-1); + } + else { // End of image + fprintf(stderr,"FAX : end of image\n"); + if (uiDockFAX->get_autosave()) { + if (save_fax() == 0 ) + fprintf(stderr,"FAX : Image saved to %s\n",fax_name.toLocal8Bit().data()); + fax_running = false; + } + } + } + + uiDockFAX->update_image(fax_image); +} + +void MainWindow::start_fax_decoder() { + qDebug() << "Starting FAX decoder."; + uiDockFAX->show_Enabled(); + rx->set_decoder_param(receiver_base_cf::RX_DECODER_FAX,"lpm",std::to_string(uiDockFAX->get_lpm())); + rx->set_decoder_param(receiver_base_cf::RX_DECODER_FAX,"black_freq",std::to_string(uiDockFAX->get_black_freq())); + rx->set_decoder_param(receiver_base_cf::RX_DECODER_FAX,"white_freq",std::to_string(uiDockFAX->get_white_freq())); + rx->set_decoder_param(receiver_base_cf::RX_DECODER_FAX,"ioc",std::to_string(uiDockFAX->get_ioc())); + rx->start_decoder(receiver_base_cf::RX_DECODER_FAX); + rx->reset_decoder(receiver_base_cf::RX_DECODER_FAX); + fax_timer->start(250); + fax_name.clear(); + fax_running = false; +} + +void MainWindow::stop_fax_decoder() { + qDebug() << "Stopping FAX decoder."; + uiDockFAX->show_Disabled(); + rx->stop_decoder(receiver_base_cf::RX_DECODER_FAX); + fax_timer->stop(); + fax_running = false; +} + +void MainWindow::set_fax_lpm(float baud_rate) { + rx->set_decoder_param(receiver_base_cf::RX_DECODER_FAX,"lpm",std::to_string(baud_rate)); +} + +void MainWindow::set_fax_black_freq(float black_freq) { + std::string Val; + rx->set_decoder_param(receiver_base_cf::RX_DECODER_FAX,"black_freq",std::to_string(black_freq)); +} + +void MainWindow::set_fax_white_freq(float white_freq) { + std::string Val; + rx->set_decoder_param(receiver_base_cf::RX_DECODER_FAX,"white_freq",std::to_string(white_freq)); +} + +void MainWindow::set_fax_ioc(float ioc) { + rx->set_decoder_param(receiver_base_cf::RX_DECODER_FAX,"ioc",std::to_string(ioc)); +} + +void MainWindow::reset_fax_decoder() { + fax_image=QImage(1,1,QImage::Format::Format_Grayscale8); + rx->reset_decoder(receiver_base_cf::RX_DECODER_FAX); + uiDockFAX->clearView(); + fax_running = false; + fax_name.clear(); +} + +void MainWindow::force_fax_reset() { + rx->set_decoder_param(receiver_base_cf::RX_DECODER_FAX,"force","reset"); + fax_name.clear(); + fax_running = false; +} + +void MainWindow::force_fax_sync() { + rx->set_decoder_param(receiver_base_cf::RX_DECODER_FAX,"force","sync"); + fax_name.clear(); + fax_running = false; +} + +void MainWindow::force_fax_start() { + rx->set_decoder_param(receiver_base_cf::RX_DECODER_FAX,"force","start"); +} + +int MainWindow::save_fax() { + qint64 freq = ui->freqCtrl->getFrequency(); + QString dir = uiDockFAX->get_directory(); + QString name; + + if (fax_name.isEmpty()) + name = QDateTime::currentDateTimeUtc().toString("gqrx_yyyyMMdd_hhmmss_%1_'fax.png'").arg(freq); + else + name = fax_name; + + if (!dir.isEmpty()) + name = dir + '/' + fax_name; + + if (fax_image.save(name)) + return 0; + else + return -1; +} + +/** RTTY message display timeout. */ +void MainWindow::rttyTimeout() { + std::string data; + int num; + + while (rx->get_decoder_data(receiver_base_cf::RX_DECODER_RTTY,(void*)&data, num)!=-1) { + if (!rtty_running && !data.empty()) { // New transmission is starting + qint64 freq = ui->freqCtrl->getFrequency(); + rtty_name = QDateTime::currentDateTimeUtc().toString("'gqrx_'yyyyMMdd_hhmmss_%1_'rtty.txt'").arg(freq); + fprintf(stderr,"RTTY : Start of transmission : %s\n",rtty_name.toLocal8Bit().data()); + uiDockRTTY->ClearText(); + rtty_running = true; + } + + if (data.empty()) { + fprintf(stderr,"RTTY : End of transmission.\n"); + if (uiDockRTTY->get_autosave()) { + if (save_rtty() == 0) + fprintf(stderr,"RTTY : Text saved to %s\n",rtty_name.toLocal8Bit().data()); + rtty_running = false; + } + } else + uiDockRTTY->update_text(QString::fromStdString(data)); + + } +} + +void MainWindow::start_rtty_decoder() { + qDebug() << "Starting RTTY decoder."; + rx->set_decoder_param(receiver_base_cf::RX_DECODER_RTTY,"baud_rate",std::to_string(uiDockRTTY->get_baud_rate())); + rx->set_decoder_param(receiver_base_cf::RX_DECODER_RTTY,"mark_freq",std::to_string(uiDockRTTY->get_mark_freq())); + rx->set_decoder_param(receiver_base_cf::RX_DECODER_RTTY,"space_freq",std::to_string(uiDockRTTY->get_space_freq())); + rx->set_decoder_param(receiver_base_cf::RX_DECODER_RTTY,"threshold",std::to_string(uiDockRTTY->get_threshold())); + rx->set_decoder_param(receiver_base_cf::RX_DECODER_RTTY,"bandwidth",std::to_string(uiDockRTTY->get_bandwidth())); + rx->set_decoder_param(receiver_base_cf::RX_DECODER_RTTY,"transwidth",std::to_string(uiDockRTTY->get_transwidth())); + rx->set_decoder_param(receiver_base_cf::RX_DECODER_RTTY,"filterlen",std::to_string(uiDockRTTY->get_filterlen())); + rx->set_decoder_param(receiver_base_cf::RX_DECODER_RTTY,"mode",std::to_string(uiDockRTTY->get_mode())); + rx->set_decoder_param(receiver_base_cf::RX_DECODER_RTTY,"parity",std::to_string(uiDockRTTY->get_parity())); + uiDockRTTY->show_Enabled(); + rx->start_decoder(receiver_base_cf::RX_DECODER_RTTY); + rx->reset_decoder(receiver_base_cf::RX_DECODER_RTTY); + rtty_name.clear(); + rtty_running=false; + rtty_timer->start(250); +} + +void MainWindow::stop_rtty_decoder() { + qDebug() << "Stopping RTTY decoder."; + uiDockRTTY->show_Disabled(); + rx->stop_decoder(receiver_base_cf::RX_DECODER_RTTY); + rtty_timer->stop(); + rtty_running=false; + rtty_name.clear(); +} + +void MainWindow::reset_rtty_decoder() { + rx->reset_decoder(receiver_base_cf::RX_DECODER_RTTY); +} + +void MainWindow::set_rtty_baud_rate(float baud_rate) { + rx->set_decoder_param(receiver_base_cf::RX_DECODER_RTTY,"baud_rate",std::to_string(baud_rate)); +} + +void MainWindow::set_rtty_mark_freq(float mark_freq) { + std::string Val; + rx->set_decoder_param(receiver_base_cf::RX_DECODER_RTTY,"mark_freq",std::to_string(mark_freq)); +} + +void MainWindow::set_rtty_space_freq(float space_freq) { + std::string Val; + rx->set_decoder_param(receiver_base_cf::RX_DECODER_RTTY,"space_freq",std::to_string(space_freq)); +} + +void MainWindow::set_rtty_threshold(float threshold) { + std::string Val; + rx->set_decoder_param(receiver_base_cf::RX_DECODER_RTTY,"threshold",std::to_string(threshold)); +} + +void MainWindow::set_rtty_bandwidth(float bandwidth) { + std::string Val; + rx->set_decoder_param(receiver_base_cf::RX_DECODER_RTTY,"bandwidth",std::to_string(bandwidth)); +} + +void MainWindow::set_rtty_transwidth(float transwidth) { + std::string Val; + rx->set_decoder_param(receiver_base_cf::RX_DECODER_RTTY,"transwidth",std::to_string(transwidth)); +} + +void MainWindow::set_rtty_filterlen(float filterlen) { + std::string Val; + rx->set_decoder_param(receiver_base_cf::RX_DECODER_RTTY,"filterlen",std::to_string(filterlen)); +} + +void MainWindow::set_rtty_mode(int mode) { + rx->set_decoder_param(receiver_base_cf::RX_DECODER_RTTY,"mode",std::to_string(mode)); +} + +void MainWindow::set_rtty_parity(int parity) { + rx->set_decoder_param(receiver_base_cf::RX_DECODER_RTTY,"parity",std::to_string(parity)); +} + +int MainWindow::save_rtty() { + auto freq = ui->freqCtrl->getFrequency(); + QString dir = uiDockRTTY->get_directory(); + QString name; + int ret; + + if (rtty_name.isEmpty()) + name = QDateTime::currentDateTimeUtc().toString("gqrx_yyyyMMdd_hhmmss_%1_'rtty.txt'").arg(freq); + else + name = rtty_name; + + if (!dir.isEmpty()) + name = dir + '/' + name; + + QFile file(name); + + if (file.open(QIODevice::WriteOnly | QIODevice::Text)) { + QTextStream out(&file); + out << uiDockRTTY->get_text(); + ret = 0; + } + else { + ret = -1; + } + + return ret; +} + void MainWindow::onBookmarkActivated(qint64 freq, const QString& demod, int bandwidth) { setNewFrequency(freq); diff --git a/src/applications/gqrx/mainwindow.h b/src/applications/gqrx/mainwindow.h index 5151bb2c1..24c8f6636 100644 --- a/src/applications/gqrx/mainwindow.h +++ b/src/applications/gqrx/mainwindow.h @@ -39,6 +39,8 @@ #include "qtgui/dockfft.h" #include "qtgui/dockbookmarks.h" #include "qtgui/dockrds.h" +#include "qtgui/dockfax.h" +#include "qtgui/dockrtty.h" #include "qtgui/afsk1200win.h" #include "qtgui/iq_tool.h" #include "qtgui/dxc_options.h" @@ -97,6 +99,12 @@ public slots: std::vector d_audioFftData; bool d_have_audio; /*!< Whether we have audio (i.e. not with demod_off. */ + QImage fax_image; + bool fax_running; + QString fax_name; + bool rtty_running; + QString rtty_name; + /* dock widgets */ DockRxOpt *uiDockRxOpt; DockAudio *uiDockAudio; @@ -104,6 +112,8 @@ public slots: DockFft *uiDockFft; DockBookmarks *uiDockBookmarks; DockRDS *uiDockRDS; + DockFAX *uiDockFAX; + DockRTTY *uiDockRTTY; CIqTool *iq_tool; DXCOptions *dxc_options; @@ -118,6 +128,9 @@ public slots: QTimer *iq_fft_timer; QTimer *audio_fft_timer; QTimer *rds_timer; + QTimer *fax_timer; + QTimer *rtty_timer; + quint64 d_last_fft_ms; float d_avg_fft_rate; bool d_frame_drop; @@ -220,6 +233,34 @@ private slots: /* RDS */ void setRdsDecoder(bool checked); + /* FAX */ + void start_fax_decoder(); + void stop_fax_decoder(); + void reset_fax_decoder(); + void set_fax_lpm(float); + void set_fax_black_freq(float); + void set_fax_white_freq(float); + void set_fax_ioc(float); + void force_fax_reset(); + void force_fax_sync(); + void force_fax_start(); + int save_fax(); + + /* RTTY */ + void start_rtty_decoder(); + void stop_rtty_decoder(); + void reset_rtty_decoder(); + void set_rtty_baud_rate(float); + void set_rtty_mark_freq(float); + void set_rtty_space_freq(float); + void set_rtty_threshold(float); + void set_rtty_bandwidth(float); + void set_rtty_transwidth(float); + void set_rtty_filterlen(float); + void set_rtty_mode(int); + void set_rtty_parity(int); + int save_rtty(); + /* Bookmarks */ void onBookmarkActivated(qint64 freq, const QString& demod, int bandwidth); @@ -261,6 +302,8 @@ private slots: void iqFftTimeout(); void audioFftTimeout(); void rdsTimeout(); + void faxTimeout(); + void rttyTimeout(); }; #endif // MAINWINDOW_H diff --git a/src/applications/gqrx/receiver.cpp b/src/applications/gqrx/receiver.cpp index 211d63e81..7c82e42ee 100644 --- a/src/applications/gqrx/receiver.cpp +++ b/src/applications/gqrx/receiver.cpp @@ -4,6 +4,7 @@ * https://gqrx.dk/ * * Copyright 2011-2014 Alexandru Csete OZ9AEC. + * Generic rx decoder interface Copyright 2022 Marc CAPDEVILLE F4JMZ * * Gqrx is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -72,6 +73,7 @@ receiver::receiver(const std::string input_device, d_iq_rev(false), d_dc_cancel(false), d_iq_balance(false), + d_rx_chain(RX_CHAIN_NONE), d_demod(RX_DEMOD_OFF) { @@ -1388,6 +1390,8 @@ void receiver::connect_all(rx_chain type) break; } + d_rx_chain = type; + // Audio path (if there is a receiver) if (type != RX_CHAIN_NONE) { @@ -1418,47 +1422,37 @@ void receiver::connect_all(rx_chain type) } } -void receiver::get_rds_data(std::string &outbuff, int &num) -{ - rx->get_rds_data(outbuff, num); +enum receiver::rx_chain receiver::get_rx_chain() { + return d_rx_chain; } -void receiver::start_rds_decoder(void) -{ - if (d_running) - { - stop(); - rx->start_rds_decoder(); - start(); - } - else - { - rx->start_rds_decoder(); - } +/* generic rx decoder functions */ +int receiver::start_decoder(enum receiver_base_cf::rx_decoder decoder_type) { + return rx->start_decoder(decoder_type); } -void receiver::stop_rds_decoder(void) -{ - if (d_running) - { - stop(); - rx->stop_rds_decoder(); - start(); - } - else - { - rx->stop_rds_decoder(); - } +int receiver::stop_decoder(enum receiver_base_cf::rx_decoder decoder_type) { + return rx->stop_decoder(decoder_type); } -bool receiver::is_rds_decoder_active(void) const -{ - return rx->is_rds_decoder_active(); +bool receiver::is_decoder_active(enum receiver_base_cf::rx_decoder decoder_type) const { + return rx->is_decoder_active(decoder_type); } -void receiver::reset_rds_parser(void) -{ - rx->reset_rds_parser(); +int receiver::reset_decoder(enum receiver_base_cf::rx_decoder decoder_type) { + return rx->reset_decoder(decoder_type); +} + +int receiver::set_decoder_param(enum receiver_base_cf::rx_decoder decoder_type, std::string param, std::string val) { + return rx->set_decoder_param(decoder_type,param,val); +} + +int receiver::get_decoder_param(enum receiver_base_cf::rx_decoder decoder_type, std::string param, std::string &val) { + return rx->get_decoder_param(decoder_type,param,val); +} + +int receiver::get_decoder_data(enum receiver_base_cf::rx_decoder decoder_type,void* data, int &num) { + return rx->get_decoder_data(decoder_type,data,num); } std::string receiver::escape_filename(std::string filename) diff --git a/src/applications/gqrx/receiver.h b/src/applications/gqrx/receiver.h index d3e8834c7..009658674 100644 --- a/src/applications/gqrx/receiver.h +++ b/src/applications/gqrx/receiver.h @@ -4,6 +4,7 @@ * https://gqrx.dk/ * * Copyright 2011-2014 Alexandru Csete OZ9AEC. + * Generic rx decoder interface Copyright 2022 Marc CAPDEVILLE F4JMZ * * Gqrx is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -219,15 +220,18 @@ class receiver bool is_recording_audio(void) const { return d_recording_wav; } bool is_snifffer_active(void) const { return d_sniffer_active; } - /* rds functions */ - void get_rds_data(std::string &outbuff, int &num); - void start_rds_decoder(void); - void stop_rds_decoder(); - bool is_rds_decoder_active(void) const; - void reset_rds_parser(void); + /* generic rx decoder functions */ + int start_decoder(enum receiver_base_cf::rx_decoder decoder_type); + int stop_decoder(enum receiver_base_cf::rx_decoder decoder_type); + bool is_decoder_active(enum receiver_base_cf::rx_decoder decoder_type) const; + int reset_decoder(enum receiver_base_cf::rx_decoder decoder_type); + int set_decoder_param(enum receiver_base_cf::rx_decoder decoder_type, std::string param, std::string val); + int get_decoder_param(enum receiver_base_cf::rx_decoder decoder_type, std::string param, std::string &val); + int get_decoder_data(enum receiver_base_cf::rx_decoder decoder_type, void* data, int& num); /* utility functions */ static std::string escape_filename(std::string filename); + enum rx_chain get_rx_chain(); private: void connect_all(rx_chain type); @@ -253,6 +257,7 @@ class receiver std::string input_devstr; /*!< Current input device string. */ std::string output_devstr; /*!< Current output device string. */ + rx_chain d_rx_chain; /*! Current Rx chain */ rx_demod d_demod; /*!< Current demodulator. */ gr::top_block_sptr tb; /*!< The GNU Radio top block. */ diff --git a/src/dsp/CMakeLists.txt b/src/dsp/CMakeLists.txt index 3f8e38e80..67bbc0b1a 100644 --- a/src/dsp/CMakeLists.txt +++ b/src/dsp/CMakeLists.txt @@ -29,6 +29,16 @@ add_source_files(SRCS_LIST lpf.h resampler_xx.cpp resampler_xx.h + rtty/rtty_demod.cpp + rtty/rtty_demod.h + rtty/fsk_demod.h + rtty/fsk_demod_impl.h + rtty/fsk_demod_impl.cpp + rtty/async_rx.h + rtty/async_rx_impl.h + rtty/async_rx_impl.cpp + rtty/char_store.h + rtty/char_store.cpp rx_agc_xx.cpp rx_agc_xx.h rx_demod_am.cpp @@ -49,4 +59,11 @@ add_source_files(SRCS_LIST sniffer_f.h stereo_demod.cpp stereo_demod.h + fax/fax_decoder.h + fax/fax_decoder_impl.cpp + fax/fax_decoder_impl.h + fax/fax_demod.cpp + fax/fax_demod.h + fax/fax_store.cpp + fax/fax_store.h ) diff --git a/src/dsp/fax/fax_decoder.h b/src/dsp/fax/fax_decoder.h new file mode 100644 index 000000000..50f7dfd66 --- /dev/null +++ b/src/dsp/fax/fax_decoder.h @@ -0,0 +1,59 @@ +/* + * fax decoder block header + * + * Copyright 2022 Marc CAPDEVILLE F4JMZ + * + * This is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3, or (at your option) + * any later version. + * + * This software is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this software; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 51 Franklin Street, + * Boston, MA 02110-1301, USA. + */ + +#ifndef FAX_DECODER_H +#define FAX_DECODER_H + +#include + +namespace gr { + namespace fax { + class fax_decoder : virtual public gr::block + { + public: + +#if GNURADIO_VERSION < 0x030900 + typedef boost::shared_ptr sptr; +#else + typedef std::shared_ptr sptr; +#endif + + static sptr make(float sample_rate, float lpm, float ioc); + + virtual void set_sample_rate(float sample_rate) = 0; + virtual float sample_rate() const = 0; + + virtual void set_lpm(float lpm) = 0; + virtual float lpm() const = 0; + + virtual void set_ioc(float ioc) = 0; + virtual float ioc() const = 0; + + virtual void set_state(int state) = 0; + virtual int state() const = 0; + + virtual void reset() = 0; + }; + + } // namespace fax +} // namespace gr + +#endif // FAX_DECODER_H diff --git a/src/dsp/fax/fax_decoder_impl.cpp b/src/dsp/fax/fax_decoder_impl.cpp new file mode 100644 index 000000000..18b773553 --- /dev/null +++ b/src/dsp/fax/fax_decoder_impl.cpp @@ -0,0 +1,329 @@ +/* + * fax decoder block implementation + * + * Copyright 2022 Marc CAPDEVILLE F4JMZ + * + * This is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3, or (at your option) + * any later version. + * + * This software is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this software; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 51 Franklin Street, + * Boston, MA 02110-1301, USA. + */ + +#include +#include +#include "fax_decoder_impl.h" + +#if defined(DEBUG) && 0 +#include +#define D(...) do { fprintf(stderr,__VA_ARGS__); } while (0) +#else +#define D(...) +#endif + +#ifndef M_PIf +# define M_PIf 3.14159265358979323846f /* pi */ +#endif + +namespace gr { + namespace fax { + + fax_decoder::sptr fax_decoder::make(float sample_rate, float lpm, float ioc) { + return gnuradio::get_initial_sptr(new fax_decoder_impl(sample_rate, lpm, ioc)); + } + + fax_decoder_impl::fax_decoder_impl(float sample_rate, float lpm, float ioc) : + gr::block("fax_decoder", + gr::io_signature::make(1, 1, sizeof(float)), + gr::io_signature::make(1, 1, sizeof(float))), + d_sample_rate(sample_rate), + d_lpm(lpm), + d_ioc(ioc), + tone_300(sample_rate,4*sample_rate * 60.0f/(lpm),sample_rate/(roundf(sample_rate/300))), + tone_675(sample_rate,4*sample_rate * 60.0f/(lpm),sample_rate/(roundf(sample_rate/675))), + tone_450(sample_rate,4*sample_rate * 60.0f/(lpm),sample_rate/(roundf(sample_rate/450))) { + + line_len = ((d_sample_rate *60.0f) / d_lpm); + pixel_len = line_len/(d_ioc*(float)M_PIf); + d_state = FAX_RESET; + in_count=0; + } + + fax_decoder_impl::~fax_decoder_impl() { + } + + void fax_decoder_impl::set_sample_rate(float sample_rate) { + std::lock_guard lock(mutex); + + d_sample_rate = sample_rate; + line_len = (d_sample_rate *60.0f / d_lpm); + pixel_len = line_len/(d_ioc*M_PIf); + in_count=0; + } + + float fax_decoder_impl::sample_rate() const { + return d_sample_rate; + } + + void fax_decoder_impl::set_lpm(float lpm) { + std::lock_guard lock(mutex); + + d_lpm = lpm; + line_len = (d_sample_rate *60.0f / d_lpm); + pixel_len = line_len/(d_ioc*M_PIf); + in_count=0; + } + + float fax_decoder_impl::lpm() const { + return d_lpm; + } + + void fax_decoder_impl::set_ioc(float ioc) { + std::lock_guard lock(mutex); + + d_ioc = ioc; + line_len = (d_sample_rate *60.0f / d_lpm); + pixel_len = line_len/(d_ioc*M_PIf); + } + + float fax_decoder_impl::ioc() const { + return d_ioc; + } + + void fax_decoder_impl::set_state(int state) { + std::lock_guard lock(mutex); + + switch (state) { + default: + case 0: d_state = FAX_RESET;in_count=0;D("FAX_RESET\n");break; + case 1: d_state = FAX_WAIT_START;D("FAX_WAIT_START\n");break; + case 2: d_state = FAX_WAIT_WHITE;D("FAX_WAIT_WHITE\n");break; + case 3: d_state = FAX_MEASURE_WHITE;D("FAX_MEASURE_WHITE\n");break; + case 4: d_state = FAX_MEASURE_BLACK;D("FAX_MEASURE_BLACK\n");break; + case 5: d_state = FAX_GET_LINES;D("FAX_GET_LINES\n");break; + }; + } + + int fax_decoder_impl::state() const { + return d_state; + } + + + void fax_decoder_impl::reset() { + std::lock_guard lock(mutex); + line_len = (d_sample_rate *60.0f / d_lpm); + pixel_len = line_len/(d_ioc*M_PIf); + d_state = FAX_RESET; + in_count=0; + D("FAX_RESET\n"); + } + + void fax_decoder_impl::forecast (int noutput_items, gr_vector_int &ninput_items_required) { + std::lock_guard lock(mutex); + + ninput_items_required[0] = roundf((noutput_items *pixel_len))+1; + } + + int fax_decoder_impl::general_work (int noutput_items, + gr_vector_int &ninput_items, + gr_vector_const_void_star &input_items, + gr_vector_void_star &output_items) { + std::lock_guard lock(mutex); + + const float* in = &((reinterpret_cast(input_items[0]))[(int)(0)]); + float *out = reinterpret_cast(output_items[0]); + int out_count = 0; + float InAcc, tone3,tone4=0,tone6; + + while( (out_count < (noutput_items)) && (roundf(in_count) < (ninput_items[0]-(int)roundf(pixel_len)))) { + volk_32f_accumulator_s32f(&InAcc,&in[(int)roundf(in_count)],(int)roundf(pixel_len)); + InAcc/=pixel_len; + + if (InAcc>0.8f) + bit = true; + else if (InAcc<-0.8f) + bit = false; + + switch (d_state) { + case FAX_RESET: + line_pos=0; + in_count=0; + noise_len=0; + line_start=0; + line_len = (d_sample_rate *60.0f / d_lpm); + pixel_len = line_len/(d_ioc*M_PIf); + bit = false; + phasing=3; + phasing_count=0; + phasing_len=0; + phasing_start=0; + tone_300.set_params(d_sample_rate,(int)roundf(line_len),300); + tone_675.set_params(d_sample_rate,(int)roundf(line_len),675); + tone_450.set_params(d_sample_rate/pixel_len,(int)roundf(line_len/pixel_len),450); + d_state = FAX_WAIT_START; + D("FAX_WAIT_START\n"); + break; + case FAX_WAIT_START: + if (tone_300.ready()) { + tone3=abs(tone_300.output()); + D("tone 300 : %f\n",tone3); + tone_300.set_params(d_sample_rate,(int)roundf(line_len),d_sample_rate/(roundf(d_sample_rate/300))); + if (tone3>0.25f) { //300Hz tone detected + noise_len = 0; + d_state = FAX_WAIT_WHITE; + D("FAX_WAIT_WHITE @300Hz\n"); + bit = false; + consume_each (ninput_items[0]); + return 0; + } + } + if (tone_675.ready()) { + tone6=abs(tone_675.output()); + D("tone 675 : %f\n",tone6); + tone_675.set_params(d_sample_rate,(int)roundf(line_len),d_sample_rate/(roundf(d_sample_rate/675))); + if (tone6>0.25f) { //675Hz tone detected + noise_len = 0; + d_state = FAX_WAIT_WHITE; + D("FAX_WAIT_WHITE @675Hz\n"); + bit = false; + consume_each (ninput_items[0]); + return 0; + } + } + tone_300.input(in[(int)roundf(in_count)]); + tone_675.input(in[(int)roundf(in_count)]); + in_count++; + break; + case FAX_WAIT_WHITE: // Wait for Black to white transition + in_count++; + if (!bit) { // Black + noise_len = 0; + } else { // White + noise_len++; + if (noise_len >= (10.0f*pixel_len)) { // Not noise : Transition to white + noise_len = 0; + line_pos = 0; + line_start = 0; + d_state = FAX_MEASURE_WHITE; + D("FAX_MEASURE_WHITE\n"); + } + } + + break; + case FAX_MEASURE_WHITE: // Measure White Len + in_count ++; + line_pos++; + if (bit) { // White + noise_len = 0; + } else { // Black + noise_len++; + if (noise_len >= (10.0f*pixel_len)) { // Not noise : Transition to black + //in_count -= noise_len; + //line_pos -= noise_len; + noise_len = 0; + if ((!line_start && (line_pos < (0.04f*line_len) || line_pos > (0.06f*line_len))) // White pulse too short + || (line_start && (line_pos < (line_start - 0.01f*line_len) || line_pos > (line_start + 0.01f*line_len)))) { + noise_len=0; + bit = false; + d_state = FAX_WAIT_WHITE; + D("FAX_WAIT_WHITE\n"); + } + else { + + line_start = line_pos; + d_state = FAX_MEASURE_BLACK; + D("FAX_MEASURE_BLACK\n"); + } + } + } + break; + case FAX_MEASURE_BLACK: // Measure Black Len + in_count ++; + line_pos++; + if (!bit) { // Black + noise_len = 0; + } else { // White + noise_len++; + if (noise_len >= (10*pixel_len)) { // Not noise : Transition to white + //in_count -= noise_len; + //line_pos -= noise_len; + if (line_pos < (line_len*0.99f) || line_pos > (line_len*1.01f)) // Black line too short + { + noise_len = 0; + d_state = FAX_MEASURE_WHITE; + D("FAX_WAIT_WHITE\n"); + } + else + { + phasing_count++; + phasing_len+=line_pos; + line_pos = 0; + phasing_start+=line_start; + line_start = phasing_start/phasing_count; + //line_len = phasing_len/phasing_count; + //pixel_len = line_len / (d_ioc*2*M_PIf); + if (phasing) { + noise_len = 0; + phasing--; + d_state = FAX_MEASURE_WHITE; + D("FAX_MEASURE_WHITE\n"); + } else { + d_state = FAX_GET_LINES; + D("FAX_GET_LINES\n"); + } + } + } + } + break; + case FAX_GET_LINES: // Get the fax until 450Hz tone + if (tone_450.ready()) { + tone4=abs(tone_450.output()); + D("tone 450 : %f\n",tone4); + tone_450.set_params(d_sample_rate/pixel_len,(int)roundf(line_len/pixel_len),450); + if (tone4 > 0.25f) { // 450Hz tone detected + // End of transmition + *out = +INFINITY; // End of image + out++; + out_count++; + D("FAX_RESET\n"); + d_state = FAX_RESET; + break; + } + } + tone_450.input(InAcc); + + *out=InAcc; + out++; + out_count++; + + in_count+=pixel_len; + line_pos+=pixel_len; + + if ( line_pos >= line_len) { // End of line + line_pos -= line_len; + *out = -INFINITY; // End of line marker + out++; + out_count++; + } + break; + + } + } + + consume_each ((int)roundf(in_count)); + in_count -= roundf(in_count); + + return (out_count); + } + + } +} diff --git a/src/dsp/fax/fax_decoder_impl.h b/src/dsp/fax/fax_decoder_impl.h new file mode 100644 index 000000000..5784c8347 --- /dev/null +++ b/src/dsp/fax/fax_decoder_impl.h @@ -0,0 +1,98 @@ +/* + * fax decoder block implementation header + * + * Copyright 2022 Marc CAPDEVILLE F4JMZ + * + * This is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3, or (at your option) + * any later version. + * + * This software is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this software; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 51 Franklin Street, + * Boston, MA 02110-1301, USA. + */ + +#ifndef FAX_DECODER_IMPL_H +#define FAX_DECODER_IMPL_H + +#include "fax_decoder.h" +#include +#include + +namespace gr { + namespace fax { + + class fax_decoder_impl : public fax_decoder + { + public: + fax_decoder_impl(float sample_rate, float lpm, float ioc); + ~fax_decoder_impl(); + + void forecast(int noutput_items, gr_vector_int &ninput_items_required); + + int general_work(int noutput_items, + gr_vector_int &ninput_items, + gr_vector_const_void_star &input_items, + gr_vector_void_star &output_items); + + void set_sample_rate(float sample_rate); + float sample_rate() const; + + void set_lpm(float lpm); + float lpm() const; + + void set_ioc(float ioc); + float ioc() const; + + void set_state(int state); + int state() const; + + void reset(); + + private: + enum fax_decoder_impl_state { + FAX_RESET = 0, // Reset state machine + FAX_WAIT_START, // Wait for start tone + FAX_WAIT_WHITE, // Wait for Black to white transition + FAX_MEASURE_WHITE, // Measure White Len + FAX_MEASURE_BLACK, // Measure Black Len + FAX_GET_LINES // Get Lines until stop tone + }; + + std::mutex mutex; + + // Properties + float d_sample_rate; + float d_lpm; + float d_ioc; + + + float pixel_len; + float line_len; + float line_pos; + float line_start; + float noise_len; + int phasing; + float phasing_len; + float phasing_start; + int phasing_count; + bool bit; + float in_count; + + enum fax_decoder_impl_state d_state; + + gr::fft::goertzel tone_300; // Start at ioc = 576 + gr::fft::goertzel tone_675; // Start at ioc = 288 + gr::fft::goertzel tone_450; // Stop + }; + } +} + +#endif // FAX_DECODER_H diff --git a/src/dsp/fax/fax_demod.cpp b/src/dsp/fax/fax_demod.cpp new file mode 100644 index 000000000..f77500ba8 --- /dev/null +++ b/src/dsp/fax/fax_demod.cpp @@ -0,0 +1,189 @@ +/* + * fax demodulator hier block implementation + * + * Copyright 2022 Marc CAPDEVILLE F4JMZ + * + * This is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3, or (at your option) + * any later version. + * + * This software is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this software; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 51 Franklin Street, + * Boston, MA 02110-1301, USA. + */ + +#include +#include +#include + +#if defined(DEBUG) && 0 +#include +#define D(...) do { fprintf(stderr,__VA_ARGS__); } while (0) +#else +#define D(...) +#endif + +#ifndef M_PIf +# define M_PIf 3.14159265358979323846f /* pi */ +#endif + +namespace gr { + namespace fax { + + /* Create a new instance of fax_demod and return a boost shared_ptr. */ + fax_demod::sptr fax_demod::make(float quad_rate, float black_freq, float white_freq,float lpm, float ioc) { + return gnuradio::get_initial_sptr(new fax_demod(quad_rate, black_freq, white_freq, lpm, ioc)); + } + + fax_demod::fax_demod(float quad_rate, float black_freq, float white_freq, float lpm, float ioc) + : gr::hier_block2 ("fax_demod", + gr::io_signature::make (1, 1, sizeof (gr_complex)), + gr::io_signature::make (0, 0, sizeof (char))), + d_quad_rate(quad_rate), + d_black_freq(black_freq), + d_white_freq(white_freq), + d_lpm(lpm), + d_ioc(ioc) { + + std::vector ctaps; + std::vector ftaps; + + delta = (d_white_freq - d_black_freq); + fc = (d_white_freq + d_black_freq)/2; + if (!delta) delta = 800; + + /* Input Filter */ + //ftaps = gr::filter::firdes::low_pass(1.0f, d_quad_rate, abs(delta/2) ,2000); + ftaps.clear(); + ftaps.resize(1); + ftaps[0] = 1; + input_filter = gr::filter::freq_xlating_fir_filter_ccf::make(1,ftaps,(double)fc,(double)d_quad_rate); + + /* quadrature demodulator */ + quad = gr::analog::quadrature_demod_cf::make(d_quad_rate / (2.0f * M_PIf * delta/2)); + + /* fax decoder */ + fax_decoder = gr::fax::fax_decoder::make(d_quad_rate,d_lpm,d_ioc); + + /* fax storage */ + fax_store = gr::fax::fax_store::make(120); // 1 minute circular buffer at 120lpm + + /* connect blocks */ + connect(self(), 0, input_filter, 0); + connect(input_filter, 0, quad, 0); + connect(quad, 0, fax_decoder, 0); + connect(fax_decoder, 0, fax_store, 0); + } + + fax_demod::~fax_demod () + { + + } + + void fax_demod::set_input_filter(void) { + std::vector ftaps; + + delta = (d_white_freq - d_black_freq); + fc = (d_white_freq + d_black_freq)/2; + if (!delta) { + delta=800; + } + + //ftaps = gr::filter::firdes::low_pass(1.0f, d_quad_rate, abs(delta/2) ,2000); + ftaps.clear(); + ftaps.resize(1); + ftaps[0] = 1; + input_filter->set_taps(ftaps); + input_filter->set_center_freq((double)fc); + } + + void fax_demod::set_quad(void) { + quad->set_gain(d_quad_rate / (2.0f * M_PIf * delta/2)); + } + + void fax_demod::set_quad_rate(float quad_rate) { + d_quad_rate = quad_rate; + + set_input_filter(); + set_quad(); + fax_decoder->set_sample_rate(d_quad_rate); + + } + + float fax_demod::quad_rate() const { + return d_quad_rate; + } + + void fax_demod::set_black_freq(float black_freq) { + d_black_freq = black_freq; + + set_input_filter(); + set_quad(); + } + + float fax_demod::black_freq() const { + return d_black_freq; + } + + void fax_demod::set_white_freq(float white_freq) { + d_white_freq = white_freq; + + set_input_filter(); + set_quad(); + } + + float fax_demod::white_freq() const { + return d_white_freq; + } + + void fax_demod::set_lpm(float lpm) { + if (!lpm) + return; + d_lpm = lpm; + set_quad(); + fax_decoder->set_lpm(d_lpm); + } + + float fax_demod::lpm() const { + return fax_decoder->lpm(); + } + + void fax_demod::set_ioc(float ioc) { + if (!ioc) + return; + d_ioc = ioc; + set_quad(); + fax_decoder->set_ioc(d_ioc); + } + + float fax_demod::ioc() const { + return fax_decoder->ioc(); + } + + void fax_demod::reset(void) { + fax_store->reset(); + fax_decoder->reset(); + } + + int fax_demod::get_data(unsigned char * data,unsigned int *len) { + + return fax_store->get_data(data,len); + } + + void fax_demod::set_decoder_state(int state) { + fax_decoder->set_state(state); + } + + int fax_demod::decoder_state() const { + return fax_decoder->state(); + } + + } +} diff --git a/src/dsp/fax/fax_demod.h b/src/dsp/fax/fax_demod.h new file mode 100644 index 000000000..bef767414 --- /dev/null +++ b/src/dsp/fax/fax_demod.h @@ -0,0 +1,106 @@ +/* + * Copyright 2022 Marc CAPDEVILLE F4JMZ + * + * fax demodulator hier block header + * + * This is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3, or (at your option) + * any later version. + * + * This software is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this software; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 51 Franklin Street, + * Boston, MA 02110-1301, USA. + */ + +#ifndef FAX_DEMOD_H +#define FAX_DEMOD_H + +#include +#include +#if GNURADIO_VERSION < 0x030800 +#include +#else +#include +#endif +#include +#include "fax_demod.h" +#include "dsp/fax/fax_decoder.h" +#include "dsp/fax/fax_store.h" +#include + + +namespace gr { + namespace fax { + class fax_demod : public gr::hier_block2 + { + + public: +#if GNURADIO_VERSION < 0x030900 + typedef boost::shared_ptr sptr; +#else + typedef std::shared_ptr sptr; +#endif + + static sptr make(float quad_rate, float black_freq, float white_freq,float lpm,float ioc); + + void set_quad_rate(float quad_rate); + float quad_rate() const; + void set_black_freq(float black_freq); + float black_freq() const; + void set_white_freq(float white_freq); + float white_freq() const; + void set_lpm(float lpm); + float lpm() const; + void set_ioc(float ioc); + float ioc() const; + void set_decoder_state(int state); + int decoder_state() const; + + void reset(); + + int get_data(unsigned char* data,unsigned int *len); + + private: + + fax_demod(float quad_rate, float black_freq, float white_freq,float lpm,float ioc); + ~fax_demod(); + + void set_input_filter(void); + void set_pixel_filter(void); + void set_quad(void); + + /* parameters */ + float d_quad_rate; /*! Sample rate */ + float d_black_freq; /*! Black frequency */ + float d_white_freq; /*! White frequency */ + float d_lpm; /*! Lines per minute */ + float d_ioc; /*! Index of cooperation */ + + /* Input filter */ + gr::filter::freq_xlating_fir_filter_ccf::sptr input_filter; + + /* quadrature demodulator */ + gr::analog::quadrature_demod_cf::sptr quad; + + /* fax decoder */ + gr::fax::fax_decoder::sptr fax_decoder; + + /* fax line storage */ + gr::fax::fax_store::sptr fax_store; + + /* internal variable */ + float fc, delta, fmin, fmax; + float pixel_rate; + int dec; + }; + + } +} +#endif // FAX_DEMOD_H diff --git a/src/dsp/fax/fax_store.cpp b/src/dsp/fax/fax_store.cpp new file mode 100644 index 000000000..1fc3ce863 --- /dev/null +++ b/src/dsp/fax/fax_store.cpp @@ -0,0 +1,117 @@ +/* + * fax decoded lines sink block implementation + * + * Copyright 2022 Marc CAPDEVILLE F4JMZ + * + * This is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3, or (at your option) + * any later version. + * + * This software is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this software; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 51 Franklin Street, + * Boston, MA 02110-1301, USA. + */ + +#include +#include "fax_store.h" + +#if defined(DEBUG) && 0 +#include +#define D(...) do { fprintf(stderr,__VA_ARGS__); } while (0) +#else +#define D(...) +#endif + +namespace gr { + namespace fax { + + fax_store::sptr fax_store::make(int len) { + return gnuradio::get_initial_sptr(new gr::fax::fax_store(len)); + } + + fax_store::fax_store(int len) : gr::sync_block ("fax_store", + gr::io_signature::make (1, 1, sizeof(float)), + gr::io_signature::make (0, 0, 0)) { + current_line.reserve(1810); + } + + fax_store::~fax_store () { + } + + void fax_store::reset() { + std::lock_guard lock(d_mutex); + current_line.clear(); + while (!d_data.empty()) + d_data.pop(); + } + + int fax_store::work (int noutput_items, + gr_vector_const_void_star &input_items, + gr_vector_void_star &output_items) { + + std::lock_guard lock(d_mutex); + const float *in = reinterpret_cast(input_items[0]); + float Gray; + int i=0; + + while (i < noutput_items) { + + if (*in == -INFINITY) { // End of line + d_data.push(current_line); + current_line.clear(); + } else if (*in == +INFINITY) { // End of image + d_data.push(current_line); + current_line.clear(); + // Insert empty line + d_data.push(current_line); + } else { + Gray = roundf(((*in)*128+128)); + if (Gray < 0 ) Gray = 0; + else if (Gray > 255) Gray=255; + current_line.push_back((unsigned char)Gray); + } + + in++; + i++; + } + + consume_each(noutput_items); + + return 0; + } + + int fax_store::get_data (unsigned char* data, unsigned int* len) { + + std::lock_guard lock(d_mutex); + + if (!d_data.empty()) { + std::vector &line = d_data.front(); + + if (*len > line.size()) + *len = line.size(); + + if (data) { + memcpy(data,line.data(),*len); + d_data.pop(); + } + + if (d_data.empty()) { + return -1; + } + + // Announce next line size + *len = d_data.front().size(); + + return 0; + } + + return -1; + } + }} diff --git a/src/dsp/fax/fax_store.h b/src/dsp/fax/fax_store.h new file mode 100644 index 000000000..a425b649d --- /dev/null +++ b/src/dsp/fax/fax_store.h @@ -0,0 +1,62 @@ +/* + * fax decoded lines sink block header + * + * Copyright 2022 Marc CAPDEVILLE F4JMZ + * + * This is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3, or (at your option) + * any later version. + * + * This software is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this software; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 51 Franklin Street, + * Boston, MA 02110-1301, USA. + */ + +#ifndef FAX_STORE_H +#define FAX_STORE_H + +#include +#include +#include +#include + +namespace gr { + namespace fax { + class fax_store : public gr::sync_block { + public: + +#if GNURADIO_VERSION < 0x030900 + typedef boost::shared_ptr sptr; +#else + typedef std::shared_ptr sptr; +#endif + + static sptr make(int len); + + int get_data(unsigned char *out,unsigned int *len); + void reset(); + + int work (int noutput_items, + gr_vector_const_void_star &input_items, + gr_vector_void_star &output_items); + + private: + fax_store(int len); + ~fax_store(); + + std::mutex d_mutex; + + std::queue> d_data; + std::vector current_line; + }; + + } +} +#endif // FAX_STORE_H diff --git a/src/dsp/rtty/async_rx.h b/src/dsp/rtty/async_rx.h new file mode 100644 index 000000000..b654dc4f1 --- /dev/null +++ b/src/dsp/rtty/async_rx.h @@ -0,0 +1,72 @@ +/* + * asynchronous receiver block header + * + * Copyright 2022 Marc CAPDEVILLE F4JMZ + * + * This is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3, or (at your option) + * any later version. + * + * This software is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this software; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 51 Franklin Street, + * Boston, MA 02110-1301, USA. + */ + +#ifndef ASYNC_RX_H +#define ASYNC_RX_H + +#include + +#define ASYNC_RX_ESC_CHAR (0x1b) +#define ASYNC_RX_SOT_CHAR (0x02) // STX +#define ASYNC_RX_EOT_CHAR (0x03) // ETX + +namespace gr { + namespace rtty { + class async_rx : virtual public gr::block + { + public: + + enum async_rx_parity { + ASYNC_RX_PARITY_NONE = 0, + ASYNC_RX_PARITY_ODD, + ASYNC_RX_PARITY_EVEN, + ASYNC_RX_PARITY_MARK, + ASYNC_RX_PARITY_SPACE, + ASYNC_RX_PARITY_DONTCARE + }; + +#if GNURADIO_VERSION < 0x030900 + typedef boost::shared_ptr sptr; +#else + typedef std::shared_ptr sptr; +#endif + + static sptr make(float sample_rate, float bit_rate, char word_len, enum async_rx_parity parity); + + virtual void set_sample_rate(float sample_rate) = 0; + virtual float sample_rate() const = 0; + + virtual void set_bit_rate(float bit_rate) = 0; + virtual float bit_rate() const = 0; + + virtual void set_word_len(char bits_per_word) = 0 ; + virtual char word_len() const = 0; + + virtual void set_parity(enum async_rx_parity parity) = 0; + virtual enum async_rx_parity parity() const = 0; + + virtual void reset() = 0; + }; + + } // namespace rtty +} // namespace gr + +#endif // ASYNC_RX_H diff --git a/src/dsp/rtty/async_rx_impl.cpp b/src/dsp/rtty/async_rx_impl.cpp new file mode 100644 index 000000000..a13821b8d --- /dev/null +++ b/src/dsp/rtty/async_rx_impl.cpp @@ -0,0 +1,311 @@ +/* + * asynchronous receiver block implementation + * + * Copyright 2022 Marc CAPDEVILLE F4JMZ + * + * This is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3, or (at your option) + * any later version. + * + * This software is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this software; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 51 Franklin Street, + * Boston, MA 02110-1301, USA. + */ + +#include +#include +#include "async_rx_impl.h" + +namespace gr { + namespace rtty { + + async_rx::sptr async_rx::make(float sample_rate, float bit_rate, char word_len, enum async_rx_parity parity) { + return gnuradio::get_initial_sptr(new async_rx_impl(sample_rate, bit_rate, word_len, parity)); + } + + async_rx_impl::async_rx_impl(float sample_rate, float bit_rate, char word_len, enum async_rx_parity parity) : + gr::block("async_rx", + gr::io_signature::make(1, 2, sizeof(float)), + gr::io_signature::make(1, 1, sizeof(unsigned char))), + d_sample_rate(sample_rate), + d_bit_rate(bit_rate), + d_word_len(word_len), + d_parity(parity) { + bit_len = (sample_rate / bit_rate); + state = ASYNC_WAIT_IDLE; + cd = false; + send_esc = false; + send_sot = false; + send_eot = false; + } + + async_rx_impl::~async_rx_impl() { + } + + void async_rx_impl::set_word_len(char word_len) { + std::lock_guard lock(d_mutex); + + d_word_len = word_len; + } + + char async_rx_impl::word_len() const { + return d_word_len; + } + + void async_rx_impl::set_sample_rate(float sample_rate) { + std::lock_guard lock(d_mutex); + + d_sample_rate = sample_rate; + bit_len = (sample_rate / d_bit_rate); + } + + float async_rx_impl::sample_rate() const { + return d_sample_rate; + } + + void async_rx_impl::set_bit_rate(float bit_rate) { + std::lock_guard lock(d_mutex); + + d_bit_rate = bit_rate; + bit_len = (d_sample_rate / bit_rate); + } + + float async_rx_impl::bit_rate() const { + return d_bit_rate; + } + + void async_rx_impl::set_parity(enum async_rx::async_rx_parity parity) { + std::lock_guard lock(d_mutex); + + d_parity = parity; + } + + enum async_rx::async_rx_parity async_rx_impl::parity() const { + return d_parity; + } + + void async_rx_impl::reset() { + std::lock_guard lock(d_mutex); + + state = ASYNC_WAIT_IDLE; + cd = false; + send_esc = false; + send_sot = false; + send_eot = false; + } + + void async_rx_impl::forecast (int noutput_items, gr_vector_int &ninput_items_required) { + std::lock_guard lock(d_mutex); + + ninput_items_required[0] = (noutput_items * (d_word_len+2+(d_parity==ASYNC_RX_PARITY_NONE?0:1))+1) * bit_len; + } + + int async_rx_impl::general_work (int noutput_items, + gr_vector_int &ninput_items, + gr_vector_const_void_star &input_items, + gr_vector_void_star &output_items) { + + std::lock_guard lock(d_mutex); + int in_count = 0; + int out_count = 0; + const float *in = reinterpret_cast(input_items[0]); + const float *carrier_detect; + unsigned char *out = reinterpret_cast(output_items[0]); + + if (input_items.size()>1 && input_items[1]!=NULL) + carrier_detect = reinterpret_cast(input_items[1]); + else carrier_detect = NULL; + + while( (out_count < noutput_items) && (in_count < (ninput_items[0]-(bit_len)))) { + + if (in[in_count] >0) + bit = true; + else bit = false; + + if (carrier_detect && in_count < ninput_items[1]) { // carrier detect signal connected + if (send_esc) { + *out = ASYNC_RX_ESC_CHAR; + out++; + out_count++; + send_esc = false; + continue; + } else if (send_sot) { + *out = ASYNC_RX_SOT_CHAR; + out++; + out_count++; + send_sot = false; + continue; + } else if (send_eot) { + *out = ASYNC_RX_EOT_CHAR; + out++; + out_count++; + send_eot = false; + continue; + } + if (!cd && carrier_detect[in_count]>0) { + cd = true; + // send ESC+SOT + *out = ASYNC_RX_ESC_CHAR; + send_sot = true; + out++; + out_count++; + continue; + } else if (cd && carrier_detect[in_count] <=0) { + cd = false; + // send ESC+EOT + *out = ASYNC_RX_ESC_CHAR; + send_eot = true; + out++; + out_count++; + continue; + } + } else { + cd = true; + send_esc = false; + send_sot = false; + send_eot = false; + } + + + switch (state) { + case ASYNC_IDLE: // Wait for MARK to SPACE transition + if (!bit) { // transition detected + state = ASYNC_CHECK_START; + word_pos=0; + } + break; + case ASYNC_CHECK_START: // Check start bit for half a bit + word_pos++; + if (!bit) { // Space + if (word_pos>=bit_len/2) { // start bit verified + state = ASYNC_GET_BIT; + bit_pos = 0; + bit_count = 0; + word = 0; + } + } else { // Noise detection on start + state = ASYNC_IDLE; + } + break; + case ASYNC_GET_BIT: + word_pos++; + if (word_pos>=(((float)bit_pos+1.5f)*bit_len)) { // sample at center of bit + if (bit) { + word |= 1<=(((float)bit_pos+1.5f)*bit_len)) { // sample at center of bit + bit_pos++; + switch (d_parity) { + default: + case ASYNC_RX_PARITY_NONE: + state = ASYNC_CHECK_STOP; + break; + case ASYNC_RX_PARITY_ODD: + if ((!bit && (bit_count&1)) || (bit && !(bit_count&1))) { + state = ASYNC_CHECK_STOP; + } + else { + if (bit) + state = ASYNC_IDLE; + else + state = ASYNC_WAIT_IDLE; + } + break; + case ASYNC_RX_PARITY_EVEN: + if ((!bit && !(bit_count&1)) || (bit && (bit_count&1))) { + state = ASYNC_CHECK_STOP; + } + else { + if (bit) + state = ASYNC_IDLE; + else + state = ASYNC_WAIT_IDLE; + } + break; + case ASYNC_RX_PARITY_MARK: + if (bit) { + state = ASYNC_CHECK_STOP; + } + else { + state = ASYNC_WAIT_IDLE; + } + break; + case ASYNC_RX_PARITY_SPACE: + if (!bit) { + state = ASYNC_CHECK_STOP; + } + else { + state = ASYNC_IDLE; + } + break; + case ASYNC_RX_PARITY_DONTCARE: + state = ASYNC_CHECK_STOP; + break; + } + } + break; + case ASYNC_CHECK_STOP: // Check stop bit + word_pos++; + if (word_pos>=(((float)bit_pos+1.5f)*bit_len)) { // sample at center of bit + bit_pos++; + if (bit) { // Stop bit verified + if (cd) { + *out = word; + out++; + out_count++; + + if (word == ASYNC_RX_ESC_CHAR) + send_esc = true; + } + state = ASYNC_IDLE; + } + else + state = ASYNC_WAIT_IDLE; + } + break; + default: + case ASYNC_WAIT_IDLE: // Wait for SPACE to MARK transition + if (bit) { // transition detected + state = ASYNC_CHECK_IDLE; + word_pos=0; + } + break; + case ASYNC_CHECK_IDLE: // Check idle for half a bit + word_pos++; + if (bit) { // Mark + if (word_pos>=(bit_len/2)) { // idle verified + state = ASYNC_IDLE; + } + } else { // Noise detection on idle + state = ASYNC_WAIT_IDLE; + } + break; + } + in_count++; + } + + consume_each (in_count); + + return (out_count); + } + } +} diff --git a/src/dsp/rtty/async_rx_impl.h b/src/dsp/rtty/async_rx_impl.h new file mode 100644 index 000000000..fb8fe46ba --- /dev/null +++ b/src/dsp/rtty/async_rx_impl.h @@ -0,0 +1,92 @@ +/* + * asynchronous receiver block implementation header + * + * Copyright 2022 Marc CAPDEVILLE F4JMZ + * + * This is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3, or (at your option) + * any later version. + * + * This software is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this software; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 51 Franklin Street, + * Boston, MA 02110-1301, USA. + */ + +#ifndef ASYNC_RX_IMPL_H +#define ASYNC_RX_IMPL_H + +#include "async_rx.h" +#include + +namespace gr { + namespace rtty { + + class async_rx_impl : public async_rx + { + public: + async_rx_impl(float sample_rate, float bit_rate, char word_len, enum async_rx_parity parity); + ~async_rx_impl(); + + void forecast(int noutput_items, gr_vector_int &ninput_items_required); + + int general_work(int noutput_items, + gr_vector_int &ninput_items, + gr_vector_const_void_star &input_items, + gr_vector_void_star &output_items); + + void set_sample_rate(float sample_rate); + float sample_rate() const ; + + void set_bit_rate(float bit_rate); + float bit_rate() const ; + + void set_word_len(char word_len); + char word_len() const ; + + void set_parity(enum async_rx_parity parity); + enum async_rx_parity parity() const ; + + void reset(); + + private: + enum async_rx_impl_state { + ASYNC_WAIT_IDLE = 0, + ASYNC_CHECK_IDLE, + ASYNC_IDLE, + ASYNC_CHECK_START, + ASYNC_GET_BIT, + ASYNC_CHECK_PARITY, + ASYNC_CHECK_STOP + }; + + float d_sample_rate; + float d_bit_rate; + unsigned char d_word_len; + enum async_rx_parity d_parity; + + std::mutex d_mutex; + + enum async_rx_impl_state state; + float bit_len; + unsigned int word_pos; // number of sample from start transition; + unsigned char bit_pos; + unsigned char bit_count; // number of mark bit (for parity) + unsigned char word; // recived word + + bool cd; + bool bit; + bool send_esc; + bool send_sot; + bool send_eot; + }; + } +} + +#endif // ASYNC_RX_H diff --git a/src/dsp/rtty/char_store.cpp b/src/dsp/rtty/char_store.cpp new file mode 100644 index 000000000..5cefb4686 --- /dev/null +++ b/src/dsp/rtty/char_store.cpp @@ -0,0 +1,149 @@ +/* + * character sink with baudot decoder block implementation + * + * Copyright 2022 Marc CAPDEVILLE F4JMZ + * + * This is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3, or (at your option) + * any later version. + * + * This software is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this software; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 51 Franklin Street, + * Boston, MA 02110-1301, USA. + */ + +#include +#include "char_store.h" +#include "async_rx.h" + +char_store::sptr char_store::make(int size,bool baudot) +{ + return gnuradio::get_initial_sptr(new char_store(size,baudot)); +} + +char_store::char_store(int size,bool baudot) : gr::sync_block ("char_store", + gr::io_signature::make (1,1, sizeof(char)), + gr::io_signature::make (0, 0, 0)), + d_baudot(baudot), + d_figures(false), + last_char(0) { + esc= false; + } + +char_store::~char_store () { +} + +void char_store::store(std::string data) { + std::lock_guard lock(d_mutex); + d_data.push(data); +} + +void char_store::set_baudot(bool baudot) { + d_baudot = baudot; + d_figures = false; + esc = false; +} + +#define BAUDOT_LETTERS 31 +#define BAUDOT_FIGURES 27 + +static const char Baudot_letters[][32] = { + "\000E\nA SIU\rDRJNFCKTZLWHYPQOBG\000MXV", +}; + +static const char Baudot_figures[][32] = { + "\0003\n- \a87\r$4',!:(5\")2#6019?&\000./;", +}; + +int char_store::work (int noutput_items, + gr_vector_const_void_star &input_items, + gr_vector_void_star &output_items) { + std::string data; + int i; + unsigned int c; + + for (i=0;i lock(d_mutex); + + if (!d_data.empty()) { + data=d_data.front(); + d_data.pop(); + + return d_data.size(); + } + + return -1; +} diff --git a/src/dsp/rtty/char_store.h b/src/dsp/rtty/char_store.h new file mode 100644 index 000000000..0a5547cea --- /dev/null +++ b/src/dsp/rtty/char_store.h @@ -0,0 +1,63 @@ +/* + * character sink with baudot decoder block header + * + * Copyright 2022 Marc CAPDEVILLE F4JMZ + * + * This is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3, or (at your option) + * any later version. + * + * This software is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this software; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 51 Franklin Street, + * Boston, MA 02110-1301, USA. + */ + +#ifndef CHAR_STORE_H +#define CHAR_STORE_H + +#include +#include +#include +#include + +class char_store : public gr::sync_block { + public: + +#if GNURADIO_VERSION < 0x030900 + typedef boost::shared_ptr sptr; +#else + typedef std::shared_ptr sptr; +#endif + + static sptr make(int size,bool baudot); + + int get_data(std::string &out); + + void set_baudot(bool baudot); + + int work (int noutput_items, + gr_vector_const_void_star &input_items, + gr_vector_void_star &output_items); + + private: + char_store(int size,bool baudot); + ~char_store(); + + void store(std::string data); + std::mutex d_mutex; + std::queue d_data; + + bool d_baudot; + bool d_figures; + int last_char; + bool esc; +}; + +#endif // CHAR_STORE_H diff --git a/src/dsp/rtty/fsk_demod.h b/src/dsp/rtty/fsk_demod.h new file mode 100644 index 000000000..cb6d8dee0 --- /dev/null +++ b/src/dsp/rtty/fsk_demod.h @@ -0,0 +1,79 @@ +/* + * fsk/afsk demodulator block header + * + * Copyright 2022 Marc CAPDEVILLE F4JMZ + * + * fsk/afsk demodulator block header + * + * This is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3, or (at your option) + * any later version. + * + * This software is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this software; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 51 Franklin Street, + * Boston, MA 02110-1301, USA. + */ + +#ifndef _FSK_DEMOD_H_ +#define _FSK_DEMOD_H_ + +#include + +namespace gr { + namespace rtty { + class fsk_demod : virtual public gr::sync_decimator { + public: +#if GNURADIO_VERSION < 0x030900 + typedef boost::shared_ptr sptr; +#else + typedef std::shared_ptr sptr; +#endif + + static sptr make(float sample_rate,unsigned int decimation,float symbol_rate, float mark_freq,float space_freq,float threshold=6.0f,float cd_len=6.0f); + + virtual int work(int noutput_items, + gr_vector_const_void_star &input_items, + gr_vector_void_star &output_items) = 0; + + virtual void set_sample_rate(float sample_rate) = 0; + virtual float sample_rate() const = 0; + + virtual void set_decimation(unsigned int decimation) = 0; + virtual int decimation() const = 0; + + virtual void set_symbol_rate(float symbol_rate) = 0; + virtual float symbol_rate() const = 0; + + virtual void set_mark_freq(float mark_freq) = 0; + virtual float mark_freq() const = 0; + + virtual void set_space_freq(float space_freq) = 0; + virtual float space_freq() const = 0; + + virtual void set_threshold(float threshold) = 0; + virtual float threshold() const = 0; + + virtual void set_cd_len(float cd_len) = 0; + virtual float cd_len() const = 0; + + virtual void set_bandwidth(float bw) = 0; + virtual float bandwidth() const = 0; + + virtual void set_transwidth(float trw) = 0; + virtual float transwidth() const = 0; + + virtual void set_filterlen(float filterlen) = 0; + virtual float filterlen() const = 0; + + }; + } // namespace rtty +} // namespace gr + +#endif // _FSK_DEMOD_H_ diff --git a/src/dsp/rtty/fsk_demod_impl.cpp b/src/dsp/rtty/fsk_demod_impl.cpp new file mode 100644 index 000000000..9f47517be --- /dev/null +++ b/src/dsp/rtty/fsk_demod_impl.cpp @@ -0,0 +1,422 @@ +/* + * fsk/afsk demodulator block implementation + * + * Copyright 2022 Marc CAPDEVILLE F4JMZ + * + * This is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3, or (at your option) + * any later version. + * + * This software is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this software; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 51 Franklin Street, + * Boston, MA 02110-1301, USA. + */ + +#include +#include +#include +#include +#include +#include "fsk_demod_impl.h" + +#ifndef M_PIf +# define M_PIf 3.14159265358979323846f /* pi */ +#endif + + + +#define FSK_DEMOD_NTAPS (d_filterlen * d_sample_rate/d_symbol_rate) // Filter on 1 symbol + +#define FSK_DEMOD_BP_ATT (FSK_DEMOD_NTAPS*22.0f*d_trw/d_sample_rate) // Attenuation + +#define FSK_DEMOD_RATE (d_sample_rate/d_decimation) // output rate +#define FSK_SYMBOL_LEN (FSK_DEMOD_RATE/d_symbol_rate) + +namespace gr { + namespace rtty { + + fsk_demod_impl::fsk_demod_impl(float sample_rate, unsigned int decimation, float symbol_rate, float mark_freq, float space_freq,float threshold,float cd_len) : + gr::sync_decimator("fsk_demod", + gr::io_signature::make(1, 1, sizeof(gr_complex)), + gr::io_signature::make(1, 5, sizeof(float)), + decimation ), +#if GNURADIO_VERSION < 0x030900 + d_mark_fir(1,{}), + d_space_fir(1,{}), +#else + d_mark_fir({}), + d_space_fir({}), +#endif + d_sample_rate(sample_rate), + d_decimation(decimation), + d_symbol_rate(symbol_rate), + d_mark_freq(mark_freq), + d_space_freq(space_freq), + d_threshold(threshold), + d_cd_len(cd_len) { + + d_bw = d_symbol_rate*2; + d_trw = d_symbol_rate/2; + d_filterlen = 1.0f; + + d_tone_filter_taps = gr::filter::firdes::low_pass_2((double)1.0, (double)d_sample_rate, (double)d_bw/2, (double)d_trw, (double)FSK_DEMOD_BP_ATT); + set_history(d_tone_filter_taps.size()); + + set_mark_freq(d_mark_freq); + set_space_freq(d_space_freq); + count_noise = 0; + count_mark=0; + count_space=0; + noise_count = 0; + cd_count = 0; + cd = false; + last_noise = true; + last_bit = false; + + set_threshold(threshold); + set_cd_len(d_cd_len); + } + + fsk_demod::sptr fsk_demod::make(float sample_rate, unsigned int decimation, float symbol_rate, float mark_freq, float space_freq,float threshold,float cd_len) { + return gnuradio::get_initial_sptr (new fsk_demod_impl(sample_rate, decimation, symbol_rate, mark_freq,space_freq,threshold,cd_len)); + } + + fsk_demod_impl::~fsk_demod_impl() { + } + + void fsk_demod_impl::set_sample_rate(float sample_rate) { + std::lock_guard lock(d_mutex); + + d_sample_rate = sample_rate; + + d_tone_filter_taps = gr::filter::firdes::low_pass_2((double)1.0f, (double)d_sample_rate, (double)d_bw/2, (double)d_trw, (double)FSK_DEMOD_BP_ATT); + set_history(d_tone_filter_taps.size()); + + set_mark_freq(d_mark_freq); + set_space_freq(d_space_freq); + + count_noise = 0; + count_mark=0; + count_space=0; + cd = false; + + set_cd_len(d_cd_len); + } + + float fsk_demod_impl::sample_rate() const { + return d_sample_rate; + } + + void fsk_demod_impl::set_decimation(unsigned int decimation) { + std::lock_guard lock(d_mutex); + d_decimation = decimation; + + gr::sync_decimator::set_decimation(decimation); + + d_tone_filter_taps = gr::filter::firdes::low_pass_2((double)1.0f, (double)d_sample_rate, (double)d_bw/2, (double)d_trw, (double)FSK_DEMOD_BP_ATT); + set_history(d_tone_filter_taps.size()); + + set_mark_freq(d_mark_freq); + set_space_freq(d_space_freq); + + while (!last_samples.empty()) + last_samples.pop(); + + count_noise = 0; + count_mark=0; + count_space=0; + + set_cd_len(d_cd_len); + } + + int fsk_demod_impl::decimation() const { + return d_decimation; + } + + void fsk_demod_impl::set_symbol_rate(float symbol_rate) { + std::lock_guard lock(d_mutex); + + d_symbol_rate = symbol_rate; + + d_tone_filter_taps = gr::filter::firdes::low_pass_2((double)1.0f, (double)d_sample_rate, (double)d_bw/2, (double)d_trw, (double)FSK_DEMOD_BP_ATT); + set_history(d_tone_filter_taps.size()); + + set_mark_freq(d_mark_freq); + set_space_freq(d_space_freq); + + while (!last_samples.empty()) + last_samples.pop(); + + count_noise = 0; + count_mark=0; + count_space=0; + + set_cd_len(d_cd_len); + } + + float fsk_demod_impl::symbol_rate() const { + return d_symbol_rate; + } + + void fsk_demod_impl::set_mark_freq(float mark_freq) { + std::lock_guard lock(d_mutex); + + std::vector ctaps(d_tone_filter_taps.size()); + float fwT0 = 2* M_PIf * (mark_freq/d_sample_rate); + + + for (unsigned int i=0; i< d_tone_filter_taps.size(); i++) { + ctaps[i] = d_tone_filter_taps[i] * exp(gr_complex(0, i * fwT0)); + } + + d_mark_fir.set_taps(ctaps); + d_mark_freq = mark_freq; + } + + float fsk_demod_impl::mark_freq() const { + return d_mark_freq; + } + + void fsk_demod_impl::set_space_freq(float space_freq) { + std::lock_guard lock(d_mutex); + + std::vector ctaps(d_tone_filter_taps.size()); + float fwT0 = 2* M_PIf * (space_freq/d_sample_rate); + + for (unsigned int i=0; i< d_tone_filter_taps.size(); i++) { + ctaps[i] = d_tone_filter_taps[i] * exp(gr_complex(0, i * fwT0)); + } + + d_space_fir.set_taps(ctaps); + d_space_freq = space_freq; + + } + + float fsk_demod_impl::space_freq() const { + return d_space_freq; + } + + void fsk_demod_impl::set_threshold(float threshold) { + d_threshold = threshold; + threshold_mark = pow(10,(d_threshold/10)); + threshold_space = 1.0f/threshold_mark; + } + + float fsk_demod_impl::threshold() const { + return d_threshold; + } + + void fsk_demod_impl::set_cd_len(float cd_len) { + d_cd_len = cd_len; + cd_count = roundf(FSK_SYMBOL_LEN*d_cd_len); + } + + float fsk_demod_impl::cd_len() const { + return d_cd_len; + } + + // band pass filter width + void fsk_demod_impl::set_bandwidth(float bw) { + d_bw = bw; + + d_tone_filter_taps = gr::filter::firdes::low_pass_2((double)1.0f, (double)d_sample_rate, (double)d_bw/2, (double)d_trw, (double)FSK_DEMOD_BP_ATT); + set_history(d_tone_filter_taps.size()); + + set_mark_freq(d_mark_freq); + set_space_freq(d_space_freq); + } + float fsk_demod_impl::bandwidth() const { + return d_bw; + } + + // band pass filter transition width + void fsk_demod_impl::set_transwidth(float trw) { + d_trw = trw; + + d_tone_filter_taps = gr::filter::firdes::low_pass_2((double)1.0f, (double)d_sample_rate, (double)d_bw/2, (double)d_trw, (double)FSK_DEMOD_BP_ATT); + set_history(d_tone_filter_taps.size()); + + set_mark_freq(d_mark_freq); + set_space_freq(d_space_freq); + } + + float fsk_demod_impl::transwidth() const { + return d_trw; + } + + // Band pass filter len in number of symbo + void fsk_demod_impl::set_filterlen(float filterlen) { + + d_filterlen = filterlen; + + d_tone_filter_taps = gr::filter::firdes::low_pass_2((double)1.0f, (double)d_sample_rate, (double)d_bw/2, (double)d_trw, (double)FSK_DEMOD_BP_ATT); + set_history(d_tone_filter_taps.size()); + + set_mark_freq(d_mark_freq); + set_space_freq(d_space_freq); + } + + float fsk_demod_impl::filterlen() const { + return d_filterlen; + } + + int fsk_demod_impl::work (int noutput_items, + gr_vector_const_void_star& input_items, + gr_vector_void_star& output_items) { + + std::lock_guard lock(d_mutex); + float * bit = reinterpret_cast(output_items[0]); + const gr_complex * in = reinterpret_cast(input_items[0]); + int al = volk_get_alignment(); + unsigned int i,j; + std::vector vcarrier_detect(al); + std::vector vmark(noutput_items + al); + std::vector vspace(noutput_items + al); + std::vector vmark_mag(al); + std::vector vspace_mag(al); + std::vector vsnr(al); + float * carrier_detect; + gr_complex * mark; + gr_complex * space; + float * mark_mag; + float * space_mag; + float * snr; + char current_sample; + + mark = (gr_complex*)((((size_t)vmark.data())+(al-1)) & ~(al-1)); + space = (gr_complex*)((((size_t)vspace.data())+(al-1)) & ~(al-1)); + + // Output carrier detection if connected + if (output_items.size()>1 && output_items[1]!=NULL) + carrier_detect = reinterpret_cast(output_items[1]); + else { + vcarrier_detect.resize(noutput_items + al); + carrier_detect = (float*)((((size_t)vcarrier_detect.data())+(al-1)) & ~(al-1)); + } + + // Output Mark power if connected + if (output_items.size()>2 && output_items[2]!=NULL) + mark_mag = reinterpret_cast(output_items[2]); + else { + vmark_mag.resize(noutput_items + al); + mark_mag = (float*)((((size_t)vmark_mag.data())+(al-1)) & ~(al-1)); + } + + // Output space power if connected + if (output_items.size()>3 && output_items[3]!=NULL) + space_mag = reinterpret_cast(output_items[3]); + else { + vspace_mag.resize(noutput_items + al); + space_mag = (float*)((((size_t)vspace_mag.data())+(al-1)) & ~(al-1)); + } + + // Output signal to noise ration in dB if connected (positive for mark tone, negative for space tone) + if (output_items.size()>4 && output_items[4]!=NULL) + snr = reinterpret_cast(output_items[4]); + else { + vsnr.resize(noutput_items + al); + snr = (float*)((((size_t)vsnr.data())+(al-1)) & ~(al-1)); + } + + // Filter tones and decimate + for (i=0,j=0;i<(unsigned int)noutput_items;i++,j+=d_decimation) { + mark[i] = d_mark_fir.filter(&in[j]); + space[i] = d_space_fir.filter(&in[j]); + } + + // Get instantaneous tones power + volk_32fc_magnitude_squared_32f(mark_mag,mark,noutput_items); + volk_32fc_magnitude_squared_32f(space_mag,space,noutput_items); + + // Calculate mark to space ratio (snr) + volk_32f_x2_divide_32f(snr,mark_mag,space_mag,noutput_items); + + for (i=0;i<(unsigned int)noutput_items;i++) { + // Calculate current sample state (mark, no tone, or space) + if (snr[i] >= threshold_mark) + current_sample = 1; + else if (snr[i] <= threshold_space) + current_sample = -1; + else + current_sample = 0; + + // Count samples state on one symbol len + if (last_samples.size() == FSK_SYMBOL_LEN) { + switch (last_samples.front()) { + case 1 : if (count_mark) count_mark--; break; + case 0 : if (count_noise) count_noise--; break; + case -1: if (count_space) count_space--; break; + } + last_samples.pop(); + } + + switch (current_sample) { + case 1 : if (count_markcount_noise && count_mark>count_space) { + last_bit = true; + last_noise = false; + bit[i] = 1; + } + else if (count_space>count_noise && count_space>count_mark) { + last_bit = false; + last_noise = false; + bit[i] = -1; + } + else if (count_noise>count_mark && count_noise>count_space) { + last_noise = true; + if (last_bit) + bit[i] = -1; + else + bit[i] = 1; + } + else if (last_noise) { + if (last_bit) + bit[i] = -1; + else + bit[i] = 1; + } else { + if (last_bit) + bit[i] = 1; + else + bit[i] = -1; + } + + // Carrier detection + if (!last_noise) { + if (signal_count < cd_count) { + noise_count=0; + signal_count++; + } else cd = true; + } else { + if (noise_count < cd_count) { + signal_count=0; + noise_count++; + } else cd = false; + } + carrier_detect[i] = cd?1:0; + } + + // Output signal to noise ratio as dB (positive for mark, negative for space) + if (output_items.size()>3 && output_items[3]!=NULL) { // convert to dB if connected + volk_32f_log2_32f(snr,snr,noutput_items); + volk_32f_s32f_multiply_32f(snr,snr,3.0f,noutput_items); + } + + return noutput_items; + } + + } /* namespace rtty */ +} /* namespace gr */ diff --git a/src/dsp/rtty/fsk_demod_impl.h b/src/dsp/rtty/fsk_demod_impl.h new file mode 100644 index 000000000..92a28def6 --- /dev/null +++ b/src/dsp/rtty/fsk_demod_impl.h @@ -0,0 +1,120 @@ +/* + * fsk/afsk demodulator block implementation header + * + * Copyright 2022 Marc CAPDEVILLE F4JMZ + * + * This is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3, or (at your option) + * any later version. + * + * This software is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this software; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 51 Franklin Street, + * Boston, MA 02110-1301, USA. + */ + +#ifndef _FSK_DEMOD_IMPL_H_ +#define _FSK_DEMOD_IMPL_H_ + +#include "fsk_demod.h" +#include +#include +#include + +namespace gr { + namespace rtty { + class fsk_demod_impl : public fsk_demod { + public: + fsk_demod_impl(float sample_rate,unsigned int decimation, float symbol_rate, float mark_freq,float space_freq,float threshold=3.0f,float cd_len=6.0f); + ~fsk_demod_impl(); + + int work(int noutput_items, + gr_vector_const_void_star &input_items, + gr_vector_void_star &output_items); + + // Sample rate in Hz + void set_sample_rate(float sample_rate); + float sample_rate() const; + + // decimation factor + void set_decimation(unsigned int decimation); + int decimation() const; + + // symbol rate in Hz + void set_symbol_rate(float symbol_rate); + float symbol_rate() const; + + // mark freq in Hz (relative to BFO) + void set_mark_freq(float mark_freq); + float mark_freq() const; + + // space freq in Hz (relative to BFO) + void set_space_freq(float space_freq); + float space_freq() const; + + // Tones detection threshold in dB + void set_threshold(float threshold); + float threshold() const; + + // Carrier detection lenght in number of symbol + void set_cd_len(float cd_len); + float cd_len() const; + + // band pass filter width + void set_bandwidth(float bw); + float bandwidth() const; + + // band pass filter transition width + void set_transwidth(float trw); + float transwidth() const; + + // band pass filter len in number of symbol + void set_filterlen(float filterlen); + float filterlen() const; + + private: + std::recursive_mutex d_mutex; + + std::vector d_tone_filter_taps; + + gr::filter::kernel::fir_filter_ccc d_mark_fir; + gr::filter::kernel::fir_filter_ccc d_space_fir; + + float d_sample_rate; + unsigned int d_decimation; + float d_symbol_rate; + float d_mark_freq; + float d_space_freq; + float d_threshold; + int d_cd_len; + float d_bw; + float d_trw; + float d_filterlen; + + float threshold_mark; + float threshold_space; + int cd_count; + + + /* circular buffer */ + std::queue last_samples; + + int count_mark,count_noise,count_space; + bool last_bit; + bool last_noise; + + bool cd; + int signal_count; + int noise_count; + + }; + } // namespace rtty +} // namespace gr + +#endif // _FSK_DEMOD_IMPL_H_ diff --git a/src/dsp/rtty/rtty_demod.cpp b/src/dsp/rtty/rtty_demod.cpp new file mode 100644 index 000000000..809dfd46a --- /dev/null +++ b/src/dsp/rtty/rtty_demod.cpp @@ -0,0 +1,267 @@ +/* + * rtty demodulator block implementation + * + * Copyright 2022 Marc CAPDEVILLE F4JMZ + * + * This is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3, or (at your option) + * any later version. + * + * This software is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this software; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 51 Franklin Street, + * Boston, MA 02110-1301, USA. + */ + +#include +#include +#include + +#define RTTY_DEMOD_RATE (8*d_baud_rate) // 8KHz fsk_demod output + +namespace gr { + namespace rtty { + + /* Create a new instance of rtty_demod and return a shared_ptr. */ + rtty_demod::sptr rtty_demod::make(float quad_rate, float mark_freq, float space_freq,float baud_rate,enum rtty_mode mode,enum rtty_parity parity) { + return gnuradio::get_initial_sptr(new rtty_demod(quad_rate, mark_freq, space_freq, baud_rate, mode, parity)); + } + + static const int MIN_IN = 1; /* Mininum number of input streams. */ + static const int MAX_IN = 1; /* Maximum number of input streams. */ + static const int MIN_OUT = 0; /* Minimum number of output streams. */ + static const int MAX_OUT = 0; /* Maximum number of output streams. */ + + rtty_demod::rtty_demod(float quad_rate, float mark_freq, float space_freq, float baud_rate,enum rtty_mode mode,enum rtty_parity parity) + : gr::hier_block2 ("rtty_demod", + gr::io_signature::make (MIN_IN, MAX_IN, sizeof (gr_complex)), + gr::io_signature::make (MIN_OUT, MAX_OUT, sizeof (char))), + d_quad_rate(quad_rate), + d_mark_freq(mark_freq), + d_space_freq(space_freq), + d_baud_rate(baud_rate), + d_mode(mode), + d_parity(parity) { + int word_len; + enum gr::rtty::async_rx::async_rx_parity async_parity; + + /* demodulator */ + d_fsk_demod = gr::rtty::fsk_demod::make(d_quad_rate,d_quad_rate/RTTY_DEMOD_RATE,d_baud_rate,mark_freq,space_freq); + + /* Decode */ + switch (d_mode) { + default: + case RTTY_MODE_5BITS_BAUDOT: + word_len = 5; + break; + case RTTY_MODE_7BITS_ASCII: + word_len = 7; + break; + case RTTY_MODE_8BITS_ASCII: + word_len = 8; + break; + } + + switch (d_parity) { + default: + case RTTY_PARITY_NONE: + async_parity = gr::rtty::async_rx::ASYNC_RX_PARITY_NONE; + break; + case RTTY_PARITY_ODD: + async_parity = gr::rtty::async_rx::ASYNC_RX_PARITY_ODD; + break; + case RTTY_PARITY_EVEN: + async_parity = gr::rtty::async_rx::ASYNC_RX_PARITY_EVEN; + break; + case RTTY_PARITY_MARK: + async_parity = gr::rtty::async_rx::ASYNC_RX_PARITY_MARK; + break; + case RTTY_PARITY_SPACE: + async_parity = gr::rtty::async_rx::ASYNC_RX_PARITY_SPACE; + break; + case RTTY_PARITY_DONTCARE: + async_parity = gr::rtty::async_rx::ASYNC_RX_PARITY_DONTCARE; + break; + } + d_async = gr::rtty::async_rx::make(RTTY_DEMOD_RATE,d_baud_rate,word_len,async_parity); + + /* set carrier detect len */ + d_fsk_demod->set_cd_len(d_async->word_len()+(d_async->parity()?2:1)); + + /* baudot decodding and storage */ + d_data = char_store::make(100,true); + + /* connect blocks */ + connect(self(), 0, d_fsk_demod, 0); + connect(d_fsk_demod, 0, d_async, 0); // bit stream + connect(d_fsk_demod, 1, d_async, 1); // carrier detect + connect(d_async, 0, d_data, 0); + } + + rtty_demod::~rtty_demod () { + } + + void rtty_demod::set_quad_rate(float quad_rate) { + d_quad_rate = quad_rate; + d_fsk_demod->set_sample_rate(d_quad_rate); + d_fsk_demod->set_decimation(roundf(d_quad_rate/RTTY_DEMOD_RATE)); + } + + float rtty_demod::quad_rate() const { + return d_quad_rate; + } + + void rtty_demod::set_mark_freq(float mark_freq) { + d_mark_freq = mark_freq; + d_fsk_demod->set_mark_freq(d_mark_freq); + } + + float rtty_demod::mark_freq() { + return d_fsk_demod->mark_freq(); + } + + void rtty_demod::set_space_freq(float space_freq) { + d_space_freq = space_freq; + d_fsk_demod->set_space_freq(d_space_freq); + } + + float rtty_demod::space_freq() { + return d_fsk_demod->space_freq(); + } + + void rtty_demod::set_threshold(float threshold) { + d_fsk_demod->set_threshold(threshold); + } + + float rtty_demod::threshold() { + return d_fsk_demod->threshold(); + } + + void rtty_demod::set_bandwidth(float bw) { + d_fsk_demod->set_bandwidth(bw); + } + + float rtty_demod::bandwidth() const { + return d_fsk_demod->bandwidth(); + } + + void rtty_demod::set_transwidth(float trw) { + d_fsk_demod->set_transwidth(trw); + } + + float rtty_demod::transwidth() const { + return d_fsk_demod->transwidth(); + } + + void rtty_demod::set_filterlen(float filterlen) { + d_fsk_demod->set_filterlen(filterlen); + } + + float rtty_demod::filterlen() const { + return d_fsk_demod->filterlen(); + } + + void rtty_demod::set_baud_rate(float baud_rate) { + if (!baud_rate) + return; + d_baud_rate = baud_rate; + d_fsk_demod->set_decimation(d_quad_rate/RTTY_DEMOD_RATE); + d_fsk_demod->set_symbol_rate(d_baud_rate); + d_async->set_sample_rate(RTTY_DEMOD_RATE); + d_async->set_bit_rate(d_baud_rate); + d_async->reset(); + } + + float rtty_demod::baud_rate() { + return d_async->bit_rate(); + } + + void rtty_demod::reset() { + d_async->reset(); + } + + int rtty_demod::get_data(std::string &data) { + + return d_data->get_data(data); + } + + void rtty_demod::set_mode(enum rtty_mode mode) { + if (mode == d_mode) + return; + + switch (mode) { + case RTTY_MODE_5BITS_BAUDOT: + d_async->set_word_len(5); + d_data->set_baudot(true); + d_async->reset(); + break; + case RTTY_MODE_7BITS_ASCII: + d_async->set_word_len(7); + d_data->set_baudot(false); + d_async->reset(); + break; + case RTTY_MODE_8BITS_ASCII: + d_async->set_word_len(8); + d_data->set_baudot(false); + d_async->reset(); + break; + default: + return; + } + + d_mode = mode; + + /* set carrier detect len */ + d_fsk_demod->set_cd_len(d_async->word_len()+(d_async->parity()?2:1)); + } + + enum rtty_demod::rtty_mode rtty_demod::mode() { + return d_mode; + } + + void rtty_demod::set_parity(enum rtty_parity parity) { + if (parity == d_parity) + return; + + switch (parity) { + default: + case RTTY_PARITY_NONE: + d_async->set_parity(gr::rtty::async_rx::ASYNC_RX_PARITY_NONE); + break; + case RTTY_PARITY_ODD: + d_async->set_parity(gr::rtty::async_rx::ASYNC_RX_PARITY_ODD); + break; + case RTTY_PARITY_EVEN: + d_async->set_parity(gr::rtty::async_rx::ASYNC_RX_PARITY_EVEN); + break; + case RTTY_PARITY_MARK: + d_async->set_parity(gr::rtty::async_rx::ASYNC_RX_PARITY_MARK); + break; + case RTTY_PARITY_SPACE: + d_async->set_parity(gr::rtty::async_rx::ASYNC_RX_PARITY_SPACE); + break; + case RTTY_PARITY_DONTCARE: + d_async->set_parity(gr::rtty::async_rx::ASYNC_RX_PARITY_DONTCARE); + break; + } + + d_async->reset(); + + d_parity = parity; + + /* set carrier detect len */ + d_fsk_demod->set_cd_len(d_async->word_len()+(d_async->parity()?2:1)); + } + + enum rtty_demod::rtty_parity rtty_demod::parity() { + return d_parity; + } + + } +} diff --git a/src/dsp/rtty/rtty_demod.h b/src/dsp/rtty/rtty_demod.h new file mode 100644 index 000000000..92443f6b3 --- /dev/null +++ b/src/dsp/rtty/rtty_demod.h @@ -0,0 +1,127 @@ +/* + * rtty demodulator block header + * + * Copyright 2022 Marc CAPDEVILLE F4JMZ + * + * This is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3, or (at your option) + * any later version. + * + * This software is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this software; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 51 Franklin Street, + * Boston, MA 02110-1301, USA. + */ + +#ifndef RTTY_DEMOD_H +#define RTTY_DEMOD_H + +#include +#include +#include +#include +#include + +namespace gr { + namespace rtty { + /*! \brief RTTY demodulator. + * \ingroup DSP + * + * This class implements the RTTY demodulator. + * + */ + class rtty_demod : public gr::hier_block2 { + + public: + enum rtty_mode { + RTTY_MODE_5BITS_BAUDOT = 0, + RTTY_MODE_7BITS_ASCII, + RTTY_MODE_8BITS_ASCII + }; + + enum rtty_parity { + RTTY_PARITY_NONE = 0, + RTTY_PARITY_ODD, + RTTY_PARITY_EVEN, + RTTY_PARITY_MARK, + RTTY_PARITY_SPACE, + RTTY_PARITY_DONTCARE + }; + +#if GNURADIO_VERSION < 0x030900 + typedef boost::shared_ptr sptr; +#else + typedef std::shared_ptr sptr; +#endif + + + /*! \brief Return a shared_ptr to a new instance of rtty_demod. + * \param quad_rate The input sample rate. + * \param mark_freq The MARK frequency + * \param space_freq The SPACE freqency + * \param baud_rate The baudrate of the signal + * + * This is effectively the public constructor. + */ + static sptr make(float quad_rate, float mark_freq, float space_freq,float baud_rate,enum rtty_mode mode,enum rtty_parity parity); + + + void set_quad_rate(float quad_rate); + float quad_rate() const; + // FSK parameters + void set_mark_freq(float mark_freq); + float mark_freq(); + void set_space_freq(float space_freq); + float space_freq(); + void set_threshold(float threshold); + float threshold(); + void set_cd_len(float cd_len); + float cd_len() const; + void set_bandwidth(float bw); + float bandwidth() const; + void set_transwidth(float trw); + float transwidth() const; + void set_filterlen(float filterlen); + float filterlen() const; + void set_baud_rate(float baud_rate); + float baud_rate(); + void set_mode(enum rtty_mode mode); + enum rtty_mode mode(); + void set_parity(enum rtty_parity mode); + enum rtty_parity parity(); + + void reset(); + + int get_data(std::string &data); + + private: + + rtty_demod(float quad_rate, float mark_freq, float space_freq,float baud_rate,enum rtty_mode mode, enum rtty_parity parity); + ~rtty_demod(); + + /* GR blocks */ + gr::rtty::fsk_demod::sptr d_fsk_demod; /*! fsk demodulator */ + gr::rtty::async_rx::sptr d_async; /*! Async receiver */ + char_store::sptr d_data; /*! char data storage */ + + /* other parameters */ + float d_quad_rate; /*! Sample rate */ + float d_mark_freq; /*! MARK frequency */ + float d_space_freq; /*! SPACE frequency */ + float d_baud_rate; /*! Baud rate */ + enum rtty_mode d_mode; /* decoder mode */ + enum rtty_parity d_parity; + + /* FIR RRC filter taps */ + std::vector d_rrc_taps; /*! tone filter taps. */ + }; + + } +} +#endif // RTTY_DEMOD_H diff --git a/src/qtgui/CMakeLists.txt b/src/qtgui/CMakeLists.txt index a525a6d30..e2daa2df2 100644 --- a/src/qtgui/CMakeLists.txt +++ b/src/qtgui/CMakeLists.txt @@ -24,6 +24,8 @@ add_source_files(SRCS_LIST dockaudio.h dockbookmarks.cpp dockbookmarks.h + dockfax.h + dockfax.cpp dockfft.cpp dockfft.h dockinputctl.cpp @@ -50,6 +52,8 @@ add_source_files(SRCS_LIST plotter.h qtcolorpicker.cpp qtcolorpicker.h + dockrtty.cpp + dockrtty.h ) ####################################################################################################################### @@ -61,6 +65,7 @@ add_source_files(UI_SRCS_LIST demod_options.ui dockaudio.ui dockbookmarks.ui + dockfax.ui dockfft.ui dockinputctl.ui dockrds.ui @@ -69,4 +74,5 @@ add_source_files(UI_SRCS_LIST ioconfig.ui iq_tool.ui nb_options.ui + dockrtty.ui ) diff --git a/src/qtgui/dockfax.cpp b/src/qtgui/dockfax.cpp new file mode 100644 index 000000000..067fdf643 --- /dev/null +++ b/src/qtgui/dockfax.cpp @@ -0,0 +1,260 @@ +/* + * fax gui widget implementation + * + * Copyright 2022 Marc CAPDEVILLE F4JMZ + * + * This is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3, or (at your option) + * any later version. + * + * This software is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this software; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 51 Franklin Street, + * Boston, MA 02110-1301, USA. + */ + +#include +#include +#include +#include +#include "dockfax.h" +#include "ui_dockfax.h" + +DockFAX::DockFAX(QWidget *parent) : + QDockWidget(parent), + ui(new Ui::DockFAX) { + + ui->setupUi(this); + d_scale=1; + ui->scrollArea->setWidgetResizable(false); + ui->view->setScaledContents(true); + ui->zoom->addItem("1:16",0.0625f); + ui->zoom->addItem("1:8",0.125f); + ui->zoom->addItem("1:4",0.25f); + ui->zoom->addItem("1:2",0.5f); + ui->zoom->addItem("1:1",1.0f); + ui->zoom->addItem("2:1",2.0f); + ui->zoom->addItem("4:1",4.0f); + ui->zoom->addItem("8:1",8.0f); + ui->zoom->addItem("16:1",16.0f); + ui->zoom->setCurrentIndex(4); + } + +DockFAX::~DockFAX() { + delete ui; +} + +void DockFAX::update_image(QImage& image) { + int w,h,x,y; + float z; + bool follow = false; + + x = ui->scrollAreaWidgetContents->pos().x(); + y = ui->scrollAreaWidgetContents->pos().y() + ui->scrollAreaWidgetContents->height(); + + if (y <= (ui->scrollArea->viewport()->height())) + follow = true; + + if (ui->fittowindow->checkState()) { + w = ui->scrollArea->viewport()->width(); + h = image.height() * w/image.width(); + } else { + z = ui->zoom->currentData().toFloat(); + w = image.width() * z; + h = image.height() * z; + } + + ui->view->resize(w,h); + ui->scrollAreaWidgetContents->resize(w,h); + + if (follow) { + y = ui->scrollArea->viewport()->height() - h; + ui->scrollAreaWidgetContents->move(x,y); + ui->scrollArea->verticalScrollBar()->setValue(ui->scrollArea->verticalScrollBar()->maximum()); + } + + ui->view->setPixmap(QPixmap::fromImage(image)); + +} + +void DockFAX::clearView() { + ui->view->clear(); +} + +void DockFAX::show_Enabled() { + if (!ui->faxEnable->isChecked()) { + ui->faxEnable->blockSignals(true); + ui->faxEnable->setChecked(true); + ui->faxEnable->blockSignals(false); + } +} + +void DockFAX::show_Disabled() { + if (ui->faxEnable->isChecked()) { + ui->faxEnable->blockSignals(true); + ui->faxEnable->setChecked(false); + ui->faxEnable->blockSignals(false); + } +} + +void DockFAX::set_Disabled() { + ui->faxEnable->setDisabled(true); + ui->faxEnable->blockSignals(true); + ui->faxEnable->setChecked(false); + ui->faxEnable->blockSignals(false); + ui->lpm->setDisabled(true); + ui->ioc->setDisabled(true); + ui->black_freq->setDisabled(true); + ui->white_freq->setDisabled(true); + ui->view->setDisabled(true); + ui->reset->setDisabled(true); + ui->sync->setDisabled(true); + ui->start->setDisabled(true); +} + +void DockFAX::set_Enabled() { + ui->faxEnable->setDisabled(false); + ui->lpm->setDisabled(false); + ui->ioc->setDisabled(false); + ui->black_freq->setDisabled(false); + ui->white_freq->setDisabled(false); + ui->view->setDisabled(false); + ui->reset->setDisabled(false); + ui->sync->setDisabled(false); + ui->start->setDisabled(false); +} + +float DockFAX::get_lpm() { + return ui->lpm->value(); +} + +float DockFAX::get_ioc() { + return ui->ioc->value(); +} + +float DockFAX::get_black_freq() { + return ui->black_freq->value(); +} + +float DockFAX::get_white_freq() { + return ui->white_freq->value(); +} +void DockFAX::set_lpm(float lpm) { + ui->lpm->setValue((double)lpm); +} + +QString DockFAX::get_directory() { + return ui->directory->text(); +} + +bool DockFAX::get_autosave() { + return ui->autosave->checkState(); +} + +void DockFAX::set_ioc(float ioc) { + ui->ioc->setValue((double)ioc); +} + +void DockFAX::set_black_freq(float black_freq) { + ui->black_freq->setValue((double)black_freq); +} + +void DockFAX::set_white_freq(float white_freq) { + ui->white_freq->setValue((double)white_freq); +} + +void DockFAX::set_directory(QString dir) { + ui->directory->setText(dir); +} + +void DockFAX::set_autosave(bool state) { + ui->autosave->setChecked(state); +} + +void DockFAX::saveSettings(QSettings *settings) { + if (!settings) + return; + + settings->beginGroup("fax"); + settings->setValue("lpm", QVariant(get_lpm()).toString()); + settings->setValue("ioc", QVariant(get_ioc()).toString()); + settings->setValue("black_freq", QVariant(get_black_freq()).toString()); + settings->setValue("white_freq", QVariant(get_white_freq()).toString()); + settings->setValue("directory", QVariant(get_directory()).toString()); + settings->setValue("autosave", QVariant(get_autosave()).toString()); + settings->endGroup(); +} + +void DockFAX::readSettings(QSettings *settings) { + if (!settings) + return; + + settings->beginGroup("fax"); + set_ioc(settings->value("ioc", 720.0f).toFloat()); + set_lpm(settings->value("lpm", 120.0f).toFloat()); + set_black_freq(settings->value("black_freq", -425.0f).toFloat()); + set_white_freq(settings->value("white_freq", 425.0f).toFloat()); + set_directory(settings->value("directory", "").toString()); + set_autosave(settings->value("autosave", false).toBool()); + settings->endGroup(); +} + +/** Enable/disable FAX decoder */ +void DockFAX::on_faxEnable_toggled(bool checked) +{ + if (checked) + emit fax_start_decoder(); + else + emit fax_stop_decoder(); +} + +void DockFAX::on_lpm_editingFinished() { + if (ui->lpm->value()>0) + emit fax_lpm_Changed(ui->lpm->value()); +} + +void DockFAX::on_ioc_editingFinished() { + if (ui->ioc->value()>0) + emit fax_ioc_Changed(ui->ioc->value()); +} + +void DockFAX::on_black_freq_editingFinished() { + emit fax_black_freq_Changed(ui->black_freq->value()); +} + +void DockFAX::on_white_freq_editingFinished() { + emit fax_white_freq_Changed(ui->white_freq->value()); +} + + +void DockFAX::on_reset_clicked() { + emit fax_reset_Clicked(); +} + +void DockFAX::on_start_clicked() { + emit fax_start_Clicked(); +} +void DockFAX::on_sync_clicked() { + emit fax_sync_Clicked(); +} + +void DockFAX::on_save_clicked() { + int ret; + ret = emit fax_save_Clicked(); + if (!ret) + QMessageBox::information(this,"fax","Saved."); + else + QMessageBox::critical(this,"fax","Not Saved"); + +} + +void DockFAX::on_select_clicked() { + QString Directory = QFileDialog::getExistingDirectory(this,"Select directory",ui->directory->text()); + ui->directory->setText(Directory); +} diff --git a/src/qtgui/dockfax.h b/src/qtgui/dockfax.h new file mode 100644 index 000000000..707f15f88 --- /dev/null +++ b/src/qtgui/dockfax.h @@ -0,0 +1,99 @@ +/* + * fax gui widget header + * + * Copyright 2022 Marc CAPDEVILLE F4JMZ + * + * This is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3, or (at your option) + * any later version. + * + * This software is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this software; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 51 Franklin Street, + * Boston, MA 02110-1301, USA. + */ + +#ifndef DOCKFAX_H +#define DOCKFAX_H +#include +#include + +namespace Ui { + class DockFAX; +} + + +class DockFAX : public QDockWidget +{ + Q_OBJECT + + public: + explicit DockFAX(QWidget *parent = 0); + ~DockFAX(); + + float get_lpm(); + float get_ioc(); + float get_black_freq(); + float get_white_freq(); + QString get_directory(); + bool get_autosave(); + + void saveSettings(QSettings *settings); + void readSettings(QSettings *settings); + + public slots: + void update_image(QImage& image); + void clearView(); + void show_Enabled(); + void show_Disabled(); + void set_Enabled(); + void set_Disabled(); + void set_lpm(float); + void set_ioc(float); + void set_black_freq(float); + void set_white_freq(float); + void set_directory(QString); + void set_autosave(bool); + + private: + float d_lpm; + float d_ioc; + float d_black_freq; + float d_white_freq; + float d_scale; + +signals: + void fax_start_decoder(); + void fax_stop_decoder(); + void fax_lpm_Changed(float); + void fax_ioc_Changed(float); + void fax_black_freq_Changed(float); + void fax_white_freq_Changed(float); + void fax_start_Clicked(); + void fax_reset_Clicked(); + void fax_sync_Clicked(); + int fax_save_Clicked(); + + private slots: + void on_faxEnable_toggled(bool); + void on_lpm_editingFinished(); + void on_ioc_editingFinished(); + void on_black_freq_editingFinished(); + void on_white_freq_editingFinished(); + void on_reset_clicked(); + void on_start_clicked(); + void on_sync_clicked(); + void on_save_clicked(); + void on_select_clicked(); + + private: + Ui::DockFAX *ui; /*! The Qt designer UI file. */ +}; + +#endif // DOCKFAX_H diff --git a/src/qtgui/dockfax.ui b/src/qtgui/dockfax.ui new file mode 100644 index 000000000..33e58656a --- /dev/null +++ b/src/qtgui/dockfax.ui @@ -0,0 +1,604 @@ + + + + DockFAX + + + true + + + + 0 + 0 + 259 + 220 + + + + + 0 + 0 + + + + + 200 + 150 + + + + false + + + Qt::LeftDockWidgetArea|Qt::RightDockWidgetArea + + + FAX + + + + + 0 + 0 + + + + + 5 + + + QLayout::SetNoConstraint + + + 5 + + + 5 + + + 5 + + + 5 + + + + + QLayout::SetNoConstraint + + + + + 4 + + + 0 + + + + + 0 + + + QLayout::SetMaximumSize + + + + + + 0 + 0 + + + + + 16777215 + 19 + + + + Enable + + + false + + + + + + + + 0 + 0 + + + + Reset + + + + + + + + 0 + 0 + + + + Sync + + + + + + + + 0 + 0 + + + + Start + + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + 0 + + + QLayout::SetMaximumSize + + + + + + 0 + 0 + + + + Black freq + + + black_freq + + + + + + + + 0 + 0 + + + + -5000.000000000000000 + + + 5000.000000000000000 + + + 50.000000000000000 + + + -425.000000000000000 + + + + + + + + 0 + 0 + + + + Lines/minute + + + lpm + + + + + + + + 0 + 0 + + + + 2 + + + 1.000000000000000 + + + 10000.000000000000000 + + + 120.000000000000000 + + + + + + + + + 0 + + + QLayout::SetMaximumSize + + + + + + 0 + 0 + + + + White freq + + + white_freq + + + + + + + + 0 + 0 + + + + -5000.000000000000000 + + + 5000.000000000000000 + + + 50.000000000000000 + + + 425.000000000000000 + + + + + + + + 0 + 0 + + + + IoC + + + ioc + + + + + + + + 0 + 0 + + + + -2048.000000000000000 + + + 2048.000000000000000 + + + 576.000000000000000 + + + + + + + + + + + + + 4 + + + QLayout::SetMaximumSize + + + + + QLayout::SetMaximumSize + + + + + + 0 + 0 + + + + + 50 + 27 + + + + Save + + + + + + + + 0 + 0 + + + + auto save + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + + + + 0 + 0 + + + + To : + + + + + + + + 0 + 0 + + + + + + + + + 0 + 0 + + + + Select + + + + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + 50 + 25 + + + + + 50 + 25 + + + + + + + -1 + + + + + + + + 0 + 0 + + + + + 50 + 25 + + + + Fit width + + + + + + + + + true + + + + 0 + 0 + + + + + 120 + 25 + + + + + 16777215 + 16777215 + + + + 1 + + + Qt::ScrollBarAsNeeded + + + Qt::ScrollBarAsNeeded + + + QAbstractScrollArea::AdjustToContents + + + true + + + Qt::AlignBottom|Qt::AlignHCenter + + + + + 0 + 0 + 245 + 23 + + + + + 0 + 0 + + + + + + 0 + 0 + 0 + 0 + + + + + 0 + 0 + + + + QFrame::Box + + + + + + Qt::AlignBottom|Qt::AlignLeading|Qt::AlignLeft + + + + + + + + + + + + + diff --git a/src/qtgui/dockrtty.cpp b/src/qtgui/dockrtty.cpp new file mode 100644 index 000000000..5d7df439e --- /dev/null +++ b/src/qtgui/dockrtty.cpp @@ -0,0 +1,422 @@ +/* + * rtty gui widget implementation + * + * Copyright 2022 Marc CAPDEVILLE F4JMZ + * + * This is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3, or (at your option) + * any later version. + * + * This software is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this software; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 51 Franklin Street, + * Boston, MA 02110-1301, USA. + */ +#include +#include +#include +#include "dockrtty.h" +#include "ui_dockrtty.h" + +DockRTTY::DockRTTY(QWidget *parent) : + QDockWidget(parent), + ui(new Ui::DockRTTY) { + ui->setupUi(this); + ui->mode->addItem(QString("5 bits baudot")); + ui->mode->addItem(QString("7 bits ascii")); + ui->mode->addItem(QString("8 bits ascii")); + + ui->parity->addItem(QString("none")); + ui->parity->addItem(QString("odd")); + ui->parity->addItem(QString("even")); + ui->parity->addItem(QString("mark")); + ui->parity->addItem(QString("space")); + ui->parity->addItem(QString("dont care")); + } + +DockRTTY::~DockRTTY() { + delete ui; +} + +QString DockRTTY::get_text() { + return ui->text->toPlainText(); +} + + +void DockRTTY::ClearText() { + ui->text->setText(""); +} + +float DockRTTY::get_baud_rate() { + return ui->baud_rate->value(); +} + +float DockRTTY::get_mark_freq() { + return ui->mark_freq->value(); +} + +float DockRTTY::get_space_freq() { + return ui->space_freq->value(); +} + +float DockRTTY::get_threshold() { + return ui->threshold->value(); +} + +float DockRTTY::get_bandwidth() { + return ui->bandwidth->value(); +} + +float DockRTTY::get_transwidth() { + return ui->transwidth->value(); +} + +float DockRTTY::get_filterlen() { + return ui->filterlen->value(); +} + +int DockRTTY::get_mode() { + return ui->mode->currentIndex(); +} + +int DockRTTY::get_parity() { + return ui->parity->currentIndex(); +} + +QString DockRTTY::get_directory() { + return ui->directory->text(); +} + +void DockRTTY::saveSettings(QSettings *parent_settings) { + if (!parent_settings) + return; + + settings = parent_settings; + + settings->beginGroup("rtty"); + settings->setValue("num_profile",QVariant(num_profile).toString()); + settings->setValue("current_profile",QVariant(ui->profile->currentData()).toString()); + settings->setValue("mark_freq", QVariant(get_mark_freq()).toString()); + settings->setValue("space_freq", QVariant(get_space_freq()).toString()); + settings->setValue("threshold", QVariant(get_threshold()).toString()); + settings->setValue("bandwidth", QVariant(get_bandwidth()).toString()); + settings->setValue("transwidth", QVariant(get_transwidth()).toString()); + settings->setValue("filterlen", QVariant(get_filterlen()).toString()); + settings->setValue("baud_rate", QVariant(get_baud_rate()).toString()); + settings->setValue("mode", QVariant(get_mode()).toString()); + settings->setValue("parity", QVariant(get_parity()).toString()); + settings->setValue("directory", QVariant(get_directory()).toString()); + settings->setValue("autosave", QVariant(get_autosave()).toString()); + settings->endGroup(); +} + +void DockRTTY::readSettings(QSettings *parent_settings) { + int i; + int current_profile; + + if (!parent_settings) + return; + + settings = parent_settings; + + num_profile = settings->value("rtty/num_profile",0).toInt(); + current_profile = settings->value("rtty/current_profile",0).toInt(); + ui->profile->setCurrentIndex(-1); + + if (num_profile) { + for (i=0;iprofile->addItem(settings->value(QString("rtty_profile_%1/name").arg(i)).toString(),i); + } + ui->profile->setCurrentIndex(ui->profile->findData(current_profile)); + } + + settings->beginGroup("rtty"); + set_mark_freq(settings->value("mark_freq", 85.0f).toFloat()); + set_space_freq(settings->value("space_freq", -85.0f).toFloat()); + set_threshold(settings->value("threshold", 6.0f).toFloat()); + set_bandwidth(settings->value("bandwidth", 90.0f).toFloat()); + set_transwidth(settings->value("transwidth", 20.0f).toFloat()); + set_filterlen(settings->value("filterlen", 0.8f).toFloat()); + set_baud_rate(settings->value("baud_rate", 45.45f).toFloat()); + set_mode(settings->value("mode", 0).toInt()); + set_parity(settings->value("parity", 0).toInt()); + set_directory(settings->value("directory", "").toString()); + set_autosave(settings->value("autosave", false).toBool()); + settings->endGroup(); + +} + +void DockRTTY::update_text(QString text) { + ui->text->moveCursor(QTextCursor::End); + ui->text->insertPlainText(text); +} + +void DockRTTY::show_Enabled() { + if (!ui->rttyEnable->isChecked()) { + ui->rttyEnable->blockSignals(true); + ui->rttyEnable->setChecked(true); + ui->rttyEnable->blockSignals(false); + } +} + +void DockRTTY::show_Disabled() { + if (ui->rttyEnable->isChecked()) { + ui->rttyEnable->blockSignals(true); + ui->rttyEnable->setChecked(false); + ui->rttyEnable->blockSignals(false); + } +} + +void DockRTTY::set_Disabled() { + ui->rttyEnable->setDisabled(false); + ui->rttyEnable->blockSignals(true); + ui->rttyEnable->setChecked(false); + ui->rttyEnable->blockSignals(false); + ui->baud_rate->setDisabled(true); + ui->mark_freq->setDisabled(true); + ui->space_freq->setDisabled(true); + ui->threshold->setDisabled(true); + ui->bandwidth->setDisabled(true); + ui->transwidth->setDisabled(true); + ui->filterlen->setDisabled(true); + ui->text->setDisabled(true); + ui->mode->setDisabled(true); +} + +void DockRTTY::set_Enabled() { + ui->rttyEnable->setDisabled(false); + ui->baud_rate->setDisabled(false); + ui->mark_freq->setDisabled(false); + ui->space_freq->setDisabled(false); + ui->threshold->setDisabled(false); + ui->bandwidth->setDisabled(false); + ui->transwidth->setDisabled(false); + ui->filterlen->setDisabled(false); + ui->text->setDisabled(false); + ui->mode->setDisabled(false); +} + +void DockRTTY::set_baud_rate(float baud_rate) { + ui->baud_rate->setValue((double)baud_rate); +} + +void DockRTTY::set_mark_freq(float mark_freq) { + ui->mark_freq->setValue((double)mark_freq); +} + +void DockRTTY::set_space_freq(float space_freq) { + ui->space_freq->setValue((double)space_freq); +} + +void DockRTTY::set_threshold(float threshold) { + ui->threshold->setValue(threshold); +} + +void DockRTTY::set_bandwidth(float bandwidth) { + ui->bandwidth->setValue((double)bandwidth); +} + +void DockRTTY::set_transwidth(float transwidth) { + ui->transwidth->setValue((double)transwidth); +} + +void DockRTTY::set_filterlen(float filterlen) { + ui->filterlen->setValue((double)filterlen); +} + +void DockRTTY::set_mode(int mode) { + ui->mode->setCurrentIndex(mode); +} + +void DockRTTY::set_parity(int parity) { + ui->parity->setCurrentIndex(parity); +} + +void DockRTTY::set_directory(QString dir) { + ui->directory->setText(dir); +} + +void DockRTTY::set_autosave(bool state) { + ui->autosave->setChecked(state); +} + +/** Enable/disable RTTY decoder */ +void DockRTTY::on_rttyEnable_toggled(bool checked) { + if (checked) + emit rtty_start_decoder(); + else + emit rtty_stop_decoder(); +} + +void DockRTTY::save_profile(int num) { + if (num<=num_profile) { + settings->beginGroup(QString("rtty_profile_%1").arg(num)); + settings->setValue("name", ui->profile->currentText()); + settings->setValue("mark_freq", QVariant(get_mark_freq()).toString()); + settings->setValue("space_freq", QVariant(get_space_freq()).toString()); + settings->setValue("threshold", QVariant(get_threshold()).toString()); + settings->setValue("bandwidth", QVariant(get_bandwidth()).toString()); + settings->setValue("transwidth", QVariant(get_transwidth()).toString()); + settings->setValue("filterlen", QVariant(get_filterlen()).toString()); + settings->setValue("baud_rate", QVariant(get_baud_rate()).toString()); + settings->setValue("mode", QVariant(get_mode()).toString()); + settings->setValue("parity", QVariant(get_parity()).toString()); + settings->setValue("directory", QVariant(get_directory()).toString()); + settings->setValue("autosave", QVariant(get_autosave()).toString()); + settings->endGroup(); + if (num == num_profile) { + num_profile++; + settings->setValue("rtty/num_profile",num_profile); + } + } +} + +void DockRTTY::load_profile(int num) { + if (numbeginGroup(QString("rtty_profile_%1").arg(num)); + set_mark_freq(settings->value("mark_freq", -85.0f).toFloat()); + set_space_freq(settings->value("space_freq", 85.0f).toFloat()); + set_threshold(settings->value("threshold", 6.0f).toFloat()); + set_bandwidth(settings->value("bandwidth", 100.0f).toFloat()); + set_transwidth(settings->value("transwidth", 25.0f).toFloat()); + set_filterlen(settings->value("filterlen", 1.0f).toFloat()); + set_baud_rate(settings->value("baud_rate", 45.45f).toFloat()); + set_mode(settings->value("mode", 0).toInt()); + set_parity(settings->value("parity", 0).toInt()); + settings->endGroup(); + } +} + +void DockRTTY::on_profile_currentIndexChanged(int idx) { + if (idx!=-1) { + + load_profile(ui->profile->currentData().toInt()); + } +} + +void DockRTTY::on_load_profile_clicked() { + if (ui->profile->currentIndex()!=-1) + load_profile(ui->profile->currentData().toInt()); +} + +void DockRTTY::on_save_profile_clicked() { + if (ui->profile->currentIndex()!=-1) { + ui->profile->setItemText(ui->profile->currentIndex(),ui->profile->currentText()); + save_profile(ui->profile->currentData().toInt()); + } +} + +void DockRTTY::on_new_profile_clicked() { + int current_profile = num_profile; + + if (ui->profile->currentText().isEmpty()) + ui->profile->setCurrentText(QString("Profile %1").arg(current_profile)); + + ui->profile->addItem(ui->profile->currentText(),current_profile); + save_profile(current_profile); + + ui->profile->setCurrentIndex(ui->profile->findData(current_profile)); + +} + +void DockRTTY::on_delete_profile_clicked() { + int i,j; + + if (ui->profile->currentIndex()!=-1) { + num_profile--; + for (i=ui->profile->currentData().toInt();ibeginGroup(QString("rtty_profile_%1").arg(i+1)); + QStringList keys = settings->allKeys(); + settings->endGroup(); + + for (j=0;jsetValue(QString("rtty_profile_%1/%2").arg(i).arg(keys.at(j)),settings->value(QString("rtty_profile_%1/%2").arg(i+1).arg(keys.at(j)))); + + ui->profile->setItemText(ui->profile->findData(i),settings->value(QString("rtty_profile_%1/name").arg(i)).toString()); + } + + ui->profile->removeItem(ui->profile->findData(num_profile)); + settings->remove(QString("rtty_profile_%1").arg(num_profile)); + settings->setValue(QString("rtty/num_profile"),num_profile); + ui->profile->setCurrentIndex(-1); + } +} + +void DockRTTY::on_baud_rate_editingFinished() { + if (ui->baud_rate->value()>0) + emit rtty_baud_rate_Changed(ui->baud_rate->value()); +} + +void DockRTTY::on_mark_freq_editingFinished() { + emit rtty_mark_freq_Changed(ui->mark_freq->value()); +} + +void DockRTTY::on_space_freq_editingFinished() { + emit rtty_space_freq_Changed(ui->space_freq->value()); +} + +void DockRTTY::on_threshold_valueChanged(int threshold) { + emit rtty_threshold_Changed(threshold); +} + +void DockRTTY::on_bandwidth_valueChanged(double bandwidth) { + emit rtty_bandwidth_Changed((float)bandwidth); +} + +void DockRTTY::on_transwidth_valueChanged(double transwidth) { + emit rtty_transwidth_Changed((float)transwidth); +} + +void DockRTTY::on_filterlen_valueChanged(double filterlen) { + emit rtty_filterlen_Changed((float)filterlen); +} + +void DockRTTY::on_mode_currentIndexChanged(int mode) { + emit rtty_mode_Changed(mode); +} + +void DockRTTY::on_parity_currentIndexChanged(int parity) { + emit rtty_parity_Changed(parity); +} + +void DockRTTY::on_reset_clicked() { + ui->text->setText(""); + emit rtty_reset_clicked(); +} + +void DockRTTY::on_swap_clicked() { + float mark,space; + + mark=ui->space_freq->value(); + space=ui->mark_freq->value(); + + ui->mark_freq->setValue((double)mark); + ui->space_freq->setValue((double)space); + + emit rtty_mark_freq_Changed(ui->mark_freq->value()); + emit rtty_space_freq_Changed(ui->space_freq->value()); +} + +void DockRTTY::on_save_clicked() { + int ret; + ret = emit rtty_save_clicked(); + if (!ret) + QMessageBox::information(this,"Rtty","Saved."); + else + QMessageBox::critical(this,"Rtty","Not Saved"); +} + +bool DockRTTY::get_autosave() { + return ui->autosave->checkState(); +} + +void DockRTTY::on_select_clicked() { + QString Directory = QFileDialog::getExistingDirectory(this,"Select directory",ui->directory->text()); + ui->directory->setText(Directory); +} diff --git a/src/qtgui/dockrtty.h b/src/qtgui/dockrtty.h new file mode 100644 index 000000000..728ec3ab0 --- /dev/null +++ b/src/qtgui/dockrtty.h @@ -0,0 +1,130 @@ +/* + * rtty gui widget header + * + * Copyright 2022 Marc CAPDEVILLE F4JMZ + * + * This is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3, or (at your option) + * any later version. + * + * This software is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this software; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 51 Franklin Street, + * Boston, MA 02110-1301, USA. + */ +#ifndef DOCKRTTY_H +#define DOCKRTTY_H +#include +#include + +namespace Ui { + class DockRTTY; +} + + +class DockRTTY : public QDockWidget +{ + Q_OBJECT + + public: + explicit DockRTTY(QWidget *parent = 0); + ~DockRTTY(); + + QString get_text(); + float get_baud_rate(); + float get_mark_freq(); + float get_space_freq(); + float get_threshold(); + float get_bandwidth(); + float get_transwidth(); + float get_filterlen(); + int get_mode(); + int get_parity(); + QString get_directory(); + bool get_autosave(); + + void saveSettings(QSettings *settings); + void readSettings(QSettings *settings); + + public slots: + void update_text(QString text); + void show_Enabled(); + void show_Disabled(); + void set_Enabled(); + void set_Disabled(); + void set_baud_rate(float); + void set_mark_freq(float); + void set_space_freq(float); + void set_threshold(float); + void set_bandwidth(float); + void set_transwidth(float); + void set_filterlen(float); + void set_mode(int); + void set_parity(int); + void set_directory(QString dir); + void set_autosave(bool state); + void ClearText(); + + private: + float d_baud_rate; + float d_mark_freq; + float d_space_freq; + float threshold; + float bandwidth; + float transwidth; + float filterlen; + int d_mode; + int d_parity; + int num_profile; + QSettings *settings; + + void save_profile(int num); + void load_profile(int num); + +signals: + void rtty_start_decoder(); + void rtty_stop_decoder(); + void rtty_reset_clicked(); + void rtty_baud_rate_Changed(float); + void rtty_mark_freq_Changed(float); + void rtty_space_freq_Changed(float); + void rtty_threshold_Changed(float); + void rtty_bandwidth_Changed(float); + void rtty_transwidth_Changed(float); + void rtty_filterlen_Changed(float); + void rtty_mode_Changed(int); + void rtty_parity_Changed(int); + int rtty_save_clicked(); + + private slots: + void on_profile_currentIndexChanged(int); + void on_load_profile_clicked(); + void on_save_profile_clicked(); + void on_new_profile_clicked(); + void on_delete_profile_clicked(); + void on_rttyEnable_toggled(bool); + void on_baud_rate_editingFinished(); + void on_mark_freq_editingFinished(); + void on_space_freq_editingFinished(); + void on_threshold_valueChanged(int); + void on_bandwidth_valueChanged(double); + void on_transwidth_valueChanged(double); + void on_filterlen_valueChanged(double); + void on_mode_currentIndexChanged(int); + void on_parity_currentIndexChanged(int); + void on_reset_clicked(); + void on_swap_clicked(); + void on_save_clicked(); + void on_select_clicked(); + + private: + Ui::DockRTTY *ui; /*! The Qt designer UI file. */ +}; + +#endif // DOCKRTTY_H diff --git a/src/qtgui/dockrtty.ui b/src/qtgui/dockrtty.ui new file mode 100644 index 000000000..569c2dc36 --- /dev/null +++ b/src/qtgui/dockrtty.ui @@ -0,0 +1,954 @@ + + + + DockRTTY + + + true + + + + 0 + 0 + 300 + 333 + + + + + 0 + 0 + + + + + 300 + 247 + + + + false + + + Qt::LeftDockWidgetArea|Qt::RightDockWidgetArea + + + RTTY + + + + + 0 + 0 + + + + + 0 + + + + + 4 + + + QLayout::SetDefaultConstraint + + + + + QLayout::SetMaximumSize + + + + + + 0 + 0 + + + + + 80 + 25 + + + + Enable + + + + + + + + 0 + 0 + + + + Reset + + + + + + + Qt::Vertical + + + QSizePolicy::Ignored + + + + 20 + 40 + + + + + + + + + + + 0 + 0 + + + + + 120 + 96 + + + + QTabWidget::North + + + 0 + + + Qt::ElideNone + + + false + + + false + + + + + 0 + 0 + + + + profile + + + + 0 + + + 0 + + + 0 + + + 0 + + + 6 + + + + + 0 + + + + + + 0 + 0 + + + + Name : + + + + + + + + 0 + 0 + + + + true + + + + + + + + + 0 + + + + + + 0 + 0 + + + + Load + + + + + + + + 0 + 0 + + + + Save + + + + + + + + + + + + 0 + 0 + + + + Delete + + + + + + + + 0 + 0 + + + + New + + + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + fsk + + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + QLayout::SetDefaultConstraint + + + + + + 0 + 0 + + + + + 80 + 25 + + + + Mark freq + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + 0 + 0 + + + + + 80 + 25 + + + + Space freq + + + + + + + + + 0 + + + + + + 0 + 0 + + + + + 0 + 0 + + + + Hz + + + 0 + + + -3000.000000000000000 + + + 3000.000000000000000 + + + -225.000000000000000 + + + + + + + true + + + + 0 + 0 + + + + + 20 + 0 + + + + + 16777215 + 16777215 + + + + <> + + + + 0 + 0 + + + + + + + + + 0 + 0 + + + + + 0 + 0 + + + + + 80 + 25 + + + + true + + + + + + Hz + + + 0 + + + -3000.000000000000000 + + + 3000.000000000000000 + + + 225.000000000000000 + + + + + + + + + 0 + + + + + + 0 + 0 + + + + Threshold : + + + + + + + + 0 + 0 + + + + + 16777213 + 16777215 + + + + dB + + + 20 + + + 6 + + + + + + + + + 0 + + + + + + 0 + 0 + + + + bandwidth + + + + + + + Hz + + + 3000.000000000000000 + + + 100.000000000000000 + + + + + + + + + + + + 0 + 0 + + + + transwidth + + + + + + + Hz + + + 3000.000000000000000 + + + 25.000000000000000 + + + + + + + + + + + + 0 + 0 + + + + filterlen + + + + + + + + + + 0.130000000000000 + + + 99.989999999999995 + + + 0.125000000000000 + + + 1.000000000000000 + + + + + + + + + + + 0 + 0 + + + + async + + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + 0 + + + + + + 0 + 0 + + + + Baud rate + + + + + + + + 0 + 0 + + + + + 0 + 0 + + + + Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter + + + QAbstractSpinBox::UpDownArrows + + + + + + + + + Bd + + + 1.000000000000000 + + + 2000.000000000000000 + + + 50.000000000000000 + + + + + + + + + 0 + + + + + + 0 + 0 + + + + Parity + + + + + + + + 0 + 0 + + + + + 0 + 0 + + + + -1 + + + true + + + 0 + + + + + + + + + 0 + + + + + + 0 + 0 + + + + Mode + + + + + + + + 0 + 0 + + + + + 0 + 0 + + + + -1 + + + true + + + 0 + + + + + + + + + Qt::Vertical + + + + 20 + 57 + + + + + + + + + + + + + + 0 + + + + + + + + 0 + 0 + + + + Save + + + + + + + + 0 + 0 + + + + auto save + + + + + + + Qt::Horizontal + + + QSizePolicy::Expanding + + + + 40 + 20 + + + + + + + + + + + + + 0 + 0 + + + + To : + + + + + + + + + + + 0 + 0 + + + + Select + + + + + + + + + true + + + + 100 + 50 + + + + + Courier 10 Pitch + + + + Qt::ActionsContextMenu + + + false + + + QAbstractScrollArea::AdjustToContents + + + RTTY decode + + + + + + + + + + + diff --git a/src/receivers/nbrx.cpp b/src/receivers/nbrx.cpp index b95c6b761..66797abcc 100644 --- a/src/receivers/nbrx.cpp +++ b/src/receivers/nbrx.cpp @@ -52,6 +52,14 @@ nbrx::nbrx(float quad_rate, float audio_rate) demod_fm = make_rx_demod_fm(PREF_QUAD_RATE, 5000.0, 75.0e-6); demod_am = make_rx_demod_am(PREF_QUAD_RATE, true); demod_amsync = make_rx_demod_amsync(PREF_QUAD_RATE, true, 0.001); + fax_decoder = gr::fax::fax_demod::make(PREF_QUAD_RATE,-425,425,120,576); + fax_decoder_enable = false; + d_rtty = gr::rtty::rtty_demod::make(PREF_QUAD_RATE, + -225,225, + 50, + gr::rtty::rtty_demod::RTTY_MODE_5BITS_BAUDOT, + gr::rtty::rtty_demod::RTTY_PARITY_NONE); + d_rtty_enable = false; // Width of rx_filter can be adjusted at run time, so the input buffer (the // output buffer of nb) needs to be large enough for the longest history @@ -322,3 +330,202 @@ void nbrx::set_amsync_pll_bw(float pll_bw) { demod_amsync->set_pll_bw(pll_bw); } + +int nbrx::start_decoder(enum rx_decoder decoder_type) { + if (!is_decoder_active(decoder_type)) { + switch (decoder_type) { + case RX_DECODER_FAX: + lock(); + connect(agc, 0, fax_decoder, 0); + unlock(); + fax_decoder_enable = true; + return 0; + case RX_DECODER_RTTY: + lock(); + connect(agc, 0, d_rtty, 0); + d_rtty_enable = true; + unlock(); + return 0; + default: + break; + } + } + + return -1; +} + +int nbrx::stop_decoder(enum rx_decoder decoder_type) { + if (is_decoder_active(decoder_type)) { + switch (decoder_type) { + case RX_DECODER_FAX: + lock(); + disconnect(agc, 0, fax_decoder, 0); + unlock(); + fax_decoder_enable = false; + return 0; + case RX_DECODER_RTTY: + lock(); + d_rtty_enable = false; + disconnect(agc, 0, d_rtty, 0); + unlock(); + return 0; + default: + break; + } + } + + return -1; +} + +bool nbrx::is_decoder_active(enum rx_decoder decoder_type) { + switch (decoder_type) { + case RX_DECODER_ANY: + return d_rtty_enable || fax_decoder_enable; + case RX_DECODER_FAX: + return fax_decoder_enable; + case RX_DECODER_RTTY: + return d_rtty_enable; + default: + break; + } + return false; +} + +int nbrx::reset_decoder(enum rx_decoder decoder_type) { + switch (decoder_type) { + case RX_DECODER_ALL: + case RX_DECODER_FAX: + fax_decoder->reset(); + return 0; + case RX_DECODER_RTTY: + d_rtty->reset(); + return 0; + default: + break; + } + + return -1; +} + +int nbrx::set_decoder_param(enum rx_decoder decoder_type, std::string param, std::string val) { + switch (decoder_type) { + case RX_DECODER_FAX: + if (param == "black_freq") + fax_decoder->set_black_freq(std::stof(val)); + else if (param == "white_freq") + fax_decoder->set_white_freq(std::stof(val)); + else if (param == "lpm") + fax_decoder->set_lpm(std::stof(val)); + else if (param == "ioc") + fax_decoder->set_ioc(std::stof(val)); + else if (param == "force") { + if (val == "reset") + fax_decoder->set_decoder_state(0); + else if (val == "sync") + fax_decoder->set_decoder_state(2); + else if (val == "start") + fax_decoder->set_decoder_state(5); + else return -1; + } + else return -1; + return 0; + case RX_DECODER_RTTY: + if (param == "mark_freq") + d_rtty->set_mark_freq(std::stof(val)); + else if (param == "space_freq") + d_rtty->set_space_freq(std::stof(val)); + else if (param == "threshold") + d_rtty->set_threshold(std::stof(val)); + else if (param == "bandwidth") + d_rtty->set_bandwidth(std::stof(val)); + else if (param == "transwidth") + d_rtty->set_transwidth(std::stof(val)); + else if (param == "filterlen") + d_rtty->set_filterlen(std::stof(val)); + else if (param == "baud_rate") + d_rtty->set_baud_rate(std::stof(val)); + else if (param == "mode") + d_rtty->set_mode((gr::rtty::rtty_demod::rtty_mode)std::stoi(val)); + else if (param == "parity") + d_rtty->set_parity((gr::rtty::rtty_demod::rtty_parity)std::stoi(val)); + else return -1; + return 0; + default: + break; + } + + return -1; +} + +int nbrx::get_decoder_param(enum rx_decoder decoder_type, std::string param, std::string &val) { + switch (decoder_type) { + case RX_DECODER_FAX: + if (param == "black_freq") + val = std::to_string(fax_decoder->black_freq()); + else if (param == "white_freq") + val = std::to_string(fax_decoder->white_freq()); + else if (param == "lpm") + val = std::to_string(fax_decoder->lpm()); + else if (param == "ioc") + val = std::to_string(fax_decoder->ioc()); + else if (param == "state") + switch (fax_decoder->decoder_state()) { + case 0: val = "Reset"; break; + case 1: val = "Wait start"; break; + case 2: val = "Wait white"; break; + case 3: val = "Measure white"; break; + case 4: val = "Measure black"; break; + case 5: val = "Get lines"; break; + default : return -1; + } + else return -1; + return 0; + case RX_DECODER_RTTY: + if (param == "mark_freq") + val = std::to_string(d_rtty->mark_freq()); + else if (param == "space_freq") + val = std::to_string(d_rtty->space_freq()); + else if (param == "threshold") + val = std::to_string(d_rtty->threshold()); + else if (param == "bandwidth") + val = std::to_string(d_rtty->bandwidth()); + else if (param == "transwidth") + val = std::to_string(d_rtty->transwidth()); + else if (param == "filterlen") + val = std::to_string(d_rtty->filterlen()); + else if (param == "baud_rate") + val = std::to_string(d_rtty->baud_rate()); + else if (param == "mode") + val = std::to_string(d_rtty->mode()); + else if (param == "parity") + val = std::to_string(d_rtty->parity()); + else return -1; + return 0; + default: + break; + } + + return -1; +} + +int nbrx::get_decoder_data(enum rx_decoder decoder_type,void* data, int& num) { + if (is_decoder_active(decoder_type)) { + switch (decoder_type) { + case RX_DECODER_FAX: + if (num==-1) + return fax_decoder->get_data((unsigned char*)NULL,(unsigned int*)&num); + else + return fax_decoder->get_data((unsigned char*)data,(unsigned int*)&num); + break; + case RX_DECODER_RTTY: + num = d_rtty->get_data(*(std::string*)data); + if (num == -1) + return -1; + return 0; + default: + break; + } + } + + return -1; +} diff --git a/src/receivers/nbrx.h b/src/receivers/nbrx.h index 20fcd7d3f..bf187f6cd 100644 --- a/src/receivers/nbrx.h +++ b/src/receivers/nbrx.h @@ -36,6 +36,8 @@ #include "dsp/rx_demod_am.h" //#include "dsp/resampler_ff.h" #include "dsp/resampler_xx.h" +#include "dsp/fax/fax_demod.h" +#include "dsp/rtty/rtty_demod.h" class nbrx; @@ -101,6 +103,15 @@ class nbrx : public receiver_base_cf void set_demod(int demod); + /* generic rx decoder interface decoder */ + int start_decoder(enum rx_decoder decoder_type); + int stop_decoder(enum rx_decoder decoder_type); + bool is_decoder_active(enum rx_decoder decoder_type); + int reset_decoder(enum rx_decoder decoder_type); + int set_decoder_param(enum rx_decoder decoder_type, std::string param, std::string val); + int get_decoder_param(enum rx_decoder decoder_type, std::string param, std::string &val); + int get_decoder_data(enum rx_decoder decoder_type,void* data, int& num); + /* FM parameters */ bool has_fm() { return true; } void set_fm_maxdev(float maxdev_hz); @@ -136,6 +147,11 @@ class nbrx : public receiver_base_cf rx_demod_amsync_sptr demod_amsync; /*!< AM-Sync demodulator. */ resampler_ff_sptr audio_rr0; /*!< Audio resampler. */ resampler_ff_sptr audio_rr1; /*!< Audio resampler. */ + gr::fax::fax_demod::sptr fax_decoder; + bool fax_decoder_enable; + gr::rtty::rtty_demod::sptr d_rtty; // RTTY decoder + bool d_rtty_enable; + gr::basic_block_sptr demod; // dummy pointer used for simplifying reconf }; diff --git a/src/receivers/receiver_base.cpp b/src/receivers/receiver_base.cpp index 0834e6d21..aa524746b 100644 --- a/src/receivers/receiver_base.cpp +++ b/src/receivers/receiver_base.cpp @@ -4,6 +4,7 @@ * https://gqrx.dk/ * * Copyright 2012 Alexandru Csete OZ9AEC. + * Generic rx decoder interface Copyright 2022 Marc CAPDEVILLE F4JMZ * * Gqrx is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -149,25 +150,41 @@ void receiver_base_cf::set_amsync_pll_bw(float pll_bw) (void) pll_bw; } -void receiver_base_cf::get_rds_data(std::string &outbuff, int &num) -{ - (void) outbuff; - (void) num; +/* Generic rx decoder virtual function default implementation */ +int receiver_base_cf::start_decoder(enum rx_decoder decoder_type) { + (void) decoder_type; + return -1; } -void receiver_base_cf::start_rds_decoder() -{ +int receiver_base_cf::stop_decoder(enum rx_decoder decoder_type) { + (void) decoder_type; + return -1; } -void receiver_base_cf::stop_rds_decoder() -{ +int receiver_base_cf::reset_decoder(enum rx_decoder decoder_type) { + (void) decoder_type; + return -1; } -void receiver_base_cf::reset_rds_parser() -{ +bool receiver_base_cf::is_decoder_active(enum rx_decoder decoder_type) { + return false; } -bool receiver_base_cf::is_rds_decoder_active() -{ - return false; +int receiver_base_cf::set_decoder_param(enum rx_decoder decoder_type, std::string param, std::string val) { + (void) param; + (void) val; + return -1; +} + +int receiver_base_cf::get_decoder_param(enum rx_decoder decoder_type, std::string param, std::string &val) { + (void) param; + (void) val; + return -1; +} + +int receiver_base_cf::get_decoder_data(enum rx_decoder decoder_type, void* data, int &num) { + (void) decoder_type; + (void) data; + (void) num; + return -1; } diff --git a/src/receivers/receiver_base.h b/src/receivers/receiver_base.h index 8a5d7cbaa..9cbbe5527 100644 --- a/src/receivers/receiver_base.h +++ b/src/receivers/receiver_base.h @@ -4,6 +4,7 @@ * https://gqrx.dk/ * * Copyright 2012-2013 Alexandru Csete OZ9AEC. + * Generic rx decoder interface Copyright 2022 Marc CAPDEVILLE F4JMZ * * Gqrx is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -52,6 +53,17 @@ class receiver_base_cf : public gr::hier_block2 receiver_base_cf(std::string src_name); virtual ~receiver_base_cf(); + /** generic rx decoder decoder type */ + enum rx_decoder { + RX_DECODER_ANY = -2, + RX_DECODER_ALL = -1, + RX_DECODER_NONE = 0, + RX_DECODER_RDS, + RX_DECODER_FAX, + RX_DECODER_RTTY, + RX_DECODER_MAX + }; + virtual bool start() = 0; virtual bool stop() = 0; @@ -99,11 +111,14 @@ class receiver_base_cf : public gr::hier_block2 virtual void set_amsync_dcr(bool enabled); virtual void set_amsync_pll_bw(float pll_bw); - virtual void get_rds_data(std::string &outbuff, int &num); - virtual void start_rds_decoder(); - virtual void stop_rds_decoder(); - virtual void reset_rds_parser(); - virtual bool is_rds_decoder_active(); + /* generic rx decoder virtual functions */ + virtual int start_decoder(enum rx_decoder decoder_type); + virtual int stop_decoder(enum rx_decoder decoder_type); + virtual int reset_decoder(enum rx_decoder decoder_type); + virtual bool is_decoder_active(enum rx_decoder decoder_type); + virtual int set_decoder_param(enum rx_decoder decoder_type, std::string param, std::string val); + virtual int get_decoder_param(enum rx_decoder decoder_type, std::string param, std::string &val); + virtual int get_decoder_data(enum rx_decoder decoder_type,void* data, int &num); }; diff --git a/src/receivers/wfmrx.cpp b/src/receivers/wfmrx.cpp index 3e5838646..781a0eb81 100644 --- a/src/receivers/wfmrx.cpp +++ b/src/receivers/wfmrx.cpp @@ -5,6 +5,7 @@ * * Copyright 2012 Alexandru Csete OZ9AEC. * FM stereo implementation by Alex Grinkov a.grinkov(at)gmail.com. + * Generic rx decoder interface and rds interface mod Copyright 2022 Marc CAPDEVILLE F4JMZ * * Gqrx is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -242,37 +243,84 @@ void wfmrx::set_fm_deemph(double tau) demod_fm->set_tau(tau); } -void wfmrx::get_rds_data(std::string &outbuff, int &num) +int wfmrx::start_decoder(enum rx_decoder decoder_type) { - rds_store->get_message(outbuff, num); + if (!is_decoder_active(decoder_type)) + switch (decoder_type) { + case RX_DECODER_RDS: + lock(); + connect(demod_fm, 0, rds, 0); + connect(rds, 0, rds_decoder, 0); + msg_connect(rds_decoder, "out", rds_parser, "in"); + msg_connect(rds_parser, "out", rds_store, "store"); + unlock(); + rds_enabled=true; + break; + default: + return -1; + } + + return 0; } -void wfmrx::start_rds_decoder() +int wfmrx::stop_decoder(enum rx_decoder decoder_type) { - connect(demod_fm, 0, rds, 0); - connect(rds, 0, rds_decoder, 0); - msg_connect(rds_decoder, "out", rds_parser, "in"); - msg_connect(rds_parser, "out", rds_store, "store"); - rds_enabled=true; + if (is_decoder_active(decoder_type)) + switch (decoder_type) { + case RX_DECODER_RDS: + lock(); + disconnect(demod_fm, 0, rds, 0); + disconnect(rds, 0, rds_decoder, 0); + msg_disconnect(rds_decoder, "out", rds_parser, "in"); + msg_disconnect(rds_parser, "out", rds_store, "store"); + rds_enabled=false; + unlock(); + break; + default: + return -1; + } + + return 0; } -void wfmrx::stop_rds_decoder() +bool wfmrx::is_decoder_active(enum rx_decoder decoder_type) { - lock(); - disconnect(demod_fm, 0, rds, 0); - disconnect(rds, 0, rds_decoder, 0); - msg_disconnect(rds_decoder, "out", rds_parser, "in"); - msg_disconnect(rds_parser, "out", rds_store, "store"); - unlock(); - rds_enabled=false; + switch (decoder_type) { + case RX_DECODER_ANY: + case RX_DECODER_RDS: + return rds_enabled; + default: + return false; + } } -void wfmrx::reset_rds_parser() +int wfmrx::reset_decoder(enum rx_decoder decoder_type) { - rds_parser->reset(); + switch (decoder_type) { + case RX_DECODER_ALL: + case RX_DECODER_RDS: + rds_parser->reset(); + break; + default: + return -1; + } + + return 0; } -bool wfmrx::is_rds_decoder_active() +int wfmrx::get_decoder_data(enum rx_decoder decoder_type, void* data, int& num) { - return rds_enabled; + std::string Buff; + + if (!is_decoder_active(decoder_type)) + return -1; + else switch (decoder_type) { + case RX_DECODER_RDS: + rds_store->get_message(*(std::string*)data, num); + break; + default: + return -1; + } + + return 0; } diff --git a/src/receivers/wfmrx.h b/src/receivers/wfmrx.h index cc0e03614..3aed263af 100644 --- a/src/receivers/wfmrx.h +++ b/src/receivers/wfmrx.h @@ -5,6 +5,7 @@ * * Copyright 2012 Alexandru Csete OZ9AEC. * FM stereo implementation by Alex Grinkov a.grinkov(at)gmail.com. + * Generic rx decoder interface Copyright 2022 Marc CAPDEVILLE F4JMZ * * Gqrx is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -63,6 +64,7 @@ class wfmrx : public receiver_base_cf WFMRX_DEMOD_STEREO_UKW = 2, /*!< UKW stereo. */ WFMRX_DEMOD_NUM = 3 /*!< Included for convenience. */ }; + wfmrx(float quad_rate, float audio_rate); ~wfmrx(); @@ -102,11 +104,12 @@ class wfmrx : public receiver_base_cf void set_fm_maxdev(float maxdev_hz); void set_fm_deemph(double tau); - void get_rds_data(std::string &outbuff, int &num); - void start_rds_decoder(); - void stop_rds_decoder(); - void reset_rds_parser(); - bool is_rds_decoder_active(); + /* generic rx decoder functions */ + int start_decoder(enum rx_decoder decoder_type); + int stop_decoder(enum rx_decoder decoder_type); + bool is_decoder_active(enum rx_decoder decoder_type); + int reset_decoder(enum rx_decoder decoder_type); + int get_decoder_data(enum rx_decoder decoder_type,void* data, int& num); private: bool d_running; /*!< Whether receiver is running or not. */ @@ -125,6 +128,7 @@ class wfmrx : public receiver_base_cf stereo_demod_sptr stereo_oirt; /*!< FM stereo oirt demodulator. */ stereo_demod_sptr mono; /*!< FM stereo demodulator OFF. */ + /* RDS decoder */ rx_rds_sptr rds; /*!< RDS decoder */ rx_rds_store_sptr rds_store; /*!< RDS decoded messages */ gr::rds::decoder::sptr rds_decoder;