Files
lmms/plugins/SpectrumAnalyzer/SaWaterfallView.cpp
saker b2f2fc4ad1 Revisit the initialization for local variables (#7143)
* clang-tidy: Apply cppcoreguidelines-init-variables everywhere (treating NaNs as zeros)

* Initialize msec and tick outside switch

* Update plugins/Vestige/Vestige.cpp

Co-authored-by: Kevin Zander <veratil@gmail.com>

* Update plugins/Vestige/Vestige.cpp

Co-authored-by: Kevin Zander <veratil@gmail.com>

* Update plugins/Vestige/Vestige.cpp

Co-authored-by: Kevin Zander <veratil@gmail.com>

* Update plugins/VstEffect/VstEffectControls.cpp

Co-authored-by: Kevin Zander <veratil@gmail.com>

* Update src/core/DrumSynth.cpp

Co-authored-by: Kevin Zander <veratil@gmail.com>

* Update plugins/VstEffect/VstEffectControls.cpp

Co-authored-by: Kevin Zander <veratil@gmail.com>

* Update plugins/VstEffect/VstEffectControls.cpp

Co-authored-by: Kevin Zander <veratil@gmail.com>

* Update src/core/DrumSynth.cpp

Co-authored-by: Kevin Zander <veratil@gmail.com>

* Update src/core/DrumSynth.cpp

Co-authored-by: Kevin Zander <veratil@gmail.com>

* Update src/core/DrumSynth.cpp

Co-authored-by: Kevin Zander <veratil@gmail.com>

* Update src/core/DrumSynth.cpp

Co-authored-by: Kevin Zander <veratil@gmail.com>

* Update src/core/DrumSynth.cpp

Co-authored-by: Kevin Zander <veratil@gmail.com>

* Update src/core/DrumSynth.cpp

Co-authored-by: Kevin Zander <veratil@gmail.com>

* Use initialization with =

* Use tabs

* Use static_cast

* Update DrumSynth.cpp

Co-authored-by: Kevin Zander <veratil@gmail.com>

* Update DrumSynth.cpp

Co-authored-by: Kevin Zander <veratil@gmail.com>

* Update DrumSynth.cpp

Co-authored-by: Kevin Zander <veratil@gmail.com>

* Update src/core/DrumSynth.cpp

Co-authored-by: Kevin Zander <veratil@gmail.com>

* Do not use tabs for alignment in src/core/DrumSynth.cpp

Co-authored-by: Dalton Messmer <messmer.dalton@gmail.com>

* Move x variable inside loop

* Use ternary operator for b variable

* Revert "Use tabs"

This reverts commit 07afd8a83f58b539c3673310b2aad4b63c9198a0.

* Remove unnecessary variables in XpressiveView

* Simplify initialization in Plugin

* Combine declaration and initialization in EqCurve

* Combine declaration and initialization in Song

* Combine declaration and initialization in AudioAlsa

* Combine declaration and initialization in EqCurve (again)

* Missed some

* Undo changes made to non-LMMS files

* Undo indentation changes in SidInstrument.cpp

* Combine declaration with assignment in IoHelper

* Combine declaration with assignment using auto in Carla

* Combine declaration with assignment

* Combine declaration with assignment in BasicFilters

* Simplify assignments in AudioFileProcessorWaveView::zoom

* Simplify out sample variable in BitInvader

* Remove sampleLength variable in DelayEffect

* Move gain variable in DynamicsProcessor

* Combine peak variable declaration with assignment in EqSpectrumView

* Move left/right lfo variables in for loop in FlangerEffect

* Use ternary operator for group variable in LadspaControlDialog

* Combine declaration with assignment in Lb302

* Combine declaration with assignment in MidiExport

* Combine declaration with assignment in MidiFile

* Combine declaration with assignment in MidiImport

* Use ternary operator for vel_adjusted variable in OpulenZ

* Move tmpL and dcblkL variables in for loop in ReverbSC

* Combine declaration with initialization in SlicerT

* Combine declaration with assignment in SaSpectrumView

* Combine declaration with assignment in SaWaterfallView

* Combine declaration with assignment in StereoEnhancerEffect

* Combine declaration with assignment in VibratingString

* Combine declaration with assignment in VstEffectControls

* Combine declaration with assignment in Xpressive

* Combine declaration with assignment in AutomatableModel

* Combine declaration with assignment in AutomationClip

* Move sample variable in for loop in BandLimitedWave

* Combine declaration with assignment in DataFile

* Combine declaration with assignment in DrumSynth

* Combine declaration with assignment in Effect

* Remove redundant assignment to nphsLeft in InstrumentPlayHandle

* Combine declaration with assignment in LadspaManager

* Combine declaration with assignment in LinkedModelGroups

* Combine declaration with assignment in MemoryHelper

* Combine declaration with assignment in AudioAlsa

* Combine declaration with assignment in AudioFileOgg

* Combine declaration with assignment in AudioPortAudio

* Combine declaration with assignment in AudioSoundIo

* Combine declaration with assignment in Lv2Evbuf

* Combine declaration with assignment in Lv2Proc

* Combine declaration with assignment in main

* Combine declaration with assignment in MidiAlsaRaw

* Combine declaration with assignment in MidiAlsaSeq

* Combine declaration with assignment in MidiController

* Combine declaration with assignment in MidiJack

* Combine declaration with assignment in MidiSndio

* Combine declaration with assignment in ControlLayout

* Combine declaration with assignment in MainWindow

* Combine declaration with assignment in ProjectNotes

* Use ternary operator for nextValue variable in AutomationClipView

* Combine declaration with assignment in AutomationEditor

* Move length variable in for-loop in PianoRoll

* Combine declaration with assignment in ControllerConnectionDialog

* Combine declaration with assignment in Graph

* Combine declaration with assignment in LcdFloatSpinBox

* Combine declaration with assignment in TimeDisplayWidget

* Remove currentNote variable in InstrumentTrack

* Combine declaration with assignment in DrumSynth (again)

* Use ternary operator for factor variable in BitInvader

* Use ternary operator for highestBandwich variable in EqCurve

Bandwich?

* Move sum variable into for loop in Graph

* Fix format in MidiSndio

* Fixup a few more

* Cleanup error variables

* Use ternary operators and combine declaration with initialization

* Combine declaration with initialization

* Update plugins/LadspaEffect/LadspaControlDialog.cpp

Co-authored-by: Kevin Zander <veratil@gmail.com>

* Update plugins/OpulenZ/OpulenZ.cpp

Co-authored-by: Kevin Zander <veratil@gmail.com>

* Update plugins/SpectrumAnalyzer/SaProcessor.cpp

Co-authored-by: Kevin Zander <veratil@gmail.com>

* Update src/core/midi/MidiAlsaRaw.cpp

Co-authored-by: Kevin Zander <veratil@gmail.com>

* Update src/gui/MainWindow.cpp

Co-authored-by: Kevin Zander <veratil@gmail.com>

* Update src/gui/clips/AutomationClipView.cpp

Co-authored-by: Kevin Zander <veratil@gmail.com>

* Update src/gui/editors/AutomationEditor.cpp

Co-authored-by: Kevin Zander <veratil@gmail.com>

* Update src/gui/widgets/Fader.cpp

Co-authored-by: Kevin Zander <veratil@gmail.com>

* Move static_cast conversion into separate variable

* Use real index when interpolating

* Remove empty line

* Make helpBtn a private member

* Move controller type into separate variable

* Fix format of DrumSynth::waveform function

* Use tabs and static_cast

* Remove redundant if branch

* Refactor using static_cast/reinterpret_cast

* Add std namespace prefix

* Store repeated conditional into boolean variable

* Cast to int before assigning to m_currentLength

* Rename note_frames to noteFrames

* Update src/core/Controller.cpp

Co-authored-by: Kevin Zander <veratil@gmail.com>

* Update src/core/DrumSynth.cpp

Co-authored-by: Kevin Zander <veratil@gmail.com>

* Update src/gui/widgets/Graph.cpp

Co-authored-by: Kevin Zander <veratil@gmail.com>

* Revert changes that initialized variables redudantly

For situations where the initialization is
more complex or passed into a function
by a pointer, we dont need to do
initialization ourselves since it is
already done for us, just in a different way.

* Remove redundant err variable

* Remove explicit check of err variable

* Clean up changes and address review

* Do not initialize to 0/nullptr when not needed

* Wrap condition in parentheses for readability

---------

Co-authored-by: Kevin Zander <veratil@gmail.com>
Co-authored-by: Dalton Messmer <messmer.dalton@gmail.com>
2024-03-28 17:21:31 -04:00

350 lines
12 KiB
C++

/* SaWaterfallViewView.cpp - implementation of SaWaterfallViewView class.
*
* Copyright (c) 2019 Martin Pavelek <he29/dot/HS/at/gmail/dot/com>
*
* This file is part of LMMS - https://lmms.io
* 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; either
* version 2 of the License, or (at your option) any later version.
*
* 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 for more details.
*
* You should have received a copy of the GNU General Public
* License along with this program (see COPYING); if not, write to the
* Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
* Boston, MA 02110-1301 USA.
*
*/
#include "SaWaterfallView.h"
#include <algorithm>
#ifdef SA_DEBUG
#include <chrono>
#endif
#include <cmath>
#include <QImage>
#include <QMouseEvent>
#include <QMutexLocker>
#include <QPainter>
#include <QString>
#include "EffectControlDialog.h"
#include "GuiApplication.h"
#include "MainWindow.h"
#include "SaControls.h"
#include "SaProcessor.h"
namespace lmms::gui
{
SaWaterfallView::SaWaterfallView(SaControls *controls, SaProcessor *processor, QWidget *_parent) :
QWidget(_parent),
m_controls(controls),
m_processor(processor)
{
m_controlDialog = (EffectControlDialog*) _parent;
setMinimumSize(300, 150);
setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
connect(getGUI()->mainWindow(), SIGNAL(periodicUpdate()), this, SLOT(periodicUpdate()));
m_displayTop = 1;
m_displayBottom = height() -2;
m_displayLeft = 26;
m_displayRight = width() -26;
m_displayWidth = m_displayRight - m_displayLeft;
m_displayHeight = m_displayBottom - m_displayTop;
m_timeTics = makeTimeTics();
m_oldSecondsPerLine = 0;
m_oldHeight = 0;
m_cursor = QPointF(0, 0);
#ifdef SA_DEBUG
m_execution_avg = 0;
#endif
}
// Compose and draw all the content; called by Qt.
// Not as performance sensitive as SaSpectrumView, most of the processing is
// done directly in SaProcessor.
void SaWaterfallView::paintEvent(QPaintEvent *event)
{
#ifdef SA_DEBUG
unsigned int draw_time = std::chrono::high_resolution_clock::now().time_since_epoch().count();
#endif
// update boundary
m_displayBottom = height() -2;
m_displayRight = width() -26;
m_displayWidth = m_displayRight - m_displayLeft;
m_displayHeight = m_displayBottom - m_displayTop;
float label_width = 20;
float label_height = 16;
float margin = 2;
QPainter painter(this);
painter.setRenderHint(QPainter::Antialiasing, true);
// check if time labels need to be rebuilt
if (secondsPerLine() != m_oldSecondsPerLine || m_processor->waterfallHeight() != m_oldHeight)
{
m_timeTics = makeTimeTics();
m_oldSecondsPerLine = secondsPerLine();
m_oldHeight = m_processor->waterfallHeight();
}
// print time labels
float pos = 0;
painter.setPen(QPen(m_controls->m_colorLabels, 1, Qt::SolidLine, Qt::RoundCap, Qt::BevelJoin));
for (auto & line: m_timeTics)
{
pos = timeToYPixel(line.first, m_displayHeight);
// align first and last label to the edge if needed, otherwise center them
if (line == m_timeTics.front() && pos < label_height / 2)
{
painter.drawText(m_displayLeft - label_width - margin, m_displayTop - 1,
label_width, label_height, Qt::AlignRight | Qt::AlignTop | Qt::TextDontClip,
QString(line.second.c_str()));
painter.drawText(m_displayRight + margin, m_displayTop - 1,
label_width, label_height, Qt::AlignLeft | Qt::AlignTop | Qt::TextDontClip,
QString(line.second.c_str()));
}
else if (line == m_timeTics.back() && pos > m_displayBottom - label_height + 2)
{
painter.drawText(m_displayLeft - label_width - margin, m_displayBottom - label_height,
label_width, label_height, Qt::AlignRight | Qt::AlignBottom | Qt::TextDontClip,
QString(line.second.c_str()));
painter.drawText(m_displayRight + margin, m_displayBottom - label_height + 2,
label_width, label_height, Qt::AlignLeft | Qt::AlignBottom | Qt::TextDontClip,
QString(line.second.c_str()));
}
else
{
painter.drawText(m_displayLeft - label_width - margin, pos - label_height / 2,
label_width, label_height, Qt::AlignRight | Qt::AlignVCenter | Qt::TextDontClip,
QString(line.second.c_str()));
painter.drawText(m_displayRight + margin, pos - label_height / 2,
label_width, label_height, Qt::AlignLeft | Qt::AlignVCenter | Qt::TextDontClip,
QString(line.second.c_str()));
}
}
// draw the spectrogram precomputed in SaProcessor
if (m_processor->waterfallNotEmpty())
{
QMutexLocker lock(&m_processor->m_reallocationAccess);
QImage temp = QImage(m_processor->getHistory(), // raw pixel data to display
m_processor->waterfallWidth(), // width = number of frequency bins
m_processor->waterfallHeight(), // height = number of history lines
QImage::Format_RGB32);
lock.unlock();
temp.setDevicePixelRatio(devicePixelRatio()); // display at native resolution
painter.drawImage(m_displayLeft, m_displayTop,
temp.scaled(m_displayWidth * devicePixelRatio(),
m_displayHeight * devicePixelRatio(),
Qt::IgnoreAspectRatio,
Qt::SmoothTransformation));
m_processor->flipRequest();
}
else
{
painter.fillRect(m_displayLeft, m_displayTop, m_displayWidth, m_displayHeight, QColor(0,0,0));
}
// draw cursor (if it is within bounds)
drawCursor(painter);
// always draw the outline
painter.setPen(QPen(m_controls->m_colorGrid, 2, Qt::SolidLine, Qt::RoundCap, Qt::BevelJoin));
painter.drawRoundedRect(m_displayLeft, m_displayTop, m_displayWidth, m_displayHeight, 2.0, 2.0);
#ifdef SA_DEBUG
draw_time = std::chrono::high_resolution_clock::now().time_since_epoch().count() - draw_time;
m_execution_avg = 0.95 * m_execution_avg + 0.05 * draw_time / 1000000.0;
painter.setPen(QPen(m_controls->m_colorLabels, 1, Qt::SolidLine, Qt::RoundCap, Qt::BevelJoin));
painter.drawText(m_displayRight -150, 10, 100, 16, Qt::AlignLeft,
QString("Exec avg.: ").append(std::to_string(m_execution_avg).substr(0, 5).c_str()).append(" ms"));
#endif
}
// Helper functions for time conversion
float SaWaterfallView::samplesPerLine()
{
return (float)m_processor->inBlockSize() / m_controls->m_windowOverlapModel.value();
}
float SaWaterfallView::secondsPerLine()
{
return samplesPerLine() / m_processor->getSampleRate();
}
// Convert time value to Y coordinate for display of given height.
float SaWaterfallView::timeToYPixel(float time, int height)
{
float pixels_per_line = (float)height / m_processor->waterfallHeight();
return pixels_per_line * time / secondsPerLine();
}
// Convert Y coordinate on display of given height back to time value.
float SaWaterfallView::yPixelToTime(float position, int height)
{
if (height == 0) {height = 1;}
float pixels_per_line = (float)height / m_processor->waterfallHeight();
return (position / pixels_per_line) * secondsPerLine();
}
// Generate labels for linear time scale.
std::vector<std::pair<float, std::string>> SaWaterfallView::makeTimeTics()
{
std::vector<std::pair<float, std::string>> result;
// get time value of the last line
float limit = yPixelToTime(m_displayBottom, m_displayHeight);
// set increment to about 30 pixels (but min. 0.1 s)
float increment = std::round(10 * limit / (m_displayHeight / 30)) / 10;
if (increment < 0.1) {increment = 0.1;}
// NOTE: labels positions are rounded to match the (rounded) label value
for (float i = 0; i <= limit; i += increment)
{
if (i > 99)
{
result.emplace_back(std::round(i), std::to_string(std::round(i)).substr(0, 3));
}
else if (i < 10)
{
result.emplace_back(std::round(i * 10) / 10, std::to_string(std::round(i * 10) / 10).substr(0, 3));
}
else
{
result.emplace_back(std::round(i), std::to_string(std::round(i)).substr(0, 2));
}
}
return result;
}
// Periodically trigger repaint and check if the widget is visible.
// If it is not, stop drawing and inform the processor.
void SaWaterfallView::periodicUpdate()
{
m_processor->setWaterfallActive(isVisible());
if (isVisible()) {update();}
}
// Adjust window size and widget visibility when waterfall is enabled or disabbled.
void SaWaterfallView::updateVisibility()
{
// get container of the control dialog to be resized if needed
QWidget *subWindow = m_controlDialog->parentWidget();
if (m_controls->m_waterfallModel.value())
{
// clear old data before showing the waterfall
m_processor->clearHistory();
setVisible(true);
// increase window size if it is too small
if (subWindow->size().height() < m_controlDialog->sizeHint().height())
{
subWindow->resize(subWindow->size().width(), m_controlDialog->sizeHint().height());
}
}
else
{
setVisible(false);
// decrease window size only if it does not violate sizeHint
subWindow->resize(subWindow->size().width(), m_controlDialog->sizeHint().height());
}
}
// Draw cursor and its coordinates if it is within display bounds.
void SaWaterfallView::drawCursor(QPainter &painter)
{
if ( m_cursor.x() >= m_displayLeft
&& m_cursor.x() <= m_displayRight
&& m_cursor.y() >= m_displayTop
&& m_cursor.y() <= m_displayBottom)
{
// cursor lines
painter.setPen(QPen(m_controls->m_colorGrid.lighter(), 1, Qt::SolidLine, Qt::RoundCap, Qt::BevelJoin));
painter.drawLine(QPointF(m_cursor.x(), m_displayTop), QPointF(m_cursor.x(), m_displayBottom));
painter.drawLine(QPointF(m_displayLeft, m_cursor.y()), QPointF(m_displayRight, m_cursor.y()));
// coordinates: background box
QFontMetrics fontMetrics = painter.fontMetrics();
unsigned int const box_left = 5;
unsigned int const box_top = 5;
unsigned int const box_margin = 3;
unsigned int const box_height = 2*(fontMetrics.size(Qt::TextSingleLine, "0 Hz").height() + box_margin);
unsigned int const box_width = fontMetrics.size(Qt::TextSingleLine, "20000 Hz ").width() + 2*box_margin;
painter.setPen(QPen(m_controls->m_colorLabels.darker(), 1, Qt::SolidLine, Qt::RoundCap, Qt::BevelJoin));
painter.fillRect(m_displayLeft + box_left, m_displayTop + box_top,
box_width, box_height, QColor(0, 0, 0, 64));
// coordinates: text
painter.setPen(QPen(m_controls->m_colorLabels, 1, Qt::SolidLine, Qt::RoundCap, Qt::BevelJoin));
QString tmps;
// frequency
int freq = (int)m_processor->xPixelToFreq(m_cursor.x() - m_displayLeft, m_displayWidth);
tmps = QString("%1 Hz").arg(freq);
painter.drawText(m_displayLeft + box_left + box_margin,
m_displayTop + box_top + box_margin,
box_width, box_height / 2, Qt::AlignLeft, tmps);
// time
float time = yPixelToTime(m_cursor.y(), m_displayBottom);
tmps = QString(std::to_string(time).substr(0, 5).c_str()).append(" s");
painter.drawText(m_displayLeft + box_left + box_margin,
m_displayTop + box_top + box_height / 2,
box_width, box_height / 2, Qt::AlignLeft, tmps);
}
}
// Handle mouse input: set new cursor position.
// For some reason (a bug?), localPos() only returns integers. As a workaround
// the fractional part is taken from windowPos() (which works correctly).
void SaWaterfallView::mouseMoveEvent(QMouseEvent *event)
{
m_cursor = QPointF( event->localPos().x() - (event->windowPos().x() - (long)event->windowPos().x()),
event->localPos().y() - (event->windowPos().y() - (long)event->windowPos().y()));
}
void SaWaterfallView::mousePressEvent(QMouseEvent *event)
{
m_cursor = QPointF( event->localPos().x() - (event->windowPos().x() - (long)event->windowPos().x()),
event->localPos().y() - (event->windowPos().y() - (long)event->windowPos().y()));
}
// Handle resize event: rebuild time labels
void SaWaterfallView::resizeEvent(QResizeEvent *event)
{
m_timeTics = makeTimeTics();
}
} // namespace lmms::gui