diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index edebe382..697bc8ac 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -46,6 +46,7 @@ MainWindow::MainWindow() connect(dock, &SpectrogramControls::openFile, this, &MainWindow::openFile); connect(dock->sampleRate, static_cast(&QLineEdit::textChanged), this, static_cast(&MainWindow::setSampleRate)); connect(dock, static_cast(&SpectrogramControls::fftOrZoomChanged), plots, &PlotView::setFFTAndZoom); + connect(dock->freqZoomLevelSlider, &QSlider::valueChanged, plots, &PlotView::setFrequencyZoom); connect(dock->powerMaxSlider, &QSlider::valueChanged, plots, &PlotView::setPowerMax); connect(dock->powerMinSlider, &QSlider::valueChanged, plots, &PlotView::setPowerMin); connect(dock->cursorsCheckBox, &QCheckBox::stateChanged, plots, &PlotView::enableCursors); diff --git a/src/plotview.cpp b/src/plotview.cpp index 798d2ab5..be7ab29b 100644 --- a/src/plotview.cpp +++ b/src/plotview.cpp @@ -472,6 +472,14 @@ void PlotView::setFFTAndZoom(int size, int zoom) updateView(true, samplesPerColumn() < oldSamplesPerColumn); } +void PlotView::setFrequencyZoom(int zoomLevel) +{ + if (spectrogramPlot != nullptr) + spectrogramPlot->setFrequencyZoomLevel(zoomLevel); + + updateView(); +} + void PlotView::setPowerMin(int power) { powerMin = power; diff --git a/src/plotview.h b/src/plotview.h index 326d547f..f8b5fd25 100644 --- a/src/plotview.h +++ b/src/plotview.h @@ -52,6 +52,7 @@ public slots: void repaint(); void setCursorSegments(int segments); void setFFTAndZoom(int fftSize, int zoomLevel); + void setFrequencyZoom(int zoomLevel); void setPowerMin(int power); void setPowerMax(int power); diff --git a/src/spectrogramcontrols.cpp b/src/spectrogramcontrols.cpp index 0e0f9aa2..8797ceee 100644 --- a/src/spectrogramcontrols.cpp +++ b/src/spectrogramcontrols.cpp @@ -55,7 +55,13 @@ SpectrogramControls::SpectrogramControls(const QString & title, QWidget * parent zoomLevelSlider->setRange(0, 10); zoomLevelSlider->setPageStep(1); - layout->addRow(new QLabel(tr("Zoom:")), zoomLevelSlider); + layout->addRow(new QLabel(tr("Time zoom:")), zoomLevelSlider); + + freqZoomLevelSlider = new QSlider(Qt::Horizontal, widget); + freqZoomLevelSlider->setRange(1, 16); + freqZoomLevelSlider->setPageStep(1); + + layout->addRow(new QLabel(tr("Freq zoom:")), freqZoomLevelSlider); powerMaxSlider = new QSlider(Qt::Horizontal, widget); powerMaxSlider->setRange(-140, 10); @@ -107,6 +113,7 @@ SpectrogramControls::SpectrogramControls(const QString & title, QWidget * parent connect(fftSizeSlider, &QSlider::valueChanged, this, &SpectrogramControls::fftSizeChanged); connect(zoomLevelSlider, &QSlider::valueChanged, this, &SpectrogramControls::zoomLevelChanged); + connect(freqZoomLevelSlider, &QSlider::valueChanged, this, &SpectrogramControls::freqZoomLevelChanged); connect(fileOpenButton, &QPushButton::clicked, this, &SpectrogramControls::fileOpenButtonClicked); connect(cursorsCheckBox, &QCheckBox::stateChanged, this, &SpectrogramControls::cursorsStateChanged); connect(powerMinSlider, &QSlider::valueChanged, this, &SpectrogramControls::powerMinChanged); @@ -146,6 +153,7 @@ void SpectrogramControls::setDefaults() powerMaxSlider->setValue(settings.value("PowerMax", 0).toInt()); powerMinSlider->setValue(settings.value("PowerMin", -100).toInt()); zoomLevelSlider->setValue(settings.value("ZoomLevel", 0).toInt()); + freqZoomLevelSlider->setValue(settings.value("FreqZoomLevel", 1).toInt()); } void SpectrogramControls::fftOrZoomChanged(void) @@ -169,6 +177,12 @@ void SpectrogramControls::zoomLevelChanged(int value) fftOrZoomChanged(); } +void SpectrogramControls::freqZoomLevelChanged(int value) +{ + QSettings settings; + settings.setValue("FreqZoomLevel", value); +} + void SpectrogramControls::powerMinChanged(int value) { QSettings settings; diff --git a/src/spectrogramcontrols.h b/src/spectrogramcontrols.h index 69e7d608..4e174260 100644 --- a/src/spectrogramcontrols.h +++ b/src/spectrogramcontrols.h @@ -49,6 +49,7 @@ public slots: private slots: void fftSizeChanged(int value); void zoomLevelChanged(int value); + void freqZoomLevelChanged(int value); void powerMinChanged(int value); void powerMaxChanged(int value); void fileOpenButtonClicked(); @@ -65,6 +66,7 @@ private slots: QLineEdit *sampleRate; QSlider *fftSizeSlider; QSlider *zoomLevelSlider; + QSlider *freqZoomLevelSlider; QSlider *powerMaxSlider; QSlider *powerMinSlider; QCheckBox *cursorsCheckBox; diff --git a/src/spectrogramplot.cpp b/src/spectrogramplot.cpp index c71763e7..2ccc6967 100644 --- a/src/spectrogramplot.cpp +++ b/src/spectrogramplot.cpp @@ -35,6 +35,7 @@ SpectrogramPlot::SpectrogramPlot(std::shared_ptr>> src) : Plot(src), inputSource(src), fftSize(512), tuner(fftSize, this) { + freqZoomLevel = 1; setFFTSize(fftSize); zoomLevel = 1; powerMax = 0.0f; @@ -229,15 +230,20 @@ void SpectrogramPlot::paintMid(QPainter &painter, QRect &rect, range_t s size_t tileID = sampleRange.minimum - sampleOffset; int xoffset = sampleOffset / getStride(); + // Source tiles hold the raw FFT bins (spectrogramHeight() rows); the + // destination is height() tall, so drawPixmap scales vertically by the + // frequency zoom level. + int srcHeight = spectrogramHeight(); + // Paint first (possibly partial) tile - painter.drawPixmap(QRect(rect.left(), rect.y(), linesPerTile() - xoffset, height()), *getPixmapTile(tileID), QRect(xoffset, 0, linesPerTile() - xoffset, height())); + painter.drawPixmap(QRect(rect.left(), rect.y(), linesPerTile() - xoffset, height()), *getPixmapTile(tileID), QRect(xoffset, 0, linesPerTile() - xoffset, srcHeight)); tileID += getStride() * linesPerTile(); // Paint remaining tiles for (int x = linesPerTile() - xoffset; x < rect.right(); x += linesPerTile()) { // TODO: don't draw past rect.right() // TODO: handle partial final tile - painter.drawPixmap(QRect(x, rect.y(), linesPerTile(), height()), *getPixmapTile(tileID), QRect(0, 0, linesPerTile(), height())); + painter.drawPixmap(QRect(x, rect.y(), linesPerTile(), height()), *getPixmapTile(tileID), QRect(0, 0, linesPerTile(), srcHeight)); tileID += getStride() * linesPerTile(); } } @@ -329,13 +335,14 @@ int SpectrogramPlot::getStride() float SpectrogramPlot::getTunerPhaseInc() { - auto freq = 0.5f - tuner.centre() / (float)fftSize; + // Convert zoomed display size to FFT bins + auto freq = 0.5f - tuner.centre() / (freqZoomLevel * (float)fftSize); return freq * Tau; } std::vector SpectrogramPlot::getTunerTaps() { - float cutoff = tuner.deviation() / (float)fftSize; + float cutoff = tuner.deviation() / (freqZoomLevel * (float)fftSize); float gain = pow(10.0f, powerMax / -10.0f); auto atten = 60.0f; auto len = estimate_req_filter_len(std::min(cutoff, 0.05f), atten); @@ -381,11 +388,7 @@ void SpectrogramPlot::setFFTSize(int size) window[i] = 0.5f * (1.0f - cos(Tau * i / (fftSize - 1))); } - if (inputSource->realSignal()) { - setHeight(fftSize/2); - } else { - setHeight(fftSize); - } + setHeight(spectrogramHeight() * freqZoomLevel); auto dev = tuner.deviation(); auto centre = tuner.centre(); tuner.setHeight(height()); @@ -411,6 +414,29 @@ void SpectrogramPlot::setZoomLevel(int zoom) zoomLevel = zoom; } +int SpectrogramPlot::spectrogramHeight() +{ + // Number of FFT bins displayed vertically, before applying freqZoomLevel + return inputSource->realSignal() ? fftSize / 2 : fftSize; +} + +void SpectrogramPlot::setFrequencyZoomLevel(int zoom) +{ + // Rescale the tuner so it stays over the same frequency after zooming + float scale = (float)zoom / (float)freqZoomLevel; + auto dev = tuner.deviation(); + auto centre = tuner.centre(); + + freqZoomLevel = zoom; + setHeight(spectrogramHeight() * freqZoomLevel); + + tuner.setHeight(height()); + tuner.setDeviation(dev * scale); + tuner.setCentre(centre * scale); + + emit repaint(); +} + void SpectrogramPlot::setSampleRate(double rate) { sampleRate = rate; diff --git a/src/spectrogramplot.h b/src/spectrogramplot.h index c75f193c..ce83d76c 100644 --- a/src/spectrogramplot.h +++ b/src/spectrogramplot.h @@ -82,6 +82,7 @@ public slots: void setPowerMax(int power); void setPowerMin(int power); void setZoomLevel(int zoom); + void setFrequencyZoomLevel(int zoom); void tunerMoved(); private: @@ -98,6 +99,7 @@ public slots: int fftSize; int zoomLevel; + int freqZoomLevel; float powerMax; float powerMin; double sampleRate; @@ -111,6 +113,7 @@ public slots: float* getFFTTile(size_t tile); void getLine(float *dest, size_t sample); int getStride(); + int spectrogramHeight(); float getTunerPhaseInc(); std::vector getTunerTaps(); int linesPerTile();