diff --git a/include-gpl/dsp/fftfilt.h b/include-gpl/dsp/fftfilt.h index 4aed393c8..4af4613fb 100644 --- a/include-gpl/dsp/fftfilt.h +++ b/include-gpl/dsp/fftfilt.h @@ -17,15 +17,17 @@ public: typedef std::complex cmplx; fftfilt(float f1, float f2, int len); - fftfilt(float f, int len); + fftfilt(float f2, int len); ~fftfilt(); // f1 < f2 ==> bandpass // f1 > f2 ==> band reject void create_filter(float f1, float f2); - void rtty_filter(float); + void create_dsb_filter(float f2); + int noFilt(const cmplx& in, cmplx **out); int runFilt(const cmplx& in, cmplx **out); int runSSB(const cmplx& in, cmplx **out, bool usb); + int runDSB(const cmplx& in, cmplx **out); protected: int flen; @@ -44,12 +46,15 @@ protected: return (i == len/2) ? 2.0 * fc: sin(2 * M_PI * fc * (i - len/2)) / (M_PI * (i - len/2)); } + inline float _blackman(int i, int len) { return (0.42 - 0.50 * cos(2.0 * M_PI * i / len) + 0.08 * cos(4.0 * M_PI * i / len)); } + void init_filter(); + void init_dsb_filter(); }; diff --git a/plugins/channel/CMakeLists.txt b/plugins/channel/CMakeLists.txt index a2a86ed41..489d11b4d 100644 --- a/plugins/channel/CMakeLists.txt +++ b/plugins/channel/CMakeLists.txt @@ -6,5 +6,6 @@ add_subdirectory(nfm) add_subdirectory(ssb) add_subdirectory(tcpsrc) add_subdirectory(wfm) +add_subdirectory(chanalyzer) #add_subdirectory(tetra) diff --git a/plugins/channel/chanalyzer/CMakeLists.txt b/plugins/channel/chanalyzer/CMakeLists.txt new file mode 100644 index 000000000..b25d28121 --- /dev/null +++ b/plugins/channel/chanalyzer/CMakeLists.txt @@ -0,0 +1,47 @@ +project(chanalyzer) + +set(chanalyzer_SOURCES + chanalyzer.cpp + chanalyzergui.cpp + chanalyzerplugin.cpp +) + +set(chanalyzer_HEADERS + chanalyzer.h + chanalyzergui.h + chanalyzerplugin.h +) + +set(chanalyzer_FORMS + chanalyzergui.ui +) + +include_directories( + . + ${CMAKE_CURRENT_BINARY_DIR} + ${CMAKE_SOURCE_DIR}/include + ${CMAKE_SOURCE_DIR}/include-gpl + ${OPENGL_INCLUDE_DIR} +) + +#include(${QT_USE_FILE}) +add_definitions(${QT_DEFINITIONS}) +add_definitions(-DQT_PLUGIN) +add_definitions(-DQT_SHARED) + +#qt5_wrap_cpp(chanalyzer_HEADERS_MOC ${chanalyzer_HEADERS}) +qt5_wrap_ui(chanalyzer_FORMS_HEADERS ${chanalyzer_FORMS}) + +add_library(chanalyzer SHARED + ${chanalyzer_SOURCES} + ${chanalyzer_HEADERS_MOC} + ${chanalyzer_FORMS_HEADERS} +) + +target_link_libraries(chanalyzer + ${QT_LIBRARIES} + ${OPENGL_LIBRARIES} + sdrbase +) + +qt5_use_modules(chanalyzer Core Widgets OpenGL Multimedia) diff --git a/plugins/channel/chanalyzer/chanalyzer.cpp b/plugins/channel/chanalyzer/chanalyzer.cpp new file mode 100644 index 000000000..2ed51a60d --- /dev/null +++ b/plugins/channel/chanalyzer/chanalyzer.cpp @@ -0,0 +1,193 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2012 maintech GmbH, Otto-Hahn-Str. 15, 97204 Hoechberg, Germany // +// written by Christian Daniel // +// (c) 2014 Modified by John Greb +// // +// This program 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 as version 3 of the License, or // +// // +// This program 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 V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + +#include +#include +#include "audio/audiooutput.h" +#include "dsp/dspcommands.h" + +#include +#include "chanalyzer.h" + +MESSAGE_CLASS_DEFINITION(ChannelAnalyzer::MsgConfigureChannelAnalyzer, Message) + +ChannelAnalyzer::ChannelAnalyzer(SampleSink* sampleSink) : + m_sampleSink(sampleSink) +{ + m_Bandwidth = 5000; + m_LowCutoff = 300; + //m_volume = 2.0; + m_spanLog2 = 3; + m_sampleRate = 96000; + m_frequency = 0; + m_nco.setFreq(m_frequency, m_sampleRate); + m_nco_test.setFreq(m_frequency, m_sampleRate); + m_interpolator.create(16, m_sampleRate, 5000); + m_sampleDistanceRemain = (Real)m_sampleRate / 48000.0; + + //m_audioBuffer.resize(512); + //m_audioBufferFill = 0; + m_undersampleCount = 0; + + m_usb = true; + m_ssb = true; + SSBFilter = new fftfilt(m_LowCutoff / 48000.0, m_Bandwidth / 48000.0, ssbFftLen); + DSBFilter = new fftfilt(m_Bandwidth / 48000.0, 2*ssbFftLen); + // if (!USBFilter) segfault; +} + +ChannelAnalyzer::~ChannelAnalyzer() +{ + if (SSBFilter) delete SSBFilter; + if (DSBFilter) delete DSBFilter; +} + +void ChannelAnalyzer::configure(MessageQueue* messageQueue, + Real Bandwidth, + Real LowCutoff, + int spanLog2, + bool ssb) +{ + Message* cmd = MsgConfigureChannelAnalyzer::create(Bandwidth, LowCutoff, spanLog2, ssb); + cmd->submit(messageQueue, this); +} + +void ChannelAnalyzer::feed(SampleVector::const_iterator begin, SampleVector::const_iterator end, bool positiveOnly) +{ + Complex ci; + fftfilt::cmplx *sideband, sum; + int n_out; + int decim = 1<<(m_spanLog2 - 1); + unsigned char decim_mask = decim - 1; // counter LSB bit mask for decimation by 2^(m_scaleLog2 - 1) + + for(SampleVector::const_iterator it = begin; it < end; ++it) { + Complex c(it->real() / 32768.0, it->imag() / 32768.0); + c *= m_nco.nextIQ(); + + /* + ci = c; + if (m_ssb) { + n_out = SSBFilter->runSSB(ci, &sideband, m_usb); + } else { + n_out = DSBFilter->noFilt(ci, &sideband); + } + */ + + if(m_interpolator.interpolate(&m_sampleDistanceRemain, c, &ci)) + { + if (m_ssb) + { + n_out = SSBFilter->runSSB(ci, &sideband, m_usb); + } + else + { + n_out = DSBFilter->runDSB(ci, &sideband); + } + m_sampleDistanceRemain += (Real)m_sampleRate / 48000.0; + } + else + { + n_out = 0; + } + + for (int i = 0; i < n_out; i++) + { + // Downsample by 2^(m_scaleLog2 - 1) for SSB band spectrum display + // smart decimation with bit gain using float arithmetic (23 bits significand) + + sum += sideband[i]; + + if (!(m_undersampleCount++ & decim_mask)) + { + sum /= decim; + m_sampleBuffer.push_back(Sample(sum.real() * 32768.0, sum.imag() * 32768.0)); + sum = 0; + } + + } + } + + if(m_sampleSink != NULL) + { + m_sampleSink->feed(m_sampleBuffer.begin(), m_sampleBuffer.end(), m_ssb); // m_ssb = positive only + } + + m_sampleBuffer.clear(); +} + +void ChannelAnalyzer::start() +{ +} + +void ChannelAnalyzer::stop() +{ +} + +bool ChannelAnalyzer::handleMessage(Message* cmd) +{ + float band, lowCutoff; + + if(DSPSignalNotification::match(cmd)) { + DSPSignalNotification* signal = (DSPSignalNotification*)cmd; + //fprintf(stderr, "%d samples/sec, %lld Hz offset", signal->getSampleRate(), signal->getFrequencyOffset()); + m_sampleRate = signal->getSampleRate(); + m_nco.setFreq(-signal->getFrequencyOffset(), m_sampleRate); + m_interpolator.create(16, m_sampleRate, m_Bandwidth); + m_sampleDistanceRemain = m_sampleRate / 48000.0; + cmd->completed(); + return true; + } else if(MsgConfigureChannelAnalyzer::match(cmd)) { + MsgConfigureChannelAnalyzer* cfg = (MsgConfigureChannelAnalyzer*)cmd; + + band = cfg->getBandwidth(); + lowCutoff = cfg->getLoCutoff(); + + if (band < 0) { + band = -band; + lowCutoff = -lowCutoff; + m_usb = false; + } else + m_usb = true; + + if (band < 100.0f) + { + band = 100.0f; + lowCutoff = 0; + } + + m_Bandwidth = band; + m_LowCutoff = lowCutoff; + + m_interpolator.create(16, m_sampleRate, band * 2.0f); + SSBFilter->create_filter(m_LowCutoff / 48000.0f, m_Bandwidth / 48000.0f); + DSBFilter->create_dsb_filter(m_Bandwidth / 48000.0f); + + //m_volume = cfg->getVolume(); + //m_volume *= m_volume * 0.1; + + m_spanLog2 = cfg->getSpanLog2(); + m_ssb = cfg->getSSB(); + + cmd->completed(); + return true; + } else { + if(m_sampleSink != NULL) + return m_sampleSink->handleMessage(cmd); + else return false; + } +} diff --git a/plugins/channel/chanalyzer/chanalyzer.h b/plugins/channel/chanalyzer/chanalyzer.h new file mode 100644 index 000000000..e80cf6249 --- /dev/null +++ b/plugins/channel/chanalyzer/chanalyzer.h @@ -0,0 +1,115 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2012 maintech GmbH, Otto-Hahn-Str. 15, 97204 Hoechberg, Germany // +// written by Christian Daniel // +// // +// This program 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 as version 3 of the License, or // +// // +// This program 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 V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + +#ifndef INCLUDE_CHANALYZER_H +#define INCLUDE_CHANALYZER_H + +#include +#include "dsp/samplesink.h" +#include "dsp/nco.h" +#include "dsp/interpolator.h" +#include "dsp/fftfilt.h" +#include "audio/audiofifo.h" +#include "util/message.h" + +#define ssbFftLen 1024 + +//class AudioFifo; + +class ChannelAnalyzer : public SampleSink { +public: + ChannelAnalyzer(SampleSink* sampleSink); + ~ChannelAnalyzer(); + + void configure(MessageQueue* messageQueue, + Real Bandwidth, + Real LowCutoff, + int spanLog2, + bool ssb); + + void feed(SampleVector::const_iterator begin, SampleVector::const_iterator end, bool positiveOnly); + void start(); + void stop(); + bool handleMessage(Message* cmd); + +private: + class MsgConfigureChannelAnalyzer : public Message { + MESSAGE_CLASS_DECLARATION + + public: + Real getBandwidth() const { return m_Bandwidth; } + Real getLoCutoff() const { return m_LowCutoff; } + int getSpanLog2() const { return m_spanLog2; } + bool getSSB() const { return m_ssb; } + + static MsgConfigureChannelAnalyzer* create(Real Bandwidth, + Real LowCutoff, + int spanLog2, + bool ssb) + { + return new MsgConfigureChannelAnalyzer(Bandwidth, LowCutoff, spanLog2, ssb); + } + + private: + Real m_Bandwidth; + Real m_LowCutoff; + int m_spanLog2; + bool m_ssb; + + MsgConfigureChannelAnalyzer(Real Bandwidth, + Real LowCutoff, + int spanLog2, + bool ssb) : + Message(), + m_Bandwidth(Bandwidth), + m_LowCutoff(LowCutoff), + m_spanLog2(spanLog2), + m_ssb(ssb) + { } + }; + + //struct AudioSample { + // qint16 l; + // qint16 r; + //}; + //typedef std::vector AudioVector; + + Real m_Bandwidth; + Real m_LowCutoff; + int m_spanLog2; + int m_undersampleCount; + int m_sampleRate; + int m_frequency; + bool m_usb; + bool m_ssb; + + NCO m_nco; + NCO m_nco_test; + Interpolator m_interpolator; + Real m_sampleDistanceRemain; + fftfilt* SSBFilter; + fftfilt* DSBFilter; + + SampleSink* m_sampleSink; + SampleVector m_sampleBuffer; + + //AudioVector m_audioBuffer; + //uint m_audioBufferFill; + //AudioFifo* m_audioFifo; +}; + +#endif // INCLUDE_CHANALYZER_H diff --git a/plugins/channel/chanalyzer/chanalyzergui.cpp b/plugins/channel/chanalyzer/chanalyzergui.cpp new file mode 100644 index 000000000..49fb9fa34 --- /dev/null +++ b/plugins/channel/chanalyzer/chanalyzergui.cpp @@ -0,0 +1,358 @@ +#include +#include +#include "ui_chanalyzergui.h" +#include "dsp/threadedsamplesink.h" +#include "dsp/channelizer.h" +#include "dsp/spectrumvis.h" +#include "gui/glspectrum.h" +#include "plugin/pluginapi.h" +#include "util/simpleserializer.h" +#include "gui/basicchannelsettingswidget.h" + +#include +#include "chanalyzer.h" +#include "chanalyzergui.h" + +ChannelAnalyzerGUI* ChannelAnalyzerGUI::create(PluginAPI* pluginAPI) +{ + ChannelAnalyzerGUI* gui = new ChannelAnalyzerGUI(pluginAPI); + return gui; +} + +void ChannelAnalyzerGUI::destroy() +{ + delete this; +} + +void ChannelAnalyzerGUI::setName(const QString& name) +{ + setObjectName(name); +} + +QString ChannelAnalyzerGUI::getName() const +{ + return objectName(); +} + +void ChannelAnalyzerGUI::resetToDefaults() +{ + ui->BW->setValue(30); + ui->deltaFrequency->setValue(0); + ui->spanLog2->setValue(3); + applySettings(); +} + +QByteArray ChannelAnalyzerGUI::serialize() const +{ + SimpleSerializer s(1); + s.writeS32(1, m_channelMarker->getCenterFrequency()); + s.writeS32(2, ui->BW->value()); + s.writeBlob(3, ui->spectrumGUI->serialize()); + s.writeU32(4, m_channelMarker->getColor().rgb()); + s.writeS32(5, ui->lowCut->value()); + s.writeS32(6, ui->spanLog2->value()); + return s.final(); +} + +bool ChannelAnalyzerGUI::deserialize(const QByteArray& data) +{ + SimpleDeserializer d(data); + + if(!d.isValid()) { + resetToDefaults(); + return false; + } + + if(d.getVersion() == 1) { + QByteArray bytetmp; + quint32 u32tmp; + qint32 tmp; + d.readS32(1, &tmp, 0); + m_channelMarker->setCenterFrequency(tmp); + d.readS32(2, &tmp, 30); + ui->BW->setValue(tmp); + d.readBlob(3, &bytetmp); + ui->spectrumGUI->deserialize(bytetmp); + if(d.readU32(4, &u32tmp)) + m_channelMarker->setColor(u32tmp); + d.readS32(5, &tmp, 3); + ui->lowCut->setValue(tmp); + d.readS32(6, &tmp, 20); + ui->spanLog2->setValue(tmp); + setNewRate(tmp); + applySettings(); + return true; + } else { + resetToDefaults(); + return false; + } +} + +bool ChannelAnalyzerGUI::handleMessage(Message* message) +{ + return false; +} + +void ChannelAnalyzerGUI::viewChanged() +{ + applySettings(); +} + +void ChannelAnalyzerGUI::on_deltaMinus_clicked(bool minus) +{ + int deltaFrequency = m_channelMarker->getCenterFrequency(); + bool minusDelta = (deltaFrequency < 0); + + if (minus ^ minusDelta) // sign change + { + m_channelMarker->setCenterFrequency(-deltaFrequency); + } +} + +void ChannelAnalyzerGUI::on_deltaFrequency_changed(quint64 value) +{ + if (ui->deltaMinus->isChecked()) { + m_channelMarker->setCenterFrequency(-value); + } else { + m_channelMarker->setCenterFrequency(value); + } +} + +void ChannelAnalyzerGUI::on_BW_valueChanged(int value) +{ + QString s = QString::number(value/10.0, 'f', 1); + ui->BWText->setText(tr("%1k").arg(s)); + m_channelMarker->setBandwidth(value * 100 * 2); + + if (value < 0) { + m_channelMarker->setSidebands(ChannelMarker::lsb); + } else { + m_channelMarker->setSidebands(ChannelMarker::usb); + } + + on_lowCut_valueChanged(m_channelMarker->getLowCutoff()/100); +} + +int ChannelAnalyzerGUI::getEffectiveLowCutoff(int lowCutoff) +{ + int ssbBW = m_channelMarker->getBandwidth() / 2; + int effectiveLowCutoff = lowCutoff; + const int guard = 100; + + if (ssbBW < 0) { + if (effectiveLowCutoff < ssbBW + guard) { + effectiveLowCutoff = ssbBW + guard; + } + if (effectiveLowCutoff > 0) { + effectiveLowCutoff = 0; + } + } else { + if (effectiveLowCutoff > ssbBW - guard) { + effectiveLowCutoff = ssbBW - guard; + } + if (effectiveLowCutoff < 0) { + effectiveLowCutoff = 0; + } + } + + return effectiveLowCutoff; +} + +void ChannelAnalyzerGUI::on_lowCut_valueChanged(int value) +{ + int lowCutoff = getEffectiveLowCutoff(value * 100); + m_channelMarker->setLowCutoff(lowCutoff); + QString s = QString::number(lowCutoff/1000.0, 'f', 1); + ui->lowCutText->setText(tr("%1k").arg(s)); + ui->lowCut->setValue(lowCutoff/100); + applySettings(); +} + +void ChannelAnalyzerGUI::on_spanLog2_valueChanged(int value) +{ + if (setNewRate(value)) { + applySettings(); + } + +} + +void ChannelAnalyzerGUI::on_ssb_toggled(bool checked) +{ + if (checked) + { + if (ui->BW->value() < 0) { + m_channelMarker->setSidebands(ChannelMarker::lsb); + } else { + m_channelMarker->setSidebands(ChannelMarker::usb); + } + + ui->glSpectrum->setCenterFrequency(m_rate/2); + ui->glSpectrum->setSampleRate(m_rate); + ui->glSpectrum->setSsbSpectrum(true); + + on_lowCut_valueChanged(m_channelMarker->getLowCutoff()/100); + } + else + { + m_channelMarker->setSidebands(ChannelMarker::dsb); + + ui->glSpectrum->setCenterFrequency(0); + ui->glSpectrum->setSampleRate(2*m_rate); + ui->glSpectrum->setSsbSpectrum(false); + + applySettings(); + } +} + +void ChannelAnalyzerGUI::onWidgetRolled(QWidget* widget, bool rollDown) +{ + /* + if((widget == ui->spectrumContainer) && (m_ssbDemod != NULL)) + m_ssbDemod->setSpectrum(m_threadedSampleSink->getMessageQueue(), rollDown); + */ +} + +void ChannelAnalyzerGUI::onMenuDoubleClicked() +{ + if(!m_basicSettingsShown) { + m_basicSettingsShown = true; + BasicChannelSettingsWidget* bcsw = new BasicChannelSettingsWidget(m_channelMarker, this); + bcsw->show(); + } +} + +ChannelAnalyzerGUI::ChannelAnalyzerGUI(PluginAPI* pluginAPI, QWidget* parent) : + RollupWidget(parent), + ui(new Ui::ChannelAnalyzerGUI), + m_pluginAPI(pluginAPI), + m_basicSettingsShown(false), + m_rate(6000), + m_spanLog2(3) +{ + ui->setupUi(this); + setAttribute(Qt::WA_DeleteOnClose, true); + connect(this, SIGNAL(widgetRolled(QWidget*,bool)), this, SLOT(onWidgetRolled(QWidget*,bool))); + connect(this, SIGNAL(menuDoubleClickEvent()), this, SLOT(onMenuDoubleClicked())); + + //m_audioFifo = new AudioFifo(4, 48000 / 4); + m_spectrumVis = new SpectrumVis(ui->glSpectrum); + m_channelAnalyzer = new ChannelAnalyzer(m_spectrumVis); + m_channelizer = new Channelizer(m_channelAnalyzer); + m_threadedSampleSink = new ThreadedSampleSink(m_channelizer); + //m_pluginAPI->addAudioSource(m_audioFifo); + m_pluginAPI->addSampleSink(m_threadedSampleSink); + + ui->glSpectrum->setCenterFrequency(m_rate/2); + ui->glSpectrum->setSampleRate(m_rate); + ui->glSpectrum->setDisplayWaterfall(true); + ui->glSpectrum->setDisplayMaxHold(true); + ui->glSpectrum->setSsbSpectrum(true); + + m_channelMarker = new ChannelMarker(this); + m_channelMarker->setColor(Qt::gray); + m_channelMarker->setBandwidth(m_rate); + m_channelMarker->setSidebands(ChannelMarker::usb); + m_channelMarker->setCenterFrequency(0); + m_channelMarker->setVisible(true); + connect(m_channelMarker, SIGNAL(changed()), this, SLOT(viewChanged())); + m_pluginAPI->addChannelMarker(m_channelMarker); + + ui->spectrumGUI->setBuddies(m_threadedSampleSink->getMessageQueue(), m_spectrumVis, ui->glSpectrum); + + applySettings(); +} + +ChannelAnalyzerGUI::~ChannelAnalyzerGUI() +{ + m_pluginAPI->removeChannelInstance(this); + //m_pluginAPI->removeAudioSource(m_audioFifo); + m_pluginAPI->removeSampleSink(m_threadedSampleSink); + delete m_threadedSampleSink; + delete m_channelizer; + delete m_channelAnalyzer; + delete m_spectrumVis; + //delete m_audioFifo; + delete m_channelMarker; + delete ui; +} + +bool ChannelAnalyzerGUI::setNewRate(int spanLog2) +{ + if ((spanLog2 < 1) || (spanLog2 > 5)) { + return false; + } + + m_spanLog2 = spanLog2; + m_rate = 48000 / (1<BW->value() < -m_rate/100) { + ui->BW->setValue(-m_rate/100); + m_channelMarker->setBandwidth(-m_rate*2); + } else if (ui->BW->value() > m_rate/100) { + ui->BW->setValue(m_rate/100); + m_channelMarker->setBandwidth(m_rate*2); + } + + if (ui->lowCut->value() < -m_rate/100) { + ui->lowCut->setValue(-m_rate/100); + m_channelMarker->setLowCutoff(-m_rate); + } else if (ui->lowCut->value() > m_rate/100) { + ui->lowCut->setValue(m_rate/100); + m_channelMarker->setLowCutoff(m_rate); + } + + ui->BW->setMinimum(-m_rate/100); + ui->lowCut->setMinimum(-m_rate/100); + ui->BW->setMaximum(m_rate/100); + ui->lowCut->setMaximum(m_rate/100); + + QString s = QString::number(m_rate/1000.0, 'f', 1); + ui->spanText->setText(tr("%1k").arg(s)); + + if (ui->ssb->isChecked()) + { + if (ui->BW->value() < 0) { + m_channelMarker->setSidebands(ChannelMarker::lsb); + } else { + m_channelMarker->setSidebands(ChannelMarker::usb); + } + + ui->glSpectrum->setCenterFrequency(m_rate/2); + ui->glSpectrum->setSampleRate(m_rate); + ui->glSpectrum->setSsbSpectrum(true); + } else { + m_channelMarker->setSidebands(ChannelMarker::dsb); + + ui->glSpectrum->setCenterFrequency(0); + ui->glSpectrum->setSampleRate(2*m_rate); + ui->glSpectrum->setSsbSpectrum(false); + } + + return true; +} + +void ChannelAnalyzerGUI::applySettings() +{ + setTitleColor(m_channelMarker->getColor()); + ui->deltaFrequency->setValue(abs(m_channelMarker->getCenterFrequency())); + ui->deltaMinus->setChecked(m_channelMarker->getCenterFrequency() < 0); + m_channelizer->configure(m_threadedSampleSink->getMessageQueue(), + 48000, + m_channelMarker->getCenterFrequency()); + m_channelAnalyzer->configure(m_threadedSampleSink->getMessageQueue(), + ui->BW->value() * 100.0, + ui->lowCut->value() * 100.0, + m_spanLog2, + ui->ssb->isChecked()); +} + +void ChannelAnalyzerGUI::leaveEvent(QEvent*) +{ + m_channelMarker->setHighlighted(false); +} + +void ChannelAnalyzerGUI::enterEvent(QEvent*) +{ + m_channelMarker->setHighlighted(true); +} + diff --git a/plugins/channel/chanalyzer/chanalyzergui.h b/plugins/channel/chanalyzer/chanalyzergui.h new file mode 100644 index 000000000..13ce581b5 --- /dev/null +++ b/plugins/channel/chanalyzer/chanalyzergui.h @@ -0,0 +1,72 @@ +#ifndef INCLUDE_CHANNELANALYZERGUI_H +#define INCLUDE_CHANNELANALYZERGUI_H + +#include "gui/rollupwidget.h" +#include "plugin/plugingui.h" + +class PluginAPI; +class ChannelMarker; + +//class AudioFifo; +class ThreadedSampleSink; +class Channelizer; +class ChannelAnalyzer; +class SpectrumVis; + +namespace Ui { + class ChannelAnalyzerGUI; +} + +class ChannelAnalyzerGUI : public RollupWidget, public PluginGUI { + Q_OBJECT + +public: + static ChannelAnalyzerGUI* create(PluginAPI* pluginAPI); + void destroy(); + + void setName(const QString& name); + QString getName() const; + + void resetToDefaults(); + QByteArray serialize() const; + bool deserialize(const QByteArray& data); + + bool handleMessage(Message* message); + +private slots: + void viewChanged(); + void on_deltaFrequency_changed(quint64 value); + void on_deltaMinus_clicked(bool minus); + void on_BW_valueChanged(int value); + void on_lowCut_valueChanged(int value); + void on_spanLog2_valueChanged(int value); + void on_ssb_toggled(bool checked); + void onWidgetRolled(QWidget* widget, bool rollDown); + void onMenuDoubleClicked(); + +private: + Ui::ChannelAnalyzerGUI* ui; + PluginAPI* m_pluginAPI; + ChannelMarker* m_channelMarker; + bool m_basicSettingsShown; + int m_rate; + int m_spanLog2; + + //AudioFifo* m_audioFifo; + ThreadedSampleSink* m_threadedSampleSink; + Channelizer* m_channelizer; + ChannelAnalyzer* m_channelAnalyzer; + SpectrumVis* m_spectrumVis; + + explicit ChannelAnalyzerGUI(PluginAPI* pluginAPI, QWidget* parent = NULL); + ~ChannelAnalyzerGUI(); + + int getEffectiveLowCutoff(int lowCutoff); + bool setNewRate(int spanLog2); + void applySettings(); + + void leaveEvent(QEvent*); + void enterEvent(QEvent*); +}; + +#endif // INCLUDE_CHANNELANALYZERGUI_H diff --git a/plugins/channel/chanalyzer/chanalyzergui.ui b/plugins/channel/chanalyzer/chanalyzergui.ui new file mode 100644 index 000000000..169e6bac6 --- /dev/null +++ b/plugins/channel/chanalyzer/chanalyzergui.ui @@ -0,0 +1,416 @@ + + + ChannelAnalyzerGUI + + + + 0 + 0 + 302 + 439 + + + + + 302 + 0 + + + + Channel Analyzer + + + + + 35 + 35 + 242 + 96 + + + + + 8 + + + + Settings + + + + 2 + + + 2 + + + 2 + + + 2 + + + 4 + + + 3 + + + + + + 8 + + + + -60 + + + 60 + + + 1 + + + 30 + + + Qt::Horizontal + + + + + + + + 50 + 0 + + + + + 8 + + + + 3.0k + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + + 0 + 0 + + + + + 32 + 16 + + + + + Monospace + 10 + + + + SizeVerCursor + + + Qt::StrongFocus + + + Demod shift frequency from center in Hz + + + + + + + + 8 + + + + BW + + + + + + + + 8 + + + + Hz + + + + + + + + 8 + + + + Span + + + + + + + + 8 + + + + Low cut. + + + + + + + + 8 + + + + Qt::RightToLeft + + + Minus + + + + + + + + 8 + + + + -60 + + + 60 + + + 1 + + + 3 + + + Qt::Horizontal + + + + + + + + 50 + 0 + + + + + 8 + + + + 0.3k + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + + 8 + + + + 6.0k + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + + 11 + + + + 1 + + + 5 + + + 1 + + + 3 + + + 3 + + + Qt::Horizontal + + + true + + + true + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + SSB + + + + + + + + + 40 + 140 + 241 + 284 + + + + Channel Spectrum + + + + 2 + + + 3 + + + 3 + + + 3 + + + 3 + + + + + + 200 + 250 + + + + + Courier + 8 + + + + + + + + + + + + + GLSpectrum + QWidget +
gui/glspectrum.h
+ 1 +
+ + GLSpectrumGUI + QWidget +
gui/glspectrumgui.h
+ 1 +
+ + RollupWidget + QWidget +
gui/rollupwidget.h
+ 1 +
+ + ValueDial + QWidget +
gui/valuedial.h
+ 1 +
+
+ + +
diff --git a/plugins/channel/chanalyzer/chanalyzerplugin.cpp b/plugins/channel/chanalyzer/chanalyzerplugin.cpp new file mode 100644 index 000000000..8941b46ac --- /dev/null +++ b/plugins/channel/chanalyzer/chanalyzerplugin.cpp @@ -0,0 +1,53 @@ +#include +#include +#include "plugin/pluginapi.h" +#include "chanalyzergui.h" +#include "chanalyzerplugin.h" + +const PluginDescriptor ChannelAnalyzerPlugin::m_pluginDescriptor = { + QString("Channel Analyzer"), + QString("1.0"), + QString("(c) 2015 Edouard Griffiths, F4EXB"), + QString("https://github.com/f4exb/rtl-sdrangelove/tree/f4exb"), + true, + QString("https://github.com/f4exb/rtl-sdrangelove/tree/f4exb") +}; + +ChannelAnalyzerPlugin::ChannelAnalyzerPlugin(QObject* parent) : + QObject(parent) +{ +} + +const PluginDescriptor& ChannelAnalyzerPlugin::getPluginDescriptor() const +{ + return m_pluginDescriptor; +} + +void ChannelAnalyzerPlugin::initPlugin(PluginAPI* pluginAPI) +{ + m_pluginAPI = pluginAPI; + + // register demodulator + QAction* action = new QAction(tr("&ChannelAnalyzer"), this); + connect(action, SIGNAL(triggered()), this, SLOT(createInstanceChannelAnalyzer())); + m_pluginAPI->registerChannel("org.f4exb.sdrangelove.channel.chanalyzer", this, action); +} + +PluginGUI* ChannelAnalyzerPlugin::createChannel(const QString& channelName) +{ + if(channelName == "org.f4exb.sdrangelove.channel.chanalyzer") { + ChannelAnalyzerGUI* gui = ChannelAnalyzerGUI::create(m_pluginAPI); + m_pluginAPI->registerChannelInstance("org.f4exb.sdrangelove.channel.chanalyzer", gui); + m_pluginAPI->addChannelRollup(gui); + return gui; + } else { + return NULL; + } +} + +void ChannelAnalyzerPlugin::createInstanceChannelAnalyzer() +{ + ChannelAnalyzerGUI* gui = ChannelAnalyzerGUI::create(m_pluginAPI); + m_pluginAPI->registerChannelInstance("org.f4exb.sdrangelove.channel.chanalyzer", gui); + m_pluginAPI->addChannelRollup(gui); +} diff --git a/plugins/channel/chanalyzer/chanalyzerplugin.h b/plugins/channel/chanalyzer/chanalyzerplugin.h new file mode 100644 index 000000000..8ef4b3507 --- /dev/null +++ b/plugins/channel/chanalyzer/chanalyzerplugin.h @@ -0,0 +1,29 @@ +#ifndef INCLUDE_CHANALYZERPLUGIN_H +#define INCLUDE_CHANALYZERPLUGIN_H + +#include +#include "plugin/plugininterface.h" + +class ChannelAnalyzerPlugin : public QObject, PluginInterface { + Q_OBJECT + Q_INTERFACES(PluginInterface) + Q_PLUGIN_METADATA(IID "org.f4exb.sdrangelove.channel.chanalyzer") + +public: + explicit ChannelAnalyzerPlugin(QObject* parent = NULL); + + const PluginDescriptor& getPluginDescriptor() const; + void initPlugin(PluginAPI* pluginAPI); + + PluginGUI* createChannel(const QString& channelName); + +private: + static const PluginDescriptor m_pluginDescriptor; + + PluginAPI* m_pluginAPI; + +private slots: + void createInstanceChannelAnalyzer(); +}; + +#endif // INCLUDE_CHANALYZERPLUGIN_H diff --git a/sdrbase/dsp/fftfilt.cxx b/sdrbase/dsp/fftfilt.cxx index 45f19c365..5a7ea33ac 100644 --- a/sdrbase/dsp/fftfilt.cxx +++ b/sdrbase/dsp/fftfilt.cxx @@ -80,6 +80,13 @@ fftfilt::fftfilt(float f1, float f2, int len) create_filter(f1, f2); } +fftfilt::fftfilt(float f2, int len) +{ + flen = len; + init_filter(); + create_dsb_filter(f2); +} + fftfilt::~fftfilt() { if (fft) delete fft; @@ -130,6 +137,43 @@ void fftfilt::create_filter(float f1, float f2) } } +// Double the size of FFT used for equivalent SSB filter or assume FFT is half the size of the one used for SSB +void fftfilt::create_dsb_filter(float f2) +{ + // initialize the filter to zero + memset(filter, 0, flen * sizeof(cmplx)); + + for (int i = 0; i < flen2; i++) { + filter[i] = fsinc(f2, i, flen2); + filter[i] *= _blackman(i, flen2); + } + + fft->ComplexFFT(filter); + + // normalize the output filter for unity gain + float scale = 0, mag; + for (int i = 0; i < flen2; i++) { + mag = abs(filter[i]); + if (mag > scale) scale = mag; + } + if (scale != 0) { + for (int i = 0; i < flen; i++) + filter[i] /= scale; + } +} + +// test bypass +int fftfilt::noFilt(const cmplx & in, cmplx **out) +{ + data[inptr++] = in; + if (inptr < flen2) + return 0; + inptr = 0; + + *out = data; + return flen2; +} + // Filter with fast convolution (overlap-add algorithm). int fftfilt::runFilt(const cmplx & in, cmplx **out) { @@ -190,6 +234,36 @@ int fftfilt::runSSB(const cmplx & in, cmplx **out, bool usb) return flen2; } +// Version for double sideband. You have to double the FFT size used for SSB. +int fftfilt::runDSB(const cmplx & in, cmplx **out) +{ + data[inptr++] = in; + if (inptr < flen2) + return 0; + inptr = 0; + + fft->ComplexFFT(data); + + for (int i = 0; i < flen2; i++) { + data[i] *= filter[i]; + data[flen2 + i] *= filter[flen2 + i]; + } + + // in-place FFT: freqdata overwritten with filtered timedata + fft->InverseComplexFFT(data); + + // overlap and add + for (int i = 0; i < flen2; i++) { + output[i] = ovlbuf[i] + data[i]; + ovlbuf[i] = data[i+flen2]; + } + + memset (data, 0, flen * sizeof(cmplx)); + + *out = output; + return flen2; +} + /* Sliding FFT from Fldigi */ struct sfft::vrot_bins_pair { diff --git a/sdrbase/gui/scaleengine.cpp b/sdrbase/gui/scaleengine.cpp index 06bde3647..da173e3a6 100644 --- a/sdrbase/gui/scaleengine.cpp +++ b/sdrbase/gui/scaleengine.cpp @@ -89,9 +89,11 @@ void ScaleEngine::calcCharSize() void ScaleEngine::calcScaleFactor() { - double median; + double median, range, freqBase; median = ((m_rangeMax - m_rangeMin) / 2.0) + m_rangeMin; + range = (m_rangeMax - m_rangeMin); + freqBase = (median == 0 ? range : median); m_scale = 1.0; switch(m_physicalUnit) { @@ -100,15 +102,15 @@ void ScaleEngine::calcScaleFactor() break; case Unit::Frequency: - if(median < 1000.0) { + if(freqBase < 1000.0) { m_unitStr = QObject::tr("Hz"); - } else if(median < 1000000.0) { + } else if(freqBase < 1000000.0) { m_unitStr = QObject::tr("kHz"); m_scale = 1000.0; - } else if(median < 1000000000.0) { + } else if(freqBase < 1000000000.0) { m_unitStr = QObject::tr("MHz"); m_scale = 1000000.0; - } else if(median < 1000000000000.0){ + } else if(freqBase < 1000000000000.0){ m_unitStr = QObject::tr("GHz"); m_scale = 1000000000.0; } else {